@instantdb/core 0.22.88 → 0.22.89-experimental.drewh-fix-export.20277749804.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} +215 -212
  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 +115 -82
  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 +177 -81
  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 +112 -77
  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 +174 -81
  55. package/dist/esm/store.js.map +1 -1
  56. package/dist/standalone/index.js +1605 -1415
  57. package/dist/standalone/index.umd.cjs +3 -3
  58. package/package.json +2 -2
  59. package/src/Reactor.js +152 -75
  60. package/src/SyncTable.ts +85 -45
  61. package/src/{instaml.js → instaml.ts} +201 -96
  62. package/src/instaql.ts +88 -62
  63. package/src/reactorTypes.ts +32 -0
  64. package/src/store.ts +257 -101
  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,76 @@ 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
+ // Uses `!` because if we get here, we should have created the attr if it doesn't
166
+ // already exist
167
+ extractLookup(attrsStore, fwdAttr['reverse-identity']![1], eidB),
153
168
  ]
154
169
  : [
155
170
  'add-triple',
156
- extractLookup(attrs, revAttr['forward-identity'][1], eidB),
157
- revAttr.id,
158
- extractLookup(attrs, etype, eidA),
171
+ // Uses `!` because if we get here, we should have created the attr if it doesn't
172
+ // already exist
173
+ extractLookup(attrsStore, revAttr!['forward-identity']![1], eidB),
174
+ revAttr?.id,
175
+ extractLookup(attrsStore, etype, eidA),
159
176
  ];
160
177
  return txStep;
161
178
  });
162
179
  });
163
- return withIdAttrForLookup(attrs, etype, eidA, addTriples);
180
+ return withIdAttrForLookup(attrsStore, etype, eidA, addTriples);
164
181
  }
165
182
 
166
- function expandUnlink({ attrs }, [etype, eidA, obj]) {
183
+ function expandUnlink({ attrsStore }: Ctx, [etype, eidA, obj]) {
167
184
  const retractTriples = Object.entries(obj).flatMap(([label, eidOrEids]) => {
168
185
  const eids = Array.isArray(eidOrEids) ? eidOrEids : [eidOrEids];
169
- const fwdAttr = getAttrByFwdIdentName(attrs, etype, label);
170
- const revAttr = getAttrByReverseIdentName(attrs, etype, label);
186
+ const fwdAttr = getAttrByFwdIdentName(attrsStore, etype, label);
187
+ const revAttr = getAttrByReverseIdentName(attrsStore, etype, label);
171
188
  return eids.map((eidB) => {
172
189
  const txStep = fwdAttr
173
190
  ? [
174
191
  'retract-triple',
175
- extractLookup(attrs, etype, eidA),
192
+ extractLookup(attrsStore, etype, eidA),
176
193
  fwdAttr.id,
177
- extractLookup(attrs, fwdAttr['reverse-identity'][1], eidB),
194
+ // Uses `!` because if we get here, we should have created the attr if it doesn't
195
+ // already exist
196
+ extractLookup(attrsStore, fwdAttr!['reverse-identity']![1], eidB),
178
197
  ]
179
198
  : [
180
199
  'retract-triple',
181
- extractLookup(attrs, revAttr['forward-identity'][1], eidB),
182
- revAttr.id,
183
- extractLookup(attrs, etype, eidA),
200
+ // Uses `!` because if we get here, we should have created the attr if it doesn't
201
+ // already exist
202
+ extractLookup(attrsStore, revAttr!['forward-identity'][1], eidB),
203
+ revAttr!.id,
204
+ extractLookup(attrsStore, etype, eidA),
184
205
  ];
185
206
  return txStep;
186
207
  });
187
208
  });
188
- return withIdAttrForLookup(attrs, etype, eidA, retractTriples);
209
+ return withIdAttrForLookup(attrsStore, etype, eidA, retractTriples);
189
210
  }
190
211
 
191
- function checkEntityExists(stores, etype, eid) {
212
+ function checkEntityExists(
213
+ stores: (Store | undefined)[] | undefined,
214
+ attrsStore: AttrsStore,
215
+ etype: string,
216
+ eid: string,
217
+ ) {
192
218
  if (Array.isArray(eid)) {
193
219
  // lookup ref
194
220
  const [entity_a, entity_v] = eid;
@@ -196,7 +222,7 @@ function checkEntityExists(stores, etype, eid) {
196
222
  const ev = store?.aev.get(entity_a);
197
223
  if (ev) {
198
224
  // This would be a lot more efficient with a ave index
199
- for (const [e_, a_, v] of allMapValues(ev, 2)) {
225
+ for (const [_e, _a, v] of allMapValues(ev, 2)) {
200
226
  if (v === entity_v) {
201
227
  return true;
202
228
  }
@@ -209,7 +235,7 @@ function checkEntityExists(stores, etype, eid) {
209
235
  const av = store?.eav.get(eid);
210
236
  if (av) {
211
237
  for (const attr_id of av.keys()) {
212
- if (store.attrs[attr_id]['forward-identity'][1] == etype) {
238
+ if (attrsStore.getAttr(attr_id)?.['forward-identity'][1] == etype) {
213
239
  return true;
214
240
  }
215
241
  }
@@ -219,26 +245,34 @@ function checkEntityExists(stores, etype, eid) {
219
245
  return false;
220
246
  }
221
247
 
222
- function convertOpts({ stores, attrs }, [etype, eid, obj_, opts]) {
248
+ type Ctx = {
249
+ stores?: (Store | undefined)[];
250
+ attrsStore: AttrsStore;
251
+ schema?: Schema;
252
+ useDateObjects?: boolean | null;
253
+ };
254
+
255
+ function convertOpts({ stores, attrsStore }: Ctx, [etype, eid, obj_, opts]) {
223
256
  return opts?.upsert === false
224
257
  ? { mode: 'update' }
225
258
  : opts?.upsert === true
226
259
  ? null
227
- : checkEntityExists(stores, etype, eid)
260
+ : checkEntityExists(stores, attrsStore, etype, eid)
228
261
  ? { mode: 'update' }
229
262
  : null; // auto mode chooses between update and upsert, not update and create, just in case
230
263
  }
231
264
 
232
- function expandCreate(ctx, step) {
233
- const { stores, attrs } = ctx;
265
+ function expandCreate(ctx: Ctx, step) {
266
+ const { attrsStore } = ctx;
234
267
  const [etype, eid, obj_, opts] = step;
235
268
  const obj = immutableRemoveUndefined(obj_);
236
- const lookup = extractLookup(attrs, etype, eid);
269
+ const lookup = extractLookup(attrsStore, etype, eid);
237
270
  // id first so that we don't clobber updates on the lookup field
238
271
  const attrTuples = [['id', lookup]]
239
272
  .concat(Object.entries(obj))
240
- .map(([identName, value]) => {
241
- const attr = getAttrByFwdIdentName(attrs, etype, identName);
273
+ .map(([identName, value]: [string, any]) => {
274
+ // Uses `!` because we should have optimistically created the attr if it doesn't exist
275
+ const attr = getAttrByFwdIdentName(attrsStore, etype, identName)!;
242
276
 
243
277
  if (attr['checked-data-type'] === 'date' && ctx.useDateObjects) {
244
278
  value = coerceToDate(value);
@@ -249,17 +283,17 @@ function expandCreate(ctx, step) {
249
283
  return attrTuples;
250
284
  }
251
285
 
252
- function expandUpdate(ctx, step) {
253
- const { stores, attrs } = ctx;
286
+ function expandUpdate(ctx: Ctx, step) {
287
+ const { attrsStore } = ctx;
254
288
  const [etype, eid, obj_, opts] = step;
255
289
  const obj = immutableRemoveUndefined(obj_);
256
- const lookup = extractLookup(attrs, etype, eid);
290
+ const lookup = extractLookup(attrsStore, etype, eid);
257
291
  const serverOpts = convertOpts(ctx, [etype, lookup, obj_, opts]);
258
292
  // id first so that we don't clobber updates on the lookup field
259
293
  const attrTuples = [['id', lookup]]
260
294
  .concat(Object.entries(obj))
261
- .map(([identName, value]) => {
262
- const attr = getAttrByFwdIdentName(attrs, etype, identName);
295
+ .map(([identName, value]: [string, any]) => {
296
+ const attr = getAttrByFwdIdentName(attrsStore, etype, identName)!;
263
297
 
264
298
  if (attr['checked-data-type'] === 'date' && ctx.useDateObjects) {
265
299
  value = coerceToDate(value);
@@ -276,19 +310,19 @@ function expandUpdate(ctx, step) {
276
310
  return attrTuples;
277
311
  }
278
312
 
279
- function expandDelete({ attrs }, [etype, eid]) {
280
- const lookup = extractLookup(attrs, etype, eid);
313
+ function expandDelete({ attrsStore }: Ctx, [etype, eid]) {
314
+ const lookup = extractLookup(attrsStore, etype, eid);
281
315
  return [['delete-entity', lookup, etype]];
282
316
  }
283
317
 
284
- function expandDeepMerge(ctx, step) {
285
- const { stores, attrs } = ctx;
318
+ function expandDeepMerge(ctx: Ctx, step) {
319
+ const { attrsStore } = ctx;
286
320
  const [etype, eid, obj_, opts] = step;
287
321
  const obj = immutableRemoveUndefined(obj_);
288
- const lookup = extractLookup(attrs, etype, eid);
322
+ const lookup = extractLookup(attrsStore, etype, eid);
289
323
  const serverOpts = convertOpts(ctx, [etype, lookup, obj_, opts]);
290
324
  const attrTuples = Object.entries(obj).map(([identName, value]) => {
291
- const attr = getAttrByFwdIdentName(attrs, etype, identName);
325
+ const attr = getAttrByFwdIdentName(attrsStore, etype, identName)!;
292
326
  return [
293
327
  'deep-merge-triple',
294
328
  lookup,
@@ -301,7 +335,7 @@ function expandDeepMerge(ctx, step) {
301
335
  const idTuple = [
302
336
  'add-triple',
303
337
  lookup,
304
- getAttrByFwdIdentName(attrs, etype, 'id').id,
338
+ getAttrByFwdIdentName(attrsStore, etype, 'id')!.id,
305
339
  lookup,
306
340
  ...(serverOpts ? [serverOpts] : []),
307
341
  ];
@@ -310,8 +344,8 @@ function expandDeepMerge(ctx, step) {
310
344
  return [idTuple].concat(attrTuples);
311
345
  }
312
346
 
313
- function expandRuleParams({ attrs }, [etype, eid, ruleParams]) {
314
- const lookup = extractLookup(attrs, etype, eid);
347
+ function expandRuleParams({ attrsStore }: Ctx, [etype, eid, ruleParams]) {
348
+ const lookup = extractLookup(attrsStore, etype, eid);
315
349
  return [['rule-params', lookup, etype, ruleParams]];
316
350
  }
317
351
 
@@ -325,7 +359,7 @@ function removeIdFromArgs(step) {
325
359
  return [op, etype, eid, newObj, ...(opts ? [opts] : [])];
326
360
  }
327
361
 
328
- function toTxSteps(ctx, step) {
362
+ function toTxSteps(ctx: Ctx, step) {
329
363
  const [action, ...args] = removeIdFromArgs(step);
330
364
  switch (action) {
331
365
  case 'merge':
@@ -398,8 +432,12 @@ function createObjectAttr(schema, etype, label, props) {
398
432
  };
399
433
  }
400
434
 
401
- function findSchemaLink(schema, etype, label) {
402
- const found = Object.values(schema.links).find((x) => {
435
+ type Link = LinkDef<any, any, any, any, any, any, any>;
436
+ type Schema = IContainEntitiesAndLinks<any, any>;
437
+
438
+ function findSchemaLink(schema: Schema, etype, label): Link | undefined {
439
+ const links: Link[] = Object.values(schema.links);
440
+ const found = links.find((x: Link) => {
403
441
  return (
404
442
  (x.forward.on === etype && x.forward.label === label) ||
405
443
  (x.reverse.on === etype && x.reverse.label === label)
@@ -408,7 +446,7 @@ function findSchemaLink(schema, etype, label) {
408
446
  return found;
409
447
  }
410
448
 
411
- function refPropsFromSchema(schema, etype, label) {
449
+ function refPropsFromSchema(schema: Schema, etype, label) {
412
450
  const found = findSchemaLink(schema, etype, label);
413
451
  if (!found) {
414
452
  throw new Error(`Couldn't find the link ${etype}.${label} in your schema`);
@@ -424,18 +462,26 @@ function refPropsFromSchema(schema, etype, label) {
424
462
  };
425
463
  }
426
464
 
427
- function createRefAttr(schema, etype, label, props) {
465
+ function createRefAttr(
466
+ schema: Schema | undefined,
467
+ etype: string,
468
+ label: string,
469
+ props?: Partial<InstantDBAttr> | undefined,
470
+ ): InstantDBAttr {
428
471
  const schemaRefProps = schema
429
472
  ? refPropsFromSchema(schema, etype, label)
430
473
  : null;
431
474
  const attrId = uuid();
432
- const fwdIdent = [uuid(), etype, label];
433
- const revIdent = [uuid(), label, etype];
475
+ const fwdIdent: InstantDBIdent = [uuid(), etype, label];
476
+ const revIdent: InstantDBIdent = [uuid(), label, etype];
434
477
  return {
435
478
  id: attrId,
479
+ // @ts-ignore: ts thinks it's any[]
436
480
  'forward-identity': fwdIdent,
481
+ // @ts-ignore: ts thinks it's any[]
437
482
  'reverse-identity': revIdent,
438
483
  'value-type': 'ref',
484
+ // @ts-ignore: ts thinks it's type string
439
485
  cardinality: 'many',
440
486
  'unique?': false,
441
487
  'index?': false,
@@ -459,11 +505,14 @@ const SUPPORTS_LOOKUP_ACTIONS = new Set([
459
505
  'ruleParams',
460
506
  ]);
461
507
 
462
- const lookupProps = { 'unique?': true, 'index?': true };
463
- const refLookupProps = { ...lookupProps, cardinality: 'one' };
508
+ const lookupProps: Partial<InstantDBAttr> = { 'unique?': true, 'index?': true };
509
+ const refLookupProps: Partial<InstantDBAttr> = {
510
+ ...lookupProps,
511
+ cardinality: 'one',
512
+ };
464
513
 
465
514
  function lookupPairsOfOp(op) {
466
- const res = [];
515
+ const res: { etype: string; lookupPair: any; linkLabel?: string }[] = [];
467
516
  const [action, etype, eid, obj] = op;
468
517
  if (!SUPPORTS_LOOKUP_ACTIONS.has(action)) {
469
518
  return res;
@@ -491,24 +540,72 @@ function lookupPairsOfOp(op) {
491
540
  return res;
492
541
  }
493
542
 
494
- function createMissingAttrs({ attrs: existingAttrs, schema }, ops) {
495
- const [addedIds, attrs, addOps] = [new Set(), { ...existingAttrs }, []];
496
- function addAttr(attr) {
497
- attrs[attr.id] = attr;
543
+ function createMissingAttrs(
544
+ { attrsStore, schema }: Ctx,
545
+ ops,
546
+ ): [AttrsStore, TXStep[]] {
547
+ const addedIds = new Set();
548
+ const localAttrs: InstantDBAttr[] = [];
549
+ const addOps: TXStep[] = [];
550
+
551
+ function attrByFwdIdent(etype, label): InstantDBAttr | undefined {
552
+ return (
553
+ getAttrByFwdIdentName(attrsStore, etype, label) ||
554
+ localAttrs.find(
555
+ (x) =>
556
+ x['forward-identity'][1] === etype &&
557
+ x['forward-identity'][2] === label,
558
+ )
559
+ );
560
+ }
561
+
562
+ function attrByRevIdent(etype, label): InstantDBAttr | undefined {
563
+ return (
564
+ getAttrByReverseIdentName(attrsStore, etype, label) ||
565
+ localAttrs.find(
566
+ (x) =>
567
+ x['reverse-identity']?.[1] === etype &&
568
+ x['reverse-identity']?.[2] === label,
569
+ )
570
+ );
571
+ }
572
+
573
+ function addAttr(attr: InstantDBAttr) {
574
+ localAttrs.push(attr);
498
575
  addOps.push(['add-attr', attr]);
499
576
  addedIds.add(attr.id);
500
577
  }
501
- function addUnsynced(attr) {
502
- if (attr?.isUnsynced && !addedIds.has(attr.id)) {
578
+ function addUnsynced(
579
+ attr:
580
+ | (InstantDBAttr & { isUnsynced?: boolean })
581
+ | InstantDBAttr
582
+ | undefined,
583
+ ) {
584
+ if (
585
+ attr &&
586
+ 'isUnsynced' in attr &&
587
+ attr.isUnsynced &&
588
+ !addedIds.has(attr.id)
589
+ ) {
590
+ localAttrs.push(attr);
503
591
  addOps.push(['add-attr', attr]);
504
592
  addedIds.add(attr.id);
505
593
  }
506
594
  }
507
595
 
596
+ function isRefLookupIdentLocal(etype: string, identName: string) {
597
+ return (
598
+ identName.indexOf('.') !== -1 &&
599
+ // attr names can have `.` in them, so use the attr we find with a `.`
600
+ // before assuming it's a ref lookup.
601
+ !attrByFwdIdent(etype, identName)
602
+ );
603
+ }
604
+
508
605
  // Adds attrs needed for a ref lookup
509
606
  function addForRef(etype, label) {
510
- const fwdAttr = getAttrByFwdIdentName(attrs, etype, label);
511
- const revAttr = getAttrByReverseIdentName(attrs, etype, label);
607
+ const fwdAttr = attrByFwdIdent(etype, label);
608
+ const revAttr = attrByRevIdent(etype, label);
512
609
  addUnsynced(fwdAttr);
513
610
  addUnsynced(revAttr);
514
611
  if (!fwdAttr && !revAttr) {
@@ -530,18 +627,18 @@ function createMissingAttrs({ attrs: existingAttrs, schema }, ops) {
530
627
 
531
628
  // Figure out the link etype so we can make sure we have the attrs
532
629
  // for the link lookup
533
- const fwdAttr = getAttrByFwdIdentName(attrs, etype, linkLabel);
534
- const revAttr = getAttrByReverseIdentName(attrs, etype, linkLabel);
630
+ const fwdAttr = attrByFwdIdent(etype, linkLabel);
631
+ const revAttr = attrByRevIdent(etype, linkLabel);
535
632
  addUnsynced(fwdAttr);
536
633
  addUnsynced(revAttr);
537
634
  const linkEtype =
538
635
  fwdAttr?.['reverse-identity']?.[1] ||
539
636
  revAttr?.['forward-identity']?.[1] ||
540
637
  linkLabel;
541
- if (isRefLookupIdent(attrs, linkEtype, identName)) {
638
+ if (isRefLookupIdentLocal(linkEtype, identName)) {
542
639
  addForRef(linkEtype, extractRefLookupFwdName(identName));
543
640
  } else {
544
- const attr = getAttrByFwdIdentName(attrs, linkEtype, identName);
641
+ const attr = attrByFwdIdent(linkEtype, identName);
545
642
  if (!attr) {
546
643
  addAttr(
547
644
  createObjectAttr(schema, linkEtype, identName, lookupProps),
@@ -549,10 +646,10 @@ function createMissingAttrs({ attrs: existingAttrs, schema }, ops) {
549
646
  }
550
647
  addUnsynced(attr);
551
648
  }
552
- } else if (isRefLookupIdent(attrs, etype, identName)) {
649
+ } else if (isRefLookupIdentLocal(etype, identName)) {
553
650
  addForRef(etype, extractRefLookupFwdName(identName));
554
651
  } else {
555
- const attr = getAttrByFwdIdentName(attrs, etype, identName);
652
+ const attr = attrByFwdIdent(etype, identName);
556
653
  if (!attr) {
557
654
  addAttr(createObjectAttr(schema, etype, identName, lookupProps));
558
655
  }
@@ -565,14 +662,14 @@ function createMissingAttrs({ attrs: existingAttrs, schema }, ops) {
565
662
  for (const op of ops) {
566
663
  const [action, etype, eid, obj] = op;
567
664
  if (OBJ_ACTIONS.has(action)) {
568
- const idAttr = getAttrByFwdIdentName(attrs, etype, 'id');
665
+ const idAttr = attrByFwdIdent(etype, 'id');
569
666
  addUnsynced(idAttr);
570
667
  if (!idAttr) {
571
668
  addAttr(createObjectAttr(schema, etype, 'id', { 'unique?': true }));
572
669
  }
573
670
 
574
671
  for (const label of Object.keys(obj)) {
575
- const fwdAttr = getAttrByFwdIdentName(attrs, etype, label);
672
+ const fwdAttr = attrByFwdIdent(etype, label);
576
673
  addUnsynced(fwdAttr);
577
674
  if (UPDATE_ACTIONS.has(action)) {
578
675
  if (!fwdAttr) {
@@ -587,7 +684,7 @@ function createMissingAttrs({ attrs: existingAttrs, schema }, ops) {
587
684
  }
588
685
  }
589
686
  if (REF_ACTIONS.has(action)) {
590
- const revAttr = getAttrByReverseIdentName(attrs, etype, label);
687
+ const revAttr = attrByRevIdent(etype, label);
591
688
  if (!fwdAttr && !revAttr) {
592
689
  addAttr(createRefAttr(schema, etype, label));
593
690
  }
@@ -596,14 +693,22 @@ function createMissingAttrs({ attrs: existingAttrs, schema }, ops) {
596
693
  }
597
694
  }
598
695
  }
599
- return [attrs, addOps];
696
+
697
+ if (localAttrs.length) {
698
+ const nextAttrs = { ...attrsStore.attrs };
699
+ for (const attr of localAttrs) {
700
+ nextAttrs[attr.id] = attr;
701
+ }
702
+ return [new AttrsStoreClass(nextAttrs, attrsStore.linkIndex), addOps];
703
+ }
704
+ return [attrsStore, addOps];
600
705
  }
601
706
 
602
- export function transform(ctx, inputChunks) {
707
+ export function transform(ctx: Ctx, inputChunks) {
603
708
  const chunks = Array.isArray(inputChunks) ? inputChunks : [inputChunks];
604
709
  const ops = chunks.flatMap((tx) => getOps(tx));
605
710
  const [newAttrs, addAttrTxSteps] = createMissingAttrs(ctx, ops);
606
- const newCtx = { ...ctx, attrs: newAttrs };
711
+ const newCtx = { ...ctx, attrsStore: newAttrs };
607
712
  const txSteps = ops.flatMap((op) => toTxSteps(newCtx, op));
608
713
  return [...addAttrTxSteps, ...txSteps];
609
714
  }