@syncular/typegen 0.3.1 → 0.4.0

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/emit-dart.js CHANGED
@@ -172,26 +172,35 @@ export function emitDartModule(ir, hash) {
172
172
  for (const line of schemaValue)
173
173
  schemaLines.push(line);
174
174
  parts.push(schemaLines.join('\n'));
175
- // Shared row-decode helpers.
176
- parts.push([
177
- '/// Lift a SQLite boolean: a real bool, or 0/1 as a number.',
178
- 'bool? _rowBool(Object? value) {',
179
- ' if (value is bool) return value;',
180
- ' if (value is num) return value != 0;',
181
- ' return null;',
182
- '}',
183
- '',
184
- "/// Decode the core's {'\\$bytes': '<hex>'} marshaling (bytes as hex).",
185
- 'List<int>? _rowBytes(Object? value) {',
186
- ' if (value is! Map) return null;',
187
- " final hex = value[r'$bytes'];",
188
- ' if (hex is! String || hex.length % 2 != 0) return null;',
189
- ' return [',
190
- ' for (var i = 0; i < hex.length; i += 2)',
191
- ' int.parse(hex.substring(i, i + 2), radix: 16),',
192
- ' ];',
193
- '}',
194
- ].join('\n'));
175
+ // Shared row-decode helpers — emitted only when a column type references
176
+ // them. An unused private helper is an ANALYZER diagnostic (unused_element),
177
+ // which the `type=lint` ignore above does not cover, so a schema without
178
+ // boolean or bytes/crdt columns would fail a fatal-warnings `dart analyze`.
179
+ const columnTypes = new Set(ir.tables.flatMap((table) => table.columns.map((column) => column.type)));
180
+ if (columnTypes.has('boolean')) {
181
+ parts.push([
182
+ '/// Lift a SQLite boolean: a real bool, or 0/1 as a number.',
183
+ 'bool? _rowBool(Object? value) {',
184
+ ' if (value is bool) return value;',
185
+ ' if (value is num) return value != 0;',
186
+ ' return null;',
187
+ '}',
188
+ ].join('\n'));
189
+ }
190
+ if (columnTypes.has('bytes') || columnTypes.has('crdt')) {
191
+ parts.push([
192
+ "/// Decode the core's {'\\$bytes': '<hex>'} marshaling (bytes as hex).",
193
+ 'List<int>? _rowBytes(Object? value) {',
194
+ ' if (value is! Map) return null;',
195
+ " final hex = value[r'$bytes'];",
196
+ ' if (hex is! String || hex.length % 2 != 0) return null;',
197
+ ' return [',
198
+ ' for (var i = 0; i < hex.length; i += 2)',
199
+ ' int.parse(hex.substring(i, i + 2), radix: 16),',
200
+ ' ];',
201
+ '}',
202
+ ].join('\n'));
203
+ }
195
204
  for (const table of ir.tables) {
196
205
  parts.push(emitClass(table).join('\n'));
197
206
  }
@@ -177,24 +177,34 @@ export function emitKotlinModule(ir, hash, packageName, objectName) {
177
177
  schemaLines.push(' }');
178
178
  schemaLines.push('}');
179
179
  parts.push(schemaLines.join('\n'));
180
- // Shared row-decode helpers.
181
- parts.push([
182
- '/** Lift a SQLite boolean: a real JSON bool, or 0/1 as a number. */',
183
- 'private fun rowBool(value: JsonValue?): Boolean? {',
184
- ' value?.bool?.let { return it }',
185
- ' value?.number?.let { return it != 0.0 }',
186
- ' return null',
187
- '}',
188
- '',
189
- '/** Decode the core\'s `{"$bytes":"<hex>"}` marshaling into a ByteArray. */',
190
- 'private fun rowBytes(value: JsonValue?): ByteArray? {',
191
- ' val hex = value?.get("\\$bytes")?.string ?: return null',
192
- ' if (hex.length % 2 != 0) return null',
193
- ' return ByteArray(hex.length / 2) { i ->',
194
- ' hex.substring(i * 2, i * 2 + 2).toInt(16).toByte()',
195
- ' }',
196
- '}',
197
- ].join('\n'));
180
+ // Shared row-decode helpers — emitted only when a column type references
181
+ // them (the Dart emitter's rule): an unused private fun is dead code in a
182
+ // generated file and an "is never used" inspection in every IDE. The
183
+ // query emitter is unaffected — it declares its own queryRowBool/
184
+ // queryRowBytes copies under distinct names.
185
+ const columnTypes = new Set(ir.tables.flatMap((table) => table.columns.map((column) => column.type)));
186
+ if (columnTypes.has('boolean')) {
187
+ parts.push([
188
+ '/** Lift a SQLite boolean: a real JSON bool, or 0/1 as a number. */',
189
+ 'private fun rowBool(value: JsonValue?): Boolean? {',
190
+ ' value?.bool?.let { return it }',
191
+ ' value?.number?.let { return it != 0.0 }',
192
+ ' return null',
193
+ '}',
194
+ ].join('\n'));
195
+ }
196
+ if (columnTypes.has('bytes') || columnTypes.has('crdt')) {
197
+ parts.push([
198
+ '/** Decode the core\'s `{"$bytes":"<hex>"}` marshaling into a ByteArray. */',
199
+ 'private fun rowBytes(value: JsonValue?): ByteArray? {',
200
+ ' val hex = value?.get("\\$bytes")?.string ?: return null',
201
+ ' if (hex.length % 2 != 0) return null',
202
+ ' return ByteArray(hex.length / 2) { i ->',
203
+ ' hex.substring(i * 2, i * 2 + 2).toInt(16).toByte()',
204
+ ' }',
205
+ '}',
206
+ ].join('\n'));
207
+ }
198
208
  for (const table of ir.tables) {
199
209
  parts.push(emitDataClass(table).join('\n'));
200
210
  }
@@ -77,9 +77,9 @@ function emitOrderByEnum(query) {
77
77
  const lines = [];
78
78
  lines.push(`/// §6 orderBy allowlist for ${query.name} — checked at generate time.`);
79
79
  lines.push(`enum ${typeName(query.name)}OrderBy {`);
80
- lines.push(query.orderBy.allowed
80
+ lines.push(`${query.orderBy.allowed
81
81
  .map((col) => ` ${camelCase(col.langName)}(${quote(col.name)})`)
82
- .join(',\n') + ';');
82
+ .join(',\n')};`);
83
83
  lines.push('');
84
84
  lines.push(` const ${typeName(query.name)}OrderBy(this.column);`);
85
85
  lines.push(' final String column;');
@@ -9,13 +9,6 @@ const KOTLIN_TYPE = {
9
9
  blob_ref: 'String',
10
10
  crdt: 'ByteArray',
11
11
  };
12
- function pascalCase(name) {
13
- return name
14
- .split(/[_-]+/)
15
- .filter((p) => p.length > 0)
16
- .map((p) => p.charAt(0).toUpperCase() + p.slice(1))
17
- .join('');
18
- }
19
12
  /** Language-facing field name — the pinned §12 naming map. */
20
13
  function camelCase(name) {
21
14
  return snakeToCamel(name);
@@ -85,9 +78,9 @@ function emitOrderByEnum(query) {
85
78
  const lines = [];
86
79
  lines.push(`/** §6 orderBy allowlist for ${query.name} — checked at generate time. */`);
87
80
  lines.push(`enum class ${typeName(query.name)}OrderBy(val column: String) {`);
88
- lines.push(query.orderBy.allowed
81
+ lines.push(`${query.orderBy.allowed
89
82
  .map((col) => ` ${camelCase(col.langName)}(${quote(col.name)})`)
90
- .join(',\n') + ';');
83
+ .join(',\n')};`);
91
84
  lines.push('}');
92
85
  return lines;
93
86
  }
@@ -9,13 +9,6 @@ const SWIFT_TYPE = {
9
9
  blob_ref: 'String',
10
10
  crdt: '[UInt8]',
11
11
  };
12
- function pascalCase(name) {
13
- return name
14
- .split(/[_-]+/)
15
- .filter((p) => p.length > 0)
16
- .map((p) => p.charAt(0).toUpperCase() + p.slice(1))
17
- .join('');
18
- }
19
12
  /** Language-facing field name — the pinned §12 naming map. */
20
13
  function camelCase(name) {
21
14
  return snakeToCamel(name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncular/typegen",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
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.2.1",
52
- "@syncular/server": "0.2.1"
51
+ "@syncular/core": "0.3.1",
52
+ "@syncular/server": "0.3.1"
53
53
  }
54
54
  }
package/src/emit-dart.ts CHANGED
@@ -212,28 +212,41 @@ export function emitDartModule(ir: IrDocument, hash: string): string {
212
212
  for (const line of schemaValue) schemaLines.push(line);
213
213
  parts.push(schemaLines.join('\n'));
214
214
 
215
- // Shared row-decode helpers.
216
- parts.push(
217
- [
218
- '/// Lift a SQLite boolean: a real bool, or 0/1 as a number.',
219
- 'bool? _rowBool(Object? value) {',
220
- ' if (value is bool) return value;',
221
- ' if (value is num) return value != 0;',
222
- ' return null;',
223
- '}',
224
- '',
225
- "/// Decode the core's {'\\$bytes': '<hex>'} marshaling (bytes as hex).",
226
- 'List<int>? _rowBytes(Object? value) {',
227
- ' if (value is! Map) return null;',
228
- " final hex = value[r'$bytes'];",
229
- ' if (hex is! String || hex.length % 2 != 0) return null;',
230
- ' return [',
231
- ' for (var i = 0; i < hex.length; i += 2)',
232
- ' int.parse(hex.substring(i, i + 2), radix: 16),',
233
- ' ];',
234
- '}',
235
- ].join('\n'),
215
+ // Shared row-decode helpers — emitted only when a column type references
216
+ // them. An unused private helper is an ANALYZER diagnostic (unused_element),
217
+ // which the `type=lint` ignore above does not cover, so a schema without
218
+ // boolean or bytes/crdt columns would fail a fatal-warnings `dart analyze`.
219
+ const columnTypes = new Set(
220
+ ir.tables.flatMap((table) => table.columns.map((column) => column.type)),
236
221
  );
222
+ if (columnTypes.has('boolean')) {
223
+ parts.push(
224
+ [
225
+ '/// Lift a SQLite boolean: a real bool, or 0/1 as a number.',
226
+ 'bool? _rowBool(Object? value) {',
227
+ ' if (value is bool) return value;',
228
+ ' if (value is num) return value != 0;',
229
+ ' return null;',
230
+ '}',
231
+ ].join('\n'),
232
+ );
233
+ }
234
+ if (columnTypes.has('bytes') || columnTypes.has('crdt')) {
235
+ parts.push(
236
+ [
237
+ "/// Decode the core's {'\\$bytes': '<hex>'} marshaling (bytes as hex).",
238
+ 'List<int>? _rowBytes(Object? value) {',
239
+ ' if (value is! Map) return null;',
240
+ " final hex = value[r'$bytes'];",
241
+ ' if (hex is! String || hex.length % 2 != 0) return null;',
242
+ ' return [',
243
+ ' for (var i = 0; i < hex.length; i += 2)',
244
+ ' int.parse(hex.substring(i, i + 2), radix: 16),',
245
+ ' ];',
246
+ '}',
247
+ ].join('\n'),
248
+ );
249
+ }
237
250
 
238
251
  for (const table of ir.tables) {
239
252
  parts.push(emitClass(table).join('\n'));
@@ -229,26 +229,40 @@ export function emitKotlinModule(
229
229
  schemaLines.push('}');
230
230
  parts.push(schemaLines.join('\n'));
231
231
 
232
- // Shared row-decode helpers.
233
- parts.push(
234
- [
235
- '/** Lift a SQLite boolean: a real JSON bool, or 0/1 as a number. */',
236
- 'private fun rowBool(value: JsonValue?): Boolean? {',
237
- ' value?.bool?.let { return it }',
238
- ' value?.number?.let { return it != 0.0 }',
239
- ' return null',
240
- '}',
241
- '',
242
- '/** Decode the core\'s `{"$bytes":"<hex>"}` marshaling into a ByteArray. */',
243
- 'private fun rowBytes(value: JsonValue?): ByteArray? {',
244
- ' val hex = value?.get("\\$bytes")?.string ?: return null',
245
- ' if (hex.length % 2 != 0) return null',
246
- ' return ByteArray(hex.length / 2) { i ->',
247
- ' hex.substring(i * 2, i * 2 + 2).toInt(16).toByte()',
248
- ' }',
249
- '}',
250
- ].join('\n'),
232
+ // Shared row-decode helpers — emitted only when a column type references
233
+ // them (the Dart emitter's rule): an unused private fun is dead code in a
234
+ // generated file and an "is never used" inspection in every IDE. The
235
+ // query emitter is unaffected it declares its own queryRowBool/
236
+ // queryRowBytes copies under distinct names.
237
+ const columnTypes = new Set(
238
+ ir.tables.flatMap((table) => table.columns.map((column) => column.type)),
251
239
  );
240
+ if (columnTypes.has('boolean')) {
241
+ parts.push(
242
+ [
243
+ '/** Lift a SQLite boolean: a real JSON bool, or 0/1 as a number. */',
244
+ 'private fun rowBool(value: JsonValue?): Boolean? {',
245
+ ' value?.bool?.let { return it }',
246
+ ' value?.number?.let { return it != 0.0 }',
247
+ ' return null',
248
+ '}',
249
+ ].join('\n'),
250
+ );
251
+ }
252
+ if (columnTypes.has('bytes') || columnTypes.has('crdt')) {
253
+ parts.push(
254
+ [
255
+ '/** Decode the core\'s `{"$bytes":"<hex>"}` marshaling into a ByteArray. */',
256
+ 'private fun rowBytes(value: JsonValue?): ByteArray? {',
257
+ ' val hex = value?.get("\\$bytes")?.string ?: return null',
258
+ ' if (hex.length % 2 != 0) return null',
259
+ ' return ByteArray(hex.length / 2) { i ->',
260
+ ' hex.substring(i * 2, i * 2 + 2).toInt(16).toByte()',
261
+ ' }',
262
+ '}',
263
+ ].join('\n'),
264
+ );
265
+ }
252
266
 
253
267
  for (const table of ir.tables) {
254
268
  parts.push(emitDataClass(table).join('\n'));
@@ -102,9 +102,9 @@ function emitOrderByEnum(query: AnalyzedQuery): string[] {
102
102
  );
103
103
  lines.push(`enum ${typeName(query.name)}OrderBy {`);
104
104
  lines.push(
105
- query.orderBy.allowed
105
+ `${query.orderBy.allowed
106
106
  .map((col) => ` ${camelCase(col.langName)}(${quote(col.name)})`)
107
- .join(',\n') + ';',
107
+ .join(',\n')};`,
108
108
  );
109
109
  lines.push('');
110
110
  lines.push(` const ${typeName(query.name)}OrderBy(this.column);`);
@@ -5,10 +5,11 @@
5
5
  * `q(client, …): List<QRow>` runner + `qTables` constant. The runner binds
6
6
  * named params positionally into the wrapper's `query(sql, params)`.
7
7
  *
8
- * Reuses the schema file's `rowBool`/`rowBytes` helpers (they are top-level
9
- * private in the schema module; queries live in the same package so re-declare
10
- * package-private copies here to stay self-contained the query file is a
11
- * separate output). Header carries the IR hash for byte-exact `--check`.
8
+ * Self-contained: declares its own `queryRowBool`/`queryRowBytes` copies of
9
+ * the schema file's row-decode helpers (distinct names both files are
10
+ * file-`private` in the same package, and the schema file only emits its
11
+ * helpers when a column type needs them). Header carries the IR hash for
12
+ * byte-exact `--check`.
12
13
  */
13
14
  import type { IrColumnType } from './ir';
14
15
  import { snakeToCamel } from './naming';
@@ -25,14 +26,6 @@ const KOTLIN_TYPE: Readonly<Record<IrColumnType, string>> = {
25
26
  crdt: 'ByteArray',
26
27
  };
27
28
 
28
- function pascalCase(name: string): string {
29
- return name
30
- .split(/[_-]+/)
31
- .filter((p) => p.length > 0)
32
- .map((p) => p.charAt(0).toUpperCase() + p.slice(1))
33
- .join('');
34
- }
35
-
36
29
  /** Language-facing field name — the pinned §12 naming map. */
37
30
  function camelCase(name: string): string {
38
31
  return snakeToCamel(name);
@@ -113,9 +106,9 @@ function emitOrderByEnum(query: AnalyzedQuery): string[] {
113
106
  );
114
107
  lines.push(`enum class ${typeName(query.name)}OrderBy(val column: String) {`);
115
108
  lines.push(
116
- query.orderBy.allowed
109
+ `${query.orderBy.allowed
117
110
  .map((col) => ` ${camelCase(col.langName)}(${quote(col.name)})`)
118
- .join(',\n') + ';',
111
+ .join(',\n')};`,
119
112
  );
120
113
  lines.push('}');
121
114
  return lines;
@@ -27,14 +27,6 @@ const SWIFT_TYPE: Readonly<Record<IrColumnType, string>> = {
27
27
  crdt: '[UInt8]',
28
28
  };
29
29
 
30
- function pascalCase(name: string): string {
31
- return name
32
- .split(/[_-]+/)
33
- .filter((p) => p.length > 0)
34
- .map((p) => p.charAt(0).toUpperCase() + p.slice(1))
35
- .join('');
36
- }
37
-
38
30
  /** Language-facing field name — the pinned §12 naming map. */
39
31
  function camelCase(name: string): string {
40
32
  return snakeToCamel(name);