@spooky-sync/core 0.0.1-canary.127 → 0.0.1-canary.129

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.
package/dist/index.d.ts CHANGED
@@ -475,6 +475,15 @@ declare class DataModule<S extends SchemaStructure> {
475
475
  * `seen` dedupes within the batch.
476
476
  */
477
477
  private collectEmbeddedChildren;
478
+ /**
479
+ * Prepare a subquery-bearing row (preload / hydration) for the schemafull
480
+ * local store: replace an embedded FORWARD-relation object (`author = { id, … }`)
481
+ * with its RecordId so a `record<…>` field coerces, and DROP reverse-subquery
482
+ * ARRAYS (`comments = [ … ]`) since their rows are cached separately as their
483
+ * own bodies. A flat record — as the live `SELECT * FROM $ids` sync returns,
484
+ * with relations already RecordIds — passes through unchanged.
485
+ */
486
+ private flattenRelationsForStorage;
478
487
  /**
479
488
  * Instant-hydrate: ingest rows fetched one-shot from the remote (the query's own
480
489
  * surql run directly) so the query DISPLAYS immediately, while the full realtime
package/dist/index.js CHANGED
@@ -2689,13 +2689,29 @@ var DataModule = class {
2689
2689
  batch.push({
2690
2690
  table,
2691
2691
  op: "CREATE",
2692
- record: tableSchema ? cleanRecord(tableSchema.columns, child) : child,
2692
+ record: this.flattenRelationsForStorage(child, tableSchema),
2693
2693
  version: child._00_rv || 1
2694
2694
  });
2695
2695
  }
2696
2696
  }
2697
2697
  }
2698
2698
  /**
2699
+ * Prepare a subquery-bearing row (preload / hydration) for the schemafull
2700
+ * local store: replace an embedded FORWARD-relation object (`author = { id, … }`)
2701
+ * with its RecordId so a `record<…>` field coerces, and DROP reverse-subquery
2702
+ * ARRAYS (`comments = [ … ]`) since their rows are cached separately as their
2703
+ * own bodies. A flat record — as the live `SELECT * FROM $ids` sync returns,
2704
+ * with relations already RecordIds — passes through unchanged.
2705
+ */
2706
+ flattenRelationsForStorage(record, tableSchema) {
2707
+ const isEmbeddedRecord = (v) => !!v && typeof v === "object" && !(v instanceof RecordId) && v.id instanceof RecordId;
2708
+ const flat = {};
2709
+ for (const [k, v] of Object.entries(record)) if (isEmbeddedRecord(v)) flat[k] = v.id;
2710
+ else if (Array.isArray(v) && v.some(isEmbeddedRecord)) continue;
2711
+ else flat[k] = v;
2712
+ return tableSchema ? cleanRecord(tableSchema.columns, flat) : flat;
2713
+ }
2714
+ /**
2699
2715
  * Instant-hydrate: ingest rows fetched one-shot from the remote (the query's own
2700
2716
  * surql run directly) so the query DISPLAYS immediately, while the full realtime
2701
2717
  * registration proceeds in the background. Ingests with versions (`_00_rv`) so the
@@ -2725,10 +2741,11 @@ var DataModule = class {
2725
2741
  * (preload).
2726
2742
  */
2727
2743
  async buildAndSaveCacheBatch(tableName, rows) {
2744
+ const tableSchema = this.schema.tables.find((t) => t.name === tableName);
2728
2745
  const batch = rows.map((record) => ({
2729
2746
  table: tableName,
2730
2747
  op: "CREATE",
2731
- record,
2748
+ record: this.flattenRelationsForStorage(record, tableSchema),
2732
2749
  version: record._00_rv || 1
2733
2750
  }));
2734
2751
  const seen = new Set(rows.map((r) => encodeRecordId(r.id)));
@@ -5101,8 +5118,8 @@ function parseBackendInfo(raw) {
5101
5118
 
5102
5119
  //#endregion
5103
5120
  //#region src/modules/devtools/index.ts
5104
- const CORE_VERSION = "0.0.1-canary.127";
5105
- const WASM_VERSION = "0.0.1-canary.127";
5121
+ const CORE_VERSION = "0.0.1-canary.129";
5122
+ const WASM_VERSION = "0.0.1-canary.129";
5106
5123
  const SURREAL_VERSION = "3.0.3";
5107
5124
  var DevToolsService = class {
5108
5125
  eventsHistory = [];
@@ -7310,6 +7327,7 @@ var Sp00kyClient = class {
7310
7327
  await this.streamProcessor.reset();
7311
7328
  this.streamProcessor.setPermissions(extractSelectPermissions(this.config.schemaSurql));
7312
7329
  this.cache.clearVersionLookups();
7330
+ this.preloadedHashes.clear();
7313
7331
  } finally {
7314
7332
  reopen();
7315
7333
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spooky-sync/core",
3
- "version": "0.0.1-canary.127",
3
+ "version": "0.0.1-canary.129",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -59,8 +59,8 @@
59
59
  }
60
60
  },
61
61
  "dependencies": {
62
- "@spooky-sync/query-builder": "0.0.1-canary.127",
63
- "@spooky-sync/ssp-wasm": "0.0.1-canary.127",
62
+ "@spooky-sync/query-builder": "0.0.1-canary.129",
63
+ "@spooky-sync/ssp-wasm": "0.0.1-canary.129",
64
64
  "@sqlite.org/sqlite-wasm": "3.53.0-build1",
65
65
  "@surrealdb/wasm": "^3.0.3",
66
66
  "fast-json-patch": "^3.1.1",
@@ -739,15 +739,47 @@ export class DataModule<S extends SchemaStructure> {
739
739
  batch.push({
740
740
  table,
741
741
  op: 'CREATE',
742
- record: (tableSchema
743
- ? cleanRecord(tableSchema.columns, child)
744
- : child) as RecordWithId,
742
+ // Flatten the child too: a nested comment carries its own embedded
743
+ // `author` object which the schemafull `record<user>` field would
744
+ // reject. (Its author is captured as a separate row by the recursion
745
+ // above.)
746
+ record: this.flattenRelationsForStorage(child, tableSchema),
745
747
  version: (child._00_rv as number) || 1,
746
748
  });
747
749
  }
748
750
  }
749
751
  }
750
752
 
753
+ /**
754
+ * Prepare a subquery-bearing row (preload / hydration) for the schemafull
755
+ * local store: replace an embedded FORWARD-relation object (`author = { id, … }`)
756
+ * with its RecordId so a `record<…>` field coerces, and DROP reverse-subquery
757
+ * ARRAYS (`comments = [ … ]`) since their rows are cached separately as their
758
+ * own bodies. A flat record — as the live `SELECT * FROM $ids` sync returns,
759
+ * with relations already RecordIds — passes through unchanged.
760
+ */
761
+ private flattenRelationsForStorage(
762
+ record: Record<string, any>,
763
+ tableSchema?: { columns: Record<string, any> }
764
+ ): RecordWithId {
765
+ const isEmbeddedRecord = (v: unknown): boolean =>
766
+ !!v &&
767
+ typeof v === 'object' &&
768
+ !(v instanceof RecordId) &&
769
+ (v as { id?: unknown }).id instanceof RecordId;
770
+ const flat: Record<string, any> = {};
771
+ for (const [k, v] of Object.entries(record)) {
772
+ if (isEmbeddedRecord(v)) {
773
+ flat[k] = (v as { id: unknown }).id;
774
+ } else if (Array.isArray(v) && v.some(isEmbeddedRecord)) {
775
+ continue;
776
+ } else {
777
+ flat[k] = v;
778
+ }
779
+ }
780
+ return (tableSchema ? cleanRecord(tableSchema.columns, flat) : flat) as RecordWithId;
781
+ }
782
+
751
783
  /**
752
784
  * Instant-hydrate: ingest rows fetched one-shot from the remote (the query's own
753
785
  * surql run directly) so the query DISPLAYS immediately, while the full realtime
@@ -792,10 +824,18 @@ export class DataModule<S extends SchemaStructure> {
792
824
  tableName: string,
793
825
  rows: RecordWithId[]
794
826
  ): Promise<void> {
827
+ const tableSchema = this.schema.tables.find((t) => t.name === tableName);
795
828
  const batch: CacheRecord[] = rows.map((record) => ({
796
829
  table: tableName,
797
830
  op: 'CREATE' as const,
798
- record,
831
+ // Flatten embedded relations first: a preload/hydration row can carry a
832
+ // forward relation as a full nested OBJECT (`author = { id, … }`) and a
833
+ // reverse subquery as an array of objects (`comments = [ … ]`). The
834
+ // schemafull local field is `record<user>`, which rejects an object
835
+ // (`Couldn't coerce … found { id: …, username: … }`) and would throw the
836
+ // WHOLE batch. Store the parent with `author` as its RecordId and drop the
837
+ // subquery arrays — the children are cached as their own rows below.
838
+ record: this.flattenRelationsForStorage(record, tableSchema),
799
839
  version: (record._00_rv as number) || 1,
800
840
  }));
801
841
 
package/src/sp00ky.ts CHANGED
@@ -589,6 +589,11 @@ export class Sp00kyClient<S extends SchemaStructure> {
589
589
  await this.streamProcessor.reset();
590
590
  this.streamProcessor.setPermissions(extractSelectPermissions(this.config.schemaSurql));
591
591
  this.cache.clearVersionLookups();
592
+ // Preload dedup is per-bucket: the `_00_preload` markers + cached rows it
593
+ // guards live in the local store we just swapped away from. Keeping the
594
+ // hashes would make `preload()` skip warming the NEW bucket (its store is
595
+ // empty), so every thread/comment prewarm silently no-ops after login.
596
+ this.preloadedHashes.clear();
592
597
  } finally {
593
598
  reopen();
594
599
  }