@syncular/typegen 0.15.44 → 0.15.46

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
@@ -1,7 +1,7 @@
1
1
  # @syncular/typegen
2
2
 
3
3
  SQL migrations + one manifest → neutral schema IR (JSON) → generated TS
4
- module (REVISE B5). This file is the authoritative contract for the three
4
+ module. This file is the authoritative contract for the three
5
5
  tool-level formats: the manifest, the IR, and the SQL subset. Wire-protocol
6
6
  semantics (column types, scope patterns, schema-version gating) live in
7
7
  [`../../docs/SPEC.md`](../../docs/SPEC.md) §2.4, §3.1, §1.5 — this document never
@@ -374,8 +374,9 @@ The emitted `*.generated.ts` module:
374
374
  (requested scopes), plus an `SParams` interface when the template has
375
375
  `{param}` placeholders.
376
376
 
377
- **Lint/freshness split**: `*.generated.ts` is excluded from biome (see
378
- `biome.json`) — hand-format rules on machine output only create churn.
377
+ **Lint/freshness split**: `*.generated.ts` is excluded from oxlint and oxfmt
378
+ (see `.oxlintrc.json` / `.oxfmtrc.json`) — hand-format rules on machine
379
+ output only create churn.
379
380
  Freshness and migration-history integrity are enforced instead by `syncular
380
381
  generate --check`, which is byte-exact and refuses locked-history drift; the
381
382
  generated fixture is still typechecked by `tsc` and exercised by tests.
@@ -450,8 +451,8 @@ revision-1 structured frontend: authoritative typed inputs, explicit
450
451
  `when(...)` conjuncts, atomic optional groups, imported hygienic predicates,
451
452
  inferred scope dependencies, explicit sync coverage, complete sort profiles,
452
453
  bounded limits, and inferred identity. Its complete normative definition is
453
- [`../../docs/SYQL.md`](../../docs/SYQL.md); the rationale and implementation
454
- record are [`../../docs/rfcs/0004-syql-language.md`](../../docs/rfcs/0004-syql-language.md).
454
+ [`../../docs/SYQL.md`](../../docs/SYQL.md). Both frontends produce the same
455
+ deterministic QueryIR consumed by every language emitter.
455
456
 
456
457
  Tooling: `syncular generate --print <name>` prints every selected checked
457
458
  statement and bind, `syncular fmt` is the semantic-preserving canonical
@@ -272,6 +272,7 @@ function emitSyqlQuery(query, hash) {
272
272
  lines.push(` sqlFor: (params: ${Params}) => ${query.name}Select(params).sql,`);
273
273
  }
274
274
  lines.push(` tables: ${query.name}Tables,`);
275
+ lines.push(` resultColumns: [${query.columns.map((column) => `{ name: ${quote(column.langName)}, type: ${quote(column.type)}, nullable: ${column.nullable} }`).join(', ')}],`);
275
276
  const reactiveUsesParams = query.reactive.dependencies.some((dependency) => dependency.scopes.some((scope) => scope.params.length > 0));
276
277
  lines.push(` dependencies: (${reactiveUsesParams ? 'params' : ''}) => [`);
277
278
  for (const dependency of query.reactive.dependencies) {
@@ -359,6 +360,7 @@ function emitQuery(query, hash) {
359
360
  lines.push(` sql: ${sqlConst},`);
360
361
  lines.push(` mapRow: ${query.name}MapRow,`);
361
362
  lines.push(` tables: ${query.name}Tables,`);
363
+ lines.push(` resultColumns: [${query.columns.map((column) => `{ name: ${quote(column.langName)}, type: ${quote(column.type)}, nullable: ${column.nullable} }`).join(', ')}],`);
362
364
  const reactiveUsesParams = query.reactive.dependencies.some((dependency) => dependency.scopes.some((scope) => scope.params.length > 0));
363
365
  lines.push(` dependencies: (${reactiveUsesParams ? 'params' : ''}) => [`);
364
366
  for (const dependency of query.reactive.dependencies) {
@@ -472,6 +474,7 @@ export function emitQueriesModule(queries, hash, irVersion) {
472
474
  ' readonly sql: string;',
473
475
  ' readonly mapRow: (row: Readonly<Record<string, unknown>>) => Row;',
474
476
  ' readonly tables: readonly string[];',
477
+ ' readonly resultColumns: readonly QueryResultColumn[];',
475
478
  ' readonly bind: (params: Params) => readonly QueryValue[];',
476
479
  ' readonly sqlFor?: (params: Params) => string;',
477
480
  ' readonly dependencies: (params: Params) => readonly QueryDependency[];',
@@ -486,6 +489,12 @@ export function emitQueriesModule(queries, hash, irVersion) {
486
489
  ' readonly scopeKeys?: readonly string[];',
487
490
  '}',
488
491
  '',
492
+ 'export interface QueryResultColumn {',
493
+ ' readonly name: string;',
494
+ " readonly type: 'string' | 'integer' | 'float' | 'boolean' | 'json' | 'bytes' | 'blob_ref' | 'crdt';",
495
+ ' readonly nullable: boolean;',
496
+ '}',
497
+ '',
489
498
  'export interface WindowCoverage {',
490
499
  ' readonly base: {',
491
500
  ' readonly table: string;',
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @syncular/typegen — SQL migrations + syncular.json → neutral schema
3
- * IR (JSON) → generated TS module (REVISE B5). Dependency-free; the CLI
3
+ * IR (JSON) → generated TS module. Dependency-free; the CLI
4
4
  * lives in `src/cli.ts` (bin `syncular`).
5
5
  */
6
6
  export * from './emit.js';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @syncular/typegen — SQL migrations + syncular.json → neutral schema
3
- * IR (JSON) → generated TS module (REVISE B5). Dependency-free; the CLI
3
+ * IR (JSON) → generated TS module. Dependency-free; the CLI
4
4
  * lives in `src/cli.ts` (bin `syncular`).
5
5
  */
6
6
  export * from './emit.js';
package/dist/ir.d.ts CHANGED
@@ -27,7 +27,7 @@ export interface IrScope {
27
27
  * One local secondary index declared by a `CREATE [UNIQUE] INDEX` migration.
28
28
  * Applied on BOTH sides: the client materializes it in its local SQLite, and
29
29
  * the server creates it on the relational per-app row table
30
- * (DESIGN-relational-server-storage.md "user indexes"; the sync read path
30
+ * (the server-storage user-index contract; the sync read path
31
31
  * itself still uses the scope inverted-index). `columns` preserves declared
32
32
  * order (a compound index is order-sensitive). Index order within a table is
33
33
  * declaration order.
package/dist/ir.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Neutral schema IR (REVISE B5).
2
+ * Neutral schema IR.
3
3
  *
4
4
  * The IR is a versioned, language-neutral JSON document: tables with the
5
5
  * SPEC §2.4 column types (+ nullability + primary key), scope patterns
package/dist/lower.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Projection lowering (DESIGN-queries.md §5): rewrite a query's top-level
2
+ * Projection lowering: rewrite a query's top-level
3
3
  * SELECT list so the runtime result keys ARE the language-facing names —
4
4
  * `select created_at from todos` lowers to
5
5
  * `select created_at as createdAt from todos` under camelCase naming, so
package/dist/manifest.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `syncular.json` manifest (REVISE B5) — designed here, minimal but
2
+ * `syncular.json` manifest: minimal but
3
3
  * forward-extensible:
4
4
  *
5
5
  * ```json
package/dist/naming.d.ts CHANGED
@@ -8,7 +8,7 @@ export declare function snakeToCamel(name: string): string;
8
8
  * small set that breaks generated FUNCTION/const identifiers). */
9
9
  export type NamingTarget = 'ts' | 'swift' | 'kotlin' | 'dart' | 'rust';
10
10
  export declare function isRustKeyword(name: string): boolean;
11
- /** Pinned Rust identifier conversion from RFC 0006. */
11
+ /** Pinned Rust identifier conversion. */
12
12
  export declare function rustSnakeCase(name: string): string;
13
13
  export declare function rustPascalCase(name: string): string;
14
14
  export interface RustNameMapping {
package/dist/naming.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The pinned naming-map algorithm (DESIGN-queries.md §5, §12): SQL stays
2
+ * The pinned naming-map algorithm: SQL stays
3
3
  * snake_case; emitters render their language's convention. The IR carries
4
4
  * SQL-truth names plus a derived, collision-checked map — this module is
5
5
  * that derivation.
@@ -259,7 +259,7 @@ const TARGET_KEYWORDS = {
259
259
  export function isRustKeyword(name) {
260
260
  return TARGET_KEYWORDS.rust.has(name);
261
261
  }
262
- /** Pinned Rust identifier conversion from RFC 0006. */
262
+ /** Pinned Rust identifier conversion. */
263
263
  export function rustSnakeCase(name) {
264
264
  if (!/^_*[A-Za-z][A-Za-z0-9_]*$/.test(name))
265
265
  return name;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * QueryIR serialization (DESIGN-queries.md §1): the frontend-agnostic,
2
+ * QueryIR serialization: the frontend-agnostic,
3
3
  * deterministic JSON form of analyzed queries. This is the golden-fixture
4
4
  * format — equivalent inputs in any frontend (`.sql` today, `.syql` later)
5
5
  * must produce byte-identical IR JSON, the same trick the wire protocol
package/dist/sql.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * SQL migration subset parser (REVISE B5).
2
+ * SQL migration subset parser.
3
3
  *
4
4
  * Parses exactly the subset v1-style migrations need:
5
5
  *
@@ -10,7 +10,7 @@
10
10
  * - `DROP INDEX [IF EXISTS] name`
11
11
  * - `DROP TABLE [IF EXISTS] name`
12
12
  * - `CREATE VIRTUAL TABLE name USING fts5(cols…, content=table,
13
- * [tokenize='allowlisted tokenizer'])` (RFC 0005 local projection)
13
+ * [tokenize='allowlisted tokenizer'])` (client-local FTS5 projection)
14
14
  * - column defs: `name TYPE [PRIMARY KEY] [NOT NULL] [NULL]
15
15
  * [DEFAULT literal]`; `ALTER TABLE … ADD COLUMN` is restricted to nullable
16
16
  * columns because Syncular does not execute SQL-default backfills
@@ -337,7 +337,7 @@ const ALLOWED_FTS_TOKENIZERS = new Set([
337
337
  'trigram',
338
338
  ]);
339
339
  /** Parse the deliberately narrow migration-subset v2 FTS5 form documented in
340
- * RFC 0005. The virtual table is attached to its owning synced
340
+ * The virtual table is attached to its owning synced
341
341
  * table and is not itself a synced table. */
342
342
  function parseCreateVirtualTable(cursor, tables, source, options) {
343
343
  cursor.expectWord('TABLE', 'after CREATE VIRTUAL');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncular/typegen",
3
- "version": "0.15.44",
3
+ "version": "0.15.46",
4
4
  "description": "Syncular schema-to-TypeScript type generator and the `syncular` CLI",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Benjamin Kniffler",
@@ -56,9 +56,9 @@
56
56
  "!dist/**/*.test.d.ts"
57
57
  ],
58
58
  "dependencies": {
59
- "@syncular/core": "0.15.44"
59
+ "@syncular/core": "0.15.46"
60
60
  },
61
61
  "devDependencies": {
62
- "@syncular/server": "0.15.44"
62
+ "@syncular/server": "0.15.46"
63
63
  }
64
64
  }
@@ -399,6 +399,9 @@ function emitSyqlQuery(query: AnalyzedQuery, hash: string): string {
399
399
  );
400
400
  }
401
401
  lines.push(` tables: ${query.name}Tables,`);
402
+ lines.push(
403
+ ` resultColumns: [${query.columns.map((column) => `{ name: ${quote(column.langName)}, type: ${quote(column.type)}, nullable: ${column.nullable} }`).join(', ')}],`,
404
+ );
402
405
  const reactiveUsesParams = query.reactive.dependencies.some((dependency) =>
403
406
  dependency.scopes.some((scope) => scope.params.length > 0),
404
407
  );
@@ -537,6 +540,9 @@ function emitQuery(query: AnalyzedQuery, hash: string): string {
537
540
  lines.push(` sql: ${sqlConst},`);
538
541
  lines.push(` mapRow: ${query.name}MapRow,`);
539
542
  lines.push(` tables: ${query.name}Tables,`);
543
+ lines.push(
544
+ ` resultColumns: [${query.columns.map((column) => `{ name: ${quote(column.langName)}, type: ${quote(column.type)}, nullable: ${column.nullable} }`).join(', ')}],`,
545
+ );
540
546
  const reactiveUsesParams = query.reactive.dependencies.some((dependency) =>
541
547
  dependency.scopes.some((scope) => scope.params.length > 0),
542
548
  );
@@ -683,6 +689,7 @@ export function emitQueriesModule(
683
689
  ' readonly sql: string;',
684
690
  ' readonly mapRow: (row: Readonly<Record<string, unknown>>) => Row;',
685
691
  ' readonly tables: readonly string[];',
692
+ ' readonly resultColumns: readonly QueryResultColumn[];',
686
693
  ' readonly bind: (params: Params) => readonly QueryValue[];',
687
694
  ' readonly sqlFor?: (params: Params) => string;',
688
695
  ' readonly dependencies: (params: Params) => readonly QueryDependency[];',
@@ -697,6 +704,12 @@ export function emitQueriesModule(
697
704
  ' readonly scopeKeys?: readonly string[];',
698
705
  '}',
699
706
  '',
707
+ 'export interface QueryResultColumn {',
708
+ ' readonly name: string;',
709
+ " readonly type: 'string' | 'integer' | 'float' | 'boolean' | 'json' | 'bytes' | 'blob_ref' | 'crdt';",
710
+ ' readonly nullable: boolean;',
711
+ '}',
712
+ '',
700
713
  'export interface WindowCoverage {',
701
714
  ' readonly base: {',
702
715
  ' readonly table: string;',
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @syncular/typegen — SQL migrations + syncular.json → neutral schema
3
- * IR (JSON) → generated TS module (REVISE B5). Dependency-free; the CLI
3
+ * IR (JSON) → generated TS module. Dependency-free; the CLI
4
4
  * lives in `src/cli.ts` (bin `syncular`).
5
5
  */
6
6
  export * from './emit';
package/src/ir.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Neutral schema IR (REVISE B5).
2
+ * Neutral schema IR.
3
3
  *
4
4
  * The IR is a versioned, language-neutral JSON document: tables with the
5
5
  * SPEC §2.4 column types (+ nullability + primary key), scope patterns
@@ -53,7 +53,7 @@ export interface IrScope {
53
53
  * One local secondary index declared by a `CREATE [UNIQUE] INDEX` migration.
54
54
  * Applied on BOTH sides: the client materializes it in its local SQLite, and
55
55
  * the server creates it on the relational per-app row table
56
- * (DESIGN-relational-server-storage.md "user indexes"; the sync read path
56
+ * (the server-storage user-index contract; the sync read path
57
57
  * itself still uses the scope inverted-index). `columns` preserves declared
58
58
  * order (a compound index is order-sensitive). Index order within a table is
59
59
  * declaration order.
package/src/lower.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Projection lowering (DESIGN-queries.md §5): rewrite a query's top-level
2
+ * Projection lowering: rewrite a query's top-level
3
3
  * SELECT list so the runtime result keys ARE the language-facing names —
4
4
  * `select created_at from todos` lowers to
5
5
  * `select created_at as createdAt from todos` under camelCase naming, so
package/src/manifest.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `syncular.json` manifest (REVISE B5) — designed here, minimal but
2
+ * `syncular.json` manifest: minimal but
3
3
  * forward-extensible:
4
4
  *
5
5
  * ```json
package/src/naming.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The pinned naming-map algorithm (DESIGN-queries.md §5, §12): SQL stays
2
+ * The pinned naming-map algorithm: SQL stays
3
3
  * snake_case; emitters render their language's convention. The IR carries
4
4
  * SQL-truth names plus a derived, collision-checked map — this module is
5
5
  * that derivation.
@@ -270,7 +270,7 @@ export function isRustKeyword(name: string): boolean {
270
270
  return TARGET_KEYWORDS.rust.has(name);
271
271
  }
272
272
 
273
- /** Pinned Rust identifier conversion from RFC 0006. */
273
+ /** Pinned Rust identifier conversion. */
274
274
  export function rustSnakeCase(name: string): string {
275
275
  if (!/^_*[A-Za-z][A-Za-z0-9_]*$/.test(name)) return name;
276
276
  const lead = /^_*/.exec(name)?.[0] ?? '';
package/src/query-ir.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * QueryIR serialization (DESIGN-queries.md §1): the frontend-agnostic,
2
+ * QueryIR serialization: the frontend-agnostic,
3
3
  * deterministic JSON form of analyzed queries. This is the golden-fixture
4
4
  * format — equivalent inputs in any frontend (`.sql` today, `.syql` later)
5
5
  * must produce byte-identical IR JSON, the same trick the wire protocol
package/src/sql.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * SQL migration subset parser (REVISE B5).
2
+ * SQL migration subset parser.
3
3
  *
4
4
  * Parses exactly the subset v1-style migrations need:
5
5
  *
@@ -10,7 +10,7 @@
10
10
  * - `DROP INDEX [IF EXISTS] name`
11
11
  * - `DROP TABLE [IF EXISTS] name`
12
12
  * - `CREATE VIRTUAL TABLE name USING fts5(cols…, content=table,
13
- * [tokenize='allowlisted tokenizer'])` (RFC 0005 local projection)
13
+ * [tokenize='allowlisted tokenizer'])` (client-local FTS5 projection)
14
14
  * - column defs: `name TYPE [PRIMARY KEY] [NOT NULL] [NULL]
15
15
  * [DEFAULT literal]`; `ALTER TABLE … ADD COLUMN` is restricted to nullable
16
16
  * columns because Syncular does not execute SQL-default backfills
@@ -434,7 +434,7 @@ const ALLOWED_FTS_TOKENIZERS = new Set([
434
434
  ]);
435
435
 
436
436
  /** Parse the deliberately narrow migration-subset v2 FTS5 form documented in
437
- * RFC 0005. The virtual table is attached to its owning synced
437
+ * The virtual table is attached to its owning synced
438
438
  * table and is not itself a synced table. */
439
439
  function parseCreateVirtualTable(
440
440
  cursor: Cursor,