Skip to content

Commit

Permalink
add logic for meta_data and attributes selectors
Browse files Browse the repository at this point in the history
  • Loading branch information
kilbot committed Jul 11, 2024
1 parent b284ac8 commit f9e691f
Show file tree
Hide file tree
Showing 5 changed files with 672 additions and 86 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@babel/preset-typescript": "^7.24.6",
"@testing-library/react": "^15.0.7",
"@types/jest": "^29.5.12",
"@types/lodash": "^4.17.6",
"babel-jest": "^29.7.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
Expand Down
123 changes: 63 additions & 60 deletions src/query-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import cloneDeep from 'lodash/cloneDeep';
import debounce from 'lodash/debounce';
import forEach from 'lodash/forEach';
import get from 'lodash/get';
import find from 'lodash/find';
import isEmpty from 'lodash/isEmpty';
import isEqual from 'lodash/isEqual';
import { ObservableResource } from 'observable-hooks';
Expand All @@ -19,6 +20,7 @@ import { map, switchMap, distinctUntilChanged, debounceTime, tap, startWith } fr

import { SubscribableBase } from './subscribable-base';
import { Search } from './search-state';
import { normalizeWhereClauses } from './utils';

import type { RxCollection, RxDocument } from 'rxdb';

Expand Down Expand Up @@ -289,18 +291,8 @@ export class Query<T extends RxCollection> extends SubscribableBase {
* Selector helpers
*/
where(field: string, value: any): this {
if (value === undefined || value === null) {
// Remove the clause if value is null or undefined
// @TODO - what about empty string, array or object?
this.whereClauses = this.whereClauses.filter((clause) => clause.field !== field);
} else {
const existingClause = this.whereClauses.find((clause) => clause.field === field);
if (existingClause) {
existingClause.value = value;
} else {
this.whereClauses.push({ field, value });
}
}
this.whereClauses.push({ field, value });
this.whereClauses = normalizeWhereClauses(this.whereClauses);
this.updateParams();
return this;
}
Expand Down Expand Up @@ -337,61 +329,29 @@ export class Query<T extends RxCollection> extends SubscribableBase {
debouncedSearch = debounce(this.search, 250);

/**
* Handle attribute selection
* Attributes queries have the form:
* {
* selector: {
* attributes: {
* $allMatch: [
* {
* name: 'Color',
* option: 'Blue',
* },
* ],
* },
* }
*
* Note: $allMatch is an array so we need to check if it exists and add/remove to it
*/
updateVariationAttributeSelector(attribute: { id: number; name: string; option: string }) {
// this is only a helper for variations
if (this.collection.name !== 'variations') {
throw new Error('updateVariationAttributeSearch is only for variations');
}

// add attribute to query
const $allMatch = get(this.getParams(), ['selector', 'attributes', '$allMatch'], []);
const index = $allMatch.findIndex((a) => a.name === attribute.name);
if (index > -1) {
$allMatch[index] = attribute;
private updateParams(additionalParams: Partial<QueryParams> = {}): void {
let selector;

// Construct the $and selector from where clauses
const andClauses = this.whereClauses.map((clause) => ({
[clause.field]: clause.value,
}));

if (andClauses.length > 0) {
selector = { $and: andClauses };
} else {
$allMatch.push(attribute);
}

this.whereClauses.push({ field: 'attributes', value: { $allMatch: [...$allMatch] } });
this.updateParams();
}

resetVariationAttributeSelector() {
if (get(this.getParams(), ['selector', 'attributes'])) {
this.whereClauses = this.whereClauses.filter((clause) => clause.field !== 'attributes');
this.updateParams();
selector = {};
}
}

/**
*
*/
private updateParams(additionalParams: Partial<QueryParams> = {}): void {
// Construct the selector from where clauses
const selector = this.whereClauses.reduce((acc, clause) => {
acc[clause.field] = clause.value;
return acc;
}, {});

// Get current params and merge them with additionalParams
const currentParams = this.getParams() || {};
const newParams: QueryParams = { ...currentParams, ...additionalParams, selector };
const newParams: QueryParams = {
...currentParams,
...additionalParams,
selector
};

// Update the BehaviorSubject
this.subjects.params.next(newParams);
Expand All @@ -410,4 +370,47 @@ export class Query<T extends RxCollection> extends SubscribableBase {
distinctUntilChanged()
);
}

/**
* Helper methods to see if $elemMatch is active
*/
findMetaDataSelector(key: string): any {
for (const clause of this.whereClauses) {
if (clause.field === 'meta_data' && clause.value?.$elemMatch) {
const match = find(clause.value.$elemMatch.$and || [clause.value.$elemMatch], { key });
if (match) return match.value;
}
}
return undefined;
}

hasMetaDataSelector(key: string, value: any): boolean {
for (const clause of this.whereClauses) {
if (clause.field === 'meta_data' && clause.value?.$elemMatch) {
const match = find(clause.value.$elemMatch.$and || [clause.value.$elemMatch], { key, value });
if (match) return true;
}
}
return false;
}

findAttributesSelector(name: string): any {
for (const clause of this.whereClauses) {
if (clause.field === 'attributes' && clause.value?.$elemMatch) {
const match = find(clause.value.$elemMatch.$and || [clause.value.$elemMatch], { name });
if (match) return match.option;
}
}
return undefined;
}

hasAttributesSelector(name: string, option: any): boolean {
for (const clause of this.whereClauses) {
if (clause.field === 'attributes' && clause.value?.$elemMatch) {
const match = find(clause.value.$elemMatch.$and || [clause.value.$elemMatch], { name, option });
if (match) return true;
}
}
return false;
}
}
39 changes: 39 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,42 @@ export function getParamValueFromEndpoint(endpoint: string, param: string) {
const params = new URLSearchParams(url.search);
return params.get(param);
}

/**
*
*/
type WhereClause = { field: string; value: any };
export function normalizeWhereClauses(clauses: WhereClause[]): WhereClause[] {
const fieldMap = new Map<string, WhereClause>();
const fieldsToRemove = new Set<string>();

for (const clause of clauses) {
if (clause.value === null) {
// Mark the field for removal
fieldsToRemove.add(clause.field);
fieldMap.delete(clause.field);
} else if (clause.value?.$elemMatch) {
const key = `${clause.field}_${clause.value.$elemMatch.key || clause.value.$elemMatch.name}`;
if (clause.value.$elemMatch.value === null || clause.value.$elemMatch.option === null) {
fieldMap.delete(key);
} else {
fieldMap.set(key, clause);
}
} else if (!fieldsToRemove.has(clause.field)) {
fieldMap.set(clause.field, clause);
}
}

// Ensure fields marked for removal are not in the final output
fieldsToRemove.forEach(field => {
for (const key of fieldMap.keys()) {
if (key.startsWith(field)) {
fieldMap.delete(key);
}
}
});

return Array.from(fieldMap.values());
}


15 changes: 11 additions & 4 deletions tests/helpers/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,21 @@ export type LogDocument = RxDocument<LogDocumentType>;
export type LogCollection = RxCollection<LogDocumentType>;
const logs: RxCollectionCreator<LogDocumentType> = { schema: logSchema };

/**
* Generate a unique database name
*/
function generateUniqueDbName(baseName: string): string {
return `${baseName}_${Date.now()}`;
}

/**
*
*/
export async function createStoreDatabase(): Promise<RxDatabase> {
const db = await createRxDatabase({
name: 'storedb',
name: generateUniqueDbName('storedb'),
storage: getRxStorageMemory(),
ignoreDuplicate: true,
// ignoreDuplicate: true,
allowSlowCount: true,
});

Expand All @@ -93,9 +100,9 @@ export async function createStoreDatabase(): Promise<RxDatabase> {
*/
export async function createSyncDatabase(): Promise<RxDatabase> {
const db = await createRxDatabase({
name: 'syncdb',
name: generateUniqueDbName('syncdb'),
storage: getRxStorageMemory(),
ignoreDuplicate: true,
// ignoreDuplicate: true,
allowSlowCount: true,
});

Expand Down
Loading

0 comments on commit f9e691f

Please sign in to comment.