@spooky-sync/core 0.0.1-canary.123 → 0.0.1-canary.125

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.js CHANGED
@@ -1178,7 +1178,8 @@ function bind(ctx, value) {
1178
1178
  return `$${name}`;
1179
1179
  }
1180
1180
  function renderComparisonSurql(c, ctx) {
1181
- const right = c.paramRef ? `$${c.paramRef}` : bind(ctx, c.value);
1181
+ const rawRight = c.paramRef ? `$${c.paramRef}` : bind(ctx, c.value);
1182
+ const right = c.field === "id" ? `type::record(<string> ${rawRight})` : rawRight;
1182
1183
  return c.swap ? `${right} ${c.op} ${c.field}` : `${c.field} ${c.op} ${right}`;
1183
1184
  }
1184
1185
  /** Render a WHERE conjunction (AND of comparisons / OR-groups) to SurrealQL. */
@@ -5100,8 +5101,8 @@ function parseBackendInfo(raw) {
5100
5101
 
5101
5102
  //#endregion
5102
5103
  //#region src/modules/devtools/index.ts
5103
- const CORE_VERSION = "0.0.1-canary.123";
5104
- const WASM_VERSION = "0.0.1-canary.123";
5104
+ const CORE_VERSION = "0.0.1-canary.125";
5105
+ const WASM_VERSION = "0.0.1-canary.125";
5105
5106
  const SURREAL_VERSION = "3.0.3";
5106
5107
  var DevToolsService = class {
5107
5108
  eventsHistory = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spooky-sync/core",
3
- "version": "0.0.1-canary.123",
3
+ "version": "0.0.1-canary.125",
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.123",
63
- "@spooky-sync/ssp-wasm": "0.0.1-canary.123",
62
+ "@spooky-sync/query-builder": "0.0.1-canary.125",
63
+ "@spooky-sync/ssp-wasm": "0.0.1-canary.125",
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",
@@ -42,6 +42,54 @@ describe('renderBaseSelectSurql', () => {
42
42
  });
43
43
  });
44
44
 
45
+ // Regression guard for the "authors + comments don't load" class: any filter on
46
+ // a RecordId column (`id`) must coerce its string value with
47
+ // `type::record(<string> …)`. On SurrealDB `id = "thread:x"` (string) never
48
+ // matches a RecordId, so a base select filtered by id resolves EMPTY and its
49
+ // whole `.related()` subtree (author, comments) loads nothing. The string-based
50
+ // MemStore in relation-resolver.test.ts can't catch this (it compares keys as
51
+ // strings), so these assert the rendered SurrealQL directly — both the parent
52
+ // (base select) and child (relation fetch) sides.
53
+ describe('record-id coercion (authors/comments loading regression)', () => {
54
+ it('coerces a base-select `id = <value>` filter to a record id', () => {
55
+ const plan: QueryPlan = { table: 'thread', where: [{ field: 'id', op: '=', value: 'thread:abc' }] };
56
+ const { sql, vars } = renderBaseSelectSurql(plan);
57
+ expect(sql).toBe('SELECT * FROM thread WHERE id = type::record(<string> $__p0);');
58
+ expect(vars).toEqual({ __p0: 'thread:abc' });
59
+ });
60
+
61
+ it('coerces a base-select `id = $paramRef` filter (the ThreadDetail path)', () => {
62
+ const plan: QueryPlan = { table: 'thread', where: [{ field: 'id', op: '=', value: undefined, paramRef: 'id' }] };
63
+ const { sql } = renderBaseSelectSurql(plan, { id: 'thread:abc' });
64
+ expect(sql).toBe('SELECT * FROM thread WHERE id = type::record(<string> $id);');
65
+ });
66
+
67
+ it('does NOT coerce non-id fields (plain string/bool columns stay literal)', () => {
68
+ const plan: QueryPlan = {
69
+ table: 'thread',
70
+ where: [{ field: 'title', op: '=', value: 'hi' }, { field: 'published', op: '=', value: true }],
71
+ };
72
+ const { sql } = renderBaseSelectSurql(plan);
73
+ expect(sql).toBe('SELECT * FROM thread WHERE title = $__p0 AND published = $__p1;');
74
+ });
75
+
76
+ it('coerces `id` inside an OR group too', () => {
77
+ const plan: QueryPlan = {
78
+ table: 'thread',
79
+ where: [{ or: [{ field: 'id', op: '=', value: 'thread:a' }, { field: 'id', op: '=', value: 'thread:b' }] }],
80
+ };
81
+ const { sql } = renderBaseSelectSurql(plan);
82
+ expect(sql).toBe(
83
+ 'SELECT * FROM thread WHERE (id = type::record(<string> $__p0) OR id = type::record(<string> $__p1));'
84
+ );
85
+ });
86
+
87
+ it('relation fetch coerces its matchField keys (the .118 fix — kept locked)', () => {
88
+ const { sql } = renderRelationFetchSurql({ table: 'user', matchField: 'id', keys: ['user:1'] });
89
+ expect(sql).toContain('id IN $__keys.map(|$__k| type::record(<string> $__k))');
90
+ });
91
+ });
92
+
45
93
  describe('renderRelationFetchSurql', () => {
46
94
  it('builds a WHERE ... IN $__keys batch fetch, omitting LIMIT', () => {
47
95
  const { sql, vars } = renderRelationFetchSurql({
@@ -26,7 +26,16 @@ function bind(ctx: RenderCtx, value: unknown): string {
26
26
  }
27
27
 
28
28
  function renderComparisonSurql(c: WhereComparison, ctx: RenderCtx): string {
29
- const right = c.paramRef ? `$${c.paramRef}` : bind(ctx, c.value);
29
+ const rawRight = c.paramRef ? `$${c.paramRef}` : bind(ctx, c.value);
30
+ // `id` is a RecordId, but correlation/filter values arrive as record-id
31
+ // STRINGS (e.g. "thread:abc"). On SurrealDB `id = "thread:abc"` never matches
32
+ // (string ≠ record), so a base select filtered by id (e.g. the ThreadDetail
33
+ // query `… FROM thread WHERE id = $id`) resolves empty — and its whole
34
+ // `.related()` subtree (author, comments) then loads nothing. Coerce with
35
+ // `type::record(<string> …)` (idempotent if already a RecordId), mirroring
36
+ // renderRelationFetchSurql's matchField coercion.
37
+ const right =
38
+ c.field === 'id' ? `type::record(<string> ${rawRight})` : rawRight;
30
39
  return c.swap ? `${right} ${c.op} ${c.field}` : `${c.field} ${c.op} ${right}`;
31
40
  }
32
41