@sprucelabs/data-stores 28.3.232 → 28.3.234

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import { Schema, SchemaFieldNames, SchemaPartialValues, SchemaPublicFieldNames,
2
2
  import { FindBatchOptions } from '../cursors/BatchCursor';
3
3
  import AbstractMutexer from '../mutexers/AbstractMutexer';
4
4
  import { Database } from '../types/database.types';
5
- import { QueryBuilder, QueryOptions } from '../types/query.types';
5
+ import { QueryBuilder, QueryOptions, ValuesWithPaths } from '../types/query.types';
6
6
  import { PrepareOptions, PrepareResults, SaveOperations, DataStore, DataStorePlugin } from '../types/stores.types';
7
7
  export default abstract class AbstractStore<FullSchema extends Schema, CreateSchema extends Schema = FullSchema, UpdateSchema extends Schema = CreateSchema, DatabaseSchema extends Schema = FullSchema, PrimaryFieldName extends SchemaFieldNames<DatabaseSchema> | 'id' = 'id', DatabaseRecord = SchemaValues<DatabaseSchema>, QueryRecord = SchemaPartialValues<FullSchema>, FullRecord = SchemaValues<FullSchema>, CreateRecord = SchemaValues<CreateSchema>, UpdateRecord = SchemaValues<UpdateSchema> & SaveOperations> extends AbstractMutexer implements DataStore {
8
8
  abstract readonly name: string;
@@ -49,7 +49,7 @@ export default abstract class AbstractStore<FullSchema extends Schema, CreateSch
49
49
  upsertOne<IncludePrivateFields extends boolean = true, CreateEntityInstances extends boolean = false, F extends SchemaFieldNames<FullSchema> = SchemaFieldNames<FullSchema>, PF extends SchemaPublicFieldNames<FullSchema> = SchemaPublicFieldNames<FullSchema>>(query: QueryBuilder<QueryRecord>, updates: UpdateRecord & CreateRecord & {
50
50
  id?: string;
51
51
  }, options?: PrepareOptions<IncludePrivateFields, FullSchema, F>): Promise<Response<FullSchema, CreateEntityInstances, IncludePrivateFields, PF, F>>;
52
- updateOne<IncludePrivateFields extends boolean = false, CreateEntityInstances extends boolean = false, F extends SchemaFieldNames<FullSchema> = SchemaFieldNames<FullSchema>, PF extends SchemaPublicFieldNames<FullSchema> = SchemaPublicFieldNames<FullSchema>>(query: QueryBuilder<QueryRecord>, updates: UpdateRecord, options?: PrepareOptions<IncludePrivateFields, FullSchema, F>): Promise<Response<FullSchema, CreateEntityInstances, IncludePrivateFields, PF, F>>;
52
+ updateOne<IncludePrivateFields extends boolean = false, CreateEntityInstances extends boolean = false, F extends SchemaFieldNames<FullSchema> = SchemaFieldNames<FullSchema>, PF extends SchemaPublicFieldNames<FullSchema> = SchemaPublicFieldNames<FullSchema>>(query: QueryBuilder<QueryRecord>, updates: ValuesWithPaths<UpdateRecord>, options?: PrepareOptions<IncludePrivateFields, FullSchema, F>): Promise<Response<FullSchema, CreateEntityInstances, IncludePrivateFields, PF, F>>;
53
53
  update(query: QueryBuilder<QueryRecord>, updates: UpdateRecord): Promise<number>;
54
54
  private findOneAndUpdate;
55
55
  private handleWillUpdateOnePlugins;
@@ -351,6 +351,7 @@ export default class AbstractStore extends AbstractMutexer {
351
351
  ? databaseRecord
352
352
  : normalizeSchemaValues(this.databaseSchema, databaseRecord, {
353
353
  shouldCreateEntityInstances: false,
354
+ shouldRetainDotSyntaxKeys: true,
354
355
  fields: Object.keys(cleanedUpdates),
355
356
  });
356
357
  for (const { name, value } of ops) {
@@ -85,6 +85,8 @@ declare const databaseAssertUtil: {
85
85
  _assertThrowsExpectedNotFoundOnUpdateOne(db: Database, query: Record<string, any>): Promise<void>;
86
86
  _assert$orReturnsExpectedTotalRecords(db: Database, $or: Record<string, any>[], expected: number): Promise<void>;
87
87
  _assertCanCreateMultiFieldIndex(connect: TestConnect, fields: string[]): Promise<void>;
88
+ canUpdateNestedField(connect: TestConnect): Promise<void>;
89
+ canUpsertNestedField(connect: TestConnect): Promise<void>;
88
90
  assertHasLowerCaseToCamelCaseMappingEnabled(store: DataStore): void;
89
91
  };
90
92
  export default databaseAssertUtil;
@@ -1958,6 +1958,53 @@ const databaseAssertUtil = {
1958
1958
  yield this.shutdown(db);
1959
1959
  });
1960
1960
  },
1961
+ canUpdateNestedField(connect) {
1962
+ return __awaiter(this, void 0, void 0, function* () {
1963
+ const db = yield connectToDabatase(connect);
1964
+ const locationId = generateId();
1965
+ const values = {
1966
+ name: 'first',
1967
+ target: {
1968
+ organizationId: generateId(),
1969
+ locationId,
1970
+ },
1971
+ };
1972
+ yield db.createOne(this.collectionName, values);
1973
+ const newOrganizationId = generateId();
1974
+ const updated = yield db.updateOne(this.collectionName, {}, {
1975
+ 'target.organizationId': newOrganizationId,
1976
+ });
1977
+ assert.isEqual(updated.target.organizationId, newOrganizationId, 'Could not update nested field target.organizationId using key "target.organizationId"');
1978
+ const match = yield db.findOne(this.collectionName, { id: updated.id });
1979
+ assert.isEqualDeep(match.target, {
1980
+ organizationId: newOrganizationId,
1981
+ locationId,
1982
+ }, `Updating nested field lost existing key 'target.locationId'`);
1983
+ yield this.shutdown(db);
1984
+ });
1985
+ },
1986
+ canUpsertNestedField(connect) {
1987
+ return __awaiter(this, void 0, void 0, function* () {
1988
+ const db = yield connectToDabatase(connect);
1989
+ const locationId = generateId();
1990
+ const values = {
1991
+ name: 'first',
1992
+ target: {
1993
+ organizationId: generateId(),
1994
+ locationId,
1995
+ },
1996
+ };
1997
+ const record = yield db.createOne(this.collectionName, values);
1998
+ const newOrganizationId = generateId();
1999
+ yield db.upsertOne(this.collectionName, { id: record.id }, {
2000
+ 'target.organizationId': newOrganizationId,
2001
+ });
2002
+ const match = yield db.findOne(this.collectionName, { id: record.id });
2003
+ assert.isEqual(match.target.organizationId, newOrganizationId, 'Could not find updated record with key "target.organizationId" set');
2004
+ assert.isEqual(match.target.locationId, locationId, `Upserting lost existing key 'target.locationId'`);
2005
+ yield this.shutdown(db);
2006
+ });
2007
+ },
1961
2008
  assertHasLowerCaseToCamelCaseMappingEnabled(store) {
1962
2009
  assert.isTrue(
1963
2010
  //@ts-ignore
@@ -5,6 +5,21 @@ export type QueryBuilder<Query, Keys extends Paths<Query> = Paths<Query>> = {
5
5
  } & {
6
6
  id?: string | QuerySelector<string>;
7
7
  } & RootQuerySelector<Query>;
8
+ export type ValuesWithPaths<Values, Keys extends Paths<Values> = Paths<Values>> = {
9
+ [K in RequiredKeys<Values, Keys>]: TypeAtPath<Values, K>;
10
+ } & {
11
+ [K in OptionalKeys<Values, Keys>]?: TypeAtPath<Values, K>;
12
+ };
13
+ type IsOptional<T, K extends keyof T> = {} extends Pick<T, K> ? true : false;
14
+ type IsPathOptional<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? IsOptional<T, K> extends true ? true : IsPathOptional<T[K], Rest> : true : P extends keyof T ? IsOptional<T, P> : true;
15
+ type RequiredKeys<Values, Keys extends Paths<Values>> = {
16
+ [K in Keys]: IsPathOptional<Values, K> extends true ? never : K;
17
+ }[Keys];
18
+ type OptionalKeys<Values, Keys extends Paths<Values>> = Exclude<Keys, RequiredKeys<Values, Keys>>;
19
+ type Paths<T, D extends number = 3> = [D] extends [never] ? never : T extends object ? {
20
+ [K in keyof T]-?: K extends string | number ? `${K}` | Join<K, Paths<T[K], Prev[D]>> : never;
21
+ }[keyof T] : '';
22
+ type TypeAtPath<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? TypeAtPath<T[K], Rest> : any : P extends keyof T ? T[P] : any;
8
23
  export interface QuerySortField {
9
24
  field: string;
10
25
  direction: 'asc' | 'desc';
@@ -44,8 +59,4 @@ type Prev = [
44
59
  20,
45
60
  ...0[]
46
61
  ];
47
- type Paths<T, D extends number = 3> = [D] extends [never] ? never : T extends object ? {
48
- [K in keyof T]-?: K extends string | number ? `${K}` | Join<K, Paths<T[K], Prev[D]>> : never;
49
- }[keyof T] : '';
50
- type TypeAtPath<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? TypeAtPath<T[K], Rest> : any : P extends keyof T ? T[P] : any;
51
62
  export {};
@@ -2,7 +2,7 @@ import { Schema, SchemaFieldNames, SchemaPartialValues, SchemaPublicFieldNames,
2
2
  import { FindBatchOptions } from '../cursors/BatchCursor';
3
3
  import AbstractMutexer from '../mutexers/AbstractMutexer';
4
4
  import { Database } from '../types/database.types';
5
- import { QueryBuilder, QueryOptions } from '../types/query.types';
5
+ import { QueryBuilder, QueryOptions, ValuesWithPaths } from '../types/query.types';
6
6
  import { PrepareOptions, PrepareResults, SaveOperations, DataStore, DataStorePlugin } from '../types/stores.types';
7
7
  export default abstract class AbstractStore<FullSchema extends Schema, CreateSchema extends Schema = FullSchema, UpdateSchema extends Schema = CreateSchema, DatabaseSchema extends Schema = FullSchema, PrimaryFieldName extends SchemaFieldNames<DatabaseSchema> | 'id' = 'id', DatabaseRecord = SchemaValues<DatabaseSchema>, QueryRecord = SchemaPartialValues<FullSchema>, FullRecord = SchemaValues<FullSchema>, CreateRecord = SchemaValues<CreateSchema>, UpdateRecord = SchemaValues<UpdateSchema> & SaveOperations> extends AbstractMutexer implements DataStore {
8
8
  abstract readonly name: string;
@@ -49,7 +49,7 @@ export default abstract class AbstractStore<FullSchema extends Schema, CreateSch
49
49
  upsertOne<IncludePrivateFields extends boolean = true, CreateEntityInstances extends boolean = false, F extends SchemaFieldNames<FullSchema> = SchemaFieldNames<FullSchema>, PF extends SchemaPublicFieldNames<FullSchema> = SchemaPublicFieldNames<FullSchema>>(query: QueryBuilder<QueryRecord>, updates: UpdateRecord & CreateRecord & {
50
50
  id?: string;
51
51
  }, options?: PrepareOptions<IncludePrivateFields, FullSchema, F>): Promise<Response<FullSchema, CreateEntityInstances, IncludePrivateFields, PF, F>>;
52
- updateOne<IncludePrivateFields extends boolean = false, CreateEntityInstances extends boolean = false, F extends SchemaFieldNames<FullSchema> = SchemaFieldNames<FullSchema>, PF extends SchemaPublicFieldNames<FullSchema> = SchemaPublicFieldNames<FullSchema>>(query: QueryBuilder<QueryRecord>, updates: UpdateRecord, options?: PrepareOptions<IncludePrivateFields, FullSchema, F>): Promise<Response<FullSchema, CreateEntityInstances, IncludePrivateFields, PF, F>>;
52
+ updateOne<IncludePrivateFields extends boolean = false, CreateEntityInstances extends boolean = false, F extends SchemaFieldNames<FullSchema> = SchemaFieldNames<FullSchema>, PF extends SchemaPublicFieldNames<FullSchema> = SchemaPublicFieldNames<FullSchema>>(query: QueryBuilder<QueryRecord>, updates: ValuesWithPaths<UpdateRecord>, options?: PrepareOptions<IncludePrivateFields, FullSchema, F>): Promise<Response<FullSchema, CreateEntityInstances, IncludePrivateFields, PF, F>>;
53
53
  update(query: QueryBuilder<QueryRecord>, updates: UpdateRecord): Promise<number>;
54
54
  private findOneAndUpdate;
55
55
  private handleWillUpdateOnePlugins;
@@ -347,6 +347,7 @@ class AbstractStore extends AbstractMutexer_1.default {
347
347
  ? databaseRecord
348
348
  : (0, schema_1.normalizeSchemaValues)(this.databaseSchema, databaseRecord, {
349
349
  shouldCreateEntityInstances: false,
350
+ shouldRetainDotSyntaxKeys: true,
350
351
  fields: Object.keys(cleanedUpdates),
351
352
  });
352
353
  for (const { name, value } of ops) {
@@ -85,6 +85,8 @@ declare const databaseAssertUtil: {
85
85
  _assertThrowsExpectedNotFoundOnUpdateOne(db: Database, query: Record<string, any>): Promise<void>;
86
86
  _assert$orReturnsExpectedTotalRecords(db: Database, $or: Record<string, any>[], expected: number): Promise<void>;
87
87
  _assertCanCreateMultiFieldIndex(connect: TestConnect, fields: string[]): Promise<void>;
88
+ canUpdateNestedField(connect: TestConnect): Promise<void>;
89
+ canUpsertNestedField(connect: TestConnect): Promise<void>;
88
90
  assertHasLowerCaseToCamelCaseMappingEnabled(store: DataStore): void;
89
91
  };
90
92
  export default databaseAssertUtil;
@@ -1798,6 +1798,49 @@ const databaseAssertUtil = {
1798
1798
  test_utils_1.assert.isEqualDeep(indexes[0].fields.map((i) => i.toLowerCase()), fields.map((f) => f.toLowerCase()));
1799
1799
  await this.shutdown(db);
1800
1800
  },
1801
+ async canUpdateNestedField(connect) {
1802
+ const db = await connectToDabatase(connect);
1803
+ const locationId = (0, generateId_1.default)();
1804
+ const values = {
1805
+ name: 'first',
1806
+ target: {
1807
+ organizationId: (0, generateId_1.default)(),
1808
+ locationId,
1809
+ },
1810
+ };
1811
+ await db.createOne(this.collectionName, values);
1812
+ const newOrganizationId = (0, generateId_1.default)();
1813
+ const updated = await db.updateOne(this.collectionName, {}, {
1814
+ 'target.organizationId': newOrganizationId,
1815
+ });
1816
+ test_utils_1.assert.isEqual(updated.target.organizationId, newOrganizationId, 'Could not update nested field target.organizationId using key "target.organizationId"');
1817
+ const match = await db.findOne(this.collectionName, { id: updated.id });
1818
+ test_utils_1.assert.isEqualDeep(match.target, {
1819
+ organizationId: newOrganizationId,
1820
+ locationId,
1821
+ }, `Updating nested field lost existing key 'target.locationId'`);
1822
+ await this.shutdown(db);
1823
+ },
1824
+ async canUpsertNestedField(connect) {
1825
+ const db = await connectToDabatase(connect);
1826
+ const locationId = (0, generateId_1.default)();
1827
+ const values = {
1828
+ name: 'first',
1829
+ target: {
1830
+ organizationId: (0, generateId_1.default)(),
1831
+ locationId,
1832
+ },
1833
+ };
1834
+ const record = await db.createOne(this.collectionName, values);
1835
+ const newOrganizationId = (0, generateId_1.default)();
1836
+ await db.upsertOne(this.collectionName, { id: record.id }, {
1837
+ 'target.organizationId': newOrganizationId,
1838
+ });
1839
+ const match = await db.findOne(this.collectionName, { id: record.id });
1840
+ test_utils_1.assert.isEqual(match.target.organizationId, newOrganizationId, 'Could not find updated record with key "target.organizationId" set');
1841
+ test_utils_1.assert.isEqual(match.target.locationId, locationId, `Upserting lost existing key 'target.locationId'`);
1842
+ await this.shutdown(db);
1843
+ },
1801
1844
  assertHasLowerCaseToCamelCaseMappingEnabled(store) {
1802
1845
  test_utils_1.assert.isTrue(
1803
1846
  //@ts-ignore
@@ -5,6 +5,21 @@ export type QueryBuilder<Query, Keys extends Paths<Query> = Paths<Query>> = {
5
5
  } & {
6
6
  id?: string | QuerySelector<string>;
7
7
  } & RootQuerySelector<Query>;
8
+ export type ValuesWithPaths<Values, Keys extends Paths<Values> = Paths<Values>> = {
9
+ [K in RequiredKeys<Values, Keys>]: TypeAtPath<Values, K>;
10
+ } & {
11
+ [K in OptionalKeys<Values, Keys>]?: TypeAtPath<Values, K>;
12
+ };
13
+ type IsOptional<T, K extends keyof T> = {} extends Pick<T, K> ? true : false;
14
+ type IsPathOptional<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? IsOptional<T, K> extends true ? true : IsPathOptional<T[K], Rest> : true : P extends keyof T ? IsOptional<T, P> : true;
15
+ type RequiredKeys<Values, Keys extends Paths<Values>> = {
16
+ [K in Keys]: IsPathOptional<Values, K> extends true ? never : K;
17
+ }[Keys];
18
+ type OptionalKeys<Values, Keys extends Paths<Values>> = Exclude<Keys, RequiredKeys<Values, Keys>>;
19
+ type Paths<T, D extends number = 3> = [D] extends [never] ? never : T extends object ? {
20
+ [K in keyof T]-?: K extends string | number ? `${K}` | Join<K, Paths<T[K], Prev[D]>> : never;
21
+ }[keyof T] : '';
22
+ type TypeAtPath<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? TypeAtPath<T[K], Rest> : any : P extends keyof T ? T[P] : any;
8
23
  export interface QuerySortField {
9
24
  field: string;
10
25
  direction: 'asc' | 'desc';
@@ -44,8 +59,4 @@ type Prev = [
44
59
  20,
45
60
  ...0[]
46
61
  ];
47
- type Paths<T, D extends number = 3> = [D] extends [never] ? never : T extends object ? {
48
- [K in keyof T]-?: K extends string | number ? `${K}` | Join<K, Paths<T[K], Prev[D]>> : never;
49
- }[keyof T] : '';
50
- type TypeAtPath<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? TypeAtPath<T[K], Rest> : any : P extends keyof T ? T[P] : any;
51
62
  export {};
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "28.3.232",
6
+ "version": "28.3.234",
7
7
  "files": [
8
8
  "build/**/*",
9
9
  "!build/__tests__",
@@ -65,31 +65,31 @@
65
65
  },
66
66
  "dependencies": {
67
67
  "@seald-io/nedb": "^4.0.4",
68
- "@sprucelabs/error": "^6.0.547",
68
+ "@sprucelabs/error": "^6.0.548",
69
69
  "@sprucelabs/globby": "^2.0.499",
70
- "@sprucelabs/schema": "^30.0.572",
71
- "@sprucelabs/spruce-skill-utils": "^31.0.639",
70
+ "@sprucelabs/schema": "^30.0.577",
71
+ "@sprucelabs/spruce-skill-utils": "^31.0.641",
72
72
  "just-clone": "^6.2.0",
73
73
  "lodash": "^4.17.21",
74
74
  "mongodb": "^6.11.0"
75
75
  },
76
76
  "devDependencies": {
77
- "@sprucelabs/esm-postbuild": "^6.0.527",
78
- "@sprucelabs/jest-json-reporter": "^8.0.549",
79
- "@sprucelabs/resolve-path-aliases": "^2.0.518",
77
+ "@sprucelabs/esm-postbuild": "^6.0.528",
78
+ "@sprucelabs/jest-json-reporter": "^8.0.550",
79
+ "@sprucelabs/resolve-path-aliases": "^2.0.519",
80
80
  "@sprucelabs/semantic-release": "^5.0.2",
81
- "@sprucelabs/test": "^9.0.61",
82
- "@sprucelabs/test-utils": "^5.1.513",
81
+ "@sprucelabs/test": "^9.0.62",
82
+ "@sprucelabs/test-utils": "^5.1.515",
83
83
  "@types/lodash": "^4.17.13",
84
- "@types/node": "^22.10.0",
84
+ "@types/node": "^22.10.1",
85
85
  "chokidar-cli": "^3.0.0",
86
86
  "concurrently": "^9.1.0",
87
87
  "dotenv": "^16.4.5",
88
- "eslint": "^9.15.0",
88
+ "eslint": "^9.16.0",
89
89
  "eslint-config-spruce": "^11.2.26",
90
90
  "jest": "^29.7.0",
91
91
  "jest-circus": "^29.7.0",
92
- "prettier": "^3.4.0",
92
+ "prettier": "^3.4.1",
93
93
  "ts-node": "^10.9.2",
94
94
  "tsc-watch": "^6.2.1",
95
95
  "tsconfig-paths": "^4.2.0",