@verdant-web/common 1.14.1 → 1.15.1

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 (70) hide show
  1. package/dist/cjs/index.d.ts +1 -1
  2. package/dist/cjs/index.js +2 -1
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/cjs/indexes.d.ts +3 -1
  5. package/dist/cjs/indexes.js +26 -3
  6. package/dist/cjs/indexes.js.map +1 -1
  7. package/dist/cjs/migration.d.ts +85 -18
  8. package/dist/cjs/migration.js +137 -13
  9. package/dist/cjs/migration.js.map +1 -1
  10. package/dist/cjs/oids.d.ts +3 -2
  11. package/dist/cjs/oids.js +14 -5
  12. package/dist/cjs/oids.js.map +1 -1
  13. package/dist/cjs/oids.test.js +19 -3
  14. package/dist/cjs/oids.test.js.map +1 -1
  15. package/dist/cjs/schema/fields.d.ts +2 -0
  16. package/dist/cjs/schema/fields.js +29 -1
  17. package/dist/cjs/schema/fields.js.map +1 -1
  18. package/dist/cjs/schema/index.d.ts +2 -2
  19. package/dist/cjs/schema/index.js +25 -2
  20. package/dist/cjs/schema/index.js.map +1 -1
  21. package/dist/cjs/schema/types/collection.d.ts +12 -40
  22. package/dist/cjs/schema/types/compounds.d.ts +6 -7
  23. package/dist/cjs/schema/types/fields.d.ts +12 -2
  24. package/dist/cjs/schema/types/shapes.d.ts +6 -6
  25. package/dist/cjs/schema/types/synthetics.d.ts +11 -2
  26. package/dist/cjs/schema/types.d.ts +3 -4
  27. package/dist/esm/index.d.ts +1 -1
  28. package/dist/esm/index.js +1 -1
  29. package/dist/esm/index.js.map +1 -1
  30. package/dist/esm/indexes.d.ts +3 -1
  31. package/dist/esm/indexes.js +23 -2
  32. package/dist/esm/indexes.js.map +1 -1
  33. package/dist/esm/migration.d.ts +85 -18
  34. package/dist/esm/migration.js +136 -13
  35. package/dist/esm/migration.js.map +1 -1
  36. package/dist/esm/oids.d.ts +3 -2
  37. package/dist/esm/oids.js +11 -3
  38. package/dist/esm/oids.js.map +1 -1
  39. package/dist/esm/oids.test.js +20 -4
  40. package/dist/esm/oids.test.js.map +1 -1
  41. package/dist/esm/replica.js +1 -1
  42. package/dist/esm/replica.js.map +1 -1
  43. package/dist/esm/schema/fields.d.ts +2 -0
  44. package/dist/esm/schema/fields.js +26 -0
  45. package/dist/esm/schema/fields.js.map +1 -1
  46. package/dist/esm/schema/index.d.ts +2 -2
  47. package/dist/esm/schema/index.js +25 -2
  48. package/dist/esm/schema/index.js.map +1 -1
  49. package/dist/esm/schema/types/collection.d.ts +12 -40
  50. package/dist/esm/schema/types/compounds.d.ts +6 -7
  51. package/dist/esm/schema/types/fields.d.ts +12 -2
  52. package/dist/esm/schema/types/shapes.d.ts +6 -6
  53. package/dist/esm/schema/types/synthetics.d.ts +11 -2
  54. package/dist/esm/schema/types.d.ts +3 -4
  55. package/dist/tsconfig-cjs.tsbuildinfo +1 -1
  56. package/dist/tsconfig.tsbuildinfo +1 -1
  57. package/package.json +3 -2
  58. package/src/index.ts +1 -0
  59. package/src/indexes.ts +51 -12
  60. package/src/migration.ts +300 -57
  61. package/src/oids.test.ts +20 -4
  62. package/src/oids.ts +12 -3
  63. package/src/schema/fields.ts +18 -0
  64. package/src/schema/index.ts +30 -3
  65. package/src/schema/types/collection.ts +14 -112
  66. package/src/schema/types/compounds.ts +29 -18
  67. package/src/schema/types/fields.ts +12 -2
  68. package/src/schema/types/shapes.ts +13 -14
  69. package/src/schema/types/synthetics.ts +31 -2
  70. package/src/schema/types.ts +3 -5
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', false);
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,23 @@ 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);
278
+ });
279
+ it('should accommodate legacy dot style oids', () => {
280
+ const [start, end] = getOidSubIdRange('test/a.foo:barrrr', false);
281
+ expect(start).toEqual('test/a:');
282
+ expect(end).toEqual('test/a:\uffff');
283
+ expect(start < end).toBe(true);
284
+ expect(isWithin('test/a:0', start, end)).toBe(true);
285
+ expect(isWithin('test/a:1', start, end)).toBe(true);
286
+ expect(isWithin('test/a:zzzzzzzzzzzzzzzzzzzzzzz', start, end)).toBe(true);
287
+ expect(isWithin('test/a:\uffff', start, end)).toBe(true);
288
+ expect(isWithin('test1/a', start, end)).toBe(false);
289
+ expect(isWithin('test/b', start, end)).toBe(false);
290
+ expect(isWithin('test/ ', start, end)).toBe(false);
291
+ expect(isWithin('test/a1', start, end)).toBe(false);
292
+ expect(isWithin('test/a1:3', start, end)).toBe(false);
277
293
  });
278
294
  });
279
295
 
package/src/oids.ts CHANGED
@@ -437,11 +437,15 @@ export function getOidRoot(oid: ObjectIdentifier) {
437
437
 
438
438
  /**
439
439
  * Returns an inclusive range of OIDs that represent
440
- * a root object and all of its sub-objects.
440
+ * all of an OID's possible sub-objects.
441
441
  */
442
- export function getOidRange(oid: ObjectIdentifier) {
442
+ export function getOidSubIdRange(oid: ObjectIdentifier, legacyDot: boolean) {
443
443
  const root = getOidRoot(oid);
444
- return [root, `${root}${RANDOM_SEPARATOR}\uffff`];
444
+ const lastSubId = createSubOid(root, () => '\uffff');
445
+ if (legacyDot) {
446
+ return [`${root}.`, lastSubId];
447
+ }
448
+ return [`${root}${RANDOM_SEPARATOR}`, lastSubId];
445
449
  }
446
450
 
447
451
  export function getRoots(oids: ObjectIdentifier[]) {
@@ -490,3 +494,8 @@ function migrateForeignOid(parentOid: ObjectIdentifier, child: any) {
490
494
  );
491
495
  }
492
496
  }
497
+
498
+ export function isLegacyDotOid(oid: ObjectIdentifier) {
499
+ const partBeforeRandomSep = oid.split(RANDOM_SEPARATOR)[0];
500
+ return partBeforeRandomSep.includes('.');
501
+ }
@@ -8,6 +8,24 @@ export function isNullable(field: StorageFieldSchema) {
8
8
  return field.nullable;
9
9
  }
10
10
 
11
+ export function hasDefault(field: StorageFieldSchema | undefined) {
12
+ if (!field) return false;
13
+ if (field.type === 'map') return true;
14
+ if (field.type === 'array') return true;
15
+ if (field.type === 'file') return false;
16
+ if (field.type === 'object') return true;
17
+ return field.default !== undefined;
18
+ }
19
+
20
+ export function isIndexed(field: StorageFieldSchema) {
21
+ if (field.type === 'map') return false;
22
+ if (field.type === 'object') return false;
23
+ if (field.type === 'array') return false;
24
+ if (field.type === 'file') return false;
25
+ if (field.type === 'any') return false;
26
+ return field.indexed;
27
+ }
28
+
11
29
  export function addFieldDefaults(
12
30
  collection: StorageCollectionSchema,
13
31
  value: any,
@@ -1,8 +1,11 @@
1
+ import { isIndexed } from './fields.js';
1
2
  import {
2
3
  CollectionCompoundIndices,
3
4
  StorageCollectionSchema,
5
+ StorageDirectSyntheticSchema,
4
6
  StorageFieldsSchema,
5
7
  StorageSchema,
8
+ StorageSyntheticIndexSchema,
6
9
  StorageSyntheticIndices,
7
10
  } from './types.js';
8
11
 
@@ -10,15 +13,39 @@ export function collection<
10
13
  Fields extends StorageFieldsSchema,
11
14
  Synthetics extends StorageSyntheticIndices<Fields>,
12
15
  Compounds extends CollectionCompoundIndices<Fields, Synthetics>,
13
- >(input: StorageCollectionSchema<Fields, Synthetics, Compounds>) {
14
- return input;
16
+ >({
17
+ synthetics,
18
+ indexes,
19
+ ...input
20
+ }: StorageCollectionSchema<
21
+ Fields,
22
+ Synthetics,
23
+ Compounds
24
+ >): StorageCollectionSchema<Fields, Synthetics, Compounds> {
25
+ // back compat - copy synthetics in with indexes
26
+ const finalIndexes = { ...synthetics, ...indexes };
27
+ // add all indexed fields into the synthetic indices (back compat)
28
+ for (const [key, field] of Object.entries(input.fields)) {
29
+ if (isIndexed(field)) {
30
+ finalIndexes[key] = {
31
+ field: key,
32
+ } as StorageDirectSyntheticSchema<Fields>;
33
+ }
34
+ }
35
+ return {
36
+ ...input,
37
+ indexes: finalIndexes as Synthetics,
38
+ };
15
39
  }
16
40
 
17
41
  export function schema<
42
+ // Fields extends StorageFieldsSchema,
43
+ // Indexes extends StorageSyntheticIndices<Fields>,
44
+ // Compounds extends CollectionCompoundIndices<Fields, Indexes>,
18
45
  Schema extends StorageSchema<{
19
46
  [key: string]: StorageCollectionSchema<any, any, any>;
20
47
  }>,
21
- >(input: Schema) {
48
+ >(input: Schema): StorageSchema {
22
49
  return input;
23
50
  }
24
51