@pol-studios/powersync 1.0.11 → 1.0.13
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 +257 -18
- package/dist/generator/index.d.ts +97 -3
- package/dist/generator/index.js +268 -13
- package/dist/generator/index.js.map +1 -1
- package/package.json +2 -1
package/dist/generator/cli.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import * as path2 from "path";
|
|
6
6
|
import * as fs2 from "fs";
|
|
7
|
+
import { createJiti } from "jiti";
|
|
7
8
|
import pc from "picocolors";
|
|
8
9
|
|
|
9
10
|
// src/generator/generator.ts
|
|
@@ -27,6 +28,14 @@ var DEFAULT_DECIMAL_PATTERNS = [
|
|
|
27
28
|
"cost",
|
|
28
29
|
"total"
|
|
29
30
|
];
|
|
31
|
+
var DEFAULT_INDEX_PATTERNS = [
|
|
32
|
+
"Id$",
|
|
33
|
+
// FK columns ending in Id (e.g., projectId, userId)
|
|
34
|
+
"^createdAt$",
|
|
35
|
+
"^updatedAt$",
|
|
36
|
+
"^status$",
|
|
37
|
+
"^type$"
|
|
38
|
+
];
|
|
30
39
|
|
|
31
40
|
// src/generator/parser.ts
|
|
32
41
|
function parseRowType(tableContent, options) {
|
|
@@ -35,45 +44,81 @@ function parseRowType(tableContent, options) {
|
|
|
35
44
|
if (!rowMatch) return columns;
|
|
36
45
|
const rowContent = rowMatch[1];
|
|
37
46
|
const includePrimaryKey = options.syncPrimaryKey ?? options.includeId ?? false;
|
|
47
|
+
const onlyColumnsSet = options.onlyColumns ? new Set(options.onlyColumns) : null;
|
|
38
48
|
const columnRegex = /(\w+)\??:\s*([^,\n]+)/g;
|
|
39
49
|
let match;
|
|
40
50
|
while ((match = columnRegex.exec(rowContent)) !== null) {
|
|
41
51
|
const [, columnName, columnType] = match;
|
|
42
|
-
|
|
43
|
-
if (
|
|
52
|
+
let shouldInclude;
|
|
53
|
+
if (onlyColumnsSet) {
|
|
54
|
+
shouldInclude = onlyColumnsSet.has(columnName) || includePrimaryKey && columnName === "id";
|
|
55
|
+
} else {
|
|
56
|
+
const isSkipped = options.skipColumns.has(columnName);
|
|
57
|
+
shouldInclude = !isSkipped || includePrimaryKey && columnName === "id";
|
|
58
|
+
}
|
|
59
|
+
if (shouldInclude) {
|
|
44
60
|
columns.set(columnName, columnType.trim());
|
|
45
61
|
}
|
|
46
62
|
}
|
|
47
63
|
return columns;
|
|
48
64
|
}
|
|
65
|
+
function escapeRegex(str) {
|
|
66
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
67
|
+
}
|
|
49
68
|
function extractTableDef(content, tableName, schema) {
|
|
69
|
+
const escapedSchema = escapeRegex(schema);
|
|
70
|
+
const escapedTableName = escapeRegex(tableName);
|
|
50
71
|
const schemaRegex = new RegExp(
|
|
51
|
-
`${
|
|
72
|
+
`${escapedSchema}:\\s*\\{[\\s\\S]*?Tables:\\s*\\{`,
|
|
52
73
|
"g"
|
|
53
74
|
);
|
|
54
75
|
const schemaMatch = schemaRegex.exec(content);
|
|
55
76
|
if (!schemaMatch) return null;
|
|
56
77
|
const startIndex = schemaMatch.index;
|
|
57
|
-
const
|
|
58
|
-
|
|
78
|
+
const searchContent = content.slice(startIndex);
|
|
79
|
+
const tableStartRegex = new RegExp(
|
|
80
|
+
`(?<![A-Za-z])${escapedTableName}:\\s*\\{`,
|
|
59
81
|
"g"
|
|
60
82
|
);
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
83
|
+
const tableStartMatch = tableStartRegex.exec(searchContent);
|
|
84
|
+
if (!tableStartMatch) return null;
|
|
85
|
+
const tableStartIndex = tableStartMatch.index;
|
|
86
|
+
const openBraceIndex = tableStartMatch.index + tableStartMatch[0].length - 1;
|
|
87
|
+
let braceCount = 1;
|
|
88
|
+
let i = openBraceIndex + 1;
|
|
89
|
+
while (i < searchContent.length && braceCount > 0) {
|
|
90
|
+
const char = searchContent[i];
|
|
91
|
+
if (char === "{") braceCount++;
|
|
92
|
+
else if (char === "}") braceCount--;
|
|
93
|
+
i++;
|
|
94
|
+
}
|
|
95
|
+
if (braceCount !== 0) return null;
|
|
96
|
+
return searchContent.slice(tableStartIndex, i);
|
|
64
97
|
}
|
|
65
|
-
function parseTypesFile(content, tables,
|
|
98
|
+
function parseTypesFile(content, tables, globalSkipColumns) {
|
|
66
99
|
const parsedTables = [];
|
|
67
100
|
for (const tableConfig of tables) {
|
|
68
|
-
const {
|
|
101
|
+
const {
|
|
102
|
+
name,
|
|
103
|
+
schema = "public",
|
|
104
|
+
syncPrimaryKey,
|
|
105
|
+
includeId,
|
|
106
|
+
skipColumns: tableSkipColumns,
|
|
107
|
+
onlyColumns
|
|
108
|
+
} = tableConfig;
|
|
69
109
|
const tableDef = extractTableDef(content, name, schema);
|
|
70
110
|
if (!tableDef) {
|
|
71
111
|
continue;
|
|
72
112
|
}
|
|
113
|
+
const mergedSkipColumns = /* @__PURE__ */ new Set([
|
|
114
|
+
...globalSkipColumns,
|
|
115
|
+
...tableSkipColumns ?? []
|
|
116
|
+
]);
|
|
73
117
|
const columns = parseRowType(tableDef, {
|
|
74
|
-
skipColumns,
|
|
118
|
+
skipColumns: mergedSkipColumns,
|
|
75
119
|
syncPrimaryKey,
|
|
76
|
-
includeId
|
|
120
|
+
includeId,
|
|
121
|
+
onlyColumns
|
|
77
122
|
});
|
|
78
123
|
if (columns.size > 0) {
|
|
79
124
|
parsedTables.push({
|
|
@@ -88,6 +133,45 @@ function parseTypesFile(content, tables, skipColumns) {
|
|
|
88
133
|
}
|
|
89
134
|
|
|
90
135
|
// src/generator/templates.ts
|
|
136
|
+
function toSnakeCase(str) {
|
|
137
|
+
return str.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
|
|
138
|
+
}
|
|
139
|
+
function generateIndexDefinitions(tableName, columns, indexPatterns, additionalColumns = []) {
|
|
140
|
+
const indexes = [];
|
|
141
|
+
const snakeTableName = toSnakeCase(tableName);
|
|
142
|
+
const columnsToIndex = /* @__PURE__ */ new Set();
|
|
143
|
+
const compiledPatterns = [];
|
|
144
|
+
for (const pattern of indexPatterns) {
|
|
145
|
+
try {
|
|
146
|
+
compiledPatterns.push(new RegExp(pattern));
|
|
147
|
+
} catch {
|
|
148
|
+
console.warn(`Warning: Invalid index pattern regex "${pattern}" - skipping`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
for (const column of columns) {
|
|
152
|
+
if (column === "id") continue;
|
|
153
|
+
for (const regex of compiledPatterns) {
|
|
154
|
+
if (regex.test(column)) {
|
|
155
|
+
columnsToIndex.add(column);
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
for (const column of additionalColumns) {
|
|
161
|
+
if (columns.includes(column) && column !== "id") {
|
|
162
|
+
columnsToIndex.add(column);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const column of columnsToIndex) {
|
|
166
|
+
const snakeColumn = toSnakeCase(column);
|
|
167
|
+
indexes.push({
|
|
168
|
+
name: `idx_${snakeTableName}_${snakeColumn}`,
|
|
169
|
+
columns: [column]
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
indexes.sort((a, b) => a.name.localeCompare(b.name));
|
|
173
|
+
return indexes;
|
|
174
|
+
}
|
|
91
175
|
function generateHeader(typesPath) {
|
|
92
176
|
return `/**
|
|
93
177
|
* PowerSync Schema Definition
|
|
@@ -101,11 +185,30 @@ function generateHeader(typesPath) {
|
|
|
101
185
|
import { column, Schema, Table } from "@powersync/react-native";
|
|
102
186
|
`;
|
|
103
187
|
}
|
|
104
|
-
function
|
|
188
|
+
function formatIndexes(indexes) {
|
|
189
|
+
if (indexes.length === 0) return "";
|
|
190
|
+
const indexLines = indexes.map((idx) => {
|
|
191
|
+
const columnsStr = idx.columns.map((c) => `'${c}'`).join(", ");
|
|
192
|
+
return ` { name: '${idx.name}', columns: [${columnsStr}] },`;
|
|
193
|
+
});
|
|
194
|
+
return `indexes: [
|
|
195
|
+
${indexLines.join("\n")}
|
|
196
|
+
]`;
|
|
197
|
+
}
|
|
198
|
+
function generateTableDefinition(table, columnDefs, indexes = []) {
|
|
105
199
|
if (columnDefs.length === 0) {
|
|
106
200
|
return `// ${table.name} - no syncable columns found`;
|
|
107
201
|
}
|
|
108
|
-
const
|
|
202
|
+
const optionsParts = [];
|
|
203
|
+
if (table.config.trackMetadata) {
|
|
204
|
+
optionsParts.push("trackMetadata: true");
|
|
205
|
+
}
|
|
206
|
+
if (indexes.length > 0) {
|
|
207
|
+
optionsParts.push(formatIndexes(indexes));
|
|
208
|
+
}
|
|
209
|
+
const optionsStr = optionsParts.length > 0 ? `, {
|
|
210
|
+
${optionsParts.join(",\n ")}
|
|
211
|
+
}` : "";
|
|
109
212
|
return `const ${table.name} = new Table({
|
|
110
213
|
${columnDefs.join("\n")}
|
|
111
214
|
}${optionsStr});`;
|
|
@@ -219,6 +322,95 @@ ${generateFKUtility()}
|
|
|
219
322
|
`;
|
|
220
323
|
}
|
|
221
324
|
|
|
325
|
+
// src/generator/fk-dependencies.ts
|
|
326
|
+
function fkColumnToTableName(columnName) {
|
|
327
|
+
if (!columnName.endsWith("Id") || columnName === "id") {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
const baseName = columnName.slice(0, -2);
|
|
331
|
+
return baseName.charAt(0).toUpperCase() + baseName.slice(1);
|
|
332
|
+
}
|
|
333
|
+
function detectFKDependencies(tables) {
|
|
334
|
+
const tableNames = new Set(tables.map((t) => t.name));
|
|
335
|
+
const dependencies = /* @__PURE__ */ new Map();
|
|
336
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
337
|
+
const fkRelationships = [];
|
|
338
|
+
for (const table of tables) {
|
|
339
|
+
dependencies.set(table.name, []);
|
|
340
|
+
dependents.set(table.name, []);
|
|
341
|
+
}
|
|
342
|
+
for (const table of tables) {
|
|
343
|
+
for (const [columnName] of table.columns) {
|
|
344
|
+
const referencedTable = fkColumnToTableName(columnName);
|
|
345
|
+
if (referencedTable && tableNames.has(referencedTable)) {
|
|
346
|
+
const tableDeps = dependencies.get(table.name) || [];
|
|
347
|
+
if (!tableDeps.includes(referencedTable)) {
|
|
348
|
+
tableDeps.push(referencedTable);
|
|
349
|
+
dependencies.set(table.name, tableDeps);
|
|
350
|
+
}
|
|
351
|
+
const refDeps = dependents.get(referencedTable) || [];
|
|
352
|
+
if (!refDeps.includes(table.name)) {
|
|
353
|
+
refDeps.push(table.name);
|
|
354
|
+
dependents.set(referencedTable, refDeps);
|
|
355
|
+
}
|
|
356
|
+
fkRelationships.push({
|
|
357
|
+
table: table.name,
|
|
358
|
+
column: columnName,
|
|
359
|
+
referencedTable
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const uploadOrder = getUploadOrder({ dependencies });
|
|
365
|
+
return {
|
|
366
|
+
dependencies,
|
|
367
|
+
dependents,
|
|
368
|
+
uploadOrder,
|
|
369
|
+
fkRelationships
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
function getUploadOrder(graph) {
|
|
373
|
+
const result = [];
|
|
374
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
375
|
+
const adjList = /* @__PURE__ */ new Map();
|
|
376
|
+
for (const [table, deps] of graph.dependencies) {
|
|
377
|
+
inDegree.set(table, deps.length);
|
|
378
|
+
adjList.set(table, []);
|
|
379
|
+
}
|
|
380
|
+
for (const [table, deps] of graph.dependencies) {
|
|
381
|
+
for (const dep of deps) {
|
|
382
|
+
const list = adjList.get(dep) || [];
|
|
383
|
+
list.push(table);
|
|
384
|
+
adjList.set(dep, list);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const queue = [];
|
|
388
|
+
for (const [table, degree] of inDegree) {
|
|
389
|
+
if (degree === 0) {
|
|
390
|
+
queue.push(table);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
queue.sort();
|
|
394
|
+
while (queue.length > 0) {
|
|
395
|
+
const table = queue.shift();
|
|
396
|
+
result.push(table);
|
|
397
|
+
const dependentTables = adjList.get(table) || [];
|
|
398
|
+
for (const dependent of dependentTables) {
|
|
399
|
+
const newDegree = (inDegree.get(dependent) || 0) - 1;
|
|
400
|
+
inDegree.set(dependent, newDegree);
|
|
401
|
+
if (newDegree === 0) {
|
|
402
|
+
queue.push(dependent);
|
|
403
|
+
queue.sort();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (result.length < graph.dependencies.size) {
|
|
408
|
+
const remaining = [...graph.dependencies.keys()].filter((t) => !result.includes(t)).sort();
|
|
409
|
+
result.push(...remaining);
|
|
410
|
+
}
|
|
411
|
+
return result;
|
|
412
|
+
}
|
|
413
|
+
|
|
222
414
|
// src/generator/generator.ts
|
|
223
415
|
function mapTypeToPowerSync(tsType, columnName, decimalPatterns) {
|
|
224
416
|
const cleanType = tsType.trim().replace(/\s*\|\s*null/g, "");
|
|
@@ -310,7 +502,26 @@ async function generateSchema(config, options) {
|
|
|
310
502
|
result.errors.push("No tables were parsed successfully");
|
|
311
503
|
return result;
|
|
312
504
|
}
|
|
505
|
+
const autoIndexes = config.autoIndexes ?? true;
|
|
506
|
+
const indexPatterns = autoIndexes ? [...DEFAULT_INDEX_PATTERNS, ...config.indexPatterns ?? []] : config.indexPatterns ?? [];
|
|
507
|
+
const indexColumns = config.indexColumns ?? [];
|
|
508
|
+
const dependencyGraph = detectFKDependencies(parsedTables);
|
|
509
|
+
result.dependencyGraph = dependencyGraph;
|
|
510
|
+
if (verbose && dependencyGraph.fkRelationships.length > 0) {
|
|
511
|
+
console.log(`
|
|
512
|
+
FK Dependencies detected: ${dependencyGraph.fkRelationships.length}`);
|
|
513
|
+
for (const fk of dependencyGraph.fkRelationships) {
|
|
514
|
+
console.log(` ${fk.table}.${fk.column} -> ${fk.referencedTable}`);
|
|
515
|
+
}
|
|
516
|
+
console.log(`
|
|
517
|
+
Recommended upload order:`);
|
|
518
|
+
dependencyGraph.uploadOrder.forEach((table, i) => {
|
|
519
|
+
console.log(` ${i + 1}. ${table}`);
|
|
520
|
+
});
|
|
521
|
+
console.log("");
|
|
522
|
+
}
|
|
313
523
|
const tableDefs = [];
|
|
524
|
+
let totalIndexes = 0;
|
|
314
525
|
for (const table of parsedTables) {
|
|
315
526
|
if (verbose) {
|
|
316
527
|
const syncPK = table.config.syncPrimaryKey || table.config.includeId;
|
|
@@ -323,8 +534,15 @@ async function generateSchema(config, options) {
|
|
|
323
534
|
result.warnings.push(`Table '${table.name}' has no syncable columns`);
|
|
324
535
|
continue;
|
|
325
536
|
}
|
|
326
|
-
|
|
537
|
+
const columnNames = [...table.columns.keys()];
|
|
538
|
+
const indexes = indexPatterns.length > 0 || indexColumns.length > 0 ? generateIndexDefinitions(table.name, columnNames, indexPatterns, indexColumns) : [];
|
|
539
|
+
totalIndexes += indexes.length;
|
|
540
|
+
if (verbose && indexes.length > 0) {
|
|
541
|
+
console.log(` Indexes: ${indexes.map((i) => i.name).join(", ")}`);
|
|
542
|
+
}
|
|
543
|
+
tableDefs.push(generateTableDefinition(table, columnDefs, indexes));
|
|
327
544
|
}
|
|
545
|
+
result.indexesGenerated = totalIndexes;
|
|
328
546
|
const schemas = [...new Set(config.tables.map((t) => t.schema ?? "public"))];
|
|
329
547
|
const relativePath = path.relative(cwd, typesPath);
|
|
330
548
|
const output = generateOutputFile(
|
|
@@ -352,6 +570,9 @@ async function generateSchema(config, options) {
|
|
|
352
570
|
}
|
|
353
571
|
|
|
354
572
|
// src/generator/cli.ts
|
|
573
|
+
var jiti = createJiti(import.meta.url, {
|
|
574
|
+
interopDefault: true
|
|
575
|
+
});
|
|
355
576
|
var program = new Command();
|
|
356
577
|
var VERSION = "1.0.0";
|
|
357
578
|
program.name("@pol-studios/powersync").description("PowerSync utilities for offline-first applications").version(VERSION);
|
|
@@ -384,8 +605,8 @@ program.command("generate-schema").description("Generate PowerSync schema from d
|
|
|
384
605
|
let config;
|
|
385
606
|
try {
|
|
386
607
|
console.log(pc.dim(` Loading config from: ${configPath}`));
|
|
387
|
-
const configModule = await import(configPath);
|
|
388
|
-
config = configModule.default;
|
|
608
|
+
const configModule = await jiti.import(configPath);
|
|
609
|
+
config = configModule.default || configModule;
|
|
389
610
|
if (!config || !config.tables || !config.typesPath || !config.outputPath) {
|
|
390
611
|
throw new Error(
|
|
391
612
|
"Invalid config: must export { typesPath, outputPath, tables }"
|
|
@@ -397,7 +618,7 @@ program.command("generate-schema").description("Generate PowerSync schema from d
|
|
|
397
618
|
Make sure your config file:
|
|
398
619
|
1. Uses 'export default defineConfig({...})'
|
|
399
620
|
2. Contains typesPath, outputPath, and tables properties
|
|
400
|
-
3.
|
|
621
|
+
3. Has valid TypeScript syntax
|
|
401
622
|
`));
|
|
402
623
|
process.exit(1);
|
|
403
624
|
}
|
|
@@ -433,6 +654,9 @@ program.command("generate-schema").description("Generate PowerSync schema from d
|
|
|
433
654
|
console.log(
|
|
434
655
|
pc.cyan(` ${pc.bold("Dry run:")} Would generate ${result.tablesGenerated} tables`)
|
|
435
656
|
);
|
|
657
|
+
if (result.indexesGenerated !== void 0 && result.indexesGenerated > 0) {
|
|
658
|
+
console.log(pc.cyan(` Indexes: ${result.indexesGenerated}`));
|
|
659
|
+
}
|
|
436
660
|
if (options.verbose && result.output) {
|
|
437
661
|
console.log(pc.dim("\n Generated output:\n"));
|
|
438
662
|
console.log(pc.dim(result.output.split("\n").map((l) => " " + l).join("\n")));
|
|
@@ -441,6 +665,12 @@ program.command("generate-schema").description("Generate PowerSync schema from d
|
|
|
441
665
|
console.log(
|
|
442
666
|
pc.green(` ${pc.bold("Success!")} Generated ${result.tablesGenerated} tables`)
|
|
443
667
|
);
|
|
668
|
+
if (result.indexesGenerated !== void 0 && result.indexesGenerated > 0) {
|
|
669
|
+
console.log(pc.green(` Indexes: ${result.indexesGenerated}`));
|
|
670
|
+
}
|
|
671
|
+
if (result.dependencyGraph && result.dependencyGraph.fkRelationships.length > 0) {
|
|
672
|
+
console.log(pc.green(` FK dependencies: ${result.dependencyGraph.fkRelationships.length}`));
|
|
673
|
+
}
|
|
444
674
|
console.log(pc.dim(` Output: ${result.outputPath}`));
|
|
445
675
|
}
|
|
446
676
|
console.log("");
|
|
@@ -514,6 +744,15 @@ export default defineConfig({
|
|
|
514
744
|
|
|
515
745
|
// Optional: column name patterns for decimal values (use column.real)
|
|
516
746
|
// decimalPatterns: ['price', 'amount', 'rate'],
|
|
747
|
+
|
|
748
|
+
// Auto-generate indexes for FK columns and common patterns (default: true)
|
|
749
|
+
// autoIndexes: true,
|
|
750
|
+
|
|
751
|
+
// Additional columns to index (exact names)
|
|
752
|
+
// indexColumns: ['name', 'email'],
|
|
753
|
+
|
|
754
|
+
// Additional index patterns (regex, in addition to defaults: Id$, createdAt, updatedAt, status, type)
|
|
755
|
+
// indexPatterns: ['^sortOrder$'],
|
|
517
756
|
});
|
|
518
757
|
`;
|
|
519
758
|
fs2.writeFileSync(configPath, template);
|
|
@@ -36,6 +36,12 @@ interface GeneratorConfig {
|
|
|
36
36
|
decimalPatterns?: string[];
|
|
37
37
|
/** Additional schemas to track (besides 'public' which is the default) */
|
|
38
38
|
schemas?: string[];
|
|
39
|
+
/** Generate indexes for FK and common columns (default: true) */
|
|
40
|
+
autoIndexes?: boolean;
|
|
41
|
+
/** Additional columns to index (exact column names) */
|
|
42
|
+
indexColumns?: string[];
|
|
43
|
+
/** Column patterns that should be indexed (regex patterns) */
|
|
44
|
+
indexPatterns?: string[];
|
|
39
45
|
}
|
|
40
46
|
/**
|
|
41
47
|
* Define a PowerSync generator configuration with type safety
|
|
@@ -49,6 +55,11 @@ declare const DEFAULT_SKIP_COLUMNS: string[];
|
|
|
49
55
|
* Default column name patterns that indicate decimal values
|
|
50
56
|
*/
|
|
51
57
|
declare const DEFAULT_DECIMAL_PATTERNS: string[];
|
|
58
|
+
/**
|
|
59
|
+
* Default column patterns that should be indexed
|
|
60
|
+
* These are regex patterns matched against column names
|
|
61
|
+
*/
|
|
62
|
+
declare const DEFAULT_INDEX_PATTERNS: string[];
|
|
52
63
|
|
|
53
64
|
/**
|
|
54
65
|
* Parser for Supabase database.types.ts files
|
|
@@ -77,6 +88,8 @@ interface ParseOptions {
|
|
|
77
88
|
syncPrimaryKey?: boolean;
|
|
78
89
|
/** @deprecated Use `syncPrimaryKey` instead */
|
|
79
90
|
includeId?: boolean;
|
|
91
|
+
/** Only include these columns (overrides skipColumns if specified) */
|
|
92
|
+
onlyColumns?: string[];
|
|
80
93
|
}
|
|
81
94
|
/**
|
|
82
95
|
* Parse the Row type from a table definition and extract columns
|
|
@@ -84,12 +97,15 @@ interface ParseOptions {
|
|
|
84
97
|
declare function parseRowType(tableContent: string, options: ParseOptions): Map<string, string>;
|
|
85
98
|
/**
|
|
86
99
|
* Extract a table definition from the database.types.ts content
|
|
100
|
+
*
|
|
101
|
+
* Uses bracket-counting to properly handle nested arrays in Relationships
|
|
102
|
+
* (e.g., columns: ["parentId"] inside relationship objects)
|
|
87
103
|
*/
|
|
88
104
|
declare function extractTableDef(content: string, tableName: string, schema: string): string | null;
|
|
89
105
|
/**
|
|
90
106
|
* Parse a database.types.ts file and extract specified tables
|
|
91
107
|
*/
|
|
92
|
-
declare function parseTypesFile(content: string, tables: TableConfig[],
|
|
108
|
+
declare function parseTypesFile(content: string, tables: TableConfig[], globalSkipColumns: Set<string>): ParsedTable[];
|
|
93
109
|
/**
|
|
94
110
|
* Get all available schemas from the types file
|
|
95
111
|
*/
|
|
@@ -99,6 +115,66 @@ declare function getAvailableSchemas(content: string): string[];
|
|
|
99
115
|
*/
|
|
100
116
|
declare function getTablesInSchema(content: string, schema: string): string[];
|
|
101
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Foreign Key Dependency Detection
|
|
120
|
+
*
|
|
121
|
+
* Analyzes parsed tables to detect FK relationships and determine
|
|
122
|
+
* optimal upload order for offline-first sync.
|
|
123
|
+
*/
|
|
124
|
+
|
|
125
|
+
interface FKDependency {
|
|
126
|
+
/** Table containing the FK column */
|
|
127
|
+
table: string;
|
|
128
|
+
/** FK column name (e.g., 'projectId') */
|
|
129
|
+
column: string;
|
|
130
|
+
/** Referenced table name (e.g., 'Project') */
|
|
131
|
+
referencedTable: string;
|
|
132
|
+
}
|
|
133
|
+
interface DependencyGraph {
|
|
134
|
+
/** Map of table -> tables it depends on (has FK references to) */
|
|
135
|
+
dependencies: Map<string, string[]>;
|
|
136
|
+
/** Map of table -> tables that depend on it (have FKs referencing this table) */
|
|
137
|
+
dependents: Map<string, string[]>;
|
|
138
|
+
/** Topologically sorted table names for optimal upload order */
|
|
139
|
+
uploadOrder: string[];
|
|
140
|
+
/** All detected FK relationships */
|
|
141
|
+
fkRelationships: FKDependency[];
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Convert a FK column name to its referenced table name
|
|
145
|
+
* Convention: columns ending in 'Id' reference the PascalCase table
|
|
146
|
+
* e.g., projectId -> Project, equipmentUnitId -> EquipmentUnit
|
|
147
|
+
*/
|
|
148
|
+
declare function fkColumnToTableName(columnName: string): string | null;
|
|
149
|
+
/**
|
|
150
|
+
* Detect FK dependencies from parsed tables
|
|
151
|
+
*
|
|
152
|
+
* Uses naming convention: columns ending in 'Id' reference the PascalCase table
|
|
153
|
+
* Only considers references to tables that are in the provided list (ignores external references)
|
|
154
|
+
*/
|
|
155
|
+
declare function detectFKDependencies(tables: ParsedTable[]): DependencyGraph;
|
|
156
|
+
/**
|
|
157
|
+
* Get optimal upload order using Kahn's topological sort algorithm
|
|
158
|
+
*
|
|
159
|
+
* Tables with no dependencies are uploaded first, then tables that depend on them, etc.
|
|
160
|
+
* This ensures that when a row references another table, the referenced row exists first.
|
|
161
|
+
*/
|
|
162
|
+
declare function getUploadOrder(graph: Pick<DependencyGraph, 'dependencies'>): string[];
|
|
163
|
+
/**
|
|
164
|
+
* Get tables that have no dependencies (root tables)
|
|
165
|
+
* These are typically reference/lookup tables like User, Project, etc.
|
|
166
|
+
*/
|
|
167
|
+
declare function getRootTables(graph: DependencyGraph): string[];
|
|
168
|
+
/**
|
|
169
|
+
* Get tables that have no dependents (leaf tables)
|
|
170
|
+
* These are typically transaction/event tables that reference other tables
|
|
171
|
+
*/
|
|
172
|
+
declare function getLeafTables(graph: DependencyGraph): string[];
|
|
173
|
+
/**
|
|
174
|
+
* Format the dependency graph as a human-readable string
|
|
175
|
+
*/
|
|
176
|
+
declare function formatDependencyGraph(graph: DependencyGraph): string;
|
|
177
|
+
|
|
102
178
|
/**
|
|
103
179
|
* PowerSync schema generator
|
|
104
180
|
*
|
|
@@ -118,6 +194,10 @@ interface GenerateResult {
|
|
|
118
194
|
warnings: string[];
|
|
119
195
|
/** Generated output (included when dryRun is true) */
|
|
120
196
|
output?: string;
|
|
197
|
+
/** Number of indexes generated */
|
|
198
|
+
indexesGenerated?: number;
|
|
199
|
+
/** FK dependency graph (when detected) */
|
|
200
|
+
dependencyGraph?: DependencyGraph;
|
|
121
201
|
}
|
|
122
202
|
/**
|
|
123
203
|
* Map TypeScript types to PowerSync column types
|
|
@@ -140,6 +220,20 @@ declare function generateSchema(config: GeneratorConfig, options?: {
|
|
|
140
220
|
* Output templates for PowerSync schema generation
|
|
141
221
|
*/
|
|
142
222
|
|
|
223
|
+
interface IndexDefinition {
|
|
224
|
+
name: string;
|
|
225
|
+
columns: string[];
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Generate index definitions for a table based on column patterns
|
|
229
|
+
*
|
|
230
|
+
* @param tableName - The table name (PascalCase)
|
|
231
|
+
* @param columns - Array of column names in the table
|
|
232
|
+
* @param indexPatterns - Regex patterns to match against column names
|
|
233
|
+
* @param additionalColumns - Specific column names to always index
|
|
234
|
+
* @returns Array of index definitions
|
|
235
|
+
*/
|
|
236
|
+
declare function generateIndexDefinitions(tableName: string, columns: string[], indexPatterns: string[], additionalColumns?: string[]): IndexDefinition[];
|
|
143
237
|
/**
|
|
144
238
|
* File header template
|
|
145
239
|
*/
|
|
@@ -147,7 +241,7 @@ declare function generateHeader(typesPath: string): string;
|
|
|
147
241
|
/**
|
|
148
242
|
* Generate the table definition for a parsed table
|
|
149
243
|
*/
|
|
150
|
-
declare function generateTableDefinition(table: ParsedTable, columnDefs: string[]): string;
|
|
244
|
+
declare function generateTableDefinition(table: ParsedTable, columnDefs: string[], indexes?: IndexDefinition[]): string;
|
|
151
245
|
/**
|
|
152
246
|
* Generate the schema export section
|
|
153
247
|
*/
|
|
@@ -165,4 +259,4 @@ declare function generateFKUtility(): string;
|
|
|
165
259
|
*/
|
|
166
260
|
declare function generateOutputFile(tables: ParsedTable[], tableDefs: string[], schemas: string[], typesPath: string): string;
|
|
167
261
|
|
|
168
|
-
export { type ColumnInfo, type ColumnMapping, DEFAULT_DECIMAL_PATTERNS, DEFAULT_SKIP_COLUMNS, type GenerateResult, type GeneratorConfig, type ParseOptions, type ParsedTable, type TableConfig, defineConfig, extractTableDef, generateColumnDefs, generateFKUtility, generateHeader, generateOutputFile, generateSchema, generateSchemaExport, generateSchemaMapping, generateTableDefinition, getAvailableSchemas, getTablesInSchema, mapTypeToPowerSync, parseRowType, parseTypesFile };
|
|
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 };
|
package/dist/generator/index.js
CHANGED
|
@@ -9,6 +9,14 @@ var DEFAULT_SKIP_COLUMNS = [
|
|
|
9
9
|
"legacyId"
|
|
10
10
|
];
|
|
11
11
|
var DEFAULT_DECIMAL_PATTERNS = ["hours", "watts", "voltage", "rate", "amount", "price", "cost", "total"];
|
|
12
|
+
var DEFAULT_INDEX_PATTERNS = [
|
|
13
|
+
"Id$",
|
|
14
|
+
// FK columns ending in Id (e.g., projectId, userId)
|
|
15
|
+
"^createdAt$",
|
|
16
|
+
"^updatedAt$",
|
|
17
|
+
"^status$",
|
|
18
|
+
"^type$"
|
|
19
|
+
];
|
|
12
20
|
|
|
13
21
|
// src/generator/generator.ts
|
|
14
22
|
import * as fs from "fs";
|
|
@@ -21,44 +29,72 @@ function parseRowType(tableContent, options) {
|
|
|
21
29
|
if (!rowMatch) return columns;
|
|
22
30
|
const rowContent = rowMatch[1];
|
|
23
31
|
const includePrimaryKey = options.syncPrimaryKey ?? options.includeId ?? false;
|
|
32
|
+
const onlyColumnsSet = options.onlyColumns ? new Set(options.onlyColumns) : null;
|
|
24
33
|
const columnRegex = /(\w+)\??:\s*([^,\n]+)/g;
|
|
25
34
|
let match;
|
|
26
35
|
while ((match = columnRegex.exec(rowContent)) !== null) {
|
|
27
36
|
const [, columnName, columnType] = match;
|
|
28
|
-
|
|
29
|
-
if (
|
|
37
|
+
let shouldInclude;
|
|
38
|
+
if (onlyColumnsSet) {
|
|
39
|
+
shouldInclude = onlyColumnsSet.has(columnName) || includePrimaryKey && columnName === "id";
|
|
40
|
+
} else {
|
|
41
|
+
const isSkipped = options.skipColumns.has(columnName);
|
|
42
|
+
shouldInclude = !isSkipped || includePrimaryKey && columnName === "id";
|
|
43
|
+
}
|
|
44
|
+
if (shouldInclude) {
|
|
30
45
|
columns.set(columnName, columnType.trim());
|
|
31
46
|
}
|
|
32
47
|
}
|
|
33
48
|
return columns;
|
|
34
49
|
}
|
|
50
|
+
function escapeRegex(str) {
|
|
51
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
52
|
+
}
|
|
35
53
|
function extractTableDef(content, tableName, schema) {
|
|
36
|
-
const
|
|
54
|
+
const escapedSchema = escapeRegex(schema);
|
|
55
|
+
const escapedTableName = escapeRegex(tableName);
|
|
56
|
+
const schemaRegex = new RegExp(`${escapedSchema}:\\s*\\{[\\s\\S]*?Tables:\\s*\\{`, "g");
|
|
37
57
|
const schemaMatch = schemaRegex.exec(content);
|
|
38
58
|
if (!schemaMatch) return null;
|
|
39
59
|
const startIndex = schemaMatch.index;
|
|
40
|
-
const tableRegex = new RegExp(`(?<![A-Za-z])${tableName}:\\s*\\{[\\s\\S]*?Row:\\s*\\{[\\s\\S]*?\\}[\\s\\S]*?Relationships:\\s*\\[[^\\]]*\\]\\s*\\}`, "g");
|
|
41
60
|
const searchContent = content.slice(startIndex);
|
|
42
|
-
const
|
|
43
|
-
|
|
61
|
+
const tableStartRegex = new RegExp(`(?<![A-Za-z])${escapedTableName}:\\s*\\{`, "g");
|
|
62
|
+
const tableStartMatch = tableStartRegex.exec(searchContent);
|
|
63
|
+
if (!tableStartMatch) return null;
|
|
64
|
+
const tableStartIndex = tableStartMatch.index;
|
|
65
|
+
const openBraceIndex = tableStartMatch.index + tableStartMatch[0].length - 1;
|
|
66
|
+
let braceCount = 1;
|
|
67
|
+
let i = openBraceIndex + 1;
|
|
68
|
+
while (i < searchContent.length && braceCount > 0) {
|
|
69
|
+
const char = searchContent[i];
|
|
70
|
+
if (char === "{") braceCount++;
|
|
71
|
+
else if (char === "}") braceCount--;
|
|
72
|
+
i++;
|
|
73
|
+
}
|
|
74
|
+
if (braceCount !== 0) return null;
|
|
75
|
+
return searchContent.slice(tableStartIndex, i);
|
|
44
76
|
}
|
|
45
|
-
function parseTypesFile(content, tables,
|
|
77
|
+
function parseTypesFile(content, tables, globalSkipColumns) {
|
|
46
78
|
const parsedTables = [];
|
|
47
79
|
for (const tableConfig of tables) {
|
|
48
80
|
const {
|
|
49
81
|
name,
|
|
50
82
|
schema = "public",
|
|
51
83
|
syncPrimaryKey,
|
|
52
|
-
includeId
|
|
84
|
+
includeId,
|
|
85
|
+
skipColumns: tableSkipColumns,
|
|
86
|
+
onlyColumns
|
|
53
87
|
} = tableConfig;
|
|
54
88
|
const tableDef = extractTableDef(content, name, schema);
|
|
55
89
|
if (!tableDef) {
|
|
56
90
|
continue;
|
|
57
91
|
}
|
|
92
|
+
const mergedSkipColumns = /* @__PURE__ */ new Set([...globalSkipColumns, ...tableSkipColumns ?? []]);
|
|
58
93
|
const columns = parseRowType(tableDef, {
|
|
59
|
-
skipColumns,
|
|
94
|
+
skipColumns: mergedSkipColumns,
|
|
60
95
|
syncPrimaryKey,
|
|
61
|
-
includeId
|
|
96
|
+
includeId,
|
|
97
|
+
onlyColumns
|
|
62
98
|
});
|
|
63
99
|
if (columns.size > 0) {
|
|
64
100
|
parsedTables.push({
|
|
@@ -95,6 +131,45 @@ function getTablesInSchema(content, schema) {
|
|
|
95
131
|
}
|
|
96
132
|
|
|
97
133
|
// src/generator/templates.ts
|
|
134
|
+
function toSnakeCase(str) {
|
|
135
|
+
return str.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
|
|
136
|
+
}
|
|
137
|
+
function generateIndexDefinitions(tableName, columns, indexPatterns, additionalColumns = []) {
|
|
138
|
+
const indexes = [];
|
|
139
|
+
const snakeTableName = toSnakeCase(tableName);
|
|
140
|
+
const columnsToIndex = /* @__PURE__ */ new Set();
|
|
141
|
+
const compiledPatterns = [];
|
|
142
|
+
for (const pattern of indexPatterns) {
|
|
143
|
+
try {
|
|
144
|
+
compiledPatterns.push(new RegExp(pattern));
|
|
145
|
+
} catch {
|
|
146
|
+
console.warn(`Warning: Invalid index pattern regex "${pattern}" - skipping`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
for (const column of columns) {
|
|
150
|
+
if (column === "id") continue;
|
|
151
|
+
for (const regex of compiledPatterns) {
|
|
152
|
+
if (regex.test(column)) {
|
|
153
|
+
columnsToIndex.add(column);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const column of additionalColumns) {
|
|
159
|
+
if (columns.includes(column) && column !== "id") {
|
|
160
|
+
columnsToIndex.add(column);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
for (const column of columnsToIndex) {
|
|
164
|
+
const snakeColumn = toSnakeCase(column);
|
|
165
|
+
indexes.push({
|
|
166
|
+
name: `idx_${snakeTableName}_${snakeColumn}`,
|
|
167
|
+
columns: [column]
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
indexes.sort((a, b) => a.name.localeCompare(b.name));
|
|
171
|
+
return indexes;
|
|
172
|
+
}
|
|
98
173
|
function generateHeader(typesPath) {
|
|
99
174
|
return `/**
|
|
100
175
|
* PowerSync Schema Definition
|
|
@@ -108,11 +183,30 @@ function generateHeader(typesPath) {
|
|
|
108
183
|
import { column, Schema, Table } from "@powersync/react-native";
|
|
109
184
|
`;
|
|
110
185
|
}
|
|
111
|
-
function
|
|
186
|
+
function formatIndexes(indexes) {
|
|
187
|
+
if (indexes.length === 0) return "";
|
|
188
|
+
const indexLines = indexes.map((idx) => {
|
|
189
|
+
const columnsStr = idx.columns.map((c) => `'${c}'`).join(", ");
|
|
190
|
+
return ` { name: '${idx.name}', columns: [${columnsStr}] },`;
|
|
191
|
+
});
|
|
192
|
+
return `indexes: [
|
|
193
|
+
${indexLines.join("\n")}
|
|
194
|
+
]`;
|
|
195
|
+
}
|
|
196
|
+
function generateTableDefinition(table, columnDefs, indexes = []) {
|
|
112
197
|
if (columnDefs.length === 0) {
|
|
113
198
|
return `// ${table.name} - no syncable columns found`;
|
|
114
199
|
}
|
|
115
|
-
const
|
|
200
|
+
const optionsParts = [];
|
|
201
|
+
if (table.config.trackMetadata) {
|
|
202
|
+
optionsParts.push("trackMetadata: true");
|
|
203
|
+
}
|
|
204
|
+
if (indexes.length > 0) {
|
|
205
|
+
optionsParts.push(formatIndexes(indexes));
|
|
206
|
+
}
|
|
207
|
+
const optionsStr = optionsParts.length > 0 ? `, {
|
|
208
|
+
${optionsParts.join(",\n ")}
|
|
209
|
+
}` : "";
|
|
116
210
|
return `const ${table.name} = new Table({
|
|
117
211
|
${columnDefs.join("\n")}
|
|
118
212
|
}${optionsStr});`;
|
|
@@ -224,6 +318,133 @@ ${generateFKUtility()}
|
|
|
224
318
|
`;
|
|
225
319
|
}
|
|
226
320
|
|
|
321
|
+
// src/generator/fk-dependencies.ts
|
|
322
|
+
function fkColumnToTableName(columnName) {
|
|
323
|
+
if (!columnName.endsWith("Id") || columnName === "id") {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
const baseName = columnName.slice(0, -2);
|
|
327
|
+
return baseName.charAt(0).toUpperCase() + baseName.slice(1);
|
|
328
|
+
}
|
|
329
|
+
function detectFKDependencies(tables) {
|
|
330
|
+
const tableNames = new Set(tables.map((t) => t.name));
|
|
331
|
+
const dependencies = /* @__PURE__ */ new Map();
|
|
332
|
+
const dependents = /* @__PURE__ */ new Map();
|
|
333
|
+
const fkRelationships = [];
|
|
334
|
+
for (const table of tables) {
|
|
335
|
+
dependencies.set(table.name, []);
|
|
336
|
+
dependents.set(table.name, []);
|
|
337
|
+
}
|
|
338
|
+
for (const table of tables) {
|
|
339
|
+
for (const [columnName] of table.columns) {
|
|
340
|
+
const referencedTable = fkColumnToTableName(columnName);
|
|
341
|
+
if (referencedTable && tableNames.has(referencedTable)) {
|
|
342
|
+
const tableDeps = dependencies.get(table.name) || [];
|
|
343
|
+
if (!tableDeps.includes(referencedTable)) {
|
|
344
|
+
tableDeps.push(referencedTable);
|
|
345
|
+
dependencies.set(table.name, tableDeps);
|
|
346
|
+
}
|
|
347
|
+
const refDeps = dependents.get(referencedTable) || [];
|
|
348
|
+
if (!refDeps.includes(table.name)) {
|
|
349
|
+
refDeps.push(table.name);
|
|
350
|
+
dependents.set(referencedTable, refDeps);
|
|
351
|
+
}
|
|
352
|
+
fkRelationships.push({
|
|
353
|
+
table: table.name,
|
|
354
|
+
column: columnName,
|
|
355
|
+
referencedTable
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const uploadOrder = getUploadOrder({
|
|
361
|
+
dependencies
|
|
362
|
+
});
|
|
363
|
+
return {
|
|
364
|
+
dependencies,
|
|
365
|
+
dependents,
|
|
366
|
+
uploadOrder,
|
|
367
|
+
fkRelationships
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function getUploadOrder(graph) {
|
|
371
|
+
const result = [];
|
|
372
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
373
|
+
const adjList = /* @__PURE__ */ new Map();
|
|
374
|
+
for (const [table, deps] of graph.dependencies) {
|
|
375
|
+
inDegree.set(table, deps.length);
|
|
376
|
+
adjList.set(table, []);
|
|
377
|
+
}
|
|
378
|
+
for (const [table, deps] of graph.dependencies) {
|
|
379
|
+
for (const dep of deps) {
|
|
380
|
+
const list = adjList.get(dep) || [];
|
|
381
|
+
list.push(table);
|
|
382
|
+
adjList.set(dep, list);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const queue = [];
|
|
386
|
+
for (const [table, degree] of inDegree) {
|
|
387
|
+
if (degree === 0) {
|
|
388
|
+
queue.push(table);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
queue.sort();
|
|
392
|
+
while (queue.length > 0) {
|
|
393
|
+
const table = queue.shift();
|
|
394
|
+
result.push(table);
|
|
395
|
+
const dependentTables = adjList.get(table) || [];
|
|
396
|
+
for (const dependent of dependentTables) {
|
|
397
|
+
const newDegree = (inDegree.get(dependent) || 0) - 1;
|
|
398
|
+
inDegree.set(dependent, newDegree);
|
|
399
|
+
if (newDegree === 0) {
|
|
400
|
+
queue.push(dependent);
|
|
401
|
+
queue.sort();
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (result.length < graph.dependencies.size) {
|
|
406
|
+
const remaining = [...graph.dependencies.keys()].filter((t) => !result.includes(t)).sort();
|
|
407
|
+
result.push(...remaining);
|
|
408
|
+
}
|
|
409
|
+
return result;
|
|
410
|
+
}
|
|
411
|
+
function getRootTables(graph) {
|
|
412
|
+
const roots = [];
|
|
413
|
+
for (const [table, deps] of graph.dependencies) {
|
|
414
|
+
if (deps.length === 0) {
|
|
415
|
+
roots.push(table);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return roots.sort();
|
|
419
|
+
}
|
|
420
|
+
function getLeafTables(graph) {
|
|
421
|
+
const leaves = [];
|
|
422
|
+
for (const [table, deps] of graph.dependents) {
|
|
423
|
+
if (deps.length === 0) {
|
|
424
|
+
leaves.push(table);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return leaves.sort();
|
|
428
|
+
}
|
|
429
|
+
function formatDependencyGraph(graph) {
|
|
430
|
+
const lines = [];
|
|
431
|
+
lines.push("FK Dependencies:");
|
|
432
|
+
for (const fk of graph.fkRelationships) {
|
|
433
|
+
lines.push(` ${fk.table}.${fk.column} -> ${fk.referencedTable}`);
|
|
434
|
+
}
|
|
435
|
+
if (graph.fkRelationships.length === 0) {
|
|
436
|
+
lines.push(" (none detected)");
|
|
437
|
+
}
|
|
438
|
+
lines.push("");
|
|
439
|
+
lines.push("Upload Order (topological):");
|
|
440
|
+
graph.uploadOrder.forEach((table, i) => {
|
|
441
|
+
const deps = graph.dependencies.get(table) || [];
|
|
442
|
+
const depStr = deps.length > 0 ? ` (depends on: ${deps.join(", ")})` : " (root)";
|
|
443
|
+
lines.push(` ${i + 1}. ${table}${depStr}`);
|
|
444
|
+
});
|
|
445
|
+
return lines.join("\n");
|
|
446
|
+
}
|
|
447
|
+
|
|
227
448
|
// src/generator/generator.ts
|
|
228
449
|
function mapTypeToPowerSync(tsType, columnName, decimalPatterns) {
|
|
229
450
|
const cleanType = tsType.trim().replace(/\s*\|\s*null/g, "");
|
|
@@ -315,7 +536,26 @@ async function generateSchema(config, options) {
|
|
|
315
536
|
result.errors.push("No tables were parsed successfully");
|
|
316
537
|
return result;
|
|
317
538
|
}
|
|
539
|
+
const autoIndexes = config.autoIndexes ?? true;
|
|
540
|
+
const indexPatterns = autoIndexes ? [...DEFAULT_INDEX_PATTERNS, ...config.indexPatterns ?? []] : config.indexPatterns ?? [];
|
|
541
|
+
const indexColumns = config.indexColumns ?? [];
|
|
542
|
+
const dependencyGraph = detectFKDependencies(parsedTables);
|
|
543
|
+
result.dependencyGraph = dependencyGraph;
|
|
544
|
+
if (verbose && dependencyGraph.fkRelationships.length > 0) {
|
|
545
|
+
console.log(`
|
|
546
|
+
FK Dependencies detected: ${dependencyGraph.fkRelationships.length}`);
|
|
547
|
+
for (const fk of dependencyGraph.fkRelationships) {
|
|
548
|
+
console.log(` ${fk.table}.${fk.column} -> ${fk.referencedTable}`);
|
|
549
|
+
}
|
|
550
|
+
console.log(`
|
|
551
|
+
Recommended upload order:`);
|
|
552
|
+
dependencyGraph.uploadOrder.forEach((table, i) => {
|
|
553
|
+
console.log(` ${i + 1}. ${table}`);
|
|
554
|
+
});
|
|
555
|
+
console.log("");
|
|
556
|
+
}
|
|
318
557
|
const tableDefs = [];
|
|
558
|
+
let totalIndexes = 0;
|
|
319
559
|
for (const table of parsedTables) {
|
|
320
560
|
if (verbose) {
|
|
321
561
|
const syncPK = table.config.syncPrimaryKey || table.config.includeId;
|
|
@@ -326,8 +566,15 @@ async function generateSchema(config, options) {
|
|
|
326
566
|
result.warnings.push(`Table '${table.name}' has no syncable columns`);
|
|
327
567
|
continue;
|
|
328
568
|
}
|
|
329
|
-
|
|
569
|
+
const columnNames = [...table.columns.keys()];
|
|
570
|
+
const indexes = indexPatterns.length > 0 || indexColumns.length > 0 ? generateIndexDefinitions(table.name, columnNames, indexPatterns, indexColumns) : [];
|
|
571
|
+
totalIndexes += indexes.length;
|
|
572
|
+
if (verbose && indexes.length > 0) {
|
|
573
|
+
console.log(` Indexes: ${indexes.map((i) => i.name).join(", ")}`);
|
|
574
|
+
}
|
|
575
|
+
tableDefs.push(generateTableDefinition(table, columnDefs, indexes));
|
|
330
576
|
}
|
|
577
|
+
result.indexesGenerated = totalIndexes;
|
|
331
578
|
const schemas = [...new Set(config.tables.map((t) => t.schema ?? "public"))];
|
|
332
579
|
const relativePath = path.relative(cwd, typesPath);
|
|
333
580
|
const output = generateOutputFile(parsedTables.filter((t) => tableDefs.some((def) => def.includes(`const ${t.name} =`))), tableDefs, schemas, relativePath);
|
|
@@ -350,19 +597,27 @@ async function generateSchema(config, options) {
|
|
|
350
597
|
}
|
|
351
598
|
export {
|
|
352
599
|
DEFAULT_DECIMAL_PATTERNS,
|
|
600
|
+
DEFAULT_INDEX_PATTERNS,
|
|
353
601
|
DEFAULT_SKIP_COLUMNS,
|
|
354
602
|
defineConfig,
|
|
603
|
+
detectFKDependencies,
|
|
355
604
|
extractTableDef,
|
|
605
|
+
fkColumnToTableName,
|
|
606
|
+
formatDependencyGraph,
|
|
356
607
|
generateColumnDefs,
|
|
357
608
|
generateFKUtility,
|
|
358
609
|
generateHeader,
|
|
610
|
+
generateIndexDefinitions,
|
|
359
611
|
generateOutputFile,
|
|
360
612
|
generateSchema,
|
|
361
613
|
generateSchemaExport,
|
|
362
614
|
generateSchemaMapping,
|
|
363
615
|
generateTableDefinition,
|
|
364
616
|
getAvailableSchemas,
|
|
617
|
+
getLeafTables,
|
|
618
|
+
getRootTables,
|
|
365
619
|
getTablesInSchema,
|
|
620
|
+
getUploadOrder,
|
|
366
621
|
mapTypeToPowerSync,
|
|
367
622
|
parseRowType,
|
|
368
623
|
parseTypesFile
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/generator/config.ts","../../src/generator/generator.ts","../../src/generator/parser.ts","../../src/generator/templates.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}\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 * 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 } from './config.js';\nimport { parseTypesFile, type ParsedTable } from './parser.js';\nimport { generateTableDefinition, generateOutputFile } from './templates.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}\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 // Generate table definitions\n const tableDefs: string[] = [];\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 tableDefs.push(generateTableDefinition(table, columnDefs));\n }\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}\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 // 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 // Skip columns unless syncPrimaryKey is true for id column\n const shouldSkip = options.skipColumns.has(columnName) && !(includePrimaryKey && columnName === 'id');\n if (!shouldSkip) {\n columns.set(columnName, columnType.trim());\n }\n }\n return columns;\n}\n\n/**\n * Extract a table definition from the database.types.ts content\n */\nexport function extractTableDef(content: string, tableName: string, schema: string): string | null {\n // Find the schema section\n const schemaRegex = new RegExp(`${schema}:\\\\s*\\\\{[\\\\s\\\\S]*?Tables:\\\\s*\\\\{`, 'g');\n const schemaMatch = schemaRegex.exec(content);\n if (!schemaMatch) return null;\n const startIndex = schemaMatch.index;\n\n // Find this specific table within the schema\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 tableRegex = new RegExp(`(?<![A-Za-z])${tableName}:\\\\s*\\\\{[\\\\s\\\\S]*?Row:\\\\s*\\\\{[\\\\s\\\\S]*?\\\\}[\\\\s\\\\S]*?Relationships:\\\\s*\\\\[[^\\\\]]*\\\\]\\\\s*\\\\}`, 'g');\n\n // Search from the schema start\n const searchContent = content.slice(startIndex);\n const tableMatch = tableRegex.exec(searchContent);\n return tableMatch ? tableMatch[0] : null;\n}\n\n/**\n * Parse a database.types.ts file and extract specified tables\n */\nexport function parseTypesFile(content: string, tables: TableConfig[], skipColumns: 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 } = tableConfig;\n const tableDef = extractTableDef(content, name, schema);\n if (!tableDef) {\n continue;\n }\n const columns = parseRowType(tableDef, {\n skipColumns,\n syncPrimaryKey,\n includeId\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';\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 * Generate the table definition for a parsed table\n */\nexport function generateTableDefinition(table: ParsedTable, columnDefs: string[]): string {\n if (columnDefs.length === 0) {\n return `// ${table.name} - no syncable columns found`;\n }\n const optionsStr = table.config.trackMetadata ? ', { trackMetadata: true }' : '';\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}"],"mappings":";AA4CO,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;;;ACrDhH,YAAY,QAAQ;AACpB,YAAY,UAAU;;;AC0Bf,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,cAAc;AACpB,MAAI;AACJ,UAAQ,QAAQ,YAAY,KAAK,UAAU,OAAO,MAAM;AACtD,UAAM,CAAC,EAAE,YAAY,UAAU,IAAI;AAEnC,UAAM,aAAa,QAAQ,YAAY,IAAI,UAAU,KAAK,EAAE,qBAAqB,eAAe;AAChG,QAAI,CAAC,YAAY;AACf,cAAQ,IAAI,YAAY,WAAW,KAAK,CAAC;AAAA,IAC3C;AAAA,EACF;AACA,SAAO;AACT;AAKO,SAAS,gBAAgB,SAAiB,WAAmB,QAA+B;AAEjG,QAAM,cAAc,IAAI,OAAO,GAAG,MAAM,oCAAoC,GAAG;AAC/E,QAAM,cAAc,YAAY,KAAK,OAAO;AAC5C,MAAI,CAAC,YAAa,QAAO;AACzB,QAAM,aAAa,YAAY;AAK/B,QAAM,aAAa,IAAI,OAAO,gBAAgB,SAAS,8FAA8F,GAAG;AAGxJ,QAAM,gBAAgB,QAAQ,MAAM,UAAU;AAC9C,QAAM,aAAa,WAAW,KAAK,aAAa;AAChD,SAAO,aAAa,WAAW,CAAC,IAAI;AACtC;AAKO,SAAS,eAAe,SAAiB,QAAuB,aAAyC;AAC9G,QAAM,eAA8B,CAAC;AACrC,aAAW,eAAe,QAAQ;AAChC,UAAM;AAAA,MACJ;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA;AAAA,IACF,IAAI;AACJ,UAAM,WAAW,gBAAgB,SAAS,MAAM,MAAM;AACtD,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AACA,UAAM,UAAU,aAAa,UAAU;AAAA,MACrC;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;;;ACvIO,SAAS,eAAe,WAA2B;AACxD,SAAO;AAAA;AAAA;AAAA,yBAGgB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQlC;AAKO,SAAS,wBAAwB,OAAoB,YAA8B;AACxF,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,MAAM,MAAM,IAAI;AAAA,EACzB;AACA,QAAM,aAAa,MAAM,OAAO,gBAAgB,8BAA8B;AAC9E,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;;;AFnIO,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,YAAsB,CAAC;AAC7B,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;AACA,cAAU,KAAK,wBAAwB,OAAO,UAAU,CAAC;AAAA,EAC3D;AAGA,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}\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":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pol-studios/powersync",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "Enterprise PowerSync integration for offline-first applications",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -146,6 +146,7 @@
|
|
|
146
146
|
"dependencies": {
|
|
147
147
|
"@pol-studios/db": "workspace:*",
|
|
148
148
|
"commander": "^12.0.0",
|
|
149
|
+
"jiti": "^2.4.0",
|
|
149
150
|
"picocolors": "^1.0.0"
|
|
150
151
|
},
|
|
151
152
|
"peerDependencies": {
|