@instantdb/core 0.22.86-experimental.separate-attrs.20122276424.1 → 0.22.86-experimental.split-store.20183617880.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 (65) hide show
  1. package/__tests__/src/Reactor.test.js +18 -11
  2. package/__tests__/src/{datalog.test.js → datalog.test.ts} +17 -5
  3. package/__tests__/src/{instaml.test.js → instaml.test.ts} +183 -119
  4. package/__tests__/src/instaql.bench.ts +34 -0
  5. package/__tests__/src/{instaql.test.js → instaql.test.ts} +342 -455
  6. package/__tests__/src/instaqlInference.test.js +13 -9
  7. package/__tests__/src/{store.test.js → store.test.ts} +188 -210
  8. package/dist/commonjs/Reactor.d.ts +23 -6
  9. package/dist/commonjs/Reactor.d.ts.map +1 -1
  10. package/dist/commonjs/Reactor.js +110 -45
  11. package/dist/commonjs/Reactor.js.map +1 -1
  12. package/dist/commonjs/SyncTable.d.ts +4 -1
  13. package/dist/commonjs/SyncTable.d.ts.map +1 -1
  14. package/dist/commonjs/SyncTable.js +35 -37
  15. package/dist/commonjs/SyncTable.js.map +1 -1
  16. package/dist/commonjs/instaml.d.ts +17 -4
  17. package/dist/commonjs/instaml.d.ts.map +1 -1
  18. package/dist/commonjs/instaml.js +105 -76
  19. package/dist/commonjs/instaml.js.map +1 -1
  20. package/dist/commonjs/instaql.d.ts +4 -3
  21. package/dist/commonjs/instaql.d.ts.map +1 -1
  22. package/dist/commonjs/instaql.js +65 -63
  23. package/dist/commonjs/instaql.js.map +1 -1
  24. package/dist/commonjs/reactorTypes.d.ts +29 -0
  25. package/dist/commonjs/reactorTypes.d.ts.map +1 -0
  26. package/dist/commonjs/reactorTypes.js +3 -0
  27. package/dist/commonjs/reactorTypes.js.map +1 -0
  28. package/dist/commonjs/store.d.ts +67 -25
  29. package/dist/commonjs/store.d.ts.map +1 -1
  30. package/dist/commonjs/store.js +170 -70
  31. package/dist/commonjs/store.js.map +1 -1
  32. package/dist/esm/Reactor.d.ts +23 -6
  33. package/dist/esm/Reactor.d.ts.map +1 -1
  34. package/dist/esm/Reactor.js +111 -46
  35. package/dist/esm/Reactor.js.map +1 -1
  36. package/dist/esm/SyncTable.d.ts +4 -1
  37. package/dist/esm/SyncTable.d.ts.map +1 -1
  38. package/dist/esm/SyncTable.js +35 -37
  39. package/dist/esm/SyncTable.js.map +1 -1
  40. package/dist/esm/instaml.d.ts +17 -4
  41. package/dist/esm/instaml.d.ts.map +1 -1
  42. package/dist/esm/instaml.js +102 -71
  43. package/dist/esm/instaml.js.map +1 -1
  44. package/dist/esm/instaql.d.ts +4 -3
  45. package/dist/esm/instaql.d.ts.map +1 -1
  46. package/dist/esm/instaql.js +65 -63
  47. package/dist/esm/instaql.js.map +1 -1
  48. package/dist/esm/reactorTypes.d.ts +29 -0
  49. package/dist/esm/reactorTypes.d.ts.map +1 -0
  50. package/dist/esm/reactorTypes.js +2 -0
  51. package/dist/esm/reactorTypes.js.map +1 -0
  52. package/dist/esm/store.d.ts +67 -25
  53. package/dist/esm/store.d.ts.map +1 -1
  54. package/dist/esm/store.js +167 -70
  55. package/dist/esm/store.js.map +1 -1
  56. package/dist/standalone/index.js +1580 -1392
  57. package/dist/standalone/index.umd.cjs +3 -3
  58. package/package.json +2 -2
  59. package/src/Reactor.js +154 -77
  60. package/src/SyncTable.ts +85 -45
  61. package/src/{instaml.js → instaml.ts} +196 -95
  62. package/src/instaql.ts +88 -62
  63. package/src/reactorTypes.ts +32 -0
  64. package/src/store.ts +248 -90
  65. package/__tests__/src/instaql.bench.js +0 -29
@@ -1,13 +1,33 @@
1
- import { allMapValues } from './store.ts';
1
+ import {
2
+ allMapValues,
3
+ AttrsStore,
4
+ AttrsStoreClass,
5
+ getAttrByFwdIdentName,
6
+ getAttrByReverseIdentName,
7
+ Store,
8
+ } from './store.ts';
2
9
  import { getOps, isLookup, parseLookup } from './instatx.ts';
3
10
  import { immutableRemoveUndefined } from './utils/object.js';
4
11
  import { coerceToDate } from './utils/dates.ts';
5
12
  import uuid from './utils/uuid.ts';
13
+ import {
14
+ EntitiesWithLinks,
15
+ IContainEntitiesAndLinks,
16
+ LinkDef,
17
+ } from './schemaTypes.ts';
18
+ import { InstantDBAttr, InstantDBIdent } from './attrTypes.ts';
19
+
20
+ export type AttrMapping = {
21
+ attrIdMap: Record<string, string>;
22
+ refSwapAttrIds: Set<string>;
23
+ };
24
+
25
+ type TXStep = any[];
6
26
 
7
27
  // Rewrites optimistic attrs with the attrs we get back from the server.
8
- export function rewriteStep(attrMapping, txStep) {
28
+ export function rewriteStep(attrMapping: AttrMapping, txStep: TXStep): TXStep {
9
29
  const { attrIdMap, refSwapAttrIds } = attrMapping;
10
- const rewritten = [];
30
+ const rewritten: TXStep = [];
11
31
  for (const part of txStep) {
12
32
  const newValue = attrIdMap[part];
13
33
 
@@ -35,22 +55,6 @@ export function rewriteStep(attrMapping, txStep) {
35
55
  return rewritten;
36
56
  }
37
57
 
38
- export function getAttrByFwdIdentName(attrs, inputEtype, inputIdentName) {
39
- return Object.values(attrs).find((attr) => {
40
- const [_id, etype, label] = attr['forward-identity'];
41
- return etype === inputEtype && label === inputIdentName;
42
- });
43
- }
44
-
45
- export function getAttrByReverseIdentName(attrs, inputEtype, inputIdentName) {
46
- return Object.values(attrs).find((attr) => {
47
- const revIdent = attr['reverse-identity'];
48
- if (!revIdent) return false;
49
- const [_id, etype, label] = revIdent;
50
- return etype === inputEtype && label === inputIdentName;
51
- });
52
- }
53
-
54
58
  function explodeLookupRef(eid) {
55
59
  if (Array.isArray(eid)) {
56
60
  return eid;
@@ -64,7 +68,7 @@ function explodeLookupRef(eid) {
64
68
  return entries[0];
65
69
  }
66
70
 
67
- function isRefLookupIdent(attrs, etype, identName) {
71
+ function isRefLookupIdent(attrs: AttrsStore, etype: string, identName: string) {
68
72
  return (
69
73
  identName.indexOf('.') !== -1 &&
70
74
  // attr names can have `.` in them, so use the attr we find with a `.`
@@ -82,7 +86,11 @@ function extractRefLookupFwdName(identName) {
82
86
  return fwdName;
83
87
  }
84
88
 
85
- function lookupIdentToAttr(attrs, etype, identName) {
89
+ function lookupIdentToAttr(
90
+ attrs: AttrsStore,
91
+ etype: string,
92
+ identName: string,
93
+ ) {
86
94
  if (!isRefLookupIdent(attrs, etype, identName)) {
87
95
  return getAttrByFwdIdentName(attrs, etype, identName);
88
96
  }
@@ -109,7 +117,7 @@ function lookupPairOfEid(eid) {
109
117
  : explodeLookupRef(eid);
110
118
  }
111
119
 
112
- function extractLookup(attrs, etype, eid) {
120
+ function extractLookup(attrs: AttrsStore, etype: string, eid: string) {
113
121
  const lookupPair = lookupPairOfEid(eid);
114
122
 
115
123
  if (lookupPair === null) {
@@ -124,7 +132,12 @@ function extractLookup(attrs, etype, eid) {
124
132
  return [attr.id, value];
125
133
  }
126
134
 
127
- function withIdAttrForLookup(attrs, etype, eidA, txSteps) {
135
+ function withIdAttrForLookup(
136
+ attrs: AttrsStore,
137
+ etype: string,
138
+ eidA: string,
139
+ txSteps: TXStep[],
140
+ ) {
128
141
  const lookup = extractLookup(attrs, etype, eidA);
129
142
  if (!Array.isArray(lookup)) {
130
143
  return txSteps;
@@ -132,63 +145,72 @@ function withIdAttrForLookup(attrs, etype, eidA, txSteps) {
132
145
  const idTuple = [
133
146
  'add-triple',
134
147
  lookup,
135
- getAttrByFwdIdentName(attrs, etype, 'id').id,
148
+ getAttrByFwdIdentName(attrs, etype, 'id')?.id,
136
149
  lookup,
137
150
  ];
138
151
  return [idTuple].concat(txSteps);
139
152
  }
140
153
 
141
- function expandLink({ attrs }, [etype, eidA, obj]) {
154
+ function expandLink({ attrsStore }: Ctx, [etype, eidA, obj]) {
142
155
  const addTriples = Object.entries(obj).flatMap(([label, eidOrEids]) => {
143
156
  const eids = Array.isArray(eidOrEids) ? eidOrEids : [eidOrEids];
144
- const fwdAttr = getAttrByFwdIdentName(attrs, etype, label);
145
- const revAttr = getAttrByReverseIdentName(attrs, etype, label);
157
+ const fwdAttr = getAttrByFwdIdentName(attrsStore, etype, label);
158
+ const revAttr = getAttrByReverseIdentName(attrsStore, etype, label);
146
159
  return eids.map((eidB) => {
147
160
  const txStep = fwdAttr
148
161
  ? [
149
162
  'add-triple',
150
- extractLookup(attrs, etype, eidA),
163
+ extractLookup(attrsStore, etype, eidA),
151
164
  fwdAttr.id,
152
- extractLookup(attrs, fwdAttr['reverse-identity'][1], eidB),
165
+ // XXX: Better error here
166
+ extractLookup(attrsStore, fwdAttr!['reverse-identity']![1], eidB),
153
167
  ]
154
168
  : [
155
169
  'add-triple',
156
- extractLookup(attrs, revAttr['forward-identity'][1], eidB),
157
- revAttr.id,
158
- extractLookup(attrs, etype, eidA),
170
+ // XXX: Better error here
171
+ extractLookup(attrsStore, revAttr!['forward-identity']![1], eidB),
172
+ revAttr?.id,
173
+ extractLookup(attrsStore, etype, eidA),
159
174
  ];
160
175
  return txStep;
161
176
  });
162
177
  });
163
- return withIdAttrForLookup(attrs, etype, eidA, addTriples);
178
+ return withIdAttrForLookup(attrsStore, etype, eidA, addTriples);
164
179
  }
165
180
 
166
- function expandUnlink({ attrs }, [etype, eidA, obj]) {
181
+ function expandUnlink({ attrsStore }: Ctx, [etype, eidA, obj]) {
167
182
  const retractTriples = Object.entries(obj).flatMap(([label, eidOrEids]) => {
168
183
  const eids = Array.isArray(eidOrEids) ? eidOrEids : [eidOrEids];
169
- const fwdAttr = getAttrByFwdIdentName(attrs, etype, label);
170
- const revAttr = getAttrByReverseIdentName(attrs, etype, label);
184
+ const fwdAttr = getAttrByFwdIdentName(attrsStore, etype, label);
185
+ const revAttr = getAttrByReverseIdentName(attrsStore, etype, label);
171
186
  return eids.map((eidB) => {
172
187
  const txStep = fwdAttr
173
188
  ? [
174
189
  'retract-triple',
175
- extractLookup(attrs, etype, eidA),
190
+ extractLookup(attrsStore, etype, eidA),
176
191
  fwdAttr.id,
177
- extractLookup(attrs, fwdAttr['reverse-identity'][1], eidB),
192
+ // XXX: Better error here
193
+ extractLookup(attrsStore, fwdAttr!['reverse-identity']![1], eidB),
178
194
  ]
179
195
  : [
180
196
  'retract-triple',
181
- extractLookup(attrs, revAttr['forward-identity'][1], eidB),
182
- revAttr.id,
183
- extractLookup(attrs, etype, eidA),
197
+ // XXX: Better error here
198
+ extractLookup(attrsStore, revAttr!['forward-identity'][1], eidB),
199
+ revAttr!.id,
200
+ extractLookup(attrsStore, etype, eidA),
184
201
  ];
185
202
  return txStep;
186
203
  });
187
204
  });
188
- return withIdAttrForLookup(attrs, etype, eidA, retractTriples);
205
+ return withIdAttrForLookup(attrsStore, etype, eidA, retractTriples);
189
206
  }
190
207
 
191
- function checkEntityExists(stores, etype, eid) {
208
+ function checkEntityExists(
209
+ stores: (Store | undefined)[] | undefined,
210
+ attrsStore: AttrsStore,
211
+ etype: string,
212
+ eid: string,
213
+ ) {
192
214
  if (Array.isArray(eid)) {
193
215
  // lookup ref
194
216
  const [entity_a, entity_v] = eid;
@@ -209,7 +231,7 @@ function checkEntityExists(stores, etype, eid) {
209
231
  const av = store?.eav.get(eid);
210
232
  if (av) {
211
233
  for (const attr_id of av.keys()) {
212
- if (store.attrs[attr_id]['forward-identity'][1] == etype) {
234
+ if (attrsStore.getAttr(attr_id)?.['forward-identity'][1] == etype) {
213
235
  return true;
214
236
  }
215
237
  }
@@ -219,26 +241,34 @@ function checkEntityExists(stores, etype, eid) {
219
241
  return false;
220
242
  }
221
243
 
222
- function convertOpts({ stores, attrs }, [etype, eid, obj_, opts]) {
244
+ type Ctx = {
245
+ stores?: (Store | undefined)[];
246
+ attrsStore: AttrsStore;
247
+ schema?: Schema;
248
+ useDateObjects?: boolean | null;
249
+ };
250
+
251
+ function convertOpts({ stores, attrsStore }: Ctx, [etype, eid, obj_, opts]) {
223
252
  return opts?.upsert === false
224
253
  ? { mode: 'update' }
225
254
  : opts?.upsert === true
226
255
  ? null
227
- : checkEntityExists(stores, etype, eid)
256
+ : checkEntityExists(stores, attrsStore, etype, eid)
228
257
  ? { mode: 'update' }
229
258
  : null; // auto mode chooses between update and upsert, not update and create, just in case
230
259
  }
231
260
 
232
- function expandCreate(ctx, step) {
233
- const { stores, attrs } = ctx;
261
+ function expandCreate(ctx: Ctx, step) {
262
+ const { attrsStore } = ctx;
234
263
  const [etype, eid, obj_, opts] = step;
235
264
  const obj = immutableRemoveUndefined(obj_);
236
- const lookup = extractLookup(attrs, etype, eid);
265
+ const lookup = extractLookup(attrsStore, etype, eid);
237
266
  // id first so that we don't clobber updates on the lookup field
238
267
  const attrTuples = [['id', lookup]]
239
268
  .concat(Object.entries(obj))
240
- .map(([identName, value]) => {
241
- const attr = getAttrByFwdIdentName(attrs, etype, identName);
269
+ .map(([identName, value]: [string, any]) => {
270
+ // XXX: missing attr?
271
+ const attr = getAttrByFwdIdentName(attrsStore, etype, identName)!;
242
272
 
243
273
  if (attr['checked-data-type'] === 'date' && ctx.useDateObjects) {
244
274
  value = coerceToDate(value);
@@ -249,17 +279,17 @@ function expandCreate(ctx, step) {
249
279
  return attrTuples;
250
280
  }
251
281
 
252
- function expandUpdate(ctx, step) {
253
- const { stores, attrs } = ctx;
282
+ function expandUpdate(ctx: Ctx, step) {
283
+ const { attrsStore } = ctx;
254
284
  const [etype, eid, obj_, opts] = step;
255
285
  const obj = immutableRemoveUndefined(obj_);
256
- const lookup = extractLookup(attrs, etype, eid);
286
+ const lookup = extractLookup(attrsStore, etype, eid);
257
287
  const serverOpts = convertOpts(ctx, [etype, lookup, obj_, opts]);
258
288
  // id first so that we don't clobber updates on the lookup field
259
289
  const attrTuples = [['id', lookup]]
260
290
  .concat(Object.entries(obj))
261
- .map(([identName, value]) => {
262
- const attr = getAttrByFwdIdentName(attrs, etype, identName);
291
+ .map(([identName, value]: [string, any]) => {
292
+ const attr = getAttrByFwdIdentName(attrsStore, etype, identName)!;
263
293
 
264
294
  if (attr['checked-data-type'] === 'date' && ctx.useDateObjects) {
265
295
  value = coerceToDate(value);
@@ -276,19 +306,19 @@ function expandUpdate(ctx, step) {
276
306
  return attrTuples;
277
307
  }
278
308
 
279
- function expandDelete({ attrs }, [etype, eid]) {
280
- const lookup = extractLookup(attrs, etype, eid);
309
+ function expandDelete({ attrsStore }: Ctx, [etype, eid]) {
310
+ const lookup = extractLookup(attrsStore, etype, eid);
281
311
  return [['delete-entity', lookup, etype]];
282
312
  }
283
313
 
284
- function expandDeepMerge(ctx, step) {
285
- const { stores, attrs } = ctx;
314
+ function expandDeepMerge(ctx: Ctx, step) {
315
+ const { attrsStore } = ctx;
286
316
  const [etype, eid, obj_, opts] = step;
287
317
  const obj = immutableRemoveUndefined(obj_);
288
- const lookup = extractLookup(attrs, etype, eid);
318
+ const lookup = extractLookup(attrsStore, etype, eid);
289
319
  const serverOpts = convertOpts(ctx, [etype, lookup, obj_, opts]);
290
320
  const attrTuples = Object.entries(obj).map(([identName, value]) => {
291
- const attr = getAttrByFwdIdentName(attrs, etype, identName);
321
+ const attr = getAttrByFwdIdentName(attrsStore, etype, identName)!;
292
322
  return [
293
323
  'deep-merge-triple',
294
324
  lookup,
@@ -301,7 +331,7 @@ function expandDeepMerge(ctx, step) {
301
331
  const idTuple = [
302
332
  'add-triple',
303
333
  lookup,
304
- getAttrByFwdIdentName(attrs, etype, 'id').id,
334
+ getAttrByFwdIdentName(attrsStore, etype, 'id')!.id,
305
335
  lookup,
306
336
  ...(serverOpts ? [serverOpts] : []),
307
337
  ];
@@ -310,8 +340,8 @@ function expandDeepMerge(ctx, step) {
310
340
  return [idTuple].concat(attrTuples);
311
341
  }
312
342
 
313
- function expandRuleParams({ attrs }, [etype, eid, ruleParams]) {
314
- const lookup = extractLookup(attrs, etype, eid);
343
+ function expandRuleParams({ attrsStore }: Ctx, [etype, eid, ruleParams]) {
344
+ const lookup = extractLookup(attrsStore, etype, eid);
315
345
  return [['rule-params', lookup, etype, ruleParams]];
316
346
  }
317
347
 
@@ -325,7 +355,7 @@ function removeIdFromArgs(step) {
325
355
  return [op, etype, eid, newObj, ...(opts ? [opts] : [])];
326
356
  }
327
357
 
328
- function toTxSteps(ctx, step) {
358
+ function toTxSteps(ctx: Ctx, step) {
329
359
  const [action, ...args] = removeIdFromArgs(step);
330
360
  switch (action) {
331
361
  case 'merge':
@@ -398,8 +428,12 @@ function createObjectAttr(schema, etype, label, props) {
398
428
  };
399
429
  }
400
430
 
401
- function findSchemaLink(schema, etype, label) {
402
- const found = Object.values(schema.links).find((x) => {
431
+ type Link = LinkDef<any, any, any, any, any, any, any>;
432
+ type Schema = IContainEntitiesAndLinks<any, any>;
433
+
434
+ function findSchemaLink(schema: Schema, etype, label): Link | undefined {
435
+ const links: Link[] = Object.values(schema.links);
436
+ const found = links.find((x: Link) => {
403
437
  return (
404
438
  (x.forward.on === etype && x.forward.label === label) ||
405
439
  (x.reverse.on === etype && x.reverse.label === label)
@@ -408,7 +442,7 @@ function findSchemaLink(schema, etype, label) {
408
442
  return found;
409
443
  }
410
444
 
411
- function refPropsFromSchema(schema, etype, label) {
445
+ function refPropsFromSchema(schema: Schema, etype, label) {
412
446
  const found = findSchemaLink(schema, etype, label);
413
447
  if (!found) {
414
448
  throw new Error(`Couldn't find the link ${etype}.${label} in your schema`);
@@ -424,18 +458,26 @@ function refPropsFromSchema(schema, etype, label) {
424
458
  };
425
459
  }
426
460
 
427
- function createRefAttr(schema, etype, label, props) {
461
+ function createRefAttr(
462
+ schema: Schema | undefined,
463
+ etype: string,
464
+ label: string,
465
+ props?: Partial<InstantDBAttr> | undefined,
466
+ ): InstantDBAttr {
428
467
  const schemaRefProps = schema
429
468
  ? refPropsFromSchema(schema, etype, label)
430
469
  : null;
431
470
  const attrId = uuid();
432
- const fwdIdent = [uuid(), etype, label];
433
- const revIdent = [uuid(), label, etype];
471
+ const fwdIdent: InstantDBIdent = [uuid(), etype, label];
472
+ const revIdent: InstantDBIdent = [uuid(), label, etype];
434
473
  return {
435
474
  id: attrId,
475
+ // @ts-ignore: ts thinks it's any[]
436
476
  'forward-identity': fwdIdent,
477
+ // @ts-ignore: ts thinks it's any[]
437
478
  'reverse-identity': revIdent,
438
479
  'value-type': 'ref',
480
+ // @ts-ignore: ts thinks it's type string
439
481
  cardinality: 'many',
440
482
  'unique?': false,
441
483
  'index?': false,
@@ -459,11 +501,14 @@ const SUPPORTS_LOOKUP_ACTIONS = new Set([
459
501
  'ruleParams',
460
502
  ]);
461
503
 
462
- const lookupProps = { 'unique?': true, 'index?': true };
463
- const refLookupProps = { ...lookupProps, cardinality: 'one' };
504
+ const lookupProps: Partial<InstantDBAttr> = { 'unique?': true, 'index?': true };
505
+ const refLookupProps: Partial<InstantDBAttr> = {
506
+ ...lookupProps,
507
+ cardinality: 'one',
508
+ };
464
509
 
465
510
  function lookupPairsOfOp(op) {
466
- const res = [];
511
+ const res: { etype: string; lookupPair: any; linkLabel?: string }[] = [];
467
512
  const [action, etype, eid, obj] = op;
468
513
  if (!SUPPORTS_LOOKUP_ACTIONS.has(action)) {
469
514
  return res;
@@ -491,24 +536,72 @@ function lookupPairsOfOp(op) {
491
536
  return res;
492
537
  }
493
538
 
494
- function createMissingAttrs({ attrs: existingAttrs, schema }, ops) {
495
- const [addedIds, attrs, addOps] = [new Set(), { ...existingAttrs }, []];
496
- function addAttr(attr) {
497
- attrs[attr.id] = attr;
539
+ function createMissingAttrs(
540
+ { attrsStore, schema }: Ctx,
541
+ ops,
542
+ ): [AttrsStore, TXStep[]] {
543
+ const addedIds = new Set();
544
+ const localAttrs: InstantDBAttr[] = [];
545
+ const addOps: TXStep[] = [];
546
+
547
+ function attrByFwdIdent(etype, label): InstantDBAttr | undefined {
548
+ return (
549
+ getAttrByFwdIdentName(attrsStore, etype, label) ||
550
+ localAttrs.find(
551
+ (x) =>
552
+ x['forward-identity'][1] === etype &&
553
+ x['forward-identity'][2] === label,
554
+ )
555
+ );
556
+ }
557
+
558
+ function attrByRevIdent(etype, label): InstantDBAttr | undefined {
559
+ return (
560
+ getAttrByReverseIdentName(attrsStore, etype, label) ||
561
+ localAttrs.find(
562
+ (x) =>
563
+ x['reverse-identity']?.[1] === etype &&
564
+ x['reverse-identity']?.[2] === label,
565
+ )
566
+ );
567
+ }
568
+
569
+ function addAttr(attr: InstantDBAttr) {
570
+ localAttrs.push(attr);
498
571
  addOps.push(['add-attr', attr]);
499
572
  addedIds.add(attr.id);
500
573
  }
501
- function addUnsynced(attr) {
502
- if (attr?.isUnsynced && !addedIds.has(attr.id)) {
574
+ function addUnsynced(
575
+ attr:
576
+ | (InstantDBAttr & { isUnsynced?: boolean })
577
+ | InstantDBAttr
578
+ | undefined,
579
+ ) {
580
+ if (
581
+ attr &&
582
+ 'isUnsynced' in attr &&
583
+ attr.isUnsynced &&
584
+ !addedIds.has(attr.id)
585
+ ) {
586
+ localAttrs.push(attr);
503
587
  addOps.push(['add-attr', attr]);
504
588
  addedIds.add(attr.id);
505
589
  }
506
590
  }
507
591
 
592
+ function isRefLookupIdentLocal(etype: string, identName: string) {
593
+ return (
594
+ identName.indexOf('.') !== -1 &&
595
+ // attr names can have `.` in them, so use the attr we find with a `.`
596
+ // before assuming it's a ref lookup.
597
+ !attrByFwdIdent(etype, identName)
598
+ );
599
+ }
600
+
508
601
  // Adds attrs needed for a ref lookup
509
602
  function addForRef(etype, label) {
510
- const fwdAttr = getAttrByFwdIdentName(attrs, etype, label);
511
- const revAttr = getAttrByReverseIdentName(attrs, etype, label);
603
+ const fwdAttr = attrByFwdIdent(etype, label);
604
+ const revAttr = attrByRevIdent(etype, label);
512
605
  addUnsynced(fwdAttr);
513
606
  addUnsynced(revAttr);
514
607
  if (!fwdAttr && !revAttr) {
@@ -530,18 +623,18 @@ function createMissingAttrs({ attrs: existingAttrs, schema }, ops) {
530
623
 
531
624
  // Figure out the link etype so we can make sure we have the attrs
532
625
  // for the link lookup
533
- const fwdAttr = getAttrByFwdIdentName(attrs, etype, linkLabel);
534
- const revAttr = getAttrByReverseIdentName(attrs, etype, linkLabel);
626
+ const fwdAttr = attrByFwdIdent(etype, linkLabel);
627
+ const revAttr = attrByRevIdent(etype, linkLabel);
535
628
  addUnsynced(fwdAttr);
536
629
  addUnsynced(revAttr);
537
630
  const linkEtype =
538
631
  fwdAttr?.['reverse-identity']?.[1] ||
539
632
  revAttr?.['forward-identity']?.[1] ||
540
633
  linkLabel;
541
- if (isRefLookupIdent(attrs, linkEtype, identName)) {
634
+ if (isRefLookupIdentLocal(linkEtype, identName)) {
542
635
  addForRef(linkEtype, extractRefLookupFwdName(identName));
543
636
  } else {
544
- const attr = getAttrByFwdIdentName(attrs, linkEtype, identName);
637
+ const attr = attrByFwdIdent(linkEtype, identName);
545
638
  if (!attr) {
546
639
  addAttr(
547
640
  createObjectAttr(schema, linkEtype, identName, lookupProps),
@@ -549,10 +642,10 @@ function createMissingAttrs({ attrs: existingAttrs, schema }, ops) {
549
642
  }
550
643
  addUnsynced(attr);
551
644
  }
552
- } else if (isRefLookupIdent(attrs, etype, identName)) {
645
+ } else if (isRefLookupIdentLocal(etype, identName)) {
553
646
  addForRef(etype, extractRefLookupFwdName(identName));
554
647
  } else {
555
- const attr = getAttrByFwdIdentName(attrs, etype, identName);
648
+ const attr = attrByFwdIdent(etype, identName);
556
649
  if (!attr) {
557
650
  addAttr(createObjectAttr(schema, etype, identName, lookupProps));
558
651
  }
@@ -565,14 +658,14 @@ function createMissingAttrs({ attrs: existingAttrs, schema }, ops) {
565
658
  for (const op of ops) {
566
659
  const [action, etype, eid, obj] = op;
567
660
  if (OBJ_ACTIONS.has(action)) {
568
- const idAttr = getAttrByFwdIdentName(attrs, etype, 'id');
661
+ const idAttr = attrByFwdIdent(etype, 'id');
569
662
  addUnsynced(idAttr);
570
663
  if (!idAttr) {
571
664
  addAttr(createObjectAttr(schema, etype, 'id', { 'unique?': true }));
572
665
  }
573
666
 
574
667
  for (const label of Object.keys(obj)) {
575
- const fwdAttr = getAttrByFwdIdentName(attrs, etype, label);
668
+ const fwdAttr = attrByFwdIdent(etype, label);
576
669
  addUnsynced(fwdAttr);
577
670
  if (UPDATE_ACTIONS.has(action)) {
578
671
  if (!fwdAttr) {
@@ -587,7 +680,7 @@ function createMissingAttrs({ attrs: existingAttrs, schema }, ops) {
587
680
  }
588
681
  }
589
682
  if (REF_ACTIONS.has(action)) {
590
- const revAttr = getAttrByReverseIdentName(attrs, etype, label);
683
+ const revAttr = attrByRevIdent(etype, label);
591
684
  if (!fwdAttr && !revAttr) {
592
685
  addAttr(createRefAttr(schema, etype, label));
593
686
  }
@@ -596,14 +689,22 @@ function createMissingAttrs({ attrs: existingAttrs, schema }, ops) {
596
689
  }
597
690
  }
598
691
  }
599
- return [attrs, addOps];
692
+
693
+ if (localAttrs.length) {
694
+ const nextAttrs = { ...attrsStore.attrs };
695
+ for (const attr of localAttrs) {
696
+ nextAttrs[attr.id] = attr;
697
+ }
698
+ return [new AttrsStoreClass(nextAttrs, attrsStore.linkIndex), addOps];
699
+ }
700
+ return [attrsStore, addOps];
600
701
  }
601
702
 
602
- export function transform(ctx, inputChunks) {
703
+ export function transform(ctx: Ctx, inputChunks) {
603
704
  const chunks = Array.isArray(inputChunks) ? inputChunks : [inputChunks];
604
705
  const ops = chunks.flatMap((tx) => getOps(tx));
605
706
  const [newAttrs, addAttrTxSteps] = createMissingAttrs(ctx, ops);
606
- const newCtx = { ...ctx, attrs: newAttrs };
707
+ const newCtx = { ...ctx, attrsStore: newAttrs };
607
708
  const txSteps = ops.flatMap((op) => toTxSteps(newCtx, op));
608
709
  return [...addAttrTxSteps, ...txSteps];
609
710
  }