@supawatch/target-seed 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +57 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -130,6 +130,12 @@ export class SeedTarget {
|
|
|
130
130
|
];
|
|
131
131
|
const tables = snapshot.tables.filter((t) => t.kind === "table");
|
|
132
132
|
const byName = new Map(tables.map((t) => [`${t.schema}.${t.name}`, t]));
|
|
133
|
+
// A base-type literal for a constrained domain is a guess against a
|
|
134
|
+
// CHECK the snapshot does not parse. Never guess: let the database
|
|
135
|
+
// fill nullable or defaulted columns of constrained domains, and
|
|
136
|
+
// skip tables that require one. Unconstrained domains behave as
|
|
137
|
+
// their base type and seed normally.
|
|
138
|
+
const constrainedDomains = new Set(snapshot.domains.filter((d) => d.hasConstraints).map((d) => d.name));
|
|
133
139
|
// The literal a table's Nth row uses for its single-column primary
|
|
134
140
|
// key. Children reuse this for their FK cells, so uuid and numeric
|
|
135
141
|
// parents both reference correctly. Deterministic by construction.
|
|
@@ -155,9 +161,10 @@ export class SeedTarget {
|
|
|
155
161
|
for (const t of cyclic) {
|
|
156
162
|
lines.push(`-- skipped ${t.schema}.${t.name}: required foreign keys form a cycle`);
|
|
157
163
|
}
|
|
164
|
+
// Pass 1: plan each table's insertable columns and its own skip
|
|
165
|
+
// reasons, without emitting yet.
|
|
166
|
+
const plans = [];
|
|
158
167
|
for (const table of ordered) {
|
|
159
|
-
const q = (s) => '"' + s.replace(/"/g, '""') + '"';
|
|
160
|
-
const ident = `${q(table.schema)}.${q(table.name)}`;
|
|
161
168
|
const pk = table.primaryKey.length === 1 ? table.primaryKey[0] : null;
|
|
162
169
|
const skipReasons = [];
|
|
163
170
|
const cols = [];
|
|
@@ -175,6 +182,15 @@ export class SeedTarget {
|
|
|
175
182
|
}
|
|
176
183
|
continue;
|
|
177
184
|
}
|
|
185
|
+
if (constrainedDomains.has(col.pgTypeName)) {
|
|
186
|
+
if (col.name === pk) {
|
|
187
|
+
skipReasons.push(`primary key ${col.name} has a constrained domain type`);
|
|
188
|
+
}
|
|
189
|
+
else if (!col.nullable && !col.hasDefault) {
|
|
190
|
+
skipReasons.push(`constrained domain type on ${col.name}`);
|
|
191
|
+
}
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
178
194
|
if (col.name === pk) {
|
|
179
195
|
cols.push(col);
|
|
180
196
|
continue;
|
|
@@ -192,6 +208,45 @@ export class SeedTarget {
|
|
|
192
208
|
}
|
|
193
209
|
cols.push(col);
|
|
194
210
|
}
|
|
211
|
+
plans.push({ table, cols, skipReasons });
|
|
212
|
+
}
|
|
213
|
+
// A required single-column FK into a table that is not seeded (its
|
|
214
|
+
// own reasons, a cycle, or outside the snapshot) makes the child
|
|
215
|
+
// unseedable too; propagate to a fixpoint so seed.sql always
|
|
216
|
+
// applies.
|
|
217
|
+
const skipped = new Set(cyclic.map((t) => `${t.schema}.${t.name}`));
|
|
218
|
+
for (const p of plans) {
|
|
219
|
+
if (p.skipReasons.length > 0)
|
|
220
|
+
skipped.add(`${p.table.schema}.${p.table.name}`);
|
|
221
|
+
}
|
|
222
|
+
let changed = true;
|
|
223
|
+
while (changed) {
|
|
224
|
+
changed = false;
|
|
225
|
+
for (const p of plans) {
|
|
226
|
+
const key = `${p.table.schema}.${p.table.name}`;
|
|
227
|
+
if (skipped.has(key))
|
|
228
|
+
continue;
|
|
229
|
+
for (const fk of p.table.foreignKeys) {
|
|
230
|
+
if (f_multi(fk))
|
|
231
|
+
continue;
|
|
232
|
+
const col = p.table.columns.find((c) => fk.columns.includes(c.name));
|
|
233
|
+
if (!col || col.nullable || col.hasDefault)
|
|
234
|
+
continue;
|
|
235
|
+
const parentKey = `${fk.referencedSchema}.${fk.referencedTable}`;
|
|
236
|
+
if (skipped.has(parentKey) || !byName.has(parentKey)) {
|
|
237
|
+
p.skipReasons.push(`required foreign key ${col.name} references ${parentKey}, which is not seeded`);
|
|
238
|
+
skipped.add(key);
|
|
239
|
+
changed = true;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// Pass 2: emit.
|
|
246
|
+
for (const { table, cols, skipReasons } of plans) {
|
|
247
|
+
const q = (s) => '"' + s.replace(/"/g, '""') + '"';
|
|
248
|
+
const ident = `${q(table.schema)}.${q(table.name)}`;
|
|
249
|
+
const pk = table.primaryKey.length === 1 ? table.primaryKey[0] : null;
|
|
195
250
|
if (skipReasons.length > 0) {
|
|
196
251
|
lines.push(`-- skipped ${table.schema}.${table.name}: ${skipReasons.join("; ")}`);
|
|
197
252
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supawatch/target-seed",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Deterministic FK-aware seed.sql generated from live Postgres by supawatch: topological order, identity overriding, sequence resync.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"supabase",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"dist"
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@supawatch/core": "0.
|
|
37
|
+
"@supawatch/core": "0.7.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^22.20.1",
|