@verdant-web/common 1.14.0 → 1.15.0

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.
Files changed (82) hide show
  1. package/dist/cjs/batching.d.ts +1 -0
  2. package/dist/cjs/batching.js +3 -0
  3. package/dist/cjs/batching.js.map +1 -1
  4. package/dist/cjs/index.d.ts +1 -1
  5. package/dist/cjs/index.js +2 -1
  6. package/dist/cjs/index.js.map +1 -1
  7. package/dist/cjs/indexes.d.ts +3 -1
  8. package/dist/cjs/indexes.js +26 -3
  9. package/dist/cjs/indexes.js.map +1 -1
  10. package/dist/cjs/migration.d.ts +85 -18
  11. package/dist/cjs/migration.js +137 -13
  12. package/dist/cjs/migration.js.map +1 -1
  13. package/dist/cjs/oids.d.ts +9 -2
  14. package/dist/cjs/oids.js +38 -5
  15. package/dist/cjs/oids.js.map +1 -1
  16. package/dist/cjs/oids.test.js +4 -3
  17. package/dist/cjs/oids.test.js.map +1 -1
  18. package/dist/cjs/schema/fields.d.ts +2 -0
  19. package/dist/cjs/schema/fields.js +29 -1
  20. package/dist/cjs/schema/fields.js.map +1 -1
  21. package/dist/cjs/schema/index.d.ts +2 -2
  22. package/dist/cjs/schema/index.js +25 -2
  23. package/dist/cjs/schema/index.js.map +1 -1
  24. package/dist/cjs/schema/types/collection.d.ts +12 -40
  25. package/dist/cjs/schema/types/compounds.d.ts +6 -7
  26. package/dist/cjs/schema/types/fields.d.ts +12 -2
  27. package/dist/cjs/schema/types/shapes.d.ts +6 -6
  28. package/dist/cjs/schema/types/synthetics.d.ts +11 -2
  29. package/dist/cjs/schema/types.d.ts +4 -4
  30. package/dist/cjs/utils.js +6 -1
  31. package/dist/cjs/utils.js.map +1 -1
  32. package/dist/esm/batching.d.ts +1 -0
  33. package/dist/esm/batching.js +3 -0
  34. package/dist/esm/batching.js.map +1 -1
  35. package/dist/esm/index.d.ts +1 -1
  36. package/dist/esm/index.js +1 -1
  37. package/dist/esm/index.js.map +1 -1
  38. package/dist/esm/indexes.d.ts +3 -1
  39. package/dist/esm/indexes.js +23 -2
  40. package/dist/esm/indexes.js.map +1 -1
  41. package/dist/esm/migration.d.ts +85 -18
  42. package/dist/esm/migration.js +136 -13
  43. package/dist/esm/migration.js.map +1 -1
  44. package/dist/esm/oids.d.ts +9 -2
  45. package/dist/esm/oids.js +35 -3
  46. package/dist/esm/oids.js.map +1 -1
  47. package/dist/esm/oids.test.js +5 -4
  48. package/dist/esm/oids.test.js.map +1 -1
  49. package/dist/esm/replica.js +1 -1
  50. package/dist/esm/replica.js.map +1 -1
  51. package/dist/esm/schema/fields.d.ts +2 -0
  52. package/dist/esm/schema/fields.js +26 -0
  53. package/dist/esm/schema/fields.js.map +1 -1
  54. package/dist/esm/schema/index.d.ts +2 -2
  55. package/dist/esm/schema/index.js +25 -2
  56. package/dist/esm/schema/index.js.map +1 -1
  57. package/dist/esm/schema/types/collection.d.ts +12 -40
  58. package/dist/esm/schema/types/compounds.d.ts +6 -7
  59. package/dist/esm/schema/types/fields.d.ts +12 -2
  60. package/dist/esm/schema/types/shapes.d.ts +6 -6
  61. package/dist/esm/schema/types/synthetics.d.ts +11 -2
  62. package/dist/esm/schema/types.d.ts +4 -4
  63. package/dist/esm/utils.js +3 -1
  64. package/dist/esm/utils.js.map +1 -1
  65. package/dist/tsconfig-cjs.tsbuildinfo +1 -1
  66. package/dist/tsconfig.tsbuildinfo +1 -1
  67. package/package.json +6 -3
  68. package/src/batching.ts +4 -0
  69. package/src/index.ts +1 -0
  70. package/src/indexes.ts +51 -12
  71. package/src/migration.ts +300 -57
  72. package/src/oids.test.ts +5 -4
  73. package/src/oids.ts +39 -3
  74. package/src/schema/fields.ts +18 -0
  75. package/src/schema/index.ts +30 -3
  76. package/src/schema/types/collection.ts +14 -112
  77. package/src/schema/types/compounds.ts +29 -18
  78. package/src/schema/types/fields.ts +12 -2
  79. package/src/schema/types/shapes.ts +13 -14
  80. package/src/schema/types/synthetics.ts +31 -2
  81. package/src/schema/types.ts +4 -6
  82. package/src/utils.ts +3 -1
package/src/indexes.ts CHANGED
@@ -2,6 +2,8 @@ import {
2
2
  StorageCollectionSchema,
3
3
  StorageSyntheticIndexSchema,
4
4
  CollectionCompoundIndex,
5
+ isIndexed,
6
+ StorageDirectSyntheticSchema,
5
7
  } from './index.js';
6
8
 
7
9
  // unlikely to be used unicode character
@@ -96,12 +98,21 @@ function expandArrayIndex(fields: IndexableFieldValue[]): string[] {
96
98
  );
97
99
  }
98
100
 
101
+ export function isDirectSynthetic(
102
+ index: any,
103
+ ): index is StorageDirectSyntheticSchema<any> {
104
+ return !!index.field;
105
+ }
106
+
99
107
  export function computeSynthetics(schema: StorageCollectionSchema, obj: any) {
100
108
  const result: Record<string, any> = {};
101
- for (const [name, property] of Object.entries(schema.synthetics || {})) {
102
- result[name] = sanitizeIndexValue(
103
- (property as StorageSyntheticIndexSchema<any>).compute(obj),
104
- );
109
+ for (const [name, property] of Object.entries(schema.indexes || {})) {
110
+ const index = property as StorageSyntheticIndexSchema<any>;
111
+ if (isDirectSynthetic(index)) {
112
+ result[name] = sanitizeIndexValue(obj[index.field]);
113
+ } else {
114
+ result[name] = sanitizeIndexValue(index.compute(obj));
115
+ }
105
116
  }
106
117
  return result;
107
118
  }
@@ -112,14 +123,42 @@ export function computeCompoundIndices(
112
123
  ): any {
113
124
  return Object.entries(schema.compounds || {}).reduce<
114
125
  Record<string, CompoundIndexValue>
115
- >((acc, [indexKey, index]) => {
116
- acc[indexKey] = createCompoundIndexValue(
117
- ...(index as CollectionCompoundIndex<any, any>).of.map(
118
- (key) => doc[key] as string | number,
119
- ),
120
- );
121
- return acc;
122
- }, {} as Record<string, CompoundIndexValue>);
126
+ >(
127
+ (acc, [indexKey, index]) => {
128
+ acc[indexKey] = createCompoundIndexValue(
129
+ ...(index as CollectionCompoundIndex<any, any>).of.map(
130
+ (key) => doc[key] as string | number,
131
+ ),
132
+ );
133
+ return acc;
134
+ },
135
+ {} as Record<string, CompoundIndexValue>,
136
+ );
137
+ }
138
+
139
+ function computeIndexedFields(schema: StorageCollectionSchema, doc: any) {
140
+ return Object.entries(schema.fields).reduce<Record<string, any>>(
141
+ (acc, [key, field]) => {
142
+ if (isIndexed(field)) {
143
+ acc[key] = sanitizeIndexValue(doc[key]);
144
+ }
145
+ return acc;
146
+ },
147
+ {},
148
+ );
149
+ }
150
+
151
+ export function getIndexValues(
152
+ schema: StorageCollectionSchema<any, any, any>,
153
+ doc: any,
154
+ ) {
155
+ return {
156
+ [schema.primaryKey]: doc[schema.primaryKey],
157
+ ...computeIndexedFields(schema, doc),
158
+ ...computeSynthetics(schema, doc),
159
+ ...computeCompoundIndices(schema, doc),
160
+ '@@@snapshot': doc,
161
+ };
123
162
  }
124
163
 
125
164
  export function assignIndexValues(
package/src/migration.ts CHANGED
@@ -11,29 +11,32 @@ import {
11
11
  hasOid,
12
12
  assignOid,
13
13
  getOid,
14
+ hasDefault,
14
15
  } from './index.js';
15
16
 
17
+ /**@deprecated */
16
18
  export interface DroppedCollectionMigrationStrategy<
17
19
  Old extends StorageCollectionSchema<any, any, any>,
18
20
  > {
19
21
  (old: Old): void | Promise<void>;
20
22
  }
23
+ /**@deprecated */
21
24
  export interface PreservedCollectionMigrationStrategy<
22
25
  Old extends StorageCollectionSchema<any, any, any>,
23
26
  New extends StorageCollectionSchema<any, any, any>,
24
27
  > {
25
- (old: StorageDocument<Old>):
26
- | StorageDocument<New>
27
- | Promise<StorageDocument<New>>;
28
+ (
29
+ old: StorageDocument<Old>,
30
+ ): StorageDocument<New> | Promise<StorageDocument<New>>;
28
31
  }
29
-
32
+ /** @deprecated */
30
33
  type MigrationStrategy<
31
34
  Old extends StorageCollectionSchema<any, any, any>,
32
35
  New extends StorageCollectionSchema<any, any, any>,
33
36
  > =
34
37
  | DroppedCollectionMigrationStrategy<Old>
35
38
  | PreservedCollectionMigrationStrategy<Old, New>;
36
-
39
+ /** @deprecated */
37
40
  export type MigrationsKeyedOnCollection<
38
41
  Old extends StorageSchema<any>,
39
42
  New extends StorageSchema<any>,
@@ -41,25 +44,17 @@ export type MigrationsKeyedOnCollection<
41
44
  | PreservedCollectionMigrations<Old, New>
42
45
  | DroppedCollectionMigrations<Old, New>;
43
46
 
47
+ /**@deprecated */
44
48
  type NotInSchema<
45
49
  Name extends string | number | symbol,
46
50
  Schema extends StorageSchema<any>,
47
51
  > = Name extends keyof Schema['collections'] ? never : Name;
52
+ /** @deprecated */
48
53
  type InSchema<
49
54
  Name extends string | number | symbol,
50
55
  Schema extends StorageSchema<any>,
51
56
  > = Name extends keyof Schema['collections'] ? Name : never;
52
-
53
- type NewCollections<
54
- Old extends StorageSchema<any>,
55
- New extends StorageSchema<any>,
56
- > = {
57
- [Key in keyof New['collections'] as NotInSchema<
58
- Key,
59
- Old
60
- >]: StorageCollectionSchema<any, any, any>;
61
- };
62
-
57
+ /** @deprecated */
63
58
  type DroppedCollections<
64
59
  Old extends StorageSchema<any>,
65
60
  New extends StorageSchema<any>,
@@ -69,7 +64,7 @@ type DroppedCollections<
69
64
  New
70
65
  >]: StorageCollectionSchema<any, any, any>;
71
66
  };
72
-
67
+ /** @deprecated */
73
68
  type PreservedCollections<
74
69
  Old extends StorageSchema<any>,
75
70
  New extends StorageSchema<any>,
@@ -79,7 +74,7 @@ type PreservedCollections<
79
74
  New
80
75
  >]: StorageCollectionSchema<any, any, any>;
81
76
  };
82
-
77
+ /** @deprecated */
83
78
  type DroppedCollectionMigrations<
84
79
  Old extends StorageSchema<any>,
85
80
  New extends StorageSchema<any>,
@@ -89,6 +84,7 @@ type DroppedCollectionMigrations<
89
84
  New
90
85
  >]: DroppedCollectionMigrationStrategy<Old['collections'][Key]>;
91
86
  };
87
+ /** @deprecated */
92
88
  type PreservedCollectionMigrations<
93
89
  Old extends StorageSchema<any>,
94
90
  New extends StorageSchema<any>,
@@ -102,6 +98,7 @@ type PreservedCollectionMigrations<
102
98
  >;
103
99
  };
104
100
 
101
+ /** @deprecated */
105
102
  type StrategyFor<
106
103
  Key extends string,
107
104
  Old extends StorageSchema<any>,
@@ -113,7 +110,8 @@ type StrategyFor<
113
110
  >
114
111
  : DroppedCollectionMigrationStrategy<Old['collections'][Key]>;
115
112
 
116
- type MigrationRunner<
113
+ /** @deprecated */
114
+ type DeprecatedMigrationRunner<
117
115
  Old extends StorageSchema<any>,
118
116
  New extends StorageSchema<any>,
119
117
  > = <Collection extends Extract<keyof Old['collections'], string>>(
@@ -121,7 +119,8 @@ type MigrationRunner<
121
119
  strategy: StrategyFor<Collection, Old, New>,
122
120
  ) => Promise<void>;
123
121
 
124
- type MigrationQueryMaker<
122
+ /** @deprecated */
123
+ type DeprecatedMigrationQueryMaker<
125
124
  Collection extends StorageCollectionSchema<any, any, any>,
126
125
  > = {
127
126
  get(primaryKey: string): Promise<StorageDocument<Collection> | undefined>;
@@ -131,13 +130,15 @@ type MigrationQueryMaker<
131
130
  findAll(query?: CollectionFilter): Promise<StorageDocument<Collection>[]>;
132
131
  };
133
132
 
134
- type MigrationQueries<Old extends StorageSchema<any>> = {
135
- [Key in keyof Old['collections']]: MigrationQueryMaker<
133
+ /** @deprecated */
134
+ type DeprecatedMigrationQueries<Old extends StorageSchema<any>> = {
135
+ [Key in keyof Old['collections']]: DeprecatedMigrationQueryMaker<
136
136
  Old['collections'][Key]
137
137
  >;
138
138
  };
139
139
 
140
- type MigrationMutations<New extends StorageSchema> = {
140
+ /** @deprecated */
141
+ type DeprecatedMigrationMutations<New extends StorageSchema> = {
141
142
  [Key in keyof New['collections']]: {
142
143
  put(
143
144
  document: StorageDocumentInit<New['collections'][Key]>,
@@ -145,12 +146,12 @@ type MigrationMutations<New extends StorageSchema> = {
145
146
  delete(primaryKey: string): Promise<void>;
146
147
  };
147
148
  };
148
-
149
- export interface MigrationTools<
149
+ /** @deprecated */
150
+ export interface DeprecatedMigrationTools<
150
151
  Old extends StorageSchema<any>,
151
152
  New extends StorageSchema<any>,
152
153
  > {
153
- migrate: MigrationRunner<Old, New>;
154
+ migrate: DeprecatedMigrationRunner<Old, New>;
154
155
  identity: <T>(val: T) => T;
155
156
  /**
156
157
  * @deprecated - default field values are automatically
@@ -159,8 +160,8 @@ export interface MigrationTools<
159
160
  * (old migrations can be updated!)
160
161
  */
161
162
  withDefaults: (collectionName: string, value: any) => any;
162
- queries: MigrationQueries<Old>;
163
- mutations: MigrationMutations<New>;
163
+ queries: DeprecatedMigrationQueries<Old>;
164
+ mutations: DeprecatedMigrationMutations<New>;
164
165
  info: {
165
166
  changedCollections: keyof Old['collections'][];
166
167
  addedCollections: keyof New['collections'][];
@@ -168,27 +169,21 @@ export interface MigrationTools<
168
169
  };
169
170
  }
170
171
 
171
- export interface MigrationEngine<
172
- Old extends StorageSchema,
173
- New extends StorageSchema,
174
- > {
175
- migrate: (
176
- collection: string,
177
- strategy: MigrationStrategy<any, any>,
178
- ) => Promise<void>;
179
- queries: MigrationQueries<Old>;
180
- mutations: MigrationMutations<New>;
172
+ export interface MigrationEngine {
173
+ migrate: (collection: string, strategy: (val: any) => any) => Promise<void>;
174
+ queries: MigrationQueries<any>;
175
+ mutations: MigrationMutations<any>;
181
176
  /** OIDs of any new documents created during the migration */
182
177
  newOids: string[];
183
178
  /** Promises that should be resolved before completing the migration */
184
179
  awaitables: Promise<any>[];
185
180
  log: (...messages: any[]) => void;
186
181
  }
187
-
188
- type MigrationProcedure<
182
+ /** @deprecated */
183
+ type DeprecatedMigrationProcedure<
189
184
  Old extends StorageSchema,
190
185
  New extends StorageSchema,
191
- > = (tools: MigrationTools<Old, New>) => Promise<void>;
186
+ > = (tools: DeprecatedMigrationTools<Old, New>) => Promise<void>;
192
187
 
193
188
  type EmptySchema = {
194
189
  version: 0;
@@ -199,14 +194,16 @@ const emptySchema: EmptySchema = {
199
194
  collections: {},
200
195
  };
201
196
 
197
+ /** @deprecated - use createMigration */
202
198
  export function migrate<Schema extends StorageSchema>(
203
199
  schema: Schema,
204
- procedure: MigrationProcedure<EmptySchema, Schema>,
200
+ procedure: DeprecatedMigrationProcedure<EmptySchema, Schema>,
205
201
  ): Migration<EmptySchema, Schema>;
202
+ /** @deprecated = use createMigration */
206
203
  export function migrate<Old extends StorageSchema, New extends StorageSchema>(
207
204
  oldSchema: Old,
208
205
  newSchema: New,
209
- procedure: MigrationProcedure<Old, New>,
206
+ procedure: DeprecatedMigrationProcedure<Old, New>,
210
207
  ): Migration<Old, New>;
211
208
  export function migrate(
212
209
  oldSchemaOrNewSchema: any,
@@ -294,7 +291,7 @@ export function migrate(
294
291
 
295
292
  return {
296
293
  version: newSchema.version,
297
- migrate: async (engine: MigrationEngine<any, any>) => {
294
+ migrate: async (engine: MigrationEngine) => {
298
295
  const migratedCollections: string[] = [];
299
296
  await procedure({
300
297
  migrate: async (collection: any, strategy: any) => {
@@ -378,7 +375,7 @@ export interface Migration<
378
375
  version: number;
379
376
  oldSchema: Old;
380
377
  newSchema: New;
381
- migrate: (engine: MigrationEngine<Old, New>) => Promise<void>;
378
+ migrate: (engine: MigrationEngine) => Promise<void>;
382
379
  /** Collections which are added in the new schema and not present in the old */
383
380
  addedCollections: string[];
384
381
  /** Collections which were removed from the old schema */
@@ -403,21 +400,12 @@ function getIndexes<Coll extends StorageCollectionSchema<any, any, any>>(
403
400
  collection: Coll | undefined,
404
401
  ): MigrationIndexDescription[] {
405
402
  if (!collection) return [];
406
- const fields = Object.keys(collection.fields)
407
- .filter((key) => collection.fields[key].indexed)
408
- .map((key) => ({
409
- name: key,
410
- multiEntry: collection.fields[key].type === 'array',
411
- synthetic: false,
412
- compound: false,
413
- }));
414
403
 
415
404
  return [
416
- ...fields,
417
- ...Object.keys(collection.synthetics || {}).map((key) => ({
405
+ ...Object.keys(collection.indexes || {}).map((key) => ({
418
406
  name: key,
419
407
  multiEntry: ['array', 'string[]', 'number[]', 'boolean[]'].includes(
420
- collection.synthetics[key].type,
408
+ collection.indexes[key].type,
421
409
  ),
422
410
  synthetic: true,
423
411
  compound: false,
@@ -426,7 +414,7 @@ function getIndexes<Coll extends StorageCollectionSchema<any, any, any>>(
426
414
  name: key,
427
415
  multiEntry: collection.compounds[key].of.some(
428
416
  (fieldName: string) =>
429
- (collection.fields[fieldName] || collection.synthetics[fieldName])
417
+ (collection.fields[fieldName] || collection.indexes[fieldName])
430
418
  .type === 'array',
431
419
  ),
432
420
  synthetic: false,
@@ -435,9 +423,11 @@ function getIndexes<Coll extends StorageCollectionSchema<any, any, any>>(
435
423
  ];
436
424
  }
437
425
 
426
+ /** @deprecated - use createMigration with no procedure function */
438
427
  export function createDefaultMigration(
439
428
  schema: StorageSchema,
440
429
  ): Migration<{ version: 0; collections: {} }>;
430
+ /** @deprecated - use createMigration with no procedure function */
441
431
  export function createDefaultMigration<Old extends StorageSchema>(
442
432
  oldSchema: Old,
443
433
  newSchema: StorageSchema,
@@ -461,3 +451,256 @@ export function createDefaultMigration(
461
451
  }
462
452
  });
463
453
  }
454
+
455
+ /** New, simpler type-safety migration tools */
456
+ type DocumentShape<Init = any, Snapshot = any> = {
457
+ init: Init;
458
+ snapshot: Snapshot;
459
+ };
460
+ type SchemaDocuments = Record<string, DocumentShape>;
461
+ type CollectionProcedure<OldSnapshot, NewInit> = {
462
+ (old: OldSnapshot): NewInit | Promise<NewInit>;
463
+ };
464
+ type MigrationQueries<Old extends SchemaDocuments> = {
465
+ [Key in keyof Old]: {
466
+ get(primaryKey: string): Promise<Old[Key]['snapshot'] | undefined>;
467
+ findOne(query: CollectionFilter): Promise<Old[Key]['snapshot'] | undefined>;
468
+ findAll(query?: CollectionFilter): Promise<Old[Key]['snapshot'][]>;
469
+ };
470
+ };
471
+ type MigrationMutations<New extends SchemaDocuments> = {
472
+ [Key in keyof New]: {
473
+ put(document: New[Key]['init']): Promise<New[Key]['snapshot']>;
474
+ delete(primaryKey: string): Promise<void>;
475
+ };
476
+ };
477
+ type MigrationTools<
478
+ Old extends SchemaDocuments,
479
+ New extends SchemaDocuments,
480
+ > = {
481
+ /**
482
+ * Process a change in a collection's documents by taking in each existing
483
+ * document and returning its new shape. This is typed so you can be
484
+ * confident the proper transformations are made.
485
+ */
486
+ migrate: <Collection extends keyof Old & keyof New>(
487
+ collection: Collection,
488
+ strategy: CollectionProcedure<
489
+ Old[Collection]['snapshot'],
490
+ New[Collection]['init']
491
+ >,
492
+ ) => Promise<void>;
493
+ mutations: MigrationMutations<New>;
494
+ queries: MigrationQueries<Old>;
495
+ info: {
496
+ changedCollections: (keyof Old & keyof New)[];
497
+ addedCollections: (keyof New)[];
498
+ removedCollections: (keyof Old)[];
499
+ };
500
+ };
501
+ type MigrationProcedure<
502
+ Old extends SchemaDocuments,
503
+ New extends SchemaDocuments,
504
+ > = {
505
+ (tools: MigrationTools<Old, New>): Promise<void>;
506
+ };
507
+ type InitialMigrationTools<New extends SchemaDocuments> = {
508
+ mutations: MigrationMutations<New>;
509
+ };
510
+ type InitialMigrationProcedure<New extends SchemaDocuments> = {
511
+ (tools: InitialMigrationTools<New>): Promise<void>;
512
+ };
513
+
514
+ export function createMigration<New extends SchemaDocuments>(
515
+ newSchema: StorageSchema,
516
+ procedure?: InitialMigrationProcedure<New>,
517
+ ): any;
518
+ export function createMigration<
519
+ Old extends SchemaDocuments,
520
+ New extends SchemaDocuments,
521
+ >(
522
+ oldSchema: StorageSchema,
523
+ newSchema: StorageSchema,
524
+ procedure?: MigrationProcedure<Old, New>,
525
+ ): any;
526
+ export function createMigration(
527
+ maybeOldSchema: StorageSchema,
528
+ maybeNewSchemaOrProcedure?:
529
+ | StorageSchema
530
+ | InitialMigrationProcedure<SchemaDocuments>,
531
+ maybeProcedure?: MigrationProcedure<SchemaDocuments, SchemaDocuments>,
532
+ ): any {
533
+ const isProcedureSecondArgument =
534
+ typeof maybeNewSchemaOrProcedure === 'function' ||
535
+ maybeNewSchemaOrProcedure === undefined;
536
+ const oldSchema = isProcedureSecondArgument ? emptySchema : maybeOldSchema;
537
+ const newSchema = isProcedureSecondArgument
538
+ ? maybeOldSchema
539
+ : maybeNewSchemaOrProcedure;
540
+ const procedure = isProcedureSecondArgument
541
+ ? maybeNewSchemaOrProcedure
542
+ : maybeProcedure;
543
+ assert(oldSchema, 'Invalid arguments to createMigration');
544
+ assert(newSchema, 'Invalid arguments to createMigration');
545
+ const {
546
+ changedCollections,
547
+ addedCollections,
548
+ removedCollections,
549
+ addedIndexes,
550
+ removedIndexes,
551
+ autoMigratedCollections,
552
+ autoMigration,
553
+ } = getMigrationInfo(oldSchema, newSchema);
554
+
555
+ return {
556
+ version: newSchema.version,
557
+ migrate: async (engine: MigrationEngine) => {
558
+ const migratedCollections: string[] = [];
559
+ const migrate = async (collection: any, strategy: any) => {
560
+ const auto = autoMigration(collection);
561
+ const wrapped = async (val: any) => {
562
+ const baseValue = await strategy(val);
563
+ // assign OID from original value in case user's strategy
564
+ // involves cloning
565
+ assignOid(baseValue, getOid(val));
566
+ const result = auto(baseValue);
567
+ return result;
568
+ };
569
+ // @ts-ignore
570
+ await engine.migrate(collection, wrapped);
571
+ migratedCollections.push(collection);
572
+ // since the user migrated this one and we wrap their
573
+ // strategy with auto-migration, we can remove it from
574
+ // the auto-migration list
575
+ autoMigratedCollections.delete(collection);
576
+ };
577
+ await procedure?.({
578
+ migrate,
579
+ info: {
580
+ changedCollections,
581
+ addedCollections,
582
+ removedCollections,
583
+ },
584
+ queries: engine.queries,
585
+ mutations: engine.mutations,
586
+ });
587
+
588
+ // mandatory migration of fields which had defaults added or
589
+ // fields removed but weren't migrated by the user
590
+
591
+ if (newSchema.version > 1) {
592
+ engine.log(
593
+ 'debug',
594
+ 'auto-migrating collections with new defaults',
595
+ autoMigratedCollections,
596
+ );
597
+ for (const name of autoMigratedCollections) {
598
+ await engine.migrate(name, autoMigration(name));
599
+ migratedCollections.push(name);
600
+ }
601
+
602
+ const unmigrated = changedCollections.filter(
603
+ (collection) => !migratedCollections.includes(collection),
604
+ );
605
+ if (unmigrated.length > 0) {
606
+ // TODO: does this deserve a full-on error?
607
+ console.error(
608
+ `Unmigrated changed collections from version ${oldSchema.version} to version ${newSchema.version}:`,
609
+ unmigrated,
610
+ );
611
+ }
612
+ }
613
+ },
614
+ removedCollections,
615
+ addedIndexes,
616
+ removedIndexes,
617
+ allCollections: Object.keys(newSchema.collections),
618
+ changedCollections,
619
+ addedCollections,
620
+ oldCollections: Object.keys(oldSchema.collections),
621
+ oldSchema,
622
+ newSchema,
623
+ };
624
+ }
625
+
626
+ // common tools
627
+ function getMigrationInfo(oldSchema: StorageSchema, newSchema: StorageSchema) {
628
+ const changedCollections: string[] = Object.keys(
629
+ newSchema.collections,
630
+ ).filter(
631
+ (key) =>
632
+ oldSchema.collections[key] &&
633
+ stableStringify(oldSchema.collections[key]) !==
634
+ stableStringify(newSchema.collections[key]),
635
+ );
636
+ const removedCollections: string[] = Object.keys(
637
+ oldSchema.collections,
638
+ ).filter((key) => !newSchema.collections[key]);
639
+ const addedCollections = Object.keys(newSchema.collections).filter(
640
+ (key) => !oldSchema.collections[key],
641
+ );
642
+ // collections which added one or more field defaults
643
+ const autoMigratedCollections = new Set<string>();
644
+ for (const collection of changedCollections) {
645
+ const oldFields = oldSchema.collections[collection].fields;
646
+ const newFields = newSchema.collections[collection].fields;
647
+ // a new default was added - we can auto-migrate it
648
+ if (
649
+ Object.keys(newFields).some(
650
+ (key) => !hasDefault(oldFields[key]) && hasDefault(newFields[key]),
651
+ )
652
+ ) {
653
+ autoMigratedCollections.add(collection);
654
+ }
655
+ // a field was removed - we can auto-migrate it
656
+ if (Object.keys(oldFields).some((key) => !newFields[key])) {
657
+ autoMigratedCollections.add(collection);
658
+ }
659
+ }
660
+
661
+ const addedIndexes: Record<string, MigrationIndexDescription[]> = {};
662
+ const removedIndexes: Record<string, MigrationIndexDescription[]> = {};
663
+ for (const changed of [...changedCollections, ...addedCollections]) {
664
+ const oldIndexes = getIndexes(oldSchema.collections[changed]);
665
+ const newIndexes = getIndexes(newSchema.collections[changed]);
666
+ const added = newIndexes.filter(
667
+ (index) => !oldIndexes.find((i) => i.name === index.name),
668
+ );
669
+ const removed = oldIndexes.filter(
670
+ (index) => !newIndexes.find((i) => i.name === index.name),
671
+ );
672
+ if (added.length > 0) {
673
+ addedIndexes[changed] = added;
674
+ // FIXME: don't o(n^2) this
675
+ if (changedCollections.includes(changed)) {
676
+ autoMigratedCollections.add(changed);
677
+ }
678
+ }
679
+ if (removed.length > 0) {
680
+ removedIndexes[changed] = removed;
681
+ // FIXME: don't o(n^2) this
682
+ if (changedCollections.includes(changed)) {
683
+ autoMigratedCollections.add(changed);
684
+ }
685
+ }
686
+ }
687
+
688
+ const withDefaults = (collectionName: string, val: any) => {
689
+ return addFieldDefaults(newSchema.collections[collectionName], val);
690
+ };
691
+ const autoMigration = (collectionName: string) => (val: any) => {
692
+ const collection = newSchema.collections[collectionName];
693
+ return addFieldDefaults(collection, removeExtraProperties(collection, val));
694
+ };
695
+
696
+ return {
697
+ changedCollections,
698
+ addedCollections,
699
+ removedCollections,
700
+ addedIndexes,
701
+ removedIndexes,
702
+ autoMigratedCollections,
703
+ withDefaults,
704
+ autoMigration,
705
+ };
706
+ }
package/src/oids.test.ts CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  createSubOid,
11
11
  decomposeOid,
12
12
  getOid,
13
- getOidRange,
13
+ getOidSubIdRange,
14
14
  getOidRoot,
15
15
  hasOid,
16
16
  maybeGetOidProperty,
@@ -262,11 +262,10 @@ describe('computing a range of oids for a whole object set', () => {
262
262
  return oid >= start && oid <= end;
263
263
  }
264
264
  it('should include the root object and any possible sub object oid', () => {
265
- const [start, end] = getOidRange('test/a');
266
- expect(start).toEqual('test/a');
265
+ const [start, end] = getOidSubIdRange('test/a');
266
+ expect(start).toEqual('test/a:');
267
267
  expect(end).toEqual('test/a:\uffff');
268
268
  expect(start < end).toBe(true);
269
- expect(isWithin('test/a', start, end)).toBe(true);
270
269
  expect(isWithin('test/a:0', start, end)).toBe(true);
271
270
  expect(isWithin('test/a:1', start, end)).toBe(true);
272
271
  expect(isWithin('test/a:zzzzzzzzzzzzzzzzzzzzzzz', start, end)).toBe(true);
@@ -274,6 +273,8 @@ describe('computing a range of oids for a whole object set', () => {
274
273
  expect(isWithin('test1/a', start, end)).toBe(false);
275
274
  expect(isWithin('test/b', start, end)).toBe(false);
276
275
  expect(isWithin('test/ ', start, end)).toBe(false);
276
+ expect(isWithin('test/a1', start, end)).toBe(false);
277
+ expect(isWithin('test/a1:3', start, end)).toBe(false);
277
278
  });
278
279
  });
279
280