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