@pol-studios/powersync 1.0.13 → 1.0.14
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/generator/cli.js +89 -13
- package/dist/generator/index.d.ts +33 -2
- package/dist/generator/index.js +90 -12
- package/dist/generator/index.js.map +1 -1
- package/package.json +1 -1
package/dist/generator/cli.js
CHANGED
|
@@ -36,6 +36,12 @@ var DEFAULT_INDEX_PATTERNS = [
|
|
|
36
36
|
"^status$",
|
|
37
37
|
"^type$"
|
|
38
38
|
];
|
|
39
|
+
var REQUIRED_AUTH_TABLES = [
|
|
40
|
+
{ name: "UserAccess", schema: "core" },
|
|
41
|
+
{ name: "UserGroup", schema: "core" },
|
|
42
|
+
{ name: "GroupAccessKey", schema: "core", requiresSyncPrimaryKey: true },
|
|
43
|
+
{ name: "Group", schema: "core", requiresSyncPrimaryKey: true }
|
|
44
|
+
];
|
|
39
45
|
|
|
40
46
|
// src/generator/parser.ts
|
|
41
47
|
function parseRowType(tableContent, options) {
|
|
@@ -196,8 +202,9 @@ ${indexLines.join("\n")}
|
|
|
196
202
|
]`;
|
|
197
203
|
}
|
|
198
204
|
function generateTableDefinition(table, columnDefs, indexes = []) {
|
|
205
|
+
const effectiveName = table.config.alias ?? table.name;
|
|
199
206
|
if (columnDefs.length === 0) {
|
|
200
|
-
return `// ${
|
|
207
|
+
return `// ${effectiveName} - no syncable columns found`;
|
|
201
208
|
}
|
|
202
209
|
const optionsParts = [];
|
|
203
210
|
if (table.config.trackMetadata) {
|
|
@@ -209,11 +216,29 @@ function generateTableDefinition(table, columnDefs, indexes = []) {
|
|
|
209
216
|
const optionsStr = optionsParts.length > 0 ? `, {
|
|
210
217
|
${optionsParts.join(",\n ")}
|
|
211
218
|
}` : "";
|
|
212
|
-
return `const ${
|
|
219
|
+
return `const ${effectiveName} = new Table({
|
|
220
|
+
${columnDefs.join("\n")}
|
|
221
|
+
}${optionsStr});`;
|
|
222
|
+
}
|
|
223
|
+
function generateLocalOnlyTableDefinition(table, columnDefs, indexes = []) {
|
|
224
|
+
const effectiveName = table.config.alias ?? table.name;
|
|
225
|
+
if (columnDefs.length === 0) {
|
|
226
|
+
return `// ${effectiveName} - no syncable columns found`;
|
|
227
|
+
}
|
|
228
|
+
const optionsParts = ["localOnly: true"];
|
|
229
|
+
if (indexes.length > 0) {
|
|
230
|
+
optionsParts.push(formatIndexes(indexes));
|
|
231
|
+
}
|
|
232
|
+
const optionsStr = `, {
|
|
233
|
+
${optionsParts.join(",\n ")}
|
|
234
|
+
}`;
|
|
235
|
+
return `// Local-only table (not synced)
|
|
236
|
+
const ${effectiveName} = new Table({
|
|
213
237
|
${columnDefs.join("\n")}
|
|
214
238
|
}${optionsStr});`;
|
|
215
239
|
}
|
|
216
|
-
function generateSchemaExport(
|
|
240
|
+
function generateSchemaExport(tables) {
|
|
241
|
+
const tableNames = tables.map((t) => t.config.alias ?? t.name);
|
|
217
242
|
return `// ============================================================================
|
|
218
243
|
// SCHEMA EXPORT
|
|
219
244
|
// ============================================================================
|
|
@@ -237,8 +262,9 @@ function generateSchemaMapping(tables, schemas) {
|
|
|
237
262
|
}
|
|
238
263
|
}
|
|
239
264
|
for (const table of tables) {
|
|
265
|
+
const effectiveName = table.config.alias ?? table.name;
|
|
240
266
|
if (table.schema !== "public" && schemaGroups.has(table.schema)) {
|
|
241
|
-
schemaGroups.get(table.schema).push(
|
|
267
|
+
schemaGroups.get(table.schema).push(effectiveName);
|
|
242
268
|
}
|
|
243
269
|
}
|
|
244
270
|
const sections = [
|
|
@@ -306,7 +332,6 @@ export function getForeignKeyTable(columnName: string): string | null {
|
|
|
306
332
|
}`;
|
|
307
333
|
}
|
|
308
334
|
function generateOutputFile(tables, tableDefs, schemas, typesPath) {
|
|
309
|
-
const tableNames = tables.map((t) => t.name);
|
|
310
335
|
return `${generateHeader(typesPath)}
|
|
311
336
|
|
|
312
337
|
// ============================================================================
|
|
@@ -315,7 +340,7 @@ function generateOutputFile(tables, tableDefs, schemas, typesPath) {
|
|
|
315
340
|
|
|
316
341
|
${tableDefs.join("\n\n")}
|
|
317
342
|
|
|
318
|
-
${generateSchemaExport(
|
|
343
|
+
${generateSchemaExport(tables)}
|
|
319
344
|
|
|
320
345
|
${generateSchemaMapping(tables, ["public", ...schemas.filter((s) => s !== "public")])}
|
|
321
346
|
${generateFKUtility()}
|
|
@@ -455,6 +480,35 @@ function generateColumnDefs(table, decimalPatterns) {
|
|
|
455
480
|
}
|
|
456
481
|
return columnDefs;
|
|
457
482
|
}
|
|
483
|
+
function validateAuthTables(tables) {
|
|
484
|
+
const warnings = [];
|
|
485
|
+
for (const authTable of REQUIRED_AUTH_TABLES) {
|
|
486
|
+
const configTable = tables.find(
|
|
487
|
+
(t) => t.name === authTable.name && (t.schema ?? "public") === authTable.schema
|
|
488
|
+
);
|
|
489
|
+
if (!configTable) {
|
|
490
|
+
warnings.push(
|
|
491
|
+
`Missing required auth table: ${authTable.schema}.${authTable.name} - offline access control may not work correctly`
|
|
492
|
+
);
|
|
493
|
+
} else if (authTable.requiresSyncPrimaryKey && !configTable.syncPrimaryKey) {
|
|
494
|
+
warnings.push(
|
|
495
|
+
`Auth table ${authTable.schema}.${authTable.name} should have syncPrimaryKey: true for proper FK references`
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return warnings;
|
|
500
|
+
}
|
|
501
|
+
function validateFKReferences(dependencyGraph, configuredTables) {
|
|
502
|
+
const warnings = [];
|
|
503
|
+
for (const fk of dependencyGraph.fkRelationships) {
|
|
504
|
+
if (!configuredTables.has(fk.referencedTable)) {
|
|
505
|
+
warnings.push(
|
|
506
|
+
`FK reference ${fk.table}.${fk.column} references table '${fk.referencedTable}' which is not in the config`
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return warnings;
|
|
511
|
+
}
|
|
458
512
|
async function generateSchema(config, options) {
|
|
459
513
|
const cwd = options?.cwd ?? process.cwd();
|
|
460
514
|
const verbose = options?.verbose ?? false;
|
|
@@ -469,6 +523,8 @@ async function generateSchema(config, options) {
|
|
|
469
523
|
const typesPath = path.isAbsolute(config.typesPath) ? config.typesPath : path.resolve(cwd, config.typesPath);
|
|
470
524
|
const outputPath = path.isAbsolute(config.outputPath) ? config.outputPath : path.resolve(cwd, config.outputPath);
|
|
471
525
|
result.outputPath = outputPath;
|
|
526
|
+
const authWarnings = validateAuthTables(config.tables);
|
|
527
|
+
result.warnings.push(...authWarnings);
|
|
472
528
|
if (!fs.existsSync(typesPath)) {
|
|
473
529
|
result.errors.push(`Types file not found: ${typesPath}`);
|
|
474
530
|
return result;
|
|
@@ -507,6 +563,9 @@ async function generateSchema(config, options) {
|
|
|
507
563
|
const indexColumns = config.indexColumns ?? [];
|
|
508
564
|
const dependencyGraph = detectFKDependencies(parsedTables);
|
|
509
565
|
result.dependencyGraph = dependencyGraph;
|
|
566
|
+
const configuredTableNames = new Set(config.tables.map((t) => t.name));
|
|
567
|
+
const fkWarnings = validateFKReferences(dependencyGraph, configuredTableNames);
|
|
568
|
+
result.warnings.push(...fkWarnings);
|
|
510
569
|
if (verbose && dependencyGraph.fkRelationships.length > 0) {
|
|
511
570
|
console.log(`
|
|
512
571
|
FK Dependencies detected: ${dependencyGraph.fkRelationships.length}`);
|
|
@@ -520,13 +579,22 @@ Recommended upload order:`);
|
|
|
520
579
|
});
|
|
521
580
|
console.log("");
|
|
522
581
|
}
|
|
523
|
-
const
|
|
582
|
+
const syncedTableDefs = [];
|
|
583
|
+
const localOnlyTableDefs = [];
|
|
524
584
|
let totalIndexes = 0;
|
|
525
585
|
for (const table of parsedTables) {
|
|
586
|
+
const isLocalOnly = table.config.localOnly ?? false;
|
|
587
|
+
const tableAlias = table.config.alias;
|
|
526
588
|
if (verbose) {
|
|
527
589
|
const syncPK = table.config.syncPrimaryKey || table.config.includeId;
|
|
590
|
+
const flags = [
|
|
591
|
+
table.config.trackMetadata ? "[trackMetadata]" : "",
|
|
592
|
+
syncPK ? "[syncPrimaryKey]" : "",
|
|
593
|
+
isLocalOnly ? "[localOnly]" : "",
|
|
594
|
+
tableAlias ? `[alias: ${tableAlias}]` : ""
|
|
595
|
+
].filter(Boolean).join(" ");
|
|
528
596
|
console.log(
|
|
529
|
-
`Processing ${table.schema}.${table.name} (${table.columns.size} columns)${
|
|
597
|
+
`Processing ${table.schema}.${table.name} (${table.columns.size} columns)${flags ? " " + flags : ""}`
|
|
530
598
|
);
|
|
531
599
|
}
|
|
532
600
|
const columnDefs = generateColumnDefs(table, decimalPatterns);
|
|
@@ -534,21 +602,29 @@ Recommended upload order:`);
|
|
|
534
602
|
result.warnings.push(`Table '${table.name}' has no syncable columns`);
|
|
535
603
|
continue;
|
|
536
604
|
}
|
|
605
|
+
const effectiveTableName = tableAlias ?? table.name;
|
|
537
606
|
const columnNames = [...table.columns.keys()];
|
|
538
|
-
const indexes = indexPatterns.length > 0 || indexColumns.length > 0 ? generateIndexDefinitions(
|
|
607
|
+
const indexes = indexPatterns.length > 0 || indexColumns.length > 0 ? generateIndexDefinitions(effectiveTableName, columnNames, indexPatterns, indexColumns) : [];
|
|
539
608
|
totalIndexes += indexes.length;
|
|
540
609
|
if (verbose && indexes.length > 0) {
|
|
541
610
|
console.log(` Indexes: ${indexes.map((i) => i.name).join(", ")}`);
|
|
542
611
|
}
|
|
543
|
-
|
|
612
|
+
if (isLocalOnly) {
|
|
613
|
+
localOnlyTableDefs.push(generateLocalOnlyTableDefinition(table, columnDefs, indexes));
|
|
614
|
+
} else {
|
|
615
|
+
syncedTableDefs.push(generateTableDefinition(table, columnDefs, indexes));
|
|
616
|
+
}
|
|
544
617
|
}
|
|
618
|
+
const tableDefs = [...syncedTableDefs, ...localOnlyTableDefs];
|
|
545
619
|
result.indexesGenerated = totalIndexes;
|
|
546
620
|
const schemas = [...new Set(config.tables.map((t) => t.schema ?? "public"))];
|
|
547
621
|
const relativePath = path.relative(cwd, typesPath);
|
|
622
|
+
const generatedTables = parsedTables.filter((t) => {
|
|
623
|
+
const effectiveName = t.config.alias ?? t.name;
|
|
624
|
+
return tableDefs.some((def) => def.includes(`const ${effectiveName} =`));
|
|
625
|
+
});
|
|
548
626
|
const output = generateOutputFile(
|
|
549
|
-
|
|
550
|
-
(t) => tableDefs.some((def) => def.includes(`const ${t.name} =`))
|
|
551
|
-
),
|
|
627
|
+
generatedTables,
|
|
552
628
|
tableDefs,
|
|
553
629
|
schemas,
|
|
554
630
|
relativePath
|
|
@@ -22,6 +22,10 @@ interface TableConfig {
|
|
|
22
22
|
skipColumns?: string[];
|
|
23
23
|
/** Only include these columns (overrides skipColumns if specified) */
|
|
24
24
|
onlyColumns?: string[];
|
|
25
|
+
/** Alias for the table in PowerSync schema (for conflicting names across schemas) */
|
|
26
|
+
alias?: string;
|
|
27
|
+
/** Table is local-only (not synced through PowerSync) */
|
|
28
|
+
localOnly?: boolean;
|
|
25
29
|
}
|
|
26
30
|
interface GeneratorConfig {
|
|
27
31
|
/** Path to Supabase-generated database.types.ts file */
|
|
@@ -60,6 +64,19 @@ declare const DEFAULT_DECIMAL_PATTERNS: string[];
|
|
|
60
64
|
* These are regex patterns matched against column names
|
|
61
65
|
*/
|
|
62
66
|
declare const DEFAULT_INDEX_PATTERNS: string[];
|
|
67
|
+
/**
|
|
68
|
+
* Required authentication/access control tables for offline auth
|
|
69
|
+
* These tables are needed for proper offline access control
|
|
70
|
+
*/
|
|
71
|
+
interface RequiredAuthTable {
|
|
72
|
+
/** Table name (PascalCase) */
|
|
73
|
+
name: string;
|
|
74
|
+
/** Schema name */
|
|
75
|
+
schema: string;
|
|
76
|
+
/** Whether this table requires syncPrimaryKey to be true */
|
|
77
|
+
requiresSyncPrimaryKey?: boolean;
|
|
78
|
+
}
|
|
79
|
+
declare const REQUIRED_AUTH_TABLES: RequiredAuthTable[];
|
|
63
80
|
|
|
64
81
|
/**
|
|
65
82
|
* Parser for Supabase database.types.ts files
|
|
@@ -207,6 +224,16 @@ declare function mapTypeToPowerSync(tsType: string, columnName: string, decimalP
|
|
|
207
224
|
* Generate column definitions for a table
|
|
208
225
|
*/
|
|
209
226
|
declare function generateColumnDefs(table: ParsedTable, decimalPatterns: string[]): string[];
|
|
227
|
+
/**
|
|
228
|
+
* Validate that required auth tables are included in config
|
|
229
|
+
* Returns warnings for missing tables or misconfigured tables
|
|
230
|
+
*/
|
|
231
|
+
declare function validateAuthTables(tables: TableConfig[]): string[];
|
|
232
|
+
/**
|
|
233
|
+
* Validate FK references to ensure all referenced tables are in the config
|
|
234
|
+
* Returns warnings for missing FK targets
|
|
235
|
+
*/
|
|
236
|
+
declare function validateFKReferences(dependencyGraph: DependencyGraph, configuredTables: Set<string>): string[];
|
|
210
237
|
/**
|
|
211
238
|
* Generate PowerSync schema from configuration
|
|
212
239
|
*/
|
|
@@ -242,10 +269,14 @@ declare function generateHeader(typesPath: string): string;
|
|
|
242
269
|
* Generate the table definition for a parsed table
|
|
243
270
|
*/
|
|
244
271
|
declare function generateTableDefinition(table: ParsedTable, columnDefs: string[], indexes?: IndexDefinition[]): string;
|
|
272
|
+
/**
|
|
273
|
+
* Generate the table definition for a local-only table (not synced through PowerSync)
|
|
274
|
+
*/
|
|
275
|
+
declare function generateLocalOnlyTableDefinition(table: ParsedTable, columnDefs: string[], indexes?: IndexDefinition[]): string;
|
|
245
276
|
/**
|
|
246
277
|
* Generate the schema export section
|
|
247
278
|
*/
|
|
248
|
-
declare function generateSchemaExport(
|
|
279
|
+
declare function generateSchemaExport(tables: ParsedTable[]): string;
|
|
249
280
|
/**
|
|
250
281
|
* Generate schema mapping utilities
|
|
251
282
|
*/
|
|
@@ -259,4 +290,4 @@ declare function generateFKUtility(): string;
|
|
|
259
290
|
*/
|
|
260
291
|
declare function generateOutputFile(tables: ParsedTable[], tableDefs: string[], schemas: string[], typesPath: string): string;
|
|
261
292
|
|
|
262
|
-
export { type ColumnInfo, type ColumnMapping, DEFAULT_DECIMAL_PATTERNS, DEFAULT_INDEX_PATTERNS, DEFAULT_SKIP_COLUMNS, type DependencyGraph, type FKDependency, type GenerateResult, type GeneratorConfig, type IndexDefinition, type ParseOptions, type ParsedTable, type TableConfig, defineConfig, detectFKDependencies, extractTableDef, fkColumnToTableName, formatDependencyGraph, generateColumnDefs, generateFKUtility, generateHeader, generateIndexDefinitions, generateOutputFile, generateSchema, generateSchemaExport, generateSchemaMapping, generateTableDefinition, getAvailableSchemas, getLeafTables, getRootTables, getTablesInSchema, getUploadOrder, mapTypeToPowerSync, parseRowType, parseTypesFile };
|
|
293
|
+
export { type ColumnInfo, type ColumnMapping, DEFAULT_DECIMAL_PATTERNS, DEFAULT_INDEX_PATTERNS, DEFAULT_SKIP_COLUMNS, type DependencyGraph, type FKDependency, type GenerateResult, type GeneratorConfig, type IndexDefinition, type ParseOptions, type ParsedTable, REQUIRED_AUTH_TABLES, type RequiredAuthTable, type TableConfig, defineConfig, detectFKDependencies, extractTableDef, fkColumnToTableName, formatDependencyGraph, generateColumnDefs, generateFKUtility, generateHeader, generateIndexDefinitions, generateLocalOnlyTableDefinition, generateOutputFile, generateSchema, generateSchemaExport, generateSchemaMapping, generateTableDefinition, getAvailableSchemas, getLeafTables, getRootTables, getTablesInSchema, getUploadOrder, mapTypeToPowerSync, parseRowType, parseTypesFile, validateAuthTables, validateFKReferences };
|
package/dist/generator/index.js
CHANGED
|
@@ -17,6 +17,21 @@ var DEFAULT_INDEX_PATTERNS = [
|
|
|
17
17
|
"^status$",
|
|
18
18
|
"^type$"
|
|
19
19
|
];
|
|
20
|
+
var REQUIRED_AUTH_TABLES = [{
|
|
21
|
+
name: "UserAccess",
|
|
22
|
+
schema: "core"
|
|
23
|
+
}, {
|
|
24
|
+
name: "UserGroup",
|
|
25
|
+
schema: "core"
|
|
26
|
+
}, {
|
|
27
|
+
name: "GroupAccessKey",
|
|
28
|
+
schema: "core",
|
|
29
|
+
requiresSyncPrimaryKey: true
|
|
30
|
+
}, {
|
|
31
|
+
name: "Group",
|
|
32
|
+
schema: "core",
|
|
33
|
+
requiresSyncPrimaryKey: true
|
|
34
|
+
}];
|
|
20
35
|
|
|
21
36
|
// src/generator/generator.ts
|
|
22
37
|
import * as fs from "fs";
|
|
@@ -194,8 +209,9 @@ ${indexLines.join("\n")}
|
|
|
194
209
|
]`;
|
|
195
210
|
}
|
|
196
211
|
function generateTableDefinition(table, columnDefs, indexes = []) {
|
|
212
|
+
const effectiveName = table.config.alias ?? table.name;
|
|
197
213
|
if (columnDefs.length === 0) {
|
|
198
|
-
return `// ${
|
|
214
|
+
return `// ${effectiveName} - no syncable columns found`;
|
|
199
215
|
}
|
|
200
216
|
const optionsParts = [];
|
|
201
217
|
if (table.config.trackMetadata) {
|
|
@@ -207,11 +223,29 @@ function generateTableDefinition(table, columnDefs, indexes = []) {
|
|
|
207
223
|
const optionsStr = optionsParts.length > 0 ? `, {
|
|
208
224
|
${optionsParts.join(",\n ")}
|
|
209
225
|
}` : "";
|
|
210
|
-
return `const ${
|
|
226
|
+
return `const ${effectiveName} = new Table({
|
|
211
227
|
${columnDefs.join("\n")}
|
|
212
228
|
}${optionsStr});`;
|
|
213
229
|
}
|
|
214
|
-
function
|
|
230
|
+
function generateLocalOnlyTableDefinition(table, columnDefs, indexes = []) {
|
|
231
|
+
const effectiveName = table.config.alias ?? table.name;
|
|
232
|
+
if (columnDefs.length === 0) {
|
|
233
|
+
return `// ${effectiveName} - no syncable columns found`;
|
|
234
|
+
}
|
|
235
|
+
const optionsParts = ["localOnly: true"];
|
|
236
|
+
if (indexes.length > 0) {
|
|
237
|
+
optionsParts.push(formatIndexes(indexes));
|
|
238
|
+
}
|
|
239
|
+
const optionsStr = `, {
|
|
240
|
+
${optionsParts.join(",\n ")}
|
|
241
|
+
}`;
|
|
242
|
+
return `// Local-only table (not synced)
|
|
243
|
+
const ${effectiveName} = new Table({
|
|
244
|
+
${columnDefs.join("\n")}
|
|
245
|
+
}${optionsStr});`;
|
|
246
|
+
}
|
|
247
|
+
function generateSchemaExport(tables) {
|
|
248
|
+
const tableNames = tables.map((t) => t.config.alias ?? t.name);
|
|
215
249
|
return `// ============================================================================
|
|
216
250
|
// SCHEMA EXPORT
|
|
217
251
|
// ============================================================================
|
|
@@ -235,8 +269,9 @@ function generateSchemaMapping(tables, schemas) {
|
|
|
235
269
|
}
|
|
236
270
|
}
|
|
237
271
|
for (const table of tables) {
|
|
272
|
+
const effectiveName = table.config.alias ?? table.name;
|
|
238
273
|
if (table.schema !== "public" && schemaGroups.has(table.schema)) {
|
|
239
|
-
schemaGroups.get(table.schema).push(
|
|
274
|
+
schemaGroups.get(table.schema).push(effectiveName);
|
|
240
275
|
}
|
|
241
276
|
}
|
|
242
277
|
const sections = [`// ============================================================================
|
|
@@ -302,7 +337,6 @@ export function getForeignKeyTable(columnName: string): string | null {
|
|
|
302
337
|
}`;
|
|
303
338
|
}
|
|
304
339
|
function generateOutputFile(tables, tableDefs, schemas, typesPath) {
|
|
305
|
-
const tableNames = tables.map((t) => t.name);
|
|
306
340
|
return `${generateHeader(typesPath)}
|
|
307
341
|
|
|
308
342
|
// ============================================================================
|
|
@@ -311,7 +345,7 @@ function generateOutputFile(tables, tableDefs, schemas, typesPath) {
|
|
|
311
345
|
|
|
312
346
|
${tableDefs.join("\n\n")}
|
|
313
347
|
|
|
314
|
-
${generateSchemaExport(
|
|
348
|
+
${generateSchemaExport(tables)}
|
|
315
349
|
|
|
316
350
|
${generateSchemaMapping(tables, ["public", ...schemas.filter((s) => s !== "public")])}
|
|
317
351
|
${generateFKUtility()}
|
|
@@ -501,6 +535,27 @@ function generateColumnDefs(table, decimalPatterns) {
|
|
|
501
535
|
}
|
|
502
536
|
return columnDefs;
|
|
503
537
|
}
|
|
538
|
+
function validateAuthTables(tables) {
|
|
539
|
+
const warnings = [];
|
|
540
|
+
for (const authTable of REQUIRED_AUTH_TABLES) {
|
|
541
|
+
const configTable = tables.find((t) => t.name === authTable.name && (t.schema ?? "public") === authTable.schema);
|
|
542
|
+
if (!configTable) {
|
|
543
|
+
warnings.push(`Missing required auth table: ${authTable.schema}.${authTable.name} - offline access control may not work correctly`);
|
|
544
|
+
} else if (authTable.requiresSyncPrimaryKey && !configTable.syncPrimaryKey) {
|
|
545
|
+
warnings.push(`Auth table ${authTable.schema}.${authTable.name} should have syncPrimaryKey: true for proper FK references`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return warnings;
|
|
549
|
+
}
|
|
550
|
+
function validateFKReferences(dependencyGraph, configuredTables) {
|
|
551
|
+
const warnings = [];
|
|
552
|
+
for (const fk of dependencyGraph.fkRelationships) {
|
|
553
|
+
if (!configuredTables.has(fk.referencedTable)) {
|
|
554
|
+
warnings.push(`FK reference ${fk.table}.${fk.column} references table '${fk.referencedTable}' which is not in the config`);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return warnings;
|
|
558
|
+
}
|
|
504
559
|
async function generateSchema(config, options) {
|
|
505
560
|
const cwd = options?.cwd ?? process.cwd();
|
|
506
561
|
const verbose = options?.verbose ?? false;
|
|
@@ -515,6 +570,8 @@ async function generateSchema(config, options) {
|
|
|
515
570
|
const typesPath = path.isAbsolute(config.typesPath) ? config.typesPath : path.resolve(cwd, config.typesPath);
|
|
516
571
|
const outputPath = path.isAbsolute(config.outputPath) ? config.outputPath : path.resolve(cwd, config.outputPath);
|
|
517
572
|
result.outputPath = outputPath;
|
|
573
|
+
const authWarnings = validateAuthTables(config.tables);
|
|
574
|
+
result.warnings.push(...authWarnings);
|
|
518
575
|
if (!fs.existsSync(typesPath)) {
|
|
519
576
|
result.errors.push(`Types file not found: ${typesPath}`);
|
|
520
577
|
return result;
|
|
@@ -541,6 +598,9 @@ async function generateSchema(config, options) {
|
|
|
541
598
|
const indexColumns = config.indexColumns ?? [];
|
|
542
599
|
const dependencyGraph = detectFKDependencies(parsedTables);
|
|
543
600
|
result.dependencyGraph = dependencyGraph;
|
|
601
|
+
const configuredTableNames = new Set(config.tables.map((t) => t.name));
|
|
602
|
+
const fkWarnings = validateFKReferences(dependencyGraph, configuredTableNames);
|
|
603
|
+
result.warnings.push(...fkWarnings);
|
|
544
604
|
if (verbose && dependencyGraph.fkRelationships.length > 0) {
|
|
545
605
|
console.log(`
|
|
546
606
|
FK Dependencies detected: ${dependencyGraph.fkRelationships.length}`);
|
|
@@ -554,30 +614,44 @@ Recommended upload order:`);
|
|
|
554
614
|
});
|
|
555
615
|
console.log("");
|
|
556
616
|
}
|
|
557
|
-
const
|
|
617
|
+
const syncedTableDefs = [];
|
|
618
|
+
const localOnlyTableDefs = [];
|
|
558
619
|
let totalIndexes = 0;
|
|
559
620
|
for (const table of parsedTables) {
|
|
621
|
+
const isLocalOnly = table.config.localOnly ?? false;
|
|
622
|
+
const tableAlias = table.config.alias;
|
|
560
623
|
if (verbose) {
|
|
561
624
|
const syncPK = table.config.syncPrimaryKey || table.config.includeId;
|
|
562
|
-
|
|
625
|
+
const flags = [table.config.trackMetadata ? "[trackMetadata]" : "", syncPK ? "[syncPrimaryKey]" : "", isLocalOnly ? "[localOnly]" : "", tableAlias ? `[alias: ${tableAlias}]` : ""].filter(Boolean).join(" ");
|
|
626
|
+
console.log(`Processing ${table.schema}.${table.name} (${table.columns.size} columns)${flags ? " " + flags : ""}`);
|
|
563
627
|
}
|
|
564
628
|
const columnDefs = generateColumnDefs(table, decimalPatterns);
|
|
565
629
|
if (columnDefs.length === 0) {
|
|
566
630
|
result.warnings.push(`Table '${table.name}' has no syncable columns`);
|
|
567
631
|
continue;
|
|
568
632
|
}
|
|
633
|
+
const effectiveTableName = tableAlias ?? table.name;
|
|
569
634
|
const columnNames = [...table.columns.keys()];
|
|
570
|
-
const indexes = indexPatterns.length > 0 || indexColumns.length > 0 ? generateIndexDefinitions(
|
|
635
|
+
const indexes = indexPatterns.length > 0 || indexColumns.length > 0 ? generateIndexDefinitions(effectiveTableName, columnNames, indexPatterns, indexColumns) : [];
|
|
571
636
|
totalIndexes += indexes.length;
|
|
572
637
|
if (verbose && indexes.length > 0) {
|
|
573
638
|
console.log(` Indexes: ${indexes.map((i) => i.name).join(", ")}`);
|
|
574
639
|
}
|
|
575
|
-
|
|
640
|
+
if (isLocalOnly) {
|
|
641
|
+
localOnlyTableDefs.push(generateLocalOnlyTableDefinition(table, columnDefs, indexes));
|
|
642
|
+
} else {
|
|
643
|
+
syncedTableDefs.push(generateTableDefinition(table, columnDefs, indexes));
|
|
644
|
+
}
|
|
576
645
|
}
|
|
646
|
+
const tableDefs = [...syncedTableDefs, ...localOnlyTableDefs];
|
|
577
647
|
result.indexesGenerated = totalIndexes;
|
|
578
648
|
const schemas = [...new Set(config.tables.map((t) => t.schema ?? "public"))];
|
|
579
649
|
const relativePath = path.relative(cwd, typesPath);
|
|
580
|
-
const
|
|
650
|
+
const generatedTables = parsedTables.filter((t) => {
|
|
651
|
+
const effectiveName = t.config.alias ?? t.name;
|
|
652
|
+
return tableDefs.some((def) => def.includes(`const ${effectiveName} =`));
|
|
653
|
+
});
|
|
654
|
+
const output = generateOutputFile(generatedTables, tableDefs, schemas, relativePath);
|
|
581
655
|
if (dryRun) {
|
|
582
656
|
result.success = true;
|
|
583
657
|
result.tablesGenerated = tableDefs.length;
|
|
@@ -599,6 +673,7 @@ export {
|
|
|
599
673
|
DEFAULT_DECIMAL_PATTERNS,
|
|
600
674
|
DEFAULT_INDEX_PATTERNS,
|
|
601
675
|
DEFAULT_SKIP_COLUMNS,
|
|
676
|
+
REQUIRED_AUTH_TABLES,
|
|
602
677
|
defineConfig,
|
|
603
678
|
detectFKDependencies,
|
|
604
679
|
extractTableDef,
|
|
@@ -608,6 +683,7 @@ export {
|
|
|
608
683
|
generateFKUtility,
|
|
609
684
|
generateHeader,
|
|
610
685
|
generateIndexDefinitions,
|
|
686
|
+
generateLocalOnlyTableDefinition,
|
|
611
687
|
generateOutputFile,
|
|
612
688
|
generateSchema,
|
|
613
689
|
generateSchemaExport,
|
|
@@ -620,6 +696,8 @@ export {
|
|
|
620
696
|
getUploadOrder,
|
|
621
697
|
mapTypeToPowerSync,
|
|
622
698
|
parseRowType,
|
|
623
|
-
parseTypesFile
|
|
699
|
+
parseTypesFile,
|
|
700
|
+
validateAuthTables,
|
|
701
|
+
validateFKReferences
|
|
624
702
|
};
|
|
625
703
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/generator/config.ts","../../src/generator/generator.ts","../../src/generator/parser.ts","../../src/generator/templates.ts","../../src/generator/fk-dependencies.ts"],"sourcesContent":["/**\n * Configuration types and helpers for PowerSync schema generator\n */\n\nexport interface TableConfig {\n /** Table name (PascalCase as it appears in database.types.ts) */\n name: string;\n /** Schema name (defaults to 'public') */\n schema?: string;\n /** Enable ps_crud timestamp tracking for optimistic UI updates */\n trackMetadata?: boolean;\n /**\n * Sync the primary key column (normally skipped as PowerSync handles it internally).\n *\n * Use this for tables with integer PKs that are referenced by FKs in other tables.\n * Example: `Group.id` is referenced by `UserGroup.groupId`, so Group needs `syncPrimaryKey: true`\n * to ensure the integer ID is available for client-side joins.\n */\n syncPrimaryKey?: boolean;\n /** @deprecated Use `syncPrimaryKey` instead */\n includeId?: boolean;\n /** Columns to skip for this specific table (in addition to global skipColumns) */\n skipColumns?: string[];\n /** Only include these columns (overrides skipColumns if specified) */\n onlyColumns?: string[];\n}\nexport interface GeneratorConfig {\n /** Path to Supabase-generated database.types.ts file */\n typesPath: string;\n /** Output path for generated PowerSync schema */\n outputPath: string;\n /** Tables to include in the PowerSync schema */\n tables: TableConfig[];\n /** Columns to always skip (in addition to defaults like 'id') */\n skipColumns?: string[];\n /** Column name patterns that should use column.real for decimal values */\n decimalPatterns?: string[];\n /** Additional schemas to track (besides 'public' which is the default) */\n schemas?: string[];\n /** Generate indexes for FK and common columns (default: true) */\n autoIndexes?: boolean;\n /** Additional columns to index (exact column names) */\n indexColumns?: string[];\n /** Column patterns that should be indexed (regex patterns) */\n indexPatterns?: string[];\n}\n\n/**\n * Define a PowerSync generator configuration with type safety\n */\nexport function defineConfig(config: GeneratorConfig): GeneratorConfig {\n return config;\n}\n\n/**\n * Default columns that are skipped during generation\n */\nexport const DEFAULT_SKIP_COLUMNS = ['id',\n// PowerSync handles id automatically\n// Legacy numeric ID columns - typically not needed after UUID migration\n'legacyId'];\n\n/**\n * Default column name patterns that indicate decimal values\n */\nexport const DEFAULT_DECIMAL_PATTERNS = ['hours', 'watts', 'voltage', 'rate', 'amount', 'price', 'cost', 'total'];\n\n/**\n * Default column patterns that should be indexed\n * These are regex patterns matched against column names\n */\nexport const DEFAULT_INDEX_PATTERNS = ['Id$',\n// FK columns ending in Id (e.g., projectId, userId)\n'^createdAt$', '^updatedAt$', '^status$', '^type$'];","/**\n * PowerSync schema generator\n *\n * Converts Supabase database.types.ts into PowerSync schema definitions\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { GeneratorConfig } from './config.js';\nimport { DEFAULT_SKIP_COLUMNS, DEFAULT_DECIMAL_PATTERNS, DEFAULT_INDEX_PATTERNS } from './config.js';\nimport { parseTypesFile, type ParsedTable } from './parser.js';\nimport { generateTableDefinition, generateOutputFile, generateIndexDefinitions, type IndexDefinition } from './templates.js';\nimport { detectFKDependencies, type DependencyGraph } from './fk-dependencies.js';\nexport interface ColumnMapping {\n type: 'column.text' | 'column.integer' | 'column.real';\n isEnum?: boolean;\n isBoolean?: boolean;\n}\nexport interface GenerateResult {\n success: boolean;\n tablesGenerated: number;\n outputPath: string;\n errors: string[];\n warnings: string[];\n /** Generated output (included when dryRun is true) */\n output?: string;\n /** Number of indexes generated */\n indexesGenerated?: number;\n /** FK dependency graph (when detected) */\n dependencyGraph?: DependencyGraph;\n}\n\n/**\n * Map TypeScript types to PowerSync column types\n */\nexport function mapTypeToPowerSync(tsType: string, columnName: string, decimalPatterns: string[]): ColumnMapping | null {\n // Clean up the type (remove nullability)\n const cleanType = tsType.trim().replace(/\\s*\\|\\s*null/g, '');\n\n // Skip complex types that can't be stored in SQLite\n if (cleanType.includes('Json') || cleanType.includes('unknown') || cleanType.includes('{')) {\n return null;\n }\n\n // Array types - skip\n if (cleanType.includes('[]')) {\n return null;\n }\n\n // Boolean -> integer (0/1)\n if (cleanType === 'boolean') {\n return {\n type: 'column.integer',\n isBoolean: true\n };\n }\n\n // Number types\n if (cleanType === 'number') {\n // Use real for columns that might have decimals\n if (decimalPatterns.some(pattern => columnName.toLowerCase().includes(pattern.toLowerCase()))) {\n return {\n type: 'column.real'\n };\n }\n return {\n type: 'column.integer'\n };\n }\n\n // String types\n if (cleanType === 'string') {\n return {\n type: 'column.text'\n };\n }\n\n // Enum types (Database[\"schema\"][\"Enums\"][\"EnumName\"]) -> store as text\n if (cleanType.includes('Database[') && cleanType.includes('Enums')) {\n return {\n type: 'column.text',\n isEnum: true\n };\n }\n\n // Default to text for unknown types (likely enums or other string-like types)\n return {\n type: 'column.text'\n };\n}\n\n/**\n * Generate column definitions for a table\n */\nexport function generateColumnDefs(table: ParsedTable, decimalPatterns: string[]): string[] {\n const columnDefs: string[] = [];\n for (const [columnName, tsType] of table.columns) {\n const mapping = mapTypeToPowerSync(tsType, columnName, decimalPatterns);\n if (mapping) {\n // Add comment for boolean and enum columns\n let comment = '';\n if (mapping.isBoolean) {\n comment = ' // boolean stored as 0/1';\n } else if (mapping.isEnum) {\n comment = ' // enum stored as text';\n }\n columnDefs.push(` ${columnName}: ${mapping.type},${comment}`);\n }\n }\n return columnDefs;\n}\n\n/**\n * Generate PowerSync schema from configuration\n */\nexport async function generateSchema(config: GeneratorConfig, options?: {\n cwd?: string;\n verbose?: boolean;\n dryRun?: boolean;\n}): Promise<GenerateResult> {\n const cwd = options?.cwd ?? process.cwd();\n const verbose = options?.verbose ?? false;\n const dryRun = options?.dryRun ?? false;\n const result: GenerateResult = {\n success: false,\n tablesGenerated: 0,\n outputPath: '',\n errors: [],\n warnings: []\n };\n\n // Resolve paths relative to cwd\n const typesPath = path.isAbsolute(config.typesPath) ? config.typesPath : path.resolve(cwd, config.typesPath);\n const outputPath = path.isAbsolute(config.outputPath) ? config.outputPath : path.resolve(cwd, config.outputPath);\n result.outputPath = outputPath;\n\n // Check if types file exists\n if (!fs.existsSync(typesPath)) {\n result.errors.push(`Types file not found: ${typesPath}`);\n return result;\n }\n\n // Read types file\n if (verbose) {\n console.log(`Reading types from: ${typesPath}`);\n }\n const typesContent = fs.readFileSync(typesPath, 'utf-8');\n\n // Build skip columns set\n const skipColumns = new Set([...DEFAULT_SKIP_COLUMNS, ...(config.skipColumns ?? [])]);\n\n // Build decimal patterns\n const decimalPatterns = [...DEFAULT_DECIMAL_PATTERNS, ...(config.decimalPatterns ?? [])];\n\n // Parse tables from types file\n const parsedTables = parseTypesFile(typesContent, config.tables, skipColumns);\n\n // Check for tables that weren't found\n for (const tableConfig of config.tables) {\n const found = parsedTables.some(t => t.name === tableConfig.name);\n if (!found) {\n result.warnings.push(`Table '${tableConfig.name}' not found in schema '${tableConfig.schema ?? 'public'}'`);\n }\n }\n if (parsedTables.length === 0) {\n result.errors.push('No tables were parsed successfully');\n return result;\n }\n\n // Build index patterns (auto-indexes enabled by default)\n const autoIndexes = config.autoIndexes ?? true;\n const indexPatterns = autoIndexes ? [...DEFAULT_INDEX_PATTERNS, ...(config.indexPatterns ?? [])] : config.indexPatterns ?? [];\n const indexColumns = config.indexColumns ?? [];\n\n // Detect FK dependencies\n const dependencyGraph = detectFKDependencies(parsedTables);\n result.dependencyGraph = dependencyGraph;\n if (verbose && dependencyGraph.fkRelationships.length > 0) {\n console.log(`\\nFK Dependencies detected: ${dependencyGraph.fkRelationships.length}`);\n for (const fk of dependencyGraph.fkRelationships) {\n console.log(` ${fk.table}.${fk.column} -> ${fk.referencedTable}`);\n }\n console.log(`\\nRecommended upload order:`);\n dependencyGraph.uploadOrder.forEach((table, i) => {\n console.log(` ${i + 1}. ${table}`);\n });\n console.log('');\n }\n\n // Generate table definitions\n const tableDefs: string[] = [];\n let totalIndexes = 0;\n for (const table of parsedTables) {\n if (verbose) {\n const syncPK = table.config.syncPrimaryKey || table.config.includeId;\n console.log(`Processing ${table.schema}.${table.name} (${table.columns.size} columns)${table.config.trackMetadata ? ' [trackMetadata]' : ''}${syncPK ? ' [syncPrimaryKey]' : ''}`);\n }\n const columnDefs = generateColumnDefs(table, decimalPatterns);\n if (columnDefs.length === 0) {\n result.warnings.push(`Table '${table.name}' has no syncable columns`);\n continue;\n }\n\n // Generate indexes for this table\n const columnNames = [...table.columns.keys()];\n const indexes = indexPatterns.length > 0 || indexColumns.length > 0 ? generateIndexDefinitions(table.name, columnNames, indexPatterns, indexColumns) : [];\n totalIndexes += indexes.length;\n if (verbose && indexes.length > 0) {\n console.log(` Indexes: ${indexes.map(i => i.name).join(', ')}`);\n }\n tableDefs.push(generateTableDefinition(table, columnDefs, indexes));\n }\n result.indexesGenerated = totalIndexes;\n\n // Collect unique schemas\n const schemas = [...new Set(config.tables.map(t => t.schema ?? 'public'))];\n\n // Generate output file content\n const relativePath = path.relative(cwd, typesPath);\n const output = generateOutputFile(parsedTables.filter(t => tableDefs.some(def => def.includes(`const ${t.name} =`))), tableDefs, schemas, relativePath);\n\n // If dry-run, return output without writing\n if (dryRun) {\n result.success = true;\n result.tablesGenerated = tableDefs.length;\n result.output = output;\n return result;\n }\n\n // Ensure output directory exists\n const outputDir = path.dirname(outputPath);\n if (!fs.existsSync(outputDir)) {\n fs.mkdirSync(outputDir, {\n recursive: true\n });\n }\n\n // Write output file\n fs.writeFileSync(outputPath, output);\n result.success = true;\n result.tablesGenerated = tableDefs.length;\n return result;\n}","/**\n * Parser for Supabase database.types.ts files\n *\n * Extracts table definitions and column types from the generated TypeScript types\n */\n\nimport type { TableConfig } from './config.js';\nexport interface ColumnInfo {\n name: string;\n tsType: string;\n isNullable: boolean;\n}\nexport interface ParsedTable {\n name: string;\n schema: string;\n columns: Map<string, string>;\n config: TableConfig;\n}\nexport interface ParseOptions {\n /** Columns to skip */\n skipColumns: Set<string>;\n /**\n * Include the id column (normally skipped).\n * Use for tables with integer PKs referenced by FKs in other tables.\n */\n syncPrimaryKey?: boolean;\n /** @deprecated Use `syncPrimaryKey` instead */\n includeId?: boolean;\n /** Only include these columns (overrides skipColumns if specified) */\n onlyColumns?: string[];\n}\n\n/**\n * Parse the Row type from a table definition and extract columns\n */\nexport function parseRowType(tableContent: string, options: ParseOptions): Map<string, string> {\n const columns = new Map<string, string>();\n\n // Find the Row block - handles nested braces in type definitions\n const rowMatch = tableContent.match(/Row:\\s*\\{([^}]+(?:\\{[^}]*\\}[^}]*)*)\\}/s);\n if (!rowMatch) return columns;\n const rowContent = rowMatch[1];\n\n // syncPrimaryKey takes precedence, with includeId as fallback for backwards compat\n const includePrimaryKey = options.syncPrimaryKey ?? options.includeId ?? false;\n\n // If onlyColumns is specified, use it as a whitelist (overrides skipColumns)\n const onlyColumnsSet = options.onlyColumns ? new Set(options.onlyColumns) : null;\n\n // Parse each column: \"columnName: type\" or \"columnName?: type\"\n const columnRegex = /(\\w+)\\??:\\s*([^,\\n]+)/g;\n let match;\n while ((match = columnRegex.exec(rowContent)) !== null) {\n const [, columnName, columnType] = match;\n\n // Determine if column should be included\n let shouldInclude: boolean;\n if (onlyColumnsSet) {\n // onlyColumns mode: only include explicitly listed columns\n // Exception: include 'id' if syncPrimaryKey is true\n shouldInclude = onlyColumnsSet.has(columnName) || includePrimaryKey && columnName === 'id';\n } else {\n // skipColumns mode: include unless explicitly skipped\n // Exception: include 'id' if syncPrimaryKey is true\n const isSkipped = options.skipColumns.has(columnName);\n shouldInclude = !isSkipped || includePrimaryKey && columnName === 'id';\n }\n if (shouldInclude) {\n columns.set(columnName, columnType.trim());\n }\n }\n return columns;\n}\n\n/**\n * Escape special regex characters in a string\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Extract a table definition from the database.types.ts content\n *\n * Uses bracket-counting to properly handle nested arrays in Relationships\n * (e.g., columns: [\"parentId\"] inside relationship objects)\n */\nexport function extractTableDef(content: string, tableName: string, schema: string): string | null {\n // Escape special characters in schema and table names for regex safety\n const escapedSchema = escapeRegex(schema);\n const escapedTableName = escapeRegex(tableName);\n\n // Find the schema section\n const schemaRegex = new RegExp(`${escapedSchema}:\\\\s*\\\\{[\\\\s\\\\S]*?Tables:\\\\s*\\\\{`, 'g');\n const schemaMatch = schemaRegex.exec(content);\n if (!schemaMatch) return null;\n const startIndex = schemaMatch.index;\n const searchContent = content.slice(startIndex);\n\n // Find the start of this specific table\n // Use negative lookbehind (?<![A-Za-z]) to avoid matching table names that are\n // substrings of other names (e.g., \"Tag\" in \"CommentTag\")\n const tableStartRegex = new RegExp(`(?<![A-Za-z])${escapedTableName}:\\\\s*\\\\{`, 'g');\n const tableStartMatch = tableStartRegex.exec(searchContent);\n if (!tableStartMatch) return null;\n\n // Use bracket counting to find the matching closing brace\n const tableStartIndex = tableStartMatch.index;\n const openBraceIndex = tableStartMatch.index + tableStartMatch[0].length - 1;\n let braceCount = 1;\n let i = openBraceIndex + 1;\n while (i < searchContent.length && braceCount > 0) {\n const char = searchContent[i];\n if (char === '{') braceCount++;else if (char === '}') braceCount--;\n i++;\n }\n if (braceCount !== 0) return null;\n return searchContent.slice(tableStartIndex, i);\n}\n\n/**\n * Parse a database.types.ts file and extract specified tables\n */\nexport function parseTypesFile(content: string, tables: TableConfig[], globalSkipColumns: Set<string>): ParsedTable[] {\n const parsedTables: ParsedTable[] = [];\n for (const tableConfig of tables) {\n const {\n name,\n schema = 'public',\n syncPrimaryKey,\n includeId,\n skipColumns: tableSkipColumns,\n onlyColumns\n } = tableConfig;\n const tableDef = extractTableDef(content, name, schema);\n if (!tableDef) {\n continue;\n }\n\n // Merge global and per-table skipColumns\n const mergedSkipColumns = new Set([...globalSkipColumns, ...(tableSkipColumns ?? [])]);\n const columns = parseRowType(tableDef, {\n skipColumns: mergedSkipColumns,\n syncPrimaryKey,\n includeId,\n onlyColumns\n });\n if (columns.size > 0) {\n parsedTables.push({\n name,\n schema,\n columns,\n config: tableConfig\n });\n }\n }\n return parsedTables;\n}\n\n/**\n * Get all available schemas from the types file\n */\nexport function getAvailableSchemas(content: string): string[] {\n const schemas: string[] = [];\n const schemaRegex = /(\\w+):\\s*\\{[\\s\\S]*?Tables:\\s*\\{/g;\n let match;\n while ((match = schemaRegex.exec(content)) !== null) {\n schemas.push(match[1]);\n }\n return schemas;\n}\n\n/**\n * Get all table names in a schema\n */\nexport function getTablesInSchema(content: string, schema: string): string[] {\n const tables: string[] = [];\n\n // Find the schema section\n const schemaRegex = new RegExp(`${schema}:\\\\s*\\\\{[\\\\s\\\\S]*?Tables:\\\\s*\\\\{([\\\\s\\\\S]*?)\\\\}\\\\s*Views:`, 'g');\n const schemaMatch = schemaRegex.exec(content);\n if (!schemaMatch) return tables;\n const tablesContent = schemaMatch[1];\n\n // Find table names (they're at the start of each table definition)\n const tableNameRegex = /^\\s*(\\w+):\\s*\\{/gm;\n let match;\n while ((match = tableNameRegex.exec(tablesContent)) !== null) {\n tables.push(match[1]);\n }\n return tables;\n}","/**\n * Output templates for PowerSync schema generation\n */\n\nimport type { ParsedTable } from './parser.js';\nexport interface IndexDefinition {\n name: string;\n columns: string[];\n}\n\n/**\n * Convert table name to snake_case for index naming\n */\nfunction toSnakeCase(str: string): string {\n return str.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();\n}\n\n/**\n * Generate index definitions for a table based on column patterns\n *\n * @param tableName - The table name (PascalCase)\n * @param columns - Array of column names in the table\n * @param indexPatterns - Regex patterns to match against column names\n * @param additionalColumns - Specific column names to always index\n * @returns Array of index definitions\n */\nexport function generateIndexDefinitions(tableName: string, columns: string[], indexPatterns: string[], additionalColumns: string[] = []): IndexDefinition[] {\n const indexes: IndexDefinition[] = [];\n const snakeTableName = toSnakeCase(tableName);\n\n // Combine pattern matching and explicit columns\n const columnsToIndex = new Set<string>();\n\n // Pre-compile regex patterns with error handling\n const compiledPatterns: RegExp[] = [];\n for (const pattern of indexPatterns) {\n try {\n compiledPatterns.push(new RegExp(pattern));\n } catch {\n console.warn(`Warning: Invalid index pattern regex \"${pattern}\" - skipping`);\n }\n }\n\n // Match columns against patterns\n for (const column of columns) {\n // Skip 'id' column - it's the primary key and already indexed\n if (column === 'id') continue;\n for (const regex of compiledPatterns) {\n if (regex.test(column)) {\n columnsToIndex.add(column);\n break;\n }\n }\n }\n\n // Add explicitly specified columns (if they exist in the table)\n for (const column of additionalColumns) {\n if (columns.includes(column) && column !== 'id') {\n columnsToIndex.add(column);\n }\n }\n\n // Create index definitions\n for (const column of columnsToIndex) {\n const snakeColumn = toSnakeCase(column);\n indexes.push({\n name: `idx_${snakeTableName}_${snakeColumn}`,\n columns: [column]\n });\n }\n\n // Sort by index name for deterministic output\n indexes.sort((a, b) => a.name.localeCompare(b.name));\n return indexes;\n}\n\n/**\n * File header template\n */\nexport function generateHeader(typesPath: string): string {\n return `/**\n * PowerSync Schema Definition\n *\n * AUTO-GENERATED from ${typesPath}\n * Run: npx @pol-studios/powersync generate-schema\n *\n * DO NOT EDIT MANUALLY - changes will be overwritten\n */\n\nimport { column, Schema, Table } from \"@powersync/react-native\";\n`;\n}\n\n/**\n * Format indexes array for output\n */\nfunction formatIndexes(indexes: IndexDefinition[]): string {\n if (indexes.length === 0) return '';\n const indexLines = indexes.map(idx => {\n const columnsStr = idx.columns.map(c => `'${c}'`).join(', ');\n return ` { name: '${idx.name}', columns: [${columnsStr}] },`;\n });\n return `indexes: [\\n${indexLines.join('\\n')}\\n ]`;\n}\n\n/**\n * Generate the table definition for a parsed table\n */\nexport function generateTableDefinition(table: ParsedTable, columnDefs: string[], indexes: IndexDefinition[] = []): string {\n if (columnDefs.length === 0) {\n return `// ${table.name} - no syncable columns found`;\n }\n\n // Build options object\n const optionsParts: string[] = [];\n if (table.config.trackMetadata) {\n optionsParts.push('trackMetadata: true');\n }\n if (indexes.length > 0) {\n optionsParts.push(formatIndexes(indexes));\n }\n const optionsStr = optionsParts.length > 0 ? `, {\\n ${optionsParts.join(',\\n ')}\\n}` : '';\n return `const ${table.name} = new Table({\n${columnDefs.join('\\n')}\n}${optionsStr});`;\n}\n\n/**\n * Generate the schema export section\n */\nexport function generateSchemaExport(tableNames: string[]): string {\n return `// ============================================================================\n// SCHEMA EXPORT\n// ============================================================================\n\n// NOTE: photo_attachments is NOT included here.\n// The AttachmentQueue from @powersync/attachments creates and manages\n// its own internal SQLite table (not a view) during queue.init().\n// This allows INSERT/UPDATE operations to work correctly.\n\nexport const AppSchema = new Schema({\n${tableNames.map(name => ` ${name},`).join('\\n')}\n});\n\nexport type Database = (typeof AppSchema)[\"types\"];`;\n}\n\n/**\n * Generate schema mapping utilities\n */\nexport function generateSchemaMapping(tables: ParsedTable[], schemas: string[]): string {\n // Group tables by non-public schemas\n const schemaGroups = new Map<string, string[]>();\n for (const schema of schemas) {\n if (schema !== 'public') {\n schemaGroups.set(schema, []);\n }\n }\n for (const table of tables) {\n if (table.schema !== 'public' && schemaGroups.has(table.schema)) {\n schemaGroups.get(table.schema)!.push(table.name);\n }\n }\n const sections: string[] = [`// ============================================================================\n// SCHEMA MAPPING FOR CONNECTOR\n// ============================================================================`];\n\n // Generate constants for each non-public schema\n for (const [schema, tableNames] of schemaGroups) {\n if (tableNames.length > 0) {\n const constName = `${schema.toUpperCase()}_SCHEMA_TABLES`;\n sections.push(`\n// Tables in the '${schema}' schema (need .schema('${schema}') in Supabase queries)\nexport const ${constName} = new Set([\n${tableNames.map(name => ` \"${name}\",`).join('\\n')}\n]);`);\n }\n }\n\n // Generate helper function\n const schemaChecks = Array.from(schemaGroups.entries()).filter(([, names]) => names.length > 0).map(([schema]) => {\n const constName = `${schema.toUpperCase()}_SCHEMA_TABLES`;\n return ` if (${constName}.has(tableName)) return \"${schema}\";`;\n });\n if (schemaChecks.length > 0) {\n sections.push(`\n/**\n * Get the Supabase schema for a table\n */\nexport function getTableSchema(tableName: string): ${schemas.map(s => `\"${s}\"`).join(' | ')} {\n${schemaChecks.join('\\n')}\n return \"public\";\n}`);\n } else {\n sections.push(`\n/**\n * Get the Supabase schema for a table\n */\nexport function getTableSchema(tableName: string): \"public\" {\n return \"public\";\n}`);\n }\n return sections.join('\\n');\n}\n\n/**\n * Generate the FK detection utility (helpful for consumers)\n */\nexport function generateFKUtility(): string {\n return `\n// ============================================================================\n// FOREIGN KEY UTILITIES\n// ============================================================================\n\n/**\n * Check if a column name represents a foreign key reference\n * Convention: columns ending in 'Id' are foreign keys (e.g., projectId -> Project table)\n */\nexport function isForeignKeyColumn(columnName: string): boolean {\n return columnName.endsWith('Id') && columnName !== 'id';\n}\n\n/**\n * Get the referenced table name from a foreign key column\n * e.g., 'projectId' -> 'Project', 'equipmentFixtureUnitId' -> 'EquipmentFixtureUnit'\n */\nexport function getForeignKeyTable(columnName: string): string | null {\n if (!isForeignKeyColumn(columnName)) return null;\n // Remove 'Id' suffix and capitalize first letter\n const baseName = columnName.slice(0, -2);\n return baseName.charAt(0).toUpperCase() + baseName.slice(1);\n}`;\n}\n\n/**\n * Generate complete output file\n */\nexport function generateOutputFile(tables: ParsedTable[], tableDefs: string[], schemas: string[], typesPath: string): string {\n const tableNames = tables.map(t => t.name);\n return `${generateHeader(typesPath)}\n\n// ============================================================================\n// TABLE DEFINITIONS\n// ============================================================================\n\n${tableDefs.join('\\n\\n')}\n\n${generateSchemaExport(tableNames)}\n\n${generateSchemaMapping(tables, ['public', ...schemas.filter(s => s !== 'public')])}\n${generateFKUtility()}\n`;\n}","/**\n * Foreign Key Dependency Detection\n *\n * Analyzes parsed tables to detect FK relationships and determine\n * optimal upload order for offline-first sync.\n */\n\nimport type { ParsedTable } from './parser.js';\nexport interface FKDependency {\n /** Table containing the FK column */\n table: string;\n /** FK column name (e.g., 'projectId') */\n column: string;\n /** Referenced table name (e.g., 'Project') */\n referencedTable: string;\n}\nexport interface DependencyGraph {\n /** Map of table -> tables it depends on (has FK references to) */\n dependencies: Map<string, string[]>;\n /** Map of table -> tables that depend on it (have FKs referencing this table) */\n dependents: Map<string, string[]>;\n /** Topologically sorted table names for optimal upload order */\n uploadOrder: string[];\n /** All detected FK relationships */\n fkRelationships: FKDependency[];\n}\n\n/**\n * Convert a FK column name to its referenced table name\n * Convention: columns ending in 'Id' reference the PascalCase table\n * e.g., projectId -> Project, equipmentUnitId -> EquipmentUnit\n */\nexport function fkColumnToTableName(columnName: string): string | null {\n if (!columnName.endsWith('Id') || columnName === 'id') {\n return null;\n }\n // Remove 'Id' suffix and capitalize first letter\n const baseName = columnName.slice(0, -2);\n return baseName.charAt(0).toUpperCase() + baseName.slice(1);\n}\n\n/**\n * Detect FK dependencies from parsed tables\n *\n * Uses naming convention: columns ending in 'Id' reference the PascalCase table\n * Only considers references to tables that are in the provided list (ignores external references)\n */\nexport function detectFKDependencies(tables: ParsedTable[]): DependencyGraph {\n const tableNames = new Set(tables.map(t => t.name));\n const dependencies = new Map<string, string[]>();\n const dependents = new Map<string, string[]>();\n const fkRelationships: FKDependency[] = [];\n\n // Initialize maps for all tables\n for (const table of tables) {\n dependencies.set(table.name, []);\n dependents.set(table.name, []);\n }\n\n // Detect FK relationships\n for (const table of tables) {\n for (const [columnName] of table.columns) {\n const referencedTable = fkColumnToTableName(columnName);\n\n // Only track references to tables in our schema\n if (referencedTable && tableNames.has(referencedTable)) {\n // This table depends on the referenced table\n const tableDeps = dependencies.get(table.name) || [];\n if (!tableDeps.includes(referencedTable)) {\n tableDeps.push(referencedTable);\n dependencies.set(table.name, tableDeps);\n }\n\n // The referenced table has this table as a dependent\n const refDeps = dependents.get(referencedTable) || [];\n if (!refDeps.includes(table.name)) {\n refDeps.push(table.name);\n dependents.set(referencedTable, refDeps);\n }\n\n // Record the FK relationship\n fkRelationships.push({\n table: table.name,\n column: columnName,\n referencedTable\n });\n }\n }\n }\n\n // Calculate upload order via topological sort\n const uploadOrder = getUploadOrder({\n dependencies\n });\n return {\n dependencies,\n dependents,\n uploadOrder,\n fkRelationships\n };\n}\n\n/**\n * Get optimal upload order using Kahn's topological sort algorithm\n *\n * Tables with no dependencies are uploaded first, then tables that depend on them, etc.\n * This ensures that when a row references another table, the referenced row exists first.\n */\nexport function getUploadOrder(graph: Pick<DependencyGraph, 'dependencies'>): string[] {\n const result: string[] = [];\n const inDegree = new Map<string, number>();\n const adjList = new Map<string, string[]>();\n\n // Initialize in-degree (number of dependencies) for each table\n for (const [table, deps] of graph.dependencies) {\n inDegree.set(table, deps.length);\n adjList.set(table, []);\n }\n\n // Build adjacency list (reverse of dependencies - who depends on this table)\n for (const [table, deps] of graph.dependencies) {\n for (const dep of deps) {\n const list = adjList.get(dep) || [];\n list.push(table);\n adjList.set(dep, list);\n }\n }\n\n // Start with tables that have no dependencies\n const queue: string[] = [];\n for (const [table, degree] of inDegree) {\n if (degree === 0) {\n queue.push(table);\n }\n }\n\n // Sort queue alphabetically for deterministic output\n queue.sort();\n\n // Process queue\n while (queue.length > 0) {\n const table = queue.shift()!;\n result.push(table);\n\n // Reduce in-degree for all tables that depend on this one\n const dependentTables = adjList.get(table) || [];\n for (const dependent of dependentTables) {\n const newDegree = (inDegree.get(dependent) || 0) - 1;\n inDegree.set(dependent, newDegree);\n if (newDegree === 0) {\n queue.push(dependent);\n // Keep queue sorted for deterministic output\n queue.sort();\n }\n }\n }\n\n // Check for cycles (if result doesn't include all tables)\n if (result.length < graph.dependencies.size) {\n // There's a cycle - just append remaining tables alphabetically\n const remaining = [...graph.dependencies.keys()].filter(t => !result.includes(t)).sort();\n result.push(...remaining);\n }\n return result;\n}\n\n/**\n * Get tables that have no dependencies (root tables)\n * These are typically reference/lookup tables like User, Project, etc.\n */\nexport function getRootTables(graph: DependencyGraph): string[] {\n const roots: string[] = [];\n for (const [table, deps] of graph.dependencies) {\n if (deps.length === 0) {\n roots.push(table);\n }\n }\n return roots.sort();\n}\n\n/**\n * Get tables that have no dependents (leaf tables)\n * These are typically transaction/event tables that reference other tables\n */\nexport function getLeafTables(graph: DependencyGraph): string[] {\n const leaves: string[] = [];\n for (const [table, deps] of graph.dependents) {\n if (deps.length === 0) {\n leaves.push(table);\n }\n }\n return leaves.sort();\n}\n\n/**\n * Format the dependency graph as a human-readable string\n */\nexport function formatDependencyGraph(graph: DependencyGraph): string {\n const lines: string[] = [];\n lines.push('FK Dependencies:');\n for (const fk of graph.fkRelationships) {\n lines.push(` ${fk.table}.${fk.column} -> ${fk.referencedTable}`);\n }\n if (graph.fkRelationships.length === 0) {\n lines.push(' (none detected)');\n }\n lines.push('');\n lines.push('Upload Order (topological):');\n graph.uploadOrder.forEach((table, i) => {\n const deps = graph.dependencies.get(table) || [];\n const depStr = deps.length > 0 ? ` (depends on: ${deps.join(', ')})` : ' (root)';\n lines.push(` ${i + 1}. ${table}${depStr}`);\n });\n return lines.join('\\n');\n}"],"mappings":";AAkDO,SAAS,aAAa,QAA0C;AACrE,SAAO;AACT;AAKO,IAAM,uBAAuB;AAAA,EAAC;AAAA;AAAA;AAAA,EAGrC;AAAU;AAKH,IAAM,2BAA2B,CAAC,SAAS,SAAS,WAAW,QAAQ,UAAU,SAAS,QAAQ,OAAO;AAMzG,IAAM,yBAAyB;AAAA,EAAC;AAAA;AAAA,EAEvC;AAAA,EAAe;AAAA,EAAe;AAAA,EAAY;AAAQ;;;ACnElD,YAAY,QAAQ;AACpB,YAAY,UAAU;;;AC4Bf,SAAS,aAAa,cAAsB,SAA4C;AAC7F,QAAM,UAAU,oBAAI,IAAoB;AAGxC,QAAM,WAAW,aAAa,MAAM,wCAAwC;AAC5E,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,aAAa,SAAS,CAAC;AAG7B,QAAM,oBAAoB,QAAQ,kBAAkB,QAAQ,aAAa;AAGzE,QAAM,iBAAiB,QAAQ,cAAc,IAAI,IAAI,QAAQ,WAAW,IAAI;AAG5E,QAAM,cAAc;AACpB,MAAI;AACJ,UAAQ,QAAQ,YAAY,KAAK,UAAU,OAAO,MAAM;AACtD,UAAM,CAAC,EAAE,YAAY,UAAU,IAAI;AAGnC,QAAI;AACJ,QAAI,gBAAgB;AAGlB,sBAAgB,eAAe,IAAI,UAAU,KAAK,qBAAqB,eAAe;AAAA,IACxF,OAAO;AAGL,YAAM,YAAY,QAAQ,YAAY,IAAI,UAAU;AACpD,sBAAgB,CAAC,aAAa,qBAAqB,eAAe;AAAA,IACpE;AACA,QAAI,eAAe;AACjB,cAAQ,IAAI,YAAY,WAAW,KAAK,CAAC;AAAA,IAC3C;AAAA,EACF;AACA,SAAO;AACT;AAKA,SAAS,YAAY,KAAqB;AACxC,SAAO,IAAI,QAAQ,uBAAuB,MAAM;AAClD;AAQO,SAAS,gBAAgB,SAAiB,WAAmB,QAA+B;AAEjG,QAAM,gBAAgB,YAAY,MAAM;AACxC,QAAM,mBAAmB,YAAY,SAAS;AAG9C,QAAM,cAAc,IAAI,OAAO,GAAG,aAAa,oCAAoC,GAAG;AACtF,QAAM,cAAc,YAAY,KAAK,OAAO;AAC5C,MAAI,CAAC,YAAa,QAAO;AACzB,QAAM,aAAa,YAAY;AAC/B,QAAM,gBAAgB,QAAQ,MAAM,UAAU;AAK9C,QAAM,kBAAkB,IAAI,OAAO,gBAAgB,gBAAgB,YAAY,GAAG;AAClF,QAAM,kBAAkB,gBAAgB,KAAK,aAAa;AAC1D,MAAI,CAAC,gBAAiB,QAAO;AAG7B,QAAM,kBAAkB,gBAAgB;AACxC,QAAM,iBAAiB,gBAAgB,QAAQ,gBAAgB,CAAC,EAAE,SAAS;AAC3E,MAAI,aAAa;AACjB,MAAI,IAAI,iBAAiB;AACzB,SAAO,IAAI,cAAc,UAAU,aAAa,GAAG;AACjD,UAAM,OAAO,cAAc,CAAC;AAC5B,QAAI,SAAS,IAAK;AAAA,aAAsB,SAAS,IAAK;AACtD;AAAA,EACF;AACA,MAAI,eAAe,EAAG,QAAO;AAC7B,SAAO,cAAc,MAAM,iBAAiB,CAAC;AAC/C;AAKO,SAAS,eAAe,SAAiB,QAAuB,mBAA+C;AACpH,QAAM,eAA8B,CAAC;AACrC,aAAW,eAAe,QAAQ;AAChC,UAAM;AAAA,MACJ;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb;AAAA,IACF,IAAI;AACJ,UAAM,WAAW,gBAAgB,SAAS,MAAM,MAAM;AACtD,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AAGA,UAAM,oBAAoB,oBAAI,IAAI,CAAC,GAAG,mBAAmB,GAAI,oBAAoB,CAAC,CAAE,CAAC;AACrF,UAAM,UAAU,aAAa,UAAU;AAAA,MACrC,aAAa;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,QAAI,QAAQ,OAAO,GAAG;AACpB,mBAAa,KAAK;AAAA,QAChB;AAAA,QACA;AAAA,QACA;AAAA,QACA,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AACT;AAKO,SAAS,oBAAoB,SAA2B;AAC7D,QAAM,UAAoB,CAAC;AAC3B,QAAM,cAAc;AACpB,MAAI;AACJ,UAAQ,QAAQ,YAAY,KAAK,OAAO,OAAO,MAAM;AACnD,YAAQ,KAAK,MAAM,CAAC,CAAC;AAAA,EACvB;AACA,SAAO;AACT;AAKO,SAAS,kBAAkB,SAAiB,QAA0B;AAC3E,QAAM,SAAmB,CAAC;AAG1B,QAAM,cAAc,IAAI,OAAO,GAAG,MAAM,6DAA6D,GAAG;AACxG,QAAM,cAAc,YAAY,KAAK,OAAO;AAC5C,MAAI,CAAC,YAAa,QAAO;AACzB,QAAM,gBAAgB,YAAY,CAAC;AAGnC,QAAM,iBAAiB;AACvB,MAAI;AACJ,UAAQ,QAAQ,eAAe,KAAK,aAAa,OAAO,MAAM;AAC5D,WAAO,KAAK,MAAM,CAAC,CAAC;AAAA,EACtB;AACA,SAAO;AACT;;;AClLA,SAAS,YAAY,KAAqB;AACxC,SAAO,IAAI,QAAQ,mBAAmB,OAAO,EAAE,YAAY;AAC7D;AAWO,SAAS,yBAAyB,WAAmB,SAAmB,eAAyB,oBAA8B,CAAC,GAAsB;AAC3J,QAAM,UAA6B,CAAC;AACpC,QAAM,iBAAiB,YAAY,SAAS;AAG5C,QAAM,iBAAiB,oBAAI,IAAY;AAGvC,QAAM,mBAA6B,CAAC;AACpC,aAAW,WAAW,eAAe;AACnC,QAAI;AACF,uBAAiB,KAAK,IAAI,OAAO,OAAO,CAAC;AAAA,IAC3C,QAAQ;AACN,cAAQ,KAAK,yCAAyC,OAAO,cAAc;AAAA,IAC7E;AAAA,EACF;AAGA,aAAW,UAAU,SAAS;AAE5B,QAAI,WAAW,KAAM;AACrB,eAAW,SAAS,kBAAkB;AACpC,UAAI,MAAM,KAAK,MAAM,GAAG;AACtB,uBAAe,IAAI,MAAM;AACzB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,aAAW,UAAU,mBAAmB;AACtC,QAAI,QAAQ,SAAS,MAAM,KAAK,WAAW,MAAM;AAC/C,qBAAe,IAAI,MAAM;AAAA,IAC3B;AAAA,EACF;AAGA,aAAW,UAAU,gBAAgB;AACnC,UAAM,cAAc,YAAY,MAAM;AACtC,YAAQ,KAAK;AAAA,MACX,MAAM,OAAO,cAAc,IAAI,WAAW;AAAA,MAC1C,SAAS,CAAC,MAAM;AAAA,IAClB,CAAC;AAAA,EACH;AAGA,UAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AACnD,SAAO;AACT;AAKO,SAAS,eAAe,WAA2B;AACxD,SAAO;AAAA;AAAA;AAAA,yBAGgB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQlC;AAKA,SAAS,cAAc,SAAoC;AACzD,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAM,aAAa,QAAQ,IAAI,SAAO;AACpC,UAAM,aAAa,IAAI,QAAQ,IAAI,OAAK,IAAI,CAAC,GAAG,EAAE,KAAK,IAAI;AAC3D,WAAO,gBAAgB,IAAI,IAAI,gBAAgB,UAAU;AAAA,EAC3D,CAAC;AACD,SAAO;AAAA,EAAe,WAAW,KAAK,IAAI,CAAC;AAAA;AAC7C;AAKO,SAAS,wBAAwB,OAAoB,YAAsB,UAA6B,CAAC,GAAW;AACzH,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,MAAM,MAAM,IAAI;AAAA,EACzB;AAGA,QAAM,eAAyB,CAAC;AAChC,MAAI,MAAM,OAAO,eAAe;AAC9B,iBAAa,KAAK,qBAAqB;AAAA,EACzC;AACA,MAAI,QAAQ,SAAS,GAAG;AACtB,iBAAa,KAAK,cAAc,OAAO,CAAC;AAAA,EAC1C;AACA,QAAM,aAAa,aAAa,SAAS,IAAI;AAAA,IAAU,aAAa,KAAK,OAAO,CAAC;AAAA,KAAQ;AACzF,SAAO,SAAS,MAAM,IAAI;AAAA,EAC1B,WAAW,KAAK,IAAI,CAAC;AAAA,GACpB,UAAU;AACb;AAKO,SAAS,qBAAqB,YAA8B;AACjE,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUP,WAAW,IAAI,UAAQ,KAAK,IAAI,GAAG,EAAE,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAIjD;AAKO,SAAS,sBAAsB,QAAuB,SAA2B;AAEtF,QAAM,eAAe,oBAAI,IAAsB;AAC/C,aAAW,UAAU,SAAS;AAC5B,QAAI,WAAW,UAAU;AACvB,mBAAa,IAAI,QAAQ,CAAC,CAAC;AAAA,IAC7B;AAAA,EACF;AACA,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,WAAW,YAAY,aAAa,IAAI,MAAM,MAAM,GAAG;AAC/D,mBAAa,IAAI,MAAM,MAAM,EAAG,KAAK,MAAM,IAAI;AAAA,IACjD;AAAA,EACF;AACA,QAAM,WAAqB,CAAC;AAAA;AAAA,gFAEkD;AAG9E,aAAW,CAAC,QAAQ,UAAU,KAAK,cAAc;AAC/C,QAAI,WAAW,SAAS,GAAG;AACzB,YAAM,YAAY,GAAG,OAAO,YAAY,CAAC;AACzC,eAAS,KAAK;AAAA,oBACA,MAAM,2BAA2B,MAAM;AAAA,eAC5C,SAAS;AAAA,EACtB,WAAW,IAAI,UAAQ,MAAM,IAAI,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,IAC/C;AAAA,IACA;AAAA,EACF;AAGA,QAAM,eAAe,MAAM,KAAK,aAAa,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,MAAM,MAAM,SAAS,CAAC,EAAE,IAAI,CAAC,CAAC,MAAM,MAAM;AAChH,UAAM,YAAY,GAAG,OAAO,YAAY,CAAC;AACzC,WAAO,SAAS,SAAS,4BAA4B,MAAM;AAAA,EAC7D,CAAC;AACD,MAAI,aAAa,SAAS,GAAG;AAC3B,aAAS,KAAK;AAAA;AAAA;AAAA;AAAA,qDAImC,QAAQ,IAAI,OAAK,IAAI,CAAC,GAAG,EAAE,KAAK,KAAK,CAAC;AAAA,EACzF,aAAa,KAAK,IAAI,CAAC;AAAA;AAAA,EAEvB;AAAA,EACA,OAAO;AACL,aAAS,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhB;AAAA,EACA;AACA,SAAO,SAAS,KAAK,IAAI;AAC3B;AAKO,SAAS,oBAA4B;AAC1C,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAuBT;AAKO,SAAS,mBAAmB,QAAuB,WAAqB,SAAmB,WAA2B;AAC3H,QAAM,aAAa,OAAO,IAAI,OAAK,EAAE,IAAI;AACzC,SAAO,GAAG,eAAe,SAAS,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMnC,UAAU,KAAK,MAAM,CAAC;AAAA;AAAA,EAEtB,qBAAqB,UAAU,CAAC;AAAA;AAAA,EAEhC,sBAAsB,QAAQ,CAAC,UAAU,GAAG,QAAQ,OAAO,OAAK,MAAM,QAAQ,CAAC,CAAC,CAAC;AAAA,EACjF,kBAAkB,CAAC;AAAA;AAErB;;;AC5NO,SAAS,oBAAoB,YAAmC;AACrE,MAAI,CAAC,WAAW,SAAS,IAAI,KAAK,eAAe,MAAM;AACrD,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,WAAW,MAAM,GAAG,EAAE;AACvC,SAAO,SAAS,OAAO,CAAC,EAAE,YAAY,IAAI,SAAS,MAAM,CAAC;AAC5D;AAQO,SAAS,qBAAqB,QAAwC;AAC3E,QAAM,aAAa,IAAI,IAAI,OAAO,IAAI,OAAK,EAAE,IAAI,CAAC;AAClD,QAAM,eAAe,oBAAI,IAAsB;AAC/C,QAAM,aAAa,oBAAI,IAAsB;AAC7C,QAAM,kBAAkC,CAAC;AAGzC,aAAW,SAAS,QAAQ;AAC1B,iBAAa,IAAI,MAAM,MAAM,CAAC,CAAC;AAC/B,eAAW,IAAI,MAAM,MAAM,CAAC,CAAC;AAAA,EAC/B;AAGA,aAAW,SAAS,QAAQ;AAC1B,eAAW,CAAC,UAAU,KAAK,MAAM,SAAS;AACxC,YAAM,kBAAkB,oBAAoB,UAAU;AAGtD,UAAI,mBAAmB,WAAW,IAAI,eAAe,GAAG;AAEtD,cAAM,YAAY,aAAa,IAAI,MAAM,IAAI,KAAK,CAAC;AACnD,YAAI,CAAC,UAAU,SAAS,eAAe,GAAG;AACxC,oBAAU,KAAK,eAAe;AAC9B,uBAAa,IAAI,MAAM,MAAM,SAAS;AAAA,QACxC;AAGA,cAAM,UAAU,WAAW,IAAI,eAAe,KAAK,CAAC;AACpD,YAAI,CAAC,QAAQ,SAAS,MAAM,IAAI,GAAG;AACjC,kBAAQ,KAAK,MAAM,IAAI;AACvB,qBAAW,IAAI,iBAAiB,OAAO;AAAA,QACzC;AAGA,wBAAgB,KAAK;AAAA,UACnB,OAAO,MAAM;AAAA,UACb,QAAQ;AAAA,UACR;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAGA,QAAM,cAAc,eAAe;AAAA,IACjC;AAAA,EACF,CAAC;AACD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAQO,SAAS,eAAe,OAAwD;AACrF,QAAM,SAAmB,CAAC;AAC1B,QAAM,WAAW,oBAAI,IAAoB;AACzC,QAAM,UAAU,oBAAI,IAAsB;AAG1C,aAAW,CAAC,OAAO,IAAI,KAAK,MAAM,cAAc;AAC9C,aAAS,IAAI,OAAO,KAAK,MAAM;AAC/B,YAAQ,IAAI,OAAO,CAAC,CAAC;AAAA,EACvB;AAGA,aAAW,CAAC,OAAO,IAAI,KAAK,MAAM,cAAc;AAC9C,eAAW,OAAO,MAAM;AACtB,YAAM,OAAO,QAAQ,IAAI,GAAG,KAAK,CAAC;AAClC,WAAK,KAAK,KAAK;AACf,cAAQ,IAAI,KAAK,IAAI;AAAA,IACvB;AAAA,EACF;AAGA,QAAM,QAAkB,CAAC;AACzB,aAAW,CAAC,OAAO,MAAM,KAAK,UAAU;AACtC,QAAI,WAAW,GAAG;AAChB,YAAM,KAAK,KAAK;AAAA,IAClB;AAAA,EACF;AAGA,QAAM,KAAK;AAGX,SAAO,MAAM,SAAS,GAAG;AACvB,UAAM,QAAQ,MAAM,MAAM;AAC1B,WAAO,KAAK,KAAK;AAGjB,UAAM,kBAAkB,QAAQ,IAAI,KAAK,KAAK,CAAC;AAC/C,eAAW,aAAa,iBAAiB;AACvC,YAAM,aAAa,SAAS,IAAI,SAAS,KAAK,KAAK;AACnD,eAAS,IAAI,WAAW,SAAS;AACjC,UAAI,cAAc,GAAG;AACnB,cAAM,KAAK,SAAS;AAEpB,cAAM,KAAK;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAGA,MAAI,OAAO,SAAS,MAAM,aAAa,MAAM;AAE3C,UAAM,YAAY,CAAC,GAAG,MAAM,aAAa,KAAK,CAAC,EAAE,OAAO,OAAK,CAAC,OAAO,SAAS,CAAC,CAAC,EAAE,KAAK;AACvF,WAAO,KAAK,GAAG,SAAS;AAAA,EAC1B;AACA,SAAO;AACT;AAMO,SAAS,cAAc,OAAkC;AAC9D,QAAM,QAAkB,CAAC;AACzB,aAAW,CAAC,OAAO,IAAI,KAAK,MAAM,cAAc;AAC9C,QAAI,KAAK,WAAW,GAAG;AACrB,YAAM,KAAK,KAAK;AAAA,IAClB;AAAA,EACF;AACA,SAAO,MAAM,KAAK;AACpB;AAMO,SAAS,cAAc,OAAkC;AAC9D,QAAM,SAAmB,CAAC;AAC1B,aAAW,CAAC,OAAO,IAAI,KAAK,MAAM,YAAY;AAC5C,QAAI,KAAK,WAAW,GAAG;AACrB,aAAO,KAAK,KAAK;AAAA,IACnB;AAAA,EACF;AACA,SAAO,OAAO,KAAK;AACrB;AAKO,SAAS,sBAAsB,OAAgC;AACpE,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,kBAAkB;AAC7B,aAAW,MAAM,MAAM,iBAAiB;AACtC,UAAM,KAAK,KAAK,GAAG,KAAK,IAAI,GAAG,MAAM,OAAO,GAAG,eAAe,EAAE;AAAA,EAClE;AACA,MAAI,MAAM,gBAAgB,WAAW,GAAG;AACtC,UAAM,KAAK,mBAAmB;AAAA,EAChC;AACA,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,6BAA6B;AACxC,QAAM,YAAY,QAAQ,CAAC,OAAO,MAAM;AACtC,UAAM,OAAO,MAAM,aAAa,IAAI,KAAK,KAAK,CAAC;AAC/C,UAAM,SAAS,KAAK,SAAS,IAAI,iBAAiB,KAAK,KAAK,IAAI,CAAC,MAAM;AACvE,UAAM,KAAK,KAAK,IAAI,CAAC,KAAK,KAAK,GAAG,MAAM,EAAE;AAAA,EAC5C,CAAC;AACD,SAAO,MAAM,KAAK,IAAI;AACxB;;;AHnLO,SAAS,mBAAmB,QAAgB,YAAoB,iBAAiD;AAEtH,QAAM,YAAY,OAAO,KAAK,EAAE,QAAQ,iBAAiB,EAAE;AAG3D,MAAI,UAAU,SAAS,MAAM,KAAK,UAAU,SAAS,SAAS,KAAK,UAAU,SAAS,GAAG,GAAG;AAC1F,WAAO;AAAA,EACT;AAGA,MAAI,UAAU,SAAS,IAAI,GAAG;AAC5B,WAAO;AAAA,EACT;AAGA,MAAI,cAAc,WAAW;AAC3B,WAAO;AAAA,MACL,MAAM;AAAA,MACN,WAAW;AAAA,IACb;AAAA,EACF;AAGA,MAAI,cAAc,UAAU;AAE1B,QAAI,gBAAgB,KAAK,aAAW,WAAW,YAAY,EAAE,SAAS,QAAQ,YAAY,CAAC,CAAC,GAAG;AAC7F,aAAO;AAAA,QACL,MAAM;AAAA,MACR;AAAA,IACF;AACA,WAAO;AAAA,MACL,MAAM;AAAA,IACR;AAAA,EACF;AAGA,MAAI,cAAc,UAAU;AAC1B,WAAO;AAAA,MACL,MAAM;AAAA,IACR;AAAA,EACF;AAGA,MAAI,UAAU,SAAS,WAAW,KAAK,UAAU,SAAS,OAAO,GAAG;AAClE,WAAO;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,IACV;AAAA,EACF;AAGA,SAAO;AAAA,IACL,MAAM;AAAA,EACR;AACF;AAKO,SAAS,mBAAmB,OAAoB,iBAAqC;AAC1F,QAAM,aAAuB,CAAC;AAC9B,aAAW,CAAC,YAAY,MAAM,KAAK,MAAM,SAAS;AAChD,UAAM,UAAU,mBAAmB,QAAQ,YAAY,eAAe;AACtE,QAAI,SAAS;AAEX,UAAI,UAAU;AACd,UAAI,QAAQ,WAAW;AACrB,kBAAU;AAAA,MACZ,WAAW,QAAQ,QAAQ;AACzB,kBAAU;AAAA,MACZ;AACA,iBAAW,KAAK,KAAK,UAAU,KAAK,QAAQ,IAAI,IAAI,OAAO,EAAE;AAAA,IAC/D;AAAA,EACF;AACA,SAAO;AACT;AAKA,eAAsB,eAAe,QAAyB,SAIlC;AAC1B,QAAM,MAAM,SAAS,OAAO,QAAQ,IAAI;AACxC,QAAM,UAAU,SAAS,WAAW;AACpC,QAAM,SAAS,SAAS,UAAU;AAClC,QAAM,SAAyB;AAAA,IAC7B,SAAS;AAAA,IACT,iBAAiB;AAAA,IACjB,YAAY;AAAA,IACZ,QAAQ,CAAC;AAAA,IACT,UAAU,CAAC;AAAA,EACb;AAGA,QAAM,YAAiB,gBAAW,OAAO,SAAS,IAAI,OAAO,YAAiB,aAAQ,KAAK,OAAO,SAAS;AAC3G,QAAM,aAAkB,gBAAW,OAAO,UAAU,IAAI,OAAO,aAAkB,aAAQ,KAAK,OAAO,UAAU;AAC/G,SAAO,aAAa;AAGpB,MAAI,CAAI,cAAW,SAAS,GAAG;AAC7B,WAAO,OAAO,KAAK,yBAAyB,SAAS,EAAE;AACvD,WAAO;AAAA,EACT;AAGA,MAAI,SAAS;AACX,YAAQ,IAAI,uBAAuB,SAAS,EAAE;AAAA,EAChD;AACA,QAAM,eAAkB,gBAAa,WAAW,OAAO;AAGvD,QAAM,cAAc,oBAAI,IAAI,CAAC,GAAG,sBAAsB,GAAI,OAAO,eAAe,CAAC,CAAE,CAAC;AAGpF,QAAM,kBAAkB,CAAC,GAAG,0BAA0B,GAAI,OAAO,mBAAmB,CAAC,CAAE;AAGvF,QAAM,eAAe,eAAe,cAAc,OAAO,QAAQ,WAAW;AAG5E,aAAW,eAAe,OAAO,QAAQ;AACvC,UAAM,QAAQ,aAAa,KAAK,OAAK,EAAE,SAAS,YAAY,IAAI;AAChE,QAAI,CAAC,OAAO;AACV,aAAO,SAAS,KAAK,UAAU,YAAY,IAAI,0BAA0B,YAAY,UAAU,QAAQ,GAAG;AAAA,IAC5G;AAAA,EACF;AACA,MAAI,aAAa,WAAW,GAAG;AAC7B,WAAO,OAAO,KAAK,oCAAoC;AACvD,WAAO;AAAA,EACT;AAGA,QAAM,cAAc,OAAO,eAAe;AAC1C,QAAM,gBAAgB,cAAc,CAAC,GAAG,wBAAwB,GAAI,OAAO,iBAAiB,CAAC,CAAE,IAAI,OAAO,iBAAiB,CAAC;AAC5H,QAAM,eAAe,OAAO,gBAAgB,CAAC;AAG7C,QAAM,kBAAkB,qBAAqB,YAAY;AACzD,SAAO,kBAAkB;AACzB,MAAI,WAAW,gBAAgB,gBAAgB,SAAS,GAAG;AACzD,YAAQ,IAAI;AAAA,4BAA+B,gBAAgB,gBAAgB,MAAM,EAAE;AACnF,eAAW,MAAM,gBAAgB,iBAAiB;AAChD,cAAQ,IAAI,KAAK,GAAG,KAAK,IAAI,GAAG,MAAM,OAAO,GAAG,eAAe,EAAE;AAAA,IACnE;AACA,YAAQ,IAAI;AAAA,0BAA6B;AACzC,oBAAgB,YAAY,QAAQ,CAAC,OAAO,MAAM;AAChD,cAAQ,IAAI,KAAK,IAAI,CAAC,KAAK,KAAK,EAAE;AAAA,IACpC,CAAC;AACD,YAAQ,IAAI,EAAE;AAAA,EAChB;AAGA,QAAM,YAAsB,CAAC;AAC7B,MAAI,eAAe;AACnB,aAAW,SAAS,cAAc;AAChC,QAAI,SAAS;AACX,YAAM,SAAS,MAAM,OAAO,kBAAkB,MAAM,OAAO;AAC3D,cAAQ,IAAI,cAAc,MAAM,MAAM,IAAI,MAAM,IAAI,KAAK,MAAM,QAAQ,IAAI,YAAY,MAAM,OAAO,gBAAgB,qBAAqB,EAAE,GAAG,SAAS,sBAAsB,EAAE,EAAE;AAAA,IACnL;AACA,UAAM,aAAa,mBAAmB,OAAO,eAAe;AAC5D,QAAI,WAAW,WAAW,GAAG;AAC3B,aAAO,SAAS,KAAK,UAAU,MAAM,IAAI,2BAA2B;AACpE;AAAA,IACF;AAGA,UAAM,cAAc,CAAC,GAAG,MAAM,QAAQ,KAAK,CAAC;AAC5C,UAAM,UAAU,cAAc,SAAS,KAAK,aAAa,SAAS,IAAI,yBAAyB,MAAM,MAAM,aAAa,eAAe,YAAY,IAAI,CAAC;AACxJ,oBAAgB,QAAQ;AACxB,QAAI,WAAW,QAAQ,SAAS,GAAG;AACjC,cAAQ,IAAI,cAAc,QAAQ,IAAI,OAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC,EAAE;AAAA,IACjE;AACA,cAAU,KAAK,wBAAwB,OAAO,YAAY,OAAO,CAAC;AAAA,EACpE;AACA,SAAO,mBAAmB;AAG1B,QAAM,UAAU,CAAC,GAAG,IAAI,IAAI,OAAO,OAAO,IAAI,OAAK,EAAE,UAAU,QAAQ,CAAC,CAAC;AAGzE,QAAM,eAAoB,cAAS,KAAK,SAAS;AACjD,QAAM,SAAS,mBAAmB,aAAa,OAAO,OAAK,UAAU,KAAK,SAAO,IAAI,SAAS,SAAS,EAAE,IAAI,IAAI,CAAC,CAAC,GAAG,WAAW,SAAS,YAAY;AAGtJ,MAAI,QAAQ;AACV,WAAO,UAAU;AACjB,WAAO,kBAAkB,UAAU;AACnC,WAAO,SAAS;AAChB,WAAO;AAAA,EACT;AAGA,QAAM,YAAiB,aAAQ,UAAU;AACzC,MAAI,CAAI,cAAW,SAAS,GAAG;AAC7B,IAAG,aAAU,WAAW;AAAA,MACtB,WAAW;AAAA,IACb,CAAC;AAAA,EACH;AAGA,EAAG,iBAAc,YAAY,MAAM;AACnC,SAAO,UAAU;AACjB,SAAO,kBAAkB,UAAU;AACnC,SAAO;AACT;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/generator/config.ts","../../src/generator/generator.ts","../../src/generator/parser.ts","../../src/generator/templates.ts","../../src/generator/fk-dependencies.ts"],"sourcesContent":["/**\n * Configuration types and helpers for PowerSync schema generator\n */\n\nexport interface TableConfig {\n /** Table name (PascalCase as it appears in database.types.ts) */\n name: string;\n /** Schema name (defaults to 'public') */\n schema?: string;\n /** Enable ps_crud timestamp tracking for optimistic UI updates */\n trackMetadata?: boolean;\n /**\n * Sync the primary key column (normally skipped as PowerSync handles it internally).\n *\n * Use this for tables with integer PKs that are referenced by FKs in other tables.\n * Example: `Group.id` is referenced by `UserGroup.groupId`, so Group needs `syncPrimaryKey: true`\n * to ensure the integer ID is available for client-side joins.\n */\n syncPrimaryKey?: boolean;\n /** @deprecated Use `syncPrimaryKey` instead */\n includeId?: boolean;\n /** Columns to skip for this specific table (in addition to global skipColumns) */\n skipColumns?: string[];\n /** Only include these columns (overrides skipColumns if specified) */\n onlyColumns?: string[];\n /** Alias for the table in PowerSync schema (for conflicting names across schemas) */\n alias?: string;\n /** Table is local-only (not synced through PowerSync) */\n localOnly?: boolean;\n}\nexport interface GeneratorConfig {\n /** Path to Supabase-generated database.types.ts file */\n typesPath: string;\n /** Output path for generated PowerSync schema */\n outputPath: string;\n /** Tables to include in the PowerSync schema */\n tables: TableConfig[];\n /** Columns to always skip (in addition to defaults like 'id') */\n skipColumns?: string[];\n /** Column name patterns that should use column.real for decimal values */\n decimalPatterns?: string[];\n /** Additional schemas to track (besides 'public' which is the default) */\n schemas?: string[];\n /** Generate indexes for FK and common columns (default: true) */\n autoIndexes?: boolean;\n /** Additional columns to index (exact column names) */\n indexColumns?: string[];\n /** Column patterns that should be indexed (regex patterns) */\n indexPatterns?: string[];\n}\n\n/**\n * Define a PowerSync generator configuration with type safety\n */\nexport function defineConfig(config: GeneratorConfig): GeneratorConfig {\n return config;\n}\n\n/**\n * Default columns that are skipped during generation\n */\nexport const DEFAULT_SKIP_COLUMNS = ['id',\n// PowerSync handles id automatically\n// Legacy numeric ID columns - typically not needed after UUID migration\n'legacyId'];\n\n/**\n * Default column name patterns that indicate decimal values\n */\nexport const DEFAULT_DECIMAL_PATTERNS = ['hours', 'watts', 'voltage', 'rate', 'amount', 'price', 'cost', 'total'];\n\n/**\n * Default column patterns that should be indexed\n * These are regex patterns matched against column names\n */\nexport const DEFAULT_INDEX_PATTERNS = ['Id$',\n// FK columns ending in Id (e.g., projectId, userId)\n'^createdAt$', '^updatedAt$', '^status$', '^type$'];\n\n/**\n * Required authentication/access control tables for offline auth\n * These tables are needed for proper offline access control\n */\nexport interface RequiredAuthTable {\n /** Table name (PascalCase) */\n name: string;\n /** Schema name */\n schema: string;\n /** Whether this table requires syncPrimaryKey to be true */\n requiresSyncPrimaryKey?: boolean;\n}\nexport const REQUIRED_AUTH_TABLES: RequiredAuthTable[] = [{\n name: 'UserAccess',\n schema: 'core'\n}, {\n name: 'UserGroup',\n schema: 'core'\n}, {\n name: 'GroupAccessKey',\n schema: 'core',\n requiresSyncPrimaryKey: true\n}, {\n name: 'Group',\n schema: 'core',\n requiresSyncPrimaryKey: true\n}];","/**\n * PowerSync schema generator\n *\n * Converts Supabase database.types.ts into PowerSync schema definitions\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { GeneratorConfig, TableConfig } from './config.js';\nimport { DEFAULT_SKIP_COLUMNS, DEFAULT_DECIMAL_PATTERNS, DEFAULT_INDEX_PATTERNS, REQUIRED_AUTH_TABLES } from './config.js';\nimport { parseTypesFile, type ParsedTable } from './parser.js';\nimport { generateTableDefinition, generateLocalOnlyTableDefinition, generateOutputFile, generateIndexDefinitions, type IndexDefinition } from './templates.js';\nimport { detectFKDependencies, type DependencyGraph } from './fk-dependencies.js';\nexport interface ColumnMapping {\n type: 'column.text' | 'column.integer' | 'column.real';\n isEnum?: boolean;\n isBoolean?: boolean;\n}\nexport interface GenerateResult {\n success: boolean;\n tablesGenerated: number;\n outputPath: string;\n errors: string[];\n warnings: string[];\n /** Generated output (included when dryRun is true) */\n output?: string;\n /** Number of indexes generated */\n indexesGenerated?: number;\n /** FK dependency graph (when detected) */\n dependencyGraph?: DependencyGraph;\n}\n\n/**\n * Map TypeScript types to PowerSync column types\n */\nexport function mapTypeToPowerSync(tsType: string, columnName: string, decimalPatterns: string[]): ColumnMapping | null {\n // Clean up the type (remove nullability)\n const cleanType = tsType.trim().replace(/\\s*\\|\\s*null/g, '');\n\n // Skip complex types that can't be stored in SQLite\n if (cleanType.includes('Json') || cleanType.includes('unknown') || cleanType.includes('{')) {\n return null;\n }\n\n // Array types - skip\n if (cleanType.includes('[]')) {\n return null;\n }\n\n // Boolean -> integer (0/1)\n if (cleanType === 'boolean') {\n return {\n type: 'column.integer',\n isBoolean: true\n };\n }\n\n // Number types\n if (cleanType === 'number') {\n // Use real for columns that might have decimals\n if (decimalPatterns.some(pattern => columnName.toLowerCase().includes(pattern.toLowerCase()))) {\n return {\n type: 'column.real'\n };\n }\n return {\n type: 'column.integer'\n };\n }\n\n // String types\n if (cleanType === 'string') {\n return {\n type: 'column.text'\n };\n }\n\n // Enum types (Database[\"schema\"][\"Enums\"][\"EnumName\"]) -> store as text\n if (cleanType.includes('Database[') && cleanType.includes('Enums')) {\n return {\n type: 'column.text',\n isEnum: true\n };\n }\n\n // Default to text for unknown types (likely enums or other string-like types)\n return {\n type: 'column.text'\n };\n}\n\n/**\n * Generate column definitions for a table\n */\nexport function generateColumnDefs(table: ParsedTable, decimalPatterns: string[]): string[] {\n const columnDefs: string[] = [];\n for (const [columnName, tsType] of table.columns) {\n const mapping = mapTypeToPowerSync(tsType, columnName, decimalPatterns);\n if (mapping) {\n // Add comment for boolean and enum columns\n let comment = '';\n if (mapping.isBoolean) {\n comment = ' // boolean stored as 0/1';\n } else if (mapping.isEnum) {\n comment = ' // enum stored as text';\n }\n columnDefs.push(` ${columnName}: ${mapping.type},${comment}`);\n }\n }\n return columnDefs;\n}\n\n/**\n * Validate that required auth tables are included in config\n * Returns warnings for missing tables or misconfigured tables\n */\nexport function validateAuthTables(tables: TableConfig[]): string[] {\n const warnings: string[] = [];\n for (const authTable of REQUIRED_AUTH_TABLES) {\n const configTable = tables.find(t => t.name === authTable.name && (t.schema ?? 'public') === authTable.schema);\n if (!configTable) {\n warnings.push(`Missing required auth table: ${authTable.schema}.${authTable.name} - offline access control may not work correctly`);\n } else if (authTable.requiresSyncPrimaryKey && !configTable.syncPrimaryKey) {\n warnings.push(`Auth table ${authTable.schema}.${authTable.name} should have syncPrimaryKey: true for proper FK references`);\n }\n }\n return warnings;\n}\n\n/**\n * Validate FK references to ensure all referenced tables are in the config\n * Returns warnings for missing FK targets\n */\nexport function validateFKReferences(dependencyGraph: DependencyGraph, configuredTables: Set<string>): string[] {\n const warnings: string[] = [];\n for (const fk of dependencyGraph.fkRelationships) {\n if (!configuredTables.has(fk.referencedTable)) {\n warnings.push(`FK reference ${fk.table}.${fk.column} references table '${fk.referencedTable}' which is not in the config`);\n }\n }\n return warnings;\n}\n\n/**\n * Generate PowerSync schema from configuration\n */\nexport async function generateSchema(config: GeneratorConfig, options?: {\n cwd?: string;\n verbose?: boolean;\n dryRun?: boolean;\n}): Promise<GenerateResult> {\n const cwd = options?.cwd ?? process.cwd();\n const verbose = options?.verbose ?? false;\n const dryRun = options?.dryRun ?? false;\n const result: GenerateResult = {\n success: false,\n tablesGenerated: 0,\n outputPath: '',\n errors: [],\n warnings: []\n };\n\n // Resolve paths relative to cwd\n const typesPath = path.isAbsolute(config.typesPath) ? config.typesPath : path.resolve(cwd, config.typesPath);\n const outputPath = path.isAbsolute(config.outputPath) ? config.outputPath : path.resolve(cwd, config.outputPath);\n result.outputPath = outputPath;\n\n // Validate required auth tables\n const authWarnings = validateAuthTables(config.tables);\n result.warnings.push(...authWarnings);\n\n // Check if types file exists\n if (!fs.existsSync(typesPath)) {\n result.errors.push(`Types file not found: ${typesPath}`);\n return result;\n }\n\n // Read types file\n if (verbose) {\n console.log(`Reading types from: ${typesPath}`);\n }\n const typesContent = fs.readFileSync(typesPath, 'utf-8');\n\n // Build skip columns set\n const skipColumns = new Set([...DEFAULT_SKIP_COLUMNS, ...(config.skipColumns ?? [])]);\n\n // Build decimal patterns\n const decimalPatterns = [...DEFAULT_DECIMAL_PATTERNS, ...(config.decimalPatterns ?? [])];\n\n // Parse tables from types file\n const parsedTables = parseTypesFile(typesContent, config.tables, skipColumns);\n\n // Check for tables that weren't found\n for (const tableConfig of config.tables) {\n const found = parsedTables.some(t => t.name === tableConfig.name);\n if (!found) {\n result.warnings.push(`Table '${tableConfig.name}' not found in schema '${tableConfig.schema ?? 'public'}'`);\n }\n }\n if (parsedTables.length === 0) {\n result.errors.push('No tables were parsed successfully');\n return result;\n }\n\n // Build index patterns (auto-indexes enabled by default)\n const autoIndexes = config.autoIndexes ?? true;\n const indexPatterns = autoIndexes ? [...DEFAULT_INDEX_PATTERNS, ...(config.indexPatterns ?? [])] : config.indexPatterns ?? [];\n const indexColumns = config.indexColumns ?? [];\n\n // Detect FK dependencies\n const dependencyGraph = detectFKDependencies(parsedTables);\n result.dependencyGraph = dependencyGraph;\n\n // Validate FK references - warn if any FK targets a table not in config\n const configuredTableNames = new Set(config.tables.map(t => t.name));\n const fkWarnings = validateFKReferences(dependencyGraph, configuredTableNames);\n result.warnings.push(...fkWarnings);\n if (verbose && dependencyGraph.fkRelationships.length > 0) {\n console.log(`\\nFK Dependencies detected: ${dependencyGraph.fkRelationships.length}`);\n for (const fk of dependencyGraph.fkRelationships) {\n console.log(` ${fk.table}.${fk.column} -> ${fk.referencedTable}`);\n }\n console.log(`\\nRecommended upload order:`);\n dependencyGraph.uploadOrder.forEach((table, i) => {\n console.log(` ${i + 1}. ${table}`);\n });\n console.log('');\n }\n\n // Generate table definitions - separate synced and local-only tables\n const syncedTableDefs: string[] = [];\n const localOnlyTableDefs: string[] = [];\n let totalIndexes = 0;\n for (const table of parsedTables) {\n const isLocalOnly = table.config.localOnly ?? false;\n const tableAlias = table.config.alias;\n if (verbose) {\n const syncPK = table.config.syncPrimaryKey || table.config.includeId;\n const flags = [table.config.trackMetadata ? '[trackMetadata]' : '', syncPK ? '[syncPrimaryKey]' : '', isLocalOnly ? '[localOnly]' : '', tableAlias ? `[alias: ${tableAlias}]` : ''].filter(Boolean).join(' ');\n console.log(`Processing ${table.schema}.${table.name} (${table.columns.size} columns)${flags ? ' ' + flags : ''}`);\n }\n const columnDefs = generateColumnDefs(table, decimalPatterns);\n if (columnDefs.length === 0) {\n result.warnings.push(`Table '${table.name}' has no syncable columns`);\n continue;\n }\n\n // Generate indexes for this table (use alias for index naming if provided)\n const effectiveTableName = tableAlias ?? table.name;\n const columnNames = [...table.columns.keys()];\n const indexes = indexPatterns.length > 0 || indexColumns.length > 0 ? generateIndexDefinitions(effectiveTableName, columnNames, indexPatterns, indexColumns) : [];\n totalIndexes += indexes.length;\n if (verbose && indexes.length > 0) {\n console.log(` Indexes: ${indexes.map(i => i.name).join(', ')}`);\n }\n\n // Generate table definition based on localOnly flag\n if (isLocalOnly) {\n localOnlyTableDefs.push(generateLocalOnlyTableDefinition(table, columnDefs, indexes));\n } else {\n syncedTableDefs.push(generateTableDefinition(table, columnDefs, indexes));\n }\n }\n\n // Combine synced and local-only table definitions\n const tableDefs = [...syncedTableDefs, ...localOnlyTableDefs];\n result.indexesGenerated = totalIndexes;\n\n // Collect unique schemas\n const schemas = [...new Set(config.tables.map(t => t.schema ?? 'public'))];\n\n // Generate output file content\n const relativePath = path.relative(cwd, typesPath);\n // Filter tables that were successfully generated (check for both name and alias)\n const generatedTables = parsedTables.filter(t => {\n const effectiveName = t.config.alias ?? t.name;\n return tableDefs.some(def => def.includes(`const ${effectiveName} =`));\n });\n const output = generateOutputFile(generatedTables, tableDefs, schemas, relativePath);\n\n // If dry-run, return output without writing\n if (dryRun) {\n result.success = true;\n result.tablesGenerated = tableDefs.length;\n result.output = output;\n return result;\n }\n\n // Ensure output directory exists\n const outputDir = path.dirname(outputPath);\n if (!fs.existsSync(outputDir)) {\n fs.mkdirSync(outputDir, {\n recursive: true\n });\n }\n\n // Write output file\n fs.writeFileSync(outputPath, output);\n result.success = true;\n result.tablesGenerated = tableDefs.length;\n return result;\n}","/**\n * Parser for Supabase database.types.ts files\n *\n * Extracts table definitions and column types from the generated TypeScript types\n */\n\nimport type { TableConfig } from './config.js';\nexport interface ColumnInfo {\n name: string;\n tsType: string;\n isNullable: boolean;\n}\nexport interface ParsedTable {\n name: string;\n schema: string;\n columns: Map<string, string>;\n config: TableConfig;\n}\nexport interface ParseOptions {\n /** Columns to skip */\n skipColumns: Set<string>;\n /**\n * Include the id column (normally skipped).\n * Use for tables with integer PKs referenced by FKs in other tables.\n */\n syncPrimaryKey?: boolean;\n /** @deprecated Use `syncPrimaryKey` instead */\n includeId?: boolean;\n /** Only include these columns (overrides skipColumns if specified) */\n onlyColumns?: string[];\n}\n\n/**\n * Parse the Row type from a table definition and extract columns\n */\nexport function parseRowType(tableContent: string, options: ParseOptions): Map<string, string> {\n const columns = new Map<string, string>();\n\n // Find the Row block - handles nested braces in type definitions\n const rowMatch = tableContent.match(/Row:\\s*\\{([^}]+(?:\\{[^}]*\\}[^}]*)*)\\}/s);\n if (!rowMatch) return columns;\n const rowContent = rowMatch[1];\n\n // syncPrimaryKey takes precedence, with includeId as fallback for backwards compat\n const includePrimaryKey = options.syncPrimaryKey ?? options.includeId ?? false;\n\n // If onlyColumns is specified, use it as a whitelist (overrides skipColumns)\n const onlyColumnsSet = options.onlyColumns ? new Set(options.onlyColumns) : null;\n\n // Parse each column: \"columnName: type\" or \"columnName?: type\"\n const columnRegex = /(\\w+)\\??:\\s*([^,\\n]+)/g;\n let match;\n while ((match = columnRegex.exec(rowContent)) !== null) {\n const [, columnName, columnType] = match;\n\n // Determine if column should be included\n let shouldInclude: boolean;\n if (onlyColumnsSet) {\n // onlyColumns mode: only include explicitly listed columns\n // Exception: include 'id' if syncPrimaryKey is true\n shouldInclude = onlyColumnsSet.has(columnName) || includePrimaryKey && columnName === 'id';\n } else {\n // skipColumns mode: include unless explicitly skipped\n // Exception: include 'id' if syncPrimaryKey is true\n const isSkipped = options.skipColumns.has(columnName);\n shouldInclude = !isSkipped || includePrimaryKey && columnName === 'id';\n }\n if (shouldInclude) {\n columns.set(columnName, columnType.trim());\n }\n }\n return columns;\n}\n\n/**\n * Escape special regex characters in a string\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Extract a table definition from the database.types.ts content\n *\n * Uses bracket-counting to properly handle nested arrays in Relationships\n * (e.g., columns: [\"parentId\"] inside relationship objects)\n */\nexport function extractTableDef(content: string, tableName: string, schema: string): string | null {\n // Escape special characters in schema and table names for regex safety\n const escapedSchema = escapeRegex(schema);\n const escapedTableName = escapeRegex(tableName);\n\n // Find the schema section\n const schemaRegex = new RegExp(`${escapedSchema}:\\\\s*\\\\{[\\\\s\\\\S]*?Tables:\\\\s*\\\\{`, 'g');\n const schemaMatch = schemaRegex.exec(content);\n if (!schemaMatch) return null;\n const startIndex = schemaMatch.index;\n const searchContent = content.slice(startIndex);\n\n // Find the start of this specific table\n // Use negative lookbehind (?<![A-Za-z]) to avoid matching table names that are\n // substrings of other names (e.g., \"Tag\" in \"CommentTag\")\n const tableStartRegex = new RegExp(`(?<![A-Za-z])${escapedTableName}:\\\\s*\\\\{`, 'g');\n const tableStartMatch = tableStartRegex.exec(searchContent);\n if (!tableStartMatch) return null;\n\n // Use bracket counting to find the matching closing brace\n const tableStartIndex = tableStartMatch.index;\n const openBraceIndex = tableStartMatch.index + tableStartMatch[0].length - 1;\n let braceCount = 1;\n let i = openBraceIndex + 1;\n while (i < searchContent.length && braceCount > 0) {\n const char = searchContent[i];\n if (char === '{') braceCount++;else if (char === '}') braceCount--;\n i++;\n }\n if (braceCount !== 0) return null;\n return searchContent.slice(tableStartIndex, i);\n}\n\n/**\n * Parse a database.types.ts file and extract specified tables\n */\nexport function parseTypesFile(content: string, tables: TableConfig[], globalSkipColumns: Set<string>): ParsedTable[] {\n const parsedTables: ParsedTable[] = [];\n for (const tableConfig of tables) {\n const {\n name,\n schema = 'public',\n syncPrimaryKey,\n includeId,\n skipColumns: tableSkipColumns,\n onlyColumns\n } = tableConfig;\n const tableDef = extractTableDef(content, name, schema);\n if (!tableDef) {\n continue;\n }\n\n // Merge global and per-table skipColumns\n const mergedSkipColumns = new Set([...globalSkipColumns, ...(tableSkipColumns ?? [])]);\n const columns = parseRowType(tableDef, {\n skipColumns: mergedSkipColumns,\n syncPrimaryKey,\n includeId,\n onlyColumns\n });\n if (columns.size > 0) {\n parsedTables.push({\n name,\n schema,\n columns,\n config: tableConfig\n });\n }\n }\n return parsedTables;\n}\n\n/**\n * Get all available schemas from the types file\n */\nexport function getAvailableSchemas(content: string): string[] {\n const schemas: string[] = [];\n const schemaRegex = /(\\w+):\\s*\\{[\\s\\S]*?Tables:\\s*\\{/g;\n let match;\n while ((match = schemaRegex.exec(content)) !== null) {\n schemas.push(match[1]);\n }\n return schemas;\n}\n\n/**\n * Get all table names in a schema\n */\nexport function getTablesInSchema(content: string, schema: string): string[] {\n const tables: string[] = [];\n\n // Find the schema section\n const schemaRegex = new RegExp(`${schema}:\\\\s*\\\\{[\\\\s\\\\S]*?Tables:\\\\s*\\\\{([\\\\s\\\\S]*?)\\\\}\\\\s*Views:`, 'g');\n const schemaMatch = schemaRegex.exec(content);\n if (!schemaMatch) return tables;\n const tablesContent = schemaMatch[1];\n\n // Find table names (they're at the start of each table definition)\n const tableNameRegex = /^\\s*(\\w+):\\s*\\{/gm;\n let match;\n while ((match = tableNameRegex.exec(tablesContent)) !== null) {\n tables.push(match[1]);\n }\n return tables;\n}","/**\n * Output templates for PowerSync schema generation\n */\n\nimport type { ParsedTable } from './parser.js';\nexport interface IndexDefinition {\n name: string;\n columns: string[];\n}\n\n/**\n * Convert table name to snake_case for index naming\n */\nfunction toSnakeCase(str: string): string {\n return str.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();\n}\n\n/**\n * Generate index definitions for a table based on column patterns\n *\n * @param tableName - The table name (PascalCase)\n * @param columns - Array of column names in the table\n * @param indexPatterns - Regex patterns to match against column names\n * @param additionalColumns - Specific column names to always index\n * @returns Array of index definitions\n */\nexport function generateIndexDefinitions(tableName: string, columns: string[], indexPatterns: string[], additionalColumns: string[] = []): IndexDefinition[] {\n const indexes: IndexDefinition[] = [];\n const snakeTableName = toSnakeCase(tableName);\n\n // Combine pattern matching and explicit columns\n const columnsToIndex = new Set<string>();\n\n // Pre-compile regex patterns with error handling\n const compiledPatterns: RegExp[] = [];\n for (const pattern of indexPatterns) {\n try {\n compiledPatterns.push(new RegExp(pattern));\n } catch {\n console.warn(`Warning: Invalid index pattern regex \"${pattern}\" - skipping`);\n }\n }\n\n // Match columns against patterns\n for (const column of columns) {\n // Skip 'id' column - it's the primary key and already indexed\n if (column === 'id') continue;\n for (const regex of compiledPatterns) {\n if (regex.test(column)) {\n columnsToIndex.add(column);\n break;\n }\n }\n }\n\n // Add explicitly specified columns (if they exist in the table)\n for (const column of additionalColumns) {\n if (columns.includes(column) && column !== 'id') {\n columnsToIndex.add(column);\n }\n }\n\n // Create index definitions\n for (const column of columnsToIndex) {\n const snakeColumn = toSnakeCase(column);\n indexes.push({\n name: `idx_${snakeTableName}_${snakeColumn}`,\n columns: [column]\n });\n }\n\n // Sort by index name for deterministic output\n indexes.sort((a, b) => a.name.localeCompare(b.name));\n return indexes;\n}\n\n/**\n * File header template\n */\nexport function generateHeader(typesPath: string): string {\n return `/**\n * PowerSync Schema Definition\n *\n * AUTO-GENERATED from ${typesPath}\n * Run: npx @pol-studios/powersync generate-schema\n *\n * DO NOT EDIT MANUALLY - changes will be overwritten\n */\n\nimport { column, Schema, Table } from \"@powersync/react-native\";\n`;\n}\n\n/**\n * Format indexes array for output\n */\nfunction formatIndexes(indexes: IndexDefinition[]): string {\n if (indexes.length === 0) return '';\n const indexLines = indexes.map(idx => {\n const columnsStr = idx.columns.map(c => `'${c}'`).join(', ');\n return ` { name: '${idx.name}', columns: [${columnsStr}] },`;\n });\n return `indexes: [\\n${indexLines.join('\\n')}\\n ]`;\n}\n\n/**\n * Generate the table definition for a parsed table\n */\nexport function generateTableDefinition(table: ParsedTable, columnDefs: string[], indexes: IndexDefinition[] = []): string {\n // Use alias if provided, otherwise use table name\n const effectiveName = table.config.alias ?? table.name;\n if (columnDefs.length === 0) {\n return `// ${effectiveName} - no syncable columns found`;\n }\n\n // Build options object\n const optionsParts: string[] = [];\n if (table.config.trackMetadata) {\n optionsParts.push('trackMetadata: true');\n }\n if (indexes.length > 0) {\n optionsParts.push(formatIndexes(indexes));\n }\n const optionsStr = optionsParts.length > 0 ? `, {\\n ${optionsParts.join(',\\n ')}\\n}` : '';\n return `const ${effectiveName} = new Table({\n${columnDefs.join('\\n')}\n}${optionsStr});`;\n}\n\n/**\n * Generate the table definition for a local-only table (not synced through PowerSync)\n */\nexport function generateLocalOnlyTableDefinition(table: ParsedTable, columnDefs: string[], indexes: IndexDefinition[] = []): string {\n // Use alias if provided, otherwise use table name\n const effectiveName = table.config.alias ?? table.name;\n if (columnDefs.length === 0) {\n return `// ${effectiveName} - no syncable columns found`;\n }\n\n // Build options object - always include localOnly: true\n const optionsParts: string[] = ['localOnly: true'];\n if (indexes.length > 0) {\n optionsParts.push(formatIndexes(indexes));\n }\n const optionsStr = `, {\\n ${optionsParts.join(',\\n ')}\\n}`;\n return `// Local-only table (not synced)\nconst ${effectiveName} = new Table({\n${columnDefs.join('\\n')}\n}${optionsStr});`;\n}\n\n/**\n * Generate the schema export section\n */\nexport function generateSchemaExport(tables: ParsedTable[]): string {\n // Use alias if provided, otherwise use table name\n const tableNames = tables.map(t => t.config.alias ?? t.name);\n return `// ============================================================================\n// SCHEMA EXPORT\n// ============================================================================\n\n// NOTE: photo_attachments is NOT included here.\n// The AttachmentQueue from @powersync/attachments creates and manages\n// its own internal SQLite table (not a view) during queue.init().\n// This allows INSERT/UPDATE operations to work correctly.\n\nexport const AppSchema = new Schema({\n${tableNames.map(name => ` ${name},`).join('\\n')}\n});\n\nexport type Database = (typeof AppSchema)[\"types\"];`;\n}\n\n/**\n * Generate schema mapping utilities\n */\nexport function generateSchemaMapping(tables: ParsedTable[], schemas: string[]): string {\n // Group tables by non-public schemas (use alias if provided)\n const schemaGroups = new Map<string, string[]>();\n for (const schema of schemas) {\n if (schema !== 'public') {\n schemaGroups.set(schema, []);\n }\n }\n for (const table of tables) {\n const effectiveName = table.config.alias ?? table.name;\n if (table.schema !== 'public' && schemaGroups.has(table.schema)) {\n schemaGroups.get(table.schema)!.push(effectiveName);\n }\n }\n const sections: string[] = [`// ============================================================================\n// SCHEMA MAPPING FOR CONNECTOR\n// ============================================================================`];\n\n // Generate constants for each non-public schema\n for (const [schema, tableNames] of schemaGroups) {\n if (tableNames.length > 0) {\n const constName = `${schema.toUpperCase()}_SCHEMA_TABLES`;\n sections.push(`\n// Tables in the '${schema}' schema (need .schema('${schema}') in Supabase queries)\nexport const ${constName} = new Set([\n${tableNames.map(name => ` \"${name}\",`).join('\\n')}\n]);`);\n }\n }\n\n // Generate helper function\n const schemaChecks = Array.from(schemaGroups.entries()).filter(([, names]) => names.length > 0).map(([schema]) => {\n const constName = `${schema.toUpperCase()}_SCHEMA_TABLES`;\n return ` if (${constName}.has(tableName)) return \"${schema}\";`;\n });\n if (schemaChecks.length > 0) {\n sections.push(`\n/**\n * Get the Supabase schema for a table\n */\nexport function getTableSchema(tableName: string): ${schemas.map(s => `\"${s}\"`).join(' | ')} {\n${schemaChecks.join('\\n')}\n return \"public\";\n}`);\n } else {\n sections.push(`\n/**\n * Get the Supabase schema for a table\n */\nexport function getTableSchema(tableName: string): \"public\" {\n return \"public\";\n}`);\n }\n return sections.join('\\n');\n}\n\n/**\n * Generate the FK detection utility (helpful for consumers)\n */\nexport function generateFKUtility(): string {\n return `\n// ============================================================================\n// FOREIGN KEY UTILITIES\n// ============================================================================\n\n/**\n * Check if a column name represents a foreign key reference\n * Convention: columns ending in 'Id' are foreign keys (e.g., projectId -> Project table)\n */\nexport function isForeignKeyColumn(columnName: string): boolean {\n return columnName.endsWith('Id') && columnName !== 'id';\n}\n\n/**\n * Get the referenced table name from a foreign key column\n * e.g., 'projectId' -> 'Project', 'equipmentFixtureUnitId' -> 'EquipmentFixtureUnit'\n */\nexport function getForeignKeyTable(columnName: string): string | null {\n if (!isForeignKeyColumn(columnName)) return null;\n // Remove 'Id' suffix and capitalize first letter\n const baseName = columnName.slice(0, -2);\n return baseName.charAt(0).toUpperCase() + baseName.slice(1);\n}`;\n}\n\n/**\n * Generate complete output file\n */\nexport function generateOutputFile(tables: ParsedTable[], tableDefs: string[], schemas: string[], typesPath: string): string {\n return `${generateHeader(typesPath)}\n\n// ============================================================================\n// TABLE DEFINITIONS\n// ============================================================================\n\n${tableDefs.join('\\n\\n')}\n\n${generateSchemaExport(tables)}\n\n${generateSchemaMapping(tables, ['public', ...schemas.filter(s => s !== 'public')])}\n${generateFKUtility()}\n`;\n}","/**\n * Foreign Key Dependency Detection\n *\n * Analyzes parsed tables to detect FK relationships and determine\n * optimal upload order for offline-first sync.\n */\n\nimport type { ParsedTable } from './parser.js';\nexport interface FKDependency {\n /** Table containing the FK column */\n table: string;\n /** FK column name (e.g., 'projectId') */\n column: string;\n /** Referenced table name (e.g., 'Project') */\n referencedTable: string;\n}\nexport interface DependencyGraph {\n /** Map of table -> tables it depends on (has FK references to) */\n dependencies: Map<string, string[]>;\n /** Map of table -> tables that depend on it (have FKs referencing this table) */\n dependents: Map<string, string[]>;\n /** Topologically sorted table names for optimal upload order */\n uploadOrder: string[];\n /** All detected FK relationships */\n fkRelationships: FKDependency[];\n}\n\n/**\n * Convert a FK column name to its referenced table name\n * Convention: columns ending in 'Id' reference the PascalCase table\n * e.g., projectId -> Project, equipmentUnitId -> EquipmentUnit\n */\nexport function fkColumnToTableName(columnName: string): string | null {\n if (!columnName.endsWith('Id') || columnName === 'id') {\n return null;\n }\n // Remove 'Id' suffix and capitalize first letter\n const baseName = columnName.slice(0, -2);\n return baseName.charAt(0).toUpperCase() + baseName.slice(1);\n}\n\n/**\n * Detect FK dependencies from parsed tables\n *\n * Uses naming convention: columns ending in 'Id' reference the PascalCase table\n * Only considers references to tables that are in the provided list (ignores external references)\n */\nexport function detectFKDependencies(tables: ParsedTable[]): DependencyGraph {\n const tableNames = new Set(tables.map(t => t.name));\n const dependencies = new Map<string, string[]>();\n const dependents = new Map<string, string[]>();\n const fkRelationships: FKDependency[] = [];\n\n // Initialize maps for all tables\n for (const table of tables) {\n dependencies.set(table.name, []);\n dependents.set(table.name, []);\n }\n\n // Detect FK relationships\n for (const table of tables) {\n for (const [columnName] of table.columns) {\n const referencedTable = fkColumnToTableName(columnName);\n\n // Only track references to tables in our schema\n if (referencedTable && tableNames.has(referencedTable)) {\n // This table depends on the referenced table\n const tableDeps = dependencies.get(table.name) || [];\n if (!tableDeps.includes(referencedTable)) {\n tableDeps.push(referencedTable);\n dependencies.set(table.name, tableDeps);\n }\n\n // The referenced table has this table as a dependent\n const refDeps = dependents.get(referencedTable) || [];\n if (!refDeps.includes(table.name)) {\n refDeps.push(table.name);\n dependents.set(referencedTable, refDeps);\n }\n\n // Record the FK relationship\n fkRelationships.push({\n table: table.name,\n column: columnName,\n referencedTable\n });\n }\n }\n }\n\n // Calculate upload order via topological sort\n const uploadOrder = getUploadOrder({\n dependencies\n });\n return {\n dependencies,\n dependents,\n uploadOrder,\n fkRelationships\n };\n}\n\n/**\n * Get optimal upload order using Kahn's topological sort algorithm\n *\n * Tables with no dependencies are uploaded first, then tables that depend on them, etc.\n * This ensures that when a row references another table, the referenced row exists first.\n */\nexport function getUploadOrder(graph: Pick<DependencyGraph, 'dependencies'>): string[] {\n const result: string[] = [];\n const inDegree = new Map<string, number>();\n const adjList = new Map<string, string[]>();\n\n // Initialize in-degree (number of dependencies) for each table\n for (const [table, deps] of graph.dependencies) {\n inDegree.set(table, deps.length);\n adjList.set(table, []);\n }\n\n // Build adjacency list (reverse of dependencies - who depends on this table)\n for (const [table, deps] of graph.dependencies) {\n for (const dep of deps) {\n const list = adjList.get(dep) || [];\n list.push(table);\n adjList.set(dep, list);\n }\n }\n\n // Start with tables that have no dependencies\n const queue: string[] = [];\n for (const [table, degree] of inDegree) {\n if (degree === 0) {\n queue.push(table);\n }\n }\n\n // Sort queue alphabetically for deterministic output\n queue.sort();\n\n // Process queue\n while (queue.length > 0) {\n const table = queue.shift()!;\n result.push(table);\n\n // Reduce in-degree for all tables that depend on this one\n const dependentTables = adjList.get(table) || [];\n for (const dependent of dependentTables) {\n const newDegree = (inDegree.get(dependent) || 0) - 1;\n inDegree.set(dependent, newDegree);\n if (newDegree === 0) {\n queue.push(dependent);\n // Keep queue sorted for deterministic output\n queue.sort();\n }\n }\n }\n\n // Check for cycles (if result doesn't include all tables)\n if (result.length < graph.dependencies.size) {\n // There's a cycle - just append remaining tables alphabetically\n const remaining = [...graph.dependencies.keys()].filter(t => !result.includes(t)).sort();\n result.push(...remaining);\n }\n return result;\n}\n\n/**\n * Get tables that have no dependencies (root tables)\n * These are typically reference/lookup tables like User, Project, etc.\n */\nexport function getRootTables(graph: DependencyGraph): string[] {\n const roots: string[] = [];\n for (const [table, deps] of graph.dependencies) {\n if (deps.length === 0) {\n roots.push(table);\n }\n }\n return roots.sort();\n}\n\n/**\n * Get tables that have no dependents (leaf tables)\n * These are typically transaction/event tables that reference other tables\n */\nexport function getLeafTables(graph: DependencyGraph): string[] {\n const leaves: string[] = [];\n for (const [table, deps] of graph.dependents) {\n if (deps.length === 0) {\n leaves.push(table);\n }\n }\n return leaves.sort();\n}\n\n/**\n * Format the dependency graph as a human-readable string\n */\nexport function formatDependencyGraph(graph: DependencyGraph): string {\n const lines: string[] = [];\n lines.push('FK Dependencies:');\n for (const fk of graph.fkRelationships) {\n lines.push(` ${fk.table}.${fk.column} -> ${fk.referencedTable}`);\n }\n if (graph.fkRelationships.length === 0) {\n lines.push(' (none detected)');\n }\n lines.push('');\n lines.push('Upload Order (topological):');\n graph.uploadOrder.forEach((table, i) => {\n const deps = graph.dependencies.get(table) || [];\n const depStr = deps.length > 0 ? ` (depends on: ${deps.join(', ')})` : ' (root)';\n lines.push(` ${i + 1}. ${table}${depStr}`);\n });\n return lines.join('\\n');\n}"],"mappings":"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","names":[]}
|