@umec/core 0.1.0-alpha.8 → 0.1.0-alpha.9

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.
Files changed (38) hide show
  1. package/dist/catalog-5BPLMAW2.js +15 -0
  2. package/dist/catalog-5BPLMAW2.js.map +1 -0
  3. package/dist/chunk-CYKUAS3D.js +9 -0
  4. package/dist/chunk-CYKUAS3D.js.map +1 -0
  5. package/dist/chunk-DN3J2NQC.js +298 -0
  6. package/dist/chunk-DN3J2NQC.js.map +1 -0
  7. package/dist/chunk-PNICEHSO.js +255 -0
  8. package/dist/chunk-PNICEHSO.js.map +1 -0
  9. package/dist/{chunk-JWTXQAOL.js → chunk-RFEX32LI.js} +1 -1
  10. package/dist/chunk-RFEX32LI.js.map +1 -0
  11. package/dist/chunk-T2PO3YLZ.js +64 -0
  12. package/dist/chunk-T2PO3YLZ.js.map +1 -0
  13. package/dist/cli.js +816 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/db/index.d.ts +130 -0
  16. package/dist/db/index.js +81 -0
  17. package/dist/db/index.js.map +1 -0
  18. package/dist/index.d.ts +2 -0
  19. package/dist/index.js +12 -1
  20. package/dist/notify.d.ts +5 -7
  21. package/dist/notify.js +1 -1
  22. package/dist/orders-DRZvcL3s.d.ts +99 -0
  23. package/dist/schema-version-F-YYC1UG.d.ts +25 -0
  24. package/dist/sql-J4YBS7N7.js +7 -0
  25. package/dist/sql-J4YBS7N7.js.map +1 -0
  26. package/package.json +10 -2
  27. package/schema/frozen-checksums.json +11 -0
  28. package/schema/migrations/0001_processed_events.sql +4 -0
  29. package/schema/migrations/0002_orders.sql +19 -0
  30. package/schema/migrations/0003_inventory.sql +5 -0
  31. package/schema/migrations/0004_admin_audit_log.sql +15 -0
  32. package/schema/migrations/0005_orders_tracking.sql +3 -0
  33. package/schema/migrations/0006_products.sql +15 -0
  34. package/schema/migrations/0007_orders_extras.sql +1 -0
  35. package/schema/snapshot.json +433 -0
  36. package/schema/upgrades/types.ts +16 -0
  37. package/schema/upgrades/v2-to-v6.ts +205 -0
  38. package/dist/chunk-JWTXQAOL.js.map +0 -1
@@ -0,0 +1,15 @@
1
+ CREATE TABLE IF NOT EXISTS admin_audit_log (
2
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
3
+ entity_type TEXT NOT NULL,
4
+ entity_id TEXT NOT NULL,
5
+ action TEXT NOT NULL,
6
+ delta INTEGER,
7
+ reason TEXT,
8
+ admin_email TEXT,
9
+ actor TEXT NOT NULL DEFAULT 'human',
10
+ detail_json TEXT,
11
+ created_at INTEGER NOT NULL
12
+ );
13
+
14
+ CREATE INDEX IF NOT EXISTS idx_admin_audit_log_entity
15
+ ON admin_audit_log(entity_type, entity_id, created_at);
@@ -0,0 +1,3 @@
1
+ ALTER TABLE orders ADD COLUMN tracking_number TEXT;
2
+ ALTER TABLE orders ADD COLUMN carrier TEXT
3
+ CHECK (carrier IN ('yamato', 'sagawa', 'japanpost', 'other') OR carrier IS NULL);
@@ -0,0 +1,15 @@
1
+ CREATE TABLE IF NOT EXISTS products (
2
+ sku TEXT PRIMARY KEY,
3
+ name TEXT NOT NULL,
4
+ description TEXT,
5
+ price INTEGER NOT NULL CHECK (price > 0),
6
+ shipping_units INTEGER NOT NULL DEFAULT 1 CHECK (shipping_units > 0),
7
+ images_json TEXT,
8
+ gtin TEXT,
9
+ category TEXT,
10
+ active INTEGER NOT NULL DEFAULT 1 CHECK (active IN (0, 1)),
11
+ low_stock_threshold INTEGER,
12
+ created_at INTEGER NOT NULL,
13
+ updated_at INTEGER NOT NULL,
14
+ updated_by TEXT
15
+ );
@@ -0,0 +1 @@
1
+ ALTER TABLE orders ADD COLUMN extras_json TEXT;
@@ -0,0 +1,433 @@
1
+ {
2
+ "version": 7,
3
+ "tables": {
4
+ "processed_events": {
5
+ "name": "processed_events",
6
+ "sql": "CREATE TABLE processed_events (\n event_id TEXT PRIMARY KEY,\n processed_at INTEGER NOT NULL\n)",
7
+ "columns": [
8
+ {
9
+ "cid": 0,
10
+ "name": "event_id",
11
+ "type": "TEXT",
12
+ "notnull": 0,
13
+ "dflt_value": null,
14
+ "pk": 1
15
+ },
16
+ {
17
+ "cid": 1,
18
+ "name": "processed_at",
19
+ "type": "INTEGER",
20
+ "notnull": 1,
21
+ "dflt_value": null,
22
+ "pk": 0
23
+ }
24
+ ],
25
+ "indexes": [],
26
+ "foreignKeys": []
27
+ },
28
+ "orders": {
29
+ "name": "orders",
30
+ "sql": "CREATE TABLE orders (\n id TEXT PRIMARY KEY,\n email TEXT NOT NULL,\n name TEXT,\n phone TEXT,\n items_json TEXT NOT NULL,\n amount INTEGER NOT NULL,\n shipping INTEGER,\n address_json TEXT,\n payment_intent TEXT,\n custom_fields_json TEXT,\n status TEXT NOT NULL DEFAULT 'pending',\n run_id TEXT,\n email_status TEXT CHECK (email_status IN ('sent', 'failed')),\n created_at INTEGER NOT NULL,\n shipped_at INTEGER\n, tracking_number TEXT, carrier TEXT\n CHECK (carrier IN ('yamato', 'sagawa', 'japanpost', 'other') OR carrier IS NULL), extras_json TEXT)",
31
+ "columns": [
32
+ {
33
+ "cid": 0,
34
+ "name": "id",
35
+ "type": "TEXT",
36
+ "notnull": 0,
37
+ "dflt_value": null,
38
+ "pk": 1
39
+ },
40
+ {
41
+ "cid": 1,
42
+ "name": "email",
43
+ "type": "TEXT",
44
+ "notnull": 1,
45
+ "dflt_value": null,
46
+ "pk": 0
47
+ },
48
+ {
49
+ "cid": 2,
50
+ "name": "name",
51
+ "type": "TEXT",
52
+ "notnull": 0,
53
+ "dflt_value": null,
54
+ "pk": 0
55
+ },
56
+ {
57
+ "cid": 3,
58
+ "name": "phone",
59
+ "type": "TEXT",
60
+ "notnull": 0,
61
+ "dflt_value": null,
62
+ "pk": 0
63
+ },
64
+ {
65
+ "cid": 4,
66
+ "name": "items_json",
67
+ "type": "TEXT",
68
+ "notnull": 1,
69
+ "dflt_value": null,
70
+ "pk": 0
71
+ },
72
+ {
73
+ "cid": 5,
74
+ "name": "amount",
75
+ "type": "INTEGER",
76
+ "notnull": 1,
77
+ "dflt_value": null,
78
+ "pk": 0
79
+ },
80
+ {
81
+ "cid": 6,
82
+ "name": "shipping",
83
+ "type": "INTEGER",
84
+ "notnull": 0,
85
+ "dflt_value": null,
86
+ "pk": 0
87
+ },
88
+ {
89
+ "cid": 7,
90
+ "name": "address_json",
91
+ "type": "TEXT",
92
+ "notnull": 0,
93
+ "dflt_value": null,
94
+ "pk": 0
95
+ },
96
+ {
97
+ "cid": 8,
98
+ "name": "payment_intent",
99
+ "type": "TEXT",
100
+ "notnull": 0,
101
+ "dflt_value": null,
102
+ "pk": 0
103
+ },
104
+ {
105
+ "cid": 9,
106
+ "name": "custom_fields_json",
107
+ "type": "TEXT",
108
+ "notnull": 0,
109
+ "dflt_value": null,
110
+ "pk": 0
111
+ },
112
+ {
113
+ "cid": 10,
114
+ "name": "status",
115
+ "type": "TEXT",
116
+ "notnull": 1,
117
+ "dflt_value": "'pending'",
118
+ "pk": 0
119
+ },
120
+ {
121
+ "cid": 11,
122
+ "name": "run_id",
123
+ "type": "TEXT",
124
+ "notnull": 0,
125
+ "dflt_value": null,
126
+ "pk": 0
127
+ },
128
+ {
129
+ "cid": 12,
130
+ "name": "email_status",
131
+ "type": "TEXT",
132
+ "notnull": 0,
133
+ "dflt_value": null,
134
+ "pk": 0
135
+ },
136
+ {
137
+ "cid": 13,
138
+ "name": "created_at",
139
+ "type": "INTEGER",
140
+ "notnull": 1,
141
+ "dflt_value": null,
142
+ "pk": 0
143
+ },
144
+ {
145
+ "cid": 14,
146
+ "name": "shipped_at",
147
+ "type": "INTEGER",
148
+ "notnull": 0,
149
+ "dflt_value": null,
150
+ "pk": 0
151
+ },
152
+ {
153
+ "cid": 15,
154
+ "name": "tracking_number",
155
+ "type": "TEXT",
156
+ "notnull": 0,
157
+ "dflt_value": null,
158
+ "pk": 0
159
+ },
160
+ {
161
+ "cid": 16,
162
+ "name": "carrier",
163
+ "type": "TEXT",
164
+ "notnull": 0,
165
+ "dflt_value": null,
166
+ "pk": 0
167
+ },
168
+ {
169
+ "cid": 17,
170
+ "name": "extras_json",
171
+ "type": "TEXT",
172
+ "notnull": 0,
173
+ "dflt_value": null,
174
+ "pk": 0
175
+ }
176
+ ],
177
+ "indexes": [
178
+ {
179
+ "name": "orders_payment_intent_idx",
180
+ "unique": 0,
181
+ "origin": "c",
182
+ "columns": [
183
+ "payment_intent"
184
+ ]
185
+ }
186
+ ],
187
+ "foreignKeys": []
188
+ },
189
+ "inventory": {
190
+ "name": "inventory",
191
+ "sql": "CREATE TABLE inventory (\n sku TEXT PRIMARY KEY,\n stock INTEGER NOT NULL DEFAULT 0,\n updated_at INTEGER NOT NULL\n)",
192
+ "columns": [
193
+ {
194
+ "cid": 0,
195
+ "name": "sku",
196
+ "type": "TEXT",
197
+ "notnull": 0,
198
+ "dflt_value": null,
199
+ "pk": 1
200
+ },
201
+ {
202
+ "cid": 1,
203
+ "name": "stock",
204
+ "type": "INTEGER",
205
+ "notnull": 1,
206
+ "dflt_value": "0",
207
+ "pk": 0
208
+ },
209
+ {
210
+ "cid": 2,
211
+ "name": "updated_at",
212
+ "type": "INTEGER",
213
+ "notnull": 1,
214
+ "dflt_value": null,
215
+ "pk": 0
216
+ }
217
+ ],
218
+ "indexes": [],
219
+ "foreignKeys": []
220
+ },
221
+ "admin_audit_log": {
222
+ "name": "admin_audit_log",
223
+ "sql": "CREATE TABLE admin_audit_log (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n entity_type TEXT NOT NULL,\n entity_id TEXT NOT NULL,\n action TEXT NOT NULL,\n delta INTEGER,\n reason TEXT,\n admin_email TEXT,\n actor TEXT NOT NULL DEFAULT 'human',\n detail_json TEXT,\n created_at INTEGER NOT NULL\n)",
224
+ "columns": [
225
+ {
226
+ "cid": 0,
227
+ "name": "id",
228
+ "type": "INTEGER",
229
+ "notnull": 0,
230
+ "dflt_value": null,
231
+ "pk": 1
232
+ },
233
+ {
234
+ "cid": 1,
235
+ "name": "entity_type",
236
+ "type": "TEXT",
237
+ "notnull": 1,
238
+ "dflt_value": null,
239
+ "pk": 0
240
+ },
241
+ {
242
+ "cid": 2,
243
+ "name": "entity_id",
244
+ "type": "TEXT",
245
+ "notnull": 1,
246
+ "dflt_value": null,
247
+ "pk": 0
248
+ },
249
+ {
250
+ "cid": 3,
251
+ "name": "action",
252
+ "type": "TEXT",
253
+ "notnull": 1,
254
+ "dflt_value": null,
255
+ "pk": 0
256
+ },
257
+ {
258
+ "cid": 4,
259
+ "name": "delta",
260
+ "type": "INTEGER",
261
+ "notnull": 0,
262
+ "dflt_value": null,
263
+ "pk": 0
264
+ },
265
+ {
266
+ "cid": 5,
267
+ "name": "reason",
268
+ "type": "TEXT",
269
+ "notnull": 0,
270
+ "dflt_value": null,
271
+ "pk": 0
272
+ },
273
+ {
274
+ "cid": 6,
275
+ "name": "admin_email",
276
+ "type": "TEXT",
277
+ "notnull": 0,
278
+ "dflt_value": null,
279
+ "pk": 0
280
+ },
281
+ {
282
+ "cid": 7,
283
+ "name": "actor",
284
+ "type": "TEXT",
285
+ "notnull": 1,
286
+ "dflt_value": "'human'",
287
+ "pk": 0
288
+ },
289
+ {
290
+ "cid": 8,
291
+ "name": "detail_json",
292
+ "type": "TEXT",
293
+ "notnull": 0,
294
+ "dflt_value": null,
295
+ "pk": 0
296
+ },
297
+ {
298
+ "cid": 9,
299
+ "name": "created_at",
300
+ "type": "INTEGER",
301
+ "notnull": 1,
302
+ "dflt_value": null,
303
+ "pk": 0
304
+ }
305
+ ],
306
+ "indexes": [
307
+ {
308
+ "name": "idx_admin_audit_log_entity",
309
+ "unique": 0,
310
+ "origin": "c",
311
+ "columns": [
312
+ "entity_type",
313
+ "entity_id",
314
+ "created_at"
315
+ ]
316
+ }
317
+ ],
318
+ "foreignKeys": []
319
+ },
320
+ "products": {
321
+ "name": "products",
322
+ "sql": "CREATE TABLE products (\n sku TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n description TEXT,\n price INTEGER NOT NULL CHECK (price > 0),\n shipping_units INTEGER NOT NULL DEFAULT 1 CHECK (shipping_units > 0),\n images_json TEXT,\n gtin TEXT,\n category TEXT,\n active INTEGER NOT NULL DEFAULT 1 CHECK (active IN (0, 1)),\n low_stock_threshold INTEGER,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n updated_by TEXT\n)",
323
+ "columns": [
324
+ {
325
+ "cid": 0,
326
+ "name": "sku",
327
+ "type": "TEXT",
328
+ "notnull": 0,
329
+ "dflt_value": null,
330
+ "pk": 1
331
+ },
332
+ {
333
+ "cid": 1,
334
+ "name": "name",
335
+ "type": "TEXT",
336
+ "notnull": 1,
337
+ "dflt_value": null,
338
+ "pk": 0
339
+ },
340
+ {
341
+ "cid": 2,
342
+ "name": "description",
343
+ "type": "TEXT",
344
+ "notnull": 0,
345
+ "dflt_value": null,
346
+ "pk": 0
347
+ },
348
+ {
349
+ "cid": 3,
350
+ "name": "price",
351
+ "type": "INTEGER",
352
+ "notnull": 1,
353
+ "dflt_value": null,
354
+ "pk": 0
355
+ },
356
+ {
357
+ "cid": 4,
358
+ "name": "shipping_units",
359
+ "type": "INTEGER",
360
+ "notnull": 1,
361
+ "dflt_value": "1",
362
+ "pk": 0
363
+ },
364
+ {
365
+ "cid": 5,
366
+ "name": "images_json",
367
+ "type": "TEXT",
368
+ "notnull": 0,
369
+ "dflt_value": null,
370
+ "pk": 0
371
+ },
372
+ {
373
+ "cid": 6,
374
+ "name": "gtin",
375
+ "type": "TEXT",
376
+ "notnull": 0,
377
+ "dflt_value": null,
378
+ "pk": 0
379
+ },
380
+ {
381
+ "cid": 7,
382
+ "name": "category",
383
+ "type": "TEXT",
384
+ "notnull": 0,
385
+ "dflt_value": null,
386
+ "pk": 0
387
+ },
388
+ {
389
+ "cid": 8,
390
+ "name": "active",
391
+ "type": "INTEGER",
392
+ "notnull": 1,
393
+ "dflt_value": "1",
394
+ "pk": 0
395
+ },
396
+ {
397
+ "cid": 9,
398
+ "name": "low_stock_threshold",
399
+ "type": "INTEGER",
400
+ "notnull": 0,
401
+ "dflt_value": null,
402
+ "pk": 0
403
+ },
404
+ {
405
+ "cid": 10,
406
+ "name": "created_at",
407
+ "type": "INTEGER",
408
+ "notnull": 1,
409
+ "dflt_value": null,
410
+ "pk": 0
411
+ },
412
+ {
413
+ "cid": 11,
414
+ "name": "updated_at",
415
+ "type": "INTEGER",
416
+ "notnull": 1,
417
+ "dflt_value": null,
418
+ "pk": 0
419
+ },
420
+ {
421
+ "cid": 12,
422
+ "name": "updated_by",
423
+ "type": "TEXT",
424
+ "notnull": 0,
425
+ "dflt_value": null,
426
+ "pk": 0
427
+ }
428
+ ],
429
+ "indexes": [],
430
+ "foreignKeys": []
431
+ }
432
+ }
433
+ }
@@ -0,0 +1,16 @@
1
+ export type UpgradeExecutor = {
2
+ exec(sql: string): Promise<void>;
3
+ all<T>(sql: string, params?: unknown[]): Promise<T[]>;
4
+ first<T>(sql: string, params?: unknown[]): Promise<T | null>;
5
+ run(sql: string, params?: unknown[]): Promise<{ changes: number }>;
6
+ };
7
+
8
+ export type UpgradeModule = {
9
+ name: string;
10
+ fromVersion: number;
11
+ toVersion: number;
12
+ description: string;
13
+ replacesChecksums: readonly string[];
14
+ canApply(db: UpgradeExecutor): Promise<boolean>;
15
+ apply(db: UpgradeExecutor): Promise<void>;
16
+ };
@@ -0,0 +1,205 @@
1
+ import type { UpgradeExecutor, UpgradeModule } from "./types.js";
2
+
3
+ export const UPGRADE_PAGE_SIZE = 500;
4
+
5
+ export const LEGACY_0002_SQL = `CREATE TABLE IF NOT EXISTS orders (
6
+ event_id TEXT PRIMARY KEY NOT NULL,
7
+ session_id TEXT NOT NULL,
8
+ amount_total INTEGER NOT NULL,
9
+ currency TEXT NOT NULL,
10
+ customer_email TEXT NOT NULL,
11
+ run_id TEXT,
12
+ created_at INTEGER NOT NULL
13
+ );
14
+ `;
15
+
16
+ export const V6_ORDERS_DDL = `CREATE TABLE orders_new (
17
+ id TEXT PRIMARY KEY,
18
+ email TEXT NOT NULL,
19
+ name TEXT,
20
+ phone TEXT,
21
+ items_json TEXT NOT NULL,
22
+ amount INTEGER NOT NULL,
23
+ shipping INTEGER,
24
+ address_json TEXT,
25
+ payment_intent TEXT,
26
+ custom_fields_json TEXT,
27
+ status TEXT NOT NULL DEFAULT 'pending',
28
+ run_id TEXT,
29
+ email_status TEXT CHECK (email_status IN ('sent', 'failed')),
30
+ created_at INTEGER NOT NULL,
31
+ shipped_at INTEGER,
32
+ tracking_number TEXT,
33
+ carrier TEXT CHECK (carrier IN ('yamato', 'sagawa', 'japanpost', 'other') OR carrier IS NULL)
34
+ )`;
35
+
36
+ type LegacyOrderRow = {
37
+ event_id: string;
38
+ session_id?: string | null;
39
+ amount_total?: number | null;
40
+ amount?: number | null;
41
+ customer_email?: string | null;
42
+ email?: string | null;
43
+ name?: string | null;
44
+ phone?: string | null;
45
+ items_json?: string | null;
46
+ shipping?: number | null;
47
+ address_json?: string | null;
48
+ payment_intent?: string | null;
49
+ custom_fields_json?: string | null;
50
+ status?: string | null;
51
+ run_id?: string | null;
52
+ email_status?: string | null;
53
+ created_at: number;
54
+ shipped_at?: number | null;
55
+ tracking_number?: string | null;
56
+ carrier?: string | null;
57
+ };
58
+
59
+ type TableInfoRow = {
60
+ name: string;
61
+ pk: number;
62
+ };
63
+
64
+ function asString(value: unknown, fallback = ""): string {
65
+ return typeof value === "string" ? value : fallback;
66
+ }
67
+
68
+ function asNullableString(value: unknown): string | null {
69
+ return typeof value === "string" ? value : null;
70
+ }
71
+
72
+ function asNumber(value: unknown, fallback = 0): number {
73
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
74
+ }
75
+
76
+ function asNullableNumber(value: unknown): number | null {
77
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
78
+ }
79
+
80
+ export async function detectLegacyOrdersPk(db: UpgradeExecutor): Promise<boolean> {
81
+ const columns = await db.all<TableInfoRow>("PRAGMA table_info(orders)");
82
+ if (columns.length === 0) return false;
83
+ const eventId = columns.find((column) => column.name === "event_id");
84
+ return eventId != null && eventId.pk === 1;
85
+ }
86
+
87
+ export function mapLegacyOrder(row: LegacyOrderRow): {
88
+ id: string;
89
+ email: string;
90
+ name: string | null;
91
+ phone: string | null;
92
+ items_json: string;
93
+ amount: number;
94
+ shipping: number | null;
95
+ address_json: string | null;
96
+ payment_intent: string | null;
97
+ custom_fields_json: string | null;
98
+ status: string;
99
+ run_id: string | null;
100
+ email_status: string | null;
101
+ created_at: number;
102
+ shipped_at: number | null;
103
+ tracking_number: string | null;
104
+ carrier: string | null;
105
+ } {
106
+ const sessionId = asNullableString(row.session_id);
107
+ const eventId = asString(row.event_id);
108
+ return {
109
+ id: sessionId && sessionId.length > 0 ? sessionId : eventId,
110
+ email: asString(row.customer_email ?? row.email),
111
+ name: asNullableString(row.name),
112
+ phone: asNullableString(row.phone),
113
+ items_json: asNullableString(row.items_json) ?? "[]",
114
+ amount: asNumber(row.amount_total ?? row.amount),
115
+ shipping: asNullableNumber(row.shipping),
116
+ address_json: asNullableString(row.address_json),
117
+ payment_intent: asNullableString(row.payment_intent),
118
+ custom_fields_json: asNullableString(row.custom_fields_json),
119
+ status: asNullableString(row.status) ?? "pending",
120
+ run_id: asNullableString(row.run_id),
121
+ email_status: asNullableString(row.email_status),
122
+ created_at: asNumber(row.created_at),
123
+ shipped_at: asNullableNumber(row.shipped_at),
124
+ tracking_number: asNullableString(row.tracking_number),
125
+ carrier: asNullableString(row.carrier),
126
+ };
127
+ }
128
+
129
+ export async function applyV2toV6(db: UpgradeExecutor): Promise<void> {
130
+ if (!(await detectLegacyOrdersPk(db))) {
131
+ throw new Error("v2-to-v6: orders.event_id primary key not found; refuse to rebuild");
132
+ }
133
+
134
+ await db.exec("DROP TABLE IF EXISTS orders_new");
135
+ await db.exec(V6_ORDERS_DDL);
136
+
137
+ let cursor: string | null = null;
138
+ for (;;) {
139
+ const pageSql = cursor
140
+ ? "SELECT * FROM orders WHERE event_id > ? ORDER BY event_id LIMIT ?"
141
+ : "SELECT * FROM orders ORDER BY event_id LIMIT ?";
142
+ const pageParams = cursor ? [cursor, UPGRADE_PAGE_SIZE] : [UPGRADE_PAGE_SIZE];
143
+ const rows: LegacyOrderRow[] = await db.all<LegacyOrderRow>(pageSql, pageParams);
144
+ if (rows.length === 0) break;
145
+
146
+ for (const row of rows) {
147
+ const mapped = mapLegacyOrder(row);
148
+ await db.run(
149
+ `INSERT INTO orders_new (
150
+ id, email, name, phone, items_json, amount, shipping, address_json,
151
+ payment_intent, custom_fields_json, status, run_id, email_status,
152
+ created_at, shipped_at, tracking_number, carrier
153
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
154
+ [
155
+ mapped.id,
156
+ mapped.email,
157
+ mapped.name,
158
+ mapped.phone,
159
+ mapped.items_json,
160
+ mapped.amount,
161
+ mapped.shipping,
162
+ mapped.address_json,
163
+ mapped.payment_intent,
164
+ mapped.custom_fields_json,
165
+ mapped.status,
166
+ mapped.run_id,
167
+ mapped.email_status,
168
+ mapped.created_at,
169
+ mapped.shipped_at,
170
+ mapped.tracking_number,
171
+ mapped.carrier,
172
+ ],
173
+ );
174
+ }
175
+
176
+ const last: LegacyOrderRow | undefined = rows[rows.length - 1];
177
+ if (!last) break;
178
+ cursor = last.event_id;
179
+ if (rows.length < UPGRADE_PAGE_SIZE) break;
180
+ }
181
+
182
+ const oldCount = await db.first<{ n: number }>("SELECT COUNT(*) AS n FROM orders");
183
+ const newCount = await db.first<{ n: number }>("SELECT COUNT(*) AS n FROM orders_new");
184
+ const expected = oldCount?.n ?? -1;
185
+ const copied = newCount?.n ?? -2;
186
+ if (expected !== copied) {
187
+ throw new Error(
188
+ `v2-to-v6: COUNT mismatch orders=${expected} orders_new=${copied}. DROP skipped. Restore from Time Travel bookmark.`,
189
+ );
190
+ }
191
+
192
+ await db.exec("DROP TABLE orders");
193
+ await db.exec("ALTER TABLE orders_new RENAME TO orders");
194
+ await db.exec("CREATE INDEX IF NOT EXISTS orders_payment_intent_idx ON orders(payment_intent)");
195
+ }
196
+
197
+ export const upgradeV2toV6: UpgradeModule = {
198
+ name: "v2-to-v6",
199
+ fromVersion: 2,
200
+ toVersion: 6,
201
+ description: "Rebuild orders from event_id PK (v2) to id PK (v6)",
202
+ replacesChecksums: ["ca6ec5c885f715a3ae65a7101787d66750ed831370dc2ecde0636cfc3cf83e24"],
203
+ canApply: detectLegacyOrdersPk,
204
+ apply: applyV2toV6,
205
+ };