@syncular/typegen 0.15.0 → 0.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.
package/README.md CHANGED
@@ -470,8 +470,10 @@ For FTS-backed named queries the synthesized type-check database creates the
470
470
  same declared columns plus `_syncular_source_id UNINDEXED`. `MATCH :query`
471
471
  infers a string parameter, and the FTS table reference is folded into its
472
472
  owning synced table for dependency and scope-coverage metadata. `bm25`,
473
- `highlight`, and `snippet` remain SQLite expressions and therefore use the
474
- normal computed-expression fidelity rules.
473
+ `highlight`, and `snippet` are accepted deterministic FTS5 auxiliary
474
+ functions only when the query references a schema-declared FTS projection;
475
+ they use the normal computed-expression fidelity rules. Arbitrary extension
476
+ functions remain outside the portable profile.
475
477
 
476
478
  **Typing fidelity** (what `bun:sqlite` actually exposes, and its honest
477
479
  boundary):
@@ -121,6 +121,16 @@ const SQLITE_346_PORTABLE_FUNCTIONS = new Set([
121
121
  'jsonb_replace',
122
122
  'jsonb_set',
123
123
  ]);
124
+ /**
125
+ * Deterministic FTS5 auxiliary functions available only when the query reads
126
+ * a schema-declared FTS projection. FTS5 is an explicit Syncular client
127
+ * requirement in that case, not an arbitrary host extension.
128
+ */
129
+ const SQLITE_FTS5_AUXILIARY_FUNCTIONS = new Set([
130
+ 'bm25',
131
+ 'highlight',
132
+ 'snippet',
133
+ ]);
124
134
  const SQL_PAREN_KEYWORDS = new Set([
125
135
  'and',
126
136
  'as',
@@ -248,8 +258,8 @@ class Validator {
248
258
  const activeStructure = this.#inspect(activeSql, location);
249
259
  this.#validateStatementShape(activeSql, activeStructure, logical.declaration, location);
250
260
  this.#validateDeterminism(activeSql, logical.declaration.statement.span);
251
- this.#validatePortableProfile(activeSql, logical.declaration.statement.span);
252
261
  const refs = scanTableRefs(activeSql, this.#ir);
262
+ this.#validatePortableProfile(activeSql, logical.declaration.statement.span, refs);
253
263
  const bindSymbols = this.#bindSymbols(logical.declaration);
254
264
  const bindTypes = this.#resolveBindTypes(logical, activeSql, refs, bindSymbols);
255
265
  const sort = this.#validateSort(logical.declaration, activeStructure, location);
@@ -512,8 +522,11 @@ class Validator {
512
522
  }
513
523
  }
514
524
  }
515
- #validatePortableProfile(sql, span) {
525
+ #validatePortableProfile(sql, span, refs) {
516
526
  const tokens = significant(lexSyqlSqlSource(span.file, sql));
527
+ const declaredFtsTables = new Set(this.#ir.tables.flatMap((table) => table.ftsIndexes.map((index) => index.name)));
528
+ const queryRefs = refs ?? scanTableRefs(sql, this.#ir);
529
+ const readsDeclaredFts = queryRefs.some((ref) => declaredFtsTables.has(ref.table));
517
530
  for (let index = 0; index < tokens.length; index += 1) {
518
531
  const token = tokens[index];
519
532
  const lower = tokenLower(token);
@@ -537,8 +550,10 @@ class Validator {
537
550
  tokens[closeIndex + 2]?.text === '(') {
538
551
  continue;
539
552
  }
540
- if (!SQLITE_346_PORTABLE_FUNCTIONS.has(lower ?? '')) {
541
- this.#fail('SYQL6002_INVALID_SQL', token.span, `${token.text}() is not a core built-in in the portable SQLite 3.46.0 profile`);
553
+ const allowedFtsAuxiliary = readsDeclaredFts && SQLITE_FTS5_AUXILIARY_FUNCTIONS.has(lower ?? '');
554
+ if (!SQLITE_346_PORTABLE_FUNCTIONS.has(lower ?? '') &&
555
+ !allowedFtsAuxiliary) {
556
+ this.#fail('SYQL6002_INVALID_SQL', token.span, `${token.text}() is not a core built-in in the portable SQLite 3.46.0 profile or an auxiliary function of a referenced schema-declared FTS5 projection`);
542
557
  }
543
558
  const argumentCount = functionArgumentCount(tokens, index + 1);
544
559
  if (lower === 'iif' && argumentCount !== 3) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncular/typegen",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "description": "Syncular schema-to-TypeScript type generator and the `syncular` CLI",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Benjamin Kniffler",
@@ -48,7 +48,7 @@
48
48
  "!dist/**/*.test.d.ts"
49
49
  ],
50
50
  "devDependencies": {
51
- "@syncular/core": "0.15.0",
52
- "@syncular/server": "0.15.0"
51
+ "@syncular/core": "0.15.1",
52
+ "@syncular/server": "0.15.1"
53
53
  }
54
54
  }
@@ -221,6 +221,16 @@ const SQLITE_346_PORTABLE_FUNCTIONS = new Set([
221
221
  'jsonb_replace',
222
222
  'jsonb_set',
223
223
  ]);
224
+ /**
225
+ * Deterministic FTS5 auxiliary functions available only when the query reads
226
+ * a schema-declared FTS projection. FTS5 is an explicit Syncular client
227
+ * requirement in that case, not an arbitrary host extension.
228
+ */
229
+ const SQLITE_FTS5_AUXILIARY_FUNCTIONS = new Set([
230
+ 'bm25',
231
+ 'highlight',
232
+ 'snippet',
233
+ ]);
224
234
  const SQL_PAREN_KEYWORDS = new Set([
225
235
  'and',
226
236
  'as',
@@ -376,12 +386,13 @@ class Validator {
376
386
  location,
377
387
  );
378
388
  this.#validateDeterminism(activeSql, logical.declaration.statement.span);
389
+ const refs = scanTableRefs(activeSql, this.#ir);
379
390
  this.#validatePortableProfile(
380
391
  activeSql,
381
392
  logical.declaration.statement.span,
393
+ refs,
382
394
  );
383
395
 
384
- const refs = scanTableRefs(activeSql, this.#ir);
385
396
  const bindSymbols = this.#bindSymbols(logical.declaration);
386
397
  const bindTypes = this.#resolveBindTypes(
387
398
  logical,
@@ -792,8 +803,21 @@ class Validator {
792
803
  }
793
804
  }
794
805
 
795
- #validatePortableProfile(sql: string, span: SyqlSourceSpan): void {
806
+ #validatePortableProfile(
807
+ sql: string,
808
+ span: SyqlSourceSpan,
809
+ refs?: readonly TableRef[],
810
+ ): void {
796
811
  const tokens = significant(lexSyqlSqlSource(span.file, sql));
812
+ const declaredFtsTables = new Set(
813
+ this.#ir.tables.flatMap((table) =>
814
+ table.ftsIndexes.map((index) => index.name),
815
+ ),
816
+ );
817
+ const queryRefs = refs ?? scanTableRefs(sql, this.#ir);
818
+ const readsDeclaredFts = queryRefs.some((ref) =>
819
+ declaredFtsTables.has(ref.table),
820
+ );
797
821
  for (let index = 0; index < tokens.length; index += 1) {
798
822
  const token = tokens[index] as SyqlToken;
799
823
  const lower = tokenLower(token);
@@ -828,11 +852,16 @@ class Validator {
828
852
  ) {
829
853
  continue;
830
854
  }
831
- if (!SQLITE_346_PORTABLE_FUNCTIONS.has(lower ?? '')) {
855
+ const allowedFtsAuxiliary =
856
+ readsDeclaredFts && SQLITE_FTS5_AUXILIARY_FUNCTIONS.has(lower ?? '');
857
+ if (
858
+ !SQLITE_346_PORTABLE_FUNCTIONS.has(lower ?? '') &&
859
+ !allowedFtsAuxiliary
860
+ ) {
832
861
  this.#fail(
833
862
  'SYQL6002_INVALID_SQL',
834
863
  token.span,
835
- `${token.text}() is not a core built-in in the portable SQLite 3.46.0 profile`,
864
+ `${token.text}() is not a core built-in in the portable SQLite 3.46.0 profile or an auxiliary function of a referenced schema-declared FTS5 projection`,
836
865
  );
837
866
  }
838
867