@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
+ import {
2
+ currentSchemaVersion,
3
+ listCoreMigrations,
4
+ loadSnapshotJson,
5
+ migrationsDir,
6
+ schemaDir
7
+ } from "./chunk-T2PO3YLZ.js";
8
+ export {
9
+ currentSchemaVersion,
10
+ listCoreMigrations,
11
+ loadSnapshotJson,
12
+ migrationsDir,
13
+ schemaDir
14
+ };
15
+ //# sourceMappingURL=catalog-5BPLMAW2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,9 @@
1
+ // src/db/sql.ts
2
+ function splitSqlStatements(sql) {
3
+ return sql.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
4
+ }
5
+
6
+ export {
7
+ splitSqlStatements
8
+ };
9
+ //# sourceMappingURL=chunk-CYKUAS3D.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/db/sql.ts"],"sourcesContent":["export function splitSqlStatements(sql: string): string[] {\n return sql\n .split(\";\")\n .map((statement) => statement.trim())\n .filter((statement) => statement.length > 0);\n}\n"],"mappings":";AAAO,SAAS,mBAAmB,KAAuB;AACxD,SAAO,IACJ,MAAM,GAAG,EACT,IAAI,CAAC,cAAc,UAAU,KAAK,CAAC,EACnC,OAAO,CAAC,cAAc,UAAU,SAAS,CAAC;AAC/C;","names":[]}
@@ -0,0 +1,298 @@
1
+ // src/db/audit.ts
2
+ async function writeAuditLog(db, entry, createdAt = Date.now()) {
3
+ await db.prepare(
4
+ `INSERT INTO admin_audit_log (
5
+ entity_type, entity_id, action, delta, reason, admin_email, actor, detail_json, created_at
6
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
7
+ ).bind(
8
+ entry.entityType,
9
+ entry.entityId,
10
+ entry.action,
11
+ entry.delta ?? null,
12
+ entry.reason ?? null,
13
+ entry.adminEmail ?? null,
14
+ entry.actor ?? "human",
15
+ entry.detail === void 0 ? null : JSON.stringify(entry.detail),
16
+ createdAt
17
+ ).run();
18
+ }
19
+ async function listAuditLog(db, options = {}) {
20
+ const limit = options.limit ?? 50;
21
+ const clauses = [];
22
+ const binds = [];
23
+ if (options.entityType) {
24
+ clauses.push("entity_type = ?");
25
+ binds.push(options.entityType);
26
+ }
27
+ if (options.entityId) {
28
+ clauses.push("entity_id = ?");
29
+ binds.push(options.entityId);
30
+ }
31
+ const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
32
+ const result = await db.prepare(
33
+ `SELECT id, entity_type, entity_id, action, delta, reason, admin_email, actor, detail_json, created_at
34
+ FROM admin_audit_log
35
+ ${where}
36
+ ORDER BY created_at DESC
37
+ LIMIT ?`
38
+ ).bind(...binds, limit).all();
39
+ return result.results ?? [];
40
+ }
41
+
42
+ // src/db/events.ts
43
+ async function claimEvent(db, eventId, now = Date.now()) {
44
+ const claim = await db.prepare("INSERT OR IGNORE INTO processed_events (event_id, processed_at) VALUES (?, ?)").bind(eventId, now).run();
45
+ return claim.meta.changes !== 0;
46
+ }
47
+
48
+ // src/db/extensions.ts
49
+ function parseExtras(schema, extrasJson) {
50
+ if (!extrasJson) return null;
51
+ const parsed = JSON.parse(extrasJson);
52
+ return schema.parse(parsed);
53
+ }
54
+ function serializeExtras(schema, value) {
55
+ return JSON.stringify(schema.parse(value));
56
+ }
57
+
58
+ // src/db/inventory.ts
59
+ var MAX_STOCK_DELTA = 1e4;
60
+ async function getStock(db, skus) {
61
+ if (skus.length === 0) return /* @__PURE__ */ new Map();
62
+ const placeholders = skus.map(() => "?").join(", ");
63
+ const result = await db.prepare(`SELECT sku, stock FROM inventory WHERE sku IN (${placeholders})`).bind(...skus).all();
64
+ return new Map((result.results ?? []).map((row) => [row.sku, row.stock]));
65
+ }
66
+ async function decrementStock(db, items) {
67
+ if (items.length === 0) return;
68
+ const updatedAt = Date.now();
69
+ for (const { sku, quantity } of items) {
70
+ const result = await db.prepare("UPDATE inventory SET stock = stock - ?, updated_at = ? WHERE sku = ? AND stock >= ?").bind(quantity, updatedAt, sku, quantity).run();
71
+ if (result.meta.changes === 0) {
72
+ console.error("[inventory] stock decrement skipped: insufficient stock", { sku, quantity });
73
+ }
74
+ }
75
+ }
76
+ async function restoreStock(db, items) {
77
+ if (items.length === 0) return;
78
+ const updatedAt = Date.now();
79
+ for (const { sku, quantity } of items) {
80
+ await db.prepare("UPDATE inventory SET stock = stock + ?, updated_at = ? WHERE sku = ?").bind(quantity, updatedAt, sku).run();
81
+ }
82
+ }
83
+ async function setStock(db, sku, stock, updatedAt = Date.now()) {
84
+ const result = await db.prepare("UPDATE inventory SET stock = ?, updated_at = ? WHERE sku = ?").bind(stock, updatedAt, sku).run();
85
+ return result.meta.changes === 0 ? "not_found" : "ok";
86
+ }
87
+ async function adjustStock(db, sku, delta) {
88
+ const existing = await db.prepare("SELECT stock FROM inventory WHERE sku = ?").bind(sku).first();
89
+ if (!existing) return "not_found";
90
+ const result = await db.prepare("UPDATE inventory SET stock = stock + ?, updated_at = ? WHERE sku = ? AND stock + ? >= 0").bind(delta, Date.now(), sku, delta).run();
91
+ if (result.meta.changes === 0) return "insufficient";
92
+ return "ok";
93
+ }
94
+
95
+ // src/db/orders.ts
96
+ function parseCheckoutItems(items) {
97
+ if (!items) return [];
98
+ try {
99
+ const parsed = JSON.parse(items);
100
+ if (!Array.isArray(parsed)) return [];
101
+ return parsed.filter((item) => {
102
+ if (typeof item !== "object" || item === null) return false;
103
+ const { sku, quantity } = item;
104
+ return typeof sku === "string" && typeof quantity === "number" && Number.isSafeInteger(quantity) && quantity > 0;
105
+ });
106
+ } catch {
107
+ return [];
108
+ }
109
+ }
110
+ async function recordOrder(db, order, extrasJson = null) {
111
+ await db.prepare(
112
+ `INSERT INTO orders (
113
+ id, email, name, phone, items_json, amount, shipping, address_json,
114
+ payment_intent, custom_fields_json, run_id, extras_json, created_at
115
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
116
+ ).bind(
117
+ order.id,
118
+ order.email,
119
+ order.name,
120
+ order.phone,
121
+ JSON.stringify(order.items),
122
+ order.amount,
123
+ order.shippingCost,
124
+ order.shippingAddress ? JSON.stringify(order.shippingAddress) : null,
125
+ order.paymentIntent,
126
+ order.customFields ? JSON.stringify(order.customFields) : null,
127
+ order.metadata?.run_id ?? null,
128
+ extrasJson,
129
+ order.createdAt
130
+ ).run();
131
+ }
132
+ async function recordRefund(db, paymentIntentId) {
133
+ await db.prepare("UPDATE orders SET status = ? WHERE payment_intent = ?").bind("refunded", paymentIntentId).run();
134
+ const orderRow = await db.prepare("SELECT items_json FROM orders WHERE payment_intent = ?").bind(paymentIntentId).first();
135
+ if (!orderRow) return null;
136
+ return { items: parseCheckoutItems(orderRow.items_json), offerSlug: null };
137
+ }
138
+ async function recordExpiry(db, sessionId) {
139
+ await db.prepare("UPDATE orders SET status = ? WHERE id = ?").bind("cancelled", sessionId).run();
140
+ const orderRow = await db.prepare("SELECT items_json FROM orders WHERE id = ?").bind(sessionId).first();
141
+ if (!orderRow) return null;
142
+ return { items: parseCheckoutItems(orderRow.items_json), offerSlug: null };
143
+ }
144
+ async function recordEmailResult(db, sessionId, status) {
145
+ await db.prepare("UPDATE orders SET email_status = ? WHERE id = ?").bind(status, sessionId).run();
146
+ }
147
+ async function listUnshipped(db, cutoff) {
148
+ const result = await db.prepare(
149
+ `SELECT id, name, items_json, amount, created_at
150
+ FROM orders
151
+ WHERE status = ? AND created_at < ?
152
+ ORDER BY created_at ASC`
153
+ ).bind("pending", cutoff).all();
154
+ return result.results ?? [];
155
+ }
156
+ async function getOrder(db, id) {
157
+ const row = await db.prepare("SELECT id, status, tracking_number, carrier FROM orders WHERE id = ?").bind(id).first();
158
+ return row ?? null;
159
+ }
160
+ async function markShipped(db, id, options = {}) {
161
+ const result = await db.prepare(
162
+ `UPDATE orders
163
+ SET status = ?, shipped_at = ?, tracking_number = COALESCE(?, tracking_number), carrier = COALESCE(?, carrier)
164
+ WHERE id = ? AND status = ?`
165
+ ).bind("shipped", options.shippedAt ?? Date.now(), options.trackingNumber ?? null, options.carrier ?? null, id, "pending").run();
166
+ return result.meta.changes === 0 ? "not_pending" : "ok";
167
+ }
168
+ async function updateTracking(db, id, trackingNumber, carrier) {
169
+ const result = await db.prepare("UPDATE orders SET tracking_number = ?, carrier = ? WHERE id = ?").bind(trackingNumber, carrier, id).run();
170
+ return result.meta.changes === 0 ? "not_found" : "ok";
171
+ }
172
+
173
+ // src/db/products.ts
174
+ async function listProductRows(db, options = {}) {
175
+ const sql = options.includeInactive ? `SELECT sku, name, description, price, shipping_units, images_json, gtin, category,
176
+ active, low_stock_threshold, created_at, updated_at, updated_by
177
+ FROM products
178
+ ORDER BY sku ASC` : `SELECT sku, name, description, price, shipping_units, images_json, gtin, category,
179
+ active, low_stock_threshold, created_at, updated_at, updated_by
180
+ FROM products
181
+ WHERE active = 1
182
+ ORDER BY sku ASC`;
183
+ const result = await db.prepare(sql).all();
184
+ return result.results ?? [];
185
+ }
186
+ async function getProductRow(db, sku) {
187
+ const row = await db.prepare(
188
+ `SELECT sku, name, description, price, shipping_units, images_json, gtin, category,
189
+ active, low_stock_threshold, created_at, updated_at, updated_by
190
+ FROM products
191
+ WHERE sku = ?`
192
+ ).bind(sku).first();
193
+ return row ?? null;
194
+ }
195
+ async function createProduct(db, sku, fields, updatedBy, now = Date.now()) {
196
+ const existing = await db.prepare("SELECT sku FROM products WHERE sku = ?").bind(sku).first();
197
+ if (existing) return "conflict";
198
+ const imagesJson = fields.images.length > 0 ? JSON.stringify(fields.images) : null;
199
+ await db.batch([
200
+ db.prepare(
201
+ `INSERT INTO products (
202
+ sku, name, description, price, shipping_units, images_json, gtin, category,
203
+ active, low_stock_threshold, created_at, updated_at, updated_by
204
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
205
+ ).bind(
206
+ sku,
207
+ fields.name,
208
+ fields.description,
209
+ fields.price,
210
+ fields.shippingUnits,
211
+ imagesJson,
212
+ fields.gtin,
213
+ fields.category,
214
+ fields.active,
215
+ fields.lowStockThreshold,
216
+ now,
217
+ now,
218
+ updatedBy
219
+ ),
220
+ db.prepare("INSERT OR IGNORE INTO inventory (sku, stock, updated_at) VALUES (?, ?, ?)").bind(sku, 0, now)
221
+ ]);
222
+ return "ok";
223
+ }
224
+ async function updateProduct(db, sku, fields, updatedBy, now = Date.now()) {
225
+ const existing = await getProductRow(db, sku);
226
+ if (!existing) return { result: "not_found", before: null, after: null };
227
+ const next = {
228
+ name: fields.name ?? existing.name,
229
+ description: fields.description !== void 0 ? fields.description : existing.description,
230
+ price: fields.price ?? existing.price,
231
+ shippingUnits: fields.shippingUnits ?? existing.shipping_units,
232
+ imagesJson: fields.images !== void 0 ? fields.images.length > 0 ? JSON.stringify(fields.images) : null : existing.images_json,
233
+ gtin: fields.gtin !== void 0 ? fields.gtin : existing.gtin,
234
+ category: fields.category !== void 0 ? fields.category : existing.category,
235
+ active: fields.active ?? existing.active,
236
+ lowStockThreshold: fields.lowStockThreshold !== void 0 ? fields.lowStockThreshold : existing.low_stock_threshold
237
+ };
238
+ await db.prepare(
239
+ `UPDATE products
240
+ SET name = ?, description = ?, price = ?, shipping_units = ?, images_json = ?,
241
+ gtin = ?, category = ?, active = ?, low_stock_threshold = ?, updated_at = ?, updated_by = ?
242
+ WHERE sku = ?`
243
+ ).bind(
244
+ next.name,
245
+ next.description,
246
+ next.price,
247
+ next.shippingUnits,
248
+ next.imagesJson,
249
+ next.gtin,
250
+ next.category,
251
+ next.active,
252
+ next.lowStockThreshold,
253
+ now,
254
+ updatedBy,
255
+ sku
256
+ ).run();
257
+ const after = await getProductRow(db, sku);
258
+ return { result: "ok", before: existing, after };
259
+ }
260
+ async function discontinueProduct(db, sku, updatedBy, now = Date.now()) {
261
+ const existing = await db.prepare("SELECT sku, active FROM products WHERE sku = ?").bind(sku).first();
262
+ if (!existing) return "not_found";
263
+ if (existing.active === 0) return "already_inactive";
264
+ await db.prepare("UPDATE products SET active = 0, updated_at = ?, updated_by = ? WHERE sku = ?").bind(now, updatedBy, sku).run();
265
+ return "ok";
266
+ }
267
+
268
+ // src/db/types.generated.ts
269
+ var SNAPSHOT_SCHEMA_VERSION = 7;
270
+
271
+ export {
272
+ writeAuditLog,
273
+ listAuditLog,
274
+ claimEvent,
275
+ parseExtras,
276
+ serializeExtras,
277
+ MAX_STOCK_DELTA,
278
+ getStock,
279
+ decrementStock,
280
+ restoreStock,
281
+ setStock,
282
+ adjustStock,
283
+ recordOrder,
284
+ recordRefund,
285
+ recordExpiry,
286
+ recordEmailResult,
287
+ listUnshipped,
288
+ getOrder,
289
+ markShipped,
290
+ updateTracking,
291
+ listProductRows,
292
+ getProductRow,
293
+ createProduct,
294
+ updateProduct,
295
+ discontinueProduct,
296
+ SNAPSHOT_SCHEMA_VERSION
297
+ };
298
+ //# sourceMappingURL=chunk-DN3J2NQC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/db/audit.ts","../src/db/events.ts","../src/db/extensions.ts","../src/db/inventory.ts","../src/db/orders.ts","../src/db/products.ts","../src/db/types.generated.ts"],"sourcesContent":["import type { ShopDatabase } from \"./d1.js\";\nimport type { AdminAuditLogRow } from \"./types.generated.js\";\n\nexport type AuditEntityType = \"inventory\" | \"product\" | \"order\";\nexport type AuditActor = \"human\" | \"system\" | \"agent\";\n\nexport type AdminAuditEntry = {\n entityType: AuditEntityType;\n entityId: string;\n action: string;\n delta?: number | null;\n reason?: string | null;\n adminEmail?: string | null;\n actor?: AuditActor;\n detail?: unknown;\n};\n\nexport async function writeAuditLog(\n db: ShopDatabase,\n entry: AdminAuditEntry,\n createdAt = Date.now(),\n): Promise<void> {\n await db\n .prepare(\n `INSERT INTO admin_audit_log (\n entity_type, entity_id, action, delta, reason, admin_email, actor, detail_json, created_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(\n entry.entityType,\n entry.entityId,\n entry.action,\n entry.delta ?? null,\n entry.reason ?? null,\n entry.adminEmail ?? null,\n entry.actor ?? \"human\",\n entry.detail === undefined ? null : JSON.stringify(entry.detail),\n createdAt,\n )\n .run();\n}\n\nexport async function listAuditLog(\n db: ShopDatabase,\n options: {\n entityType?: AuditEntityType;\n entityId?: string;\n limit?: number;\n } = {},\n): Promise<AdminAuditLogRow[]> {\n const limit = options.limit ?? 50;\n const clauses: string[] = [];\n const binds: unknown[] = [];\n\n if (options.entityType) {\n clauses.push(\"entity_type = ?\");\n binds.push(options.entityType);\n }\n if (options.entityId) {\n clauses.push(\"entity_id = ?\");\n binds.push(options.entityId);\n }\n\n const where = clauses.length > 0 ? `WHERE ${clauses.join(\" AND \")}` : \"\";\n const result = await db\n .prepare(\n `SELECT id, entity_type, entity_id, action, delta, reason, admin_email, actor, detail_json, created_at\n FROM admin_audit_log\n ${where}\n ORDER BY created_at DESC\n LIMIT ?`,\n )\n .bind(...binds, limit)\n .all<AdminAuditLogRow>();\n\n return result.results ?? [];\n}\n","import type { ShopDatabase } from \"./d1.js\";\n\nexport async function claimEvent(db: ShopDatabase, eventId: string, now = Date.now()): Promise<boolean> {\n const claim = await db\n .prepare(\"INSERT OR IGNORE INTO processed_events (event_id, processed_at) VALUES (?, ?)\")\n .bind(eventId, now)\n .run();\n return claim.meta.changes !== 0;\n}\n","import type { z } from \"zod\";\n\nexport function parseExtras<S extends z.ZodType>(schema: S, extrasJson: string | null): z.infer<S> | null {\n if (!extrasJson) return null;\n const parsed: unknown = JSON.parse(extrasJson);\n return schema.parse(parsed);\n}\n\nexport function serializeExtras<S extends z.ZodType>(schema: S, value: unknown): string {\n return JSON.stringify(schema.parse(value));\n}\n","import type { ShopDatabase } from \"./d1.js\";\n\nexport type InventoryItem = {\n sku: string;\n quantity: number;\n};\n\nexport const MAX_STOCK_DELTA = 10000;\n\nexport type AdjustStockResult = \"ok\" | \"not_found\" | \"insufficient\";\nexport type SetStockResult = \"ok\" | \"not_found\";\n\nexport async function getStock(db: ShopDatabase, skus: string[]): Promise<Map<string, number>> {\n if (skus.length === 0) return new Map();\n\n const placeholders = skus.map(() => \"?\").join(\", \");\n const result = await db\n .prepare(`SELECT sku, stock FROM inventory WHERE sku IN (${placeholders})`)\n .bind(...skus)\n .all<{ sku: string; stock: number }>();\n\n return new Map((result.results ?? []).map((row) => [row.sku, row.stock]));\n}\n\nexport async function decrementStock(db: ShopDatabase, items: InventoryItem[]): Promise<void> {\n if (items.length === 0) return;\n\n const updatedAt = Date.now();\n for (const { sku, quantity } of items) {\n const result = await db\n .prepare(\"UPDATE inventory SET stock = stock - ?, updated_at = ? WHERE sku = ? AND stock >= ?\")\n .bind(quantity, updatedAt, sku, quantity)\n .run();\n\n if (result.meta.changes === 0) {\n console.error(\"[inventory] stock decrement skipped: insufficient stock\", { sku, quantity });\n }\n }\n}\n\nexport async function restoreStock(db: ShopDatabase, items: InventoryItem[]): Promise<void> {\n if (items.length === 0) return;\n\n const updatedAt = Date.now();\n for (const { sku, quantity } of items) {\n await db\n .prepare(\"UPDATE inventory SET stock = stock + ?, updated_at = ? WHERE sku = ?\")\n .bind(quantity, updatedAt, sku)\n .run();\n }\n}\n\nexport async function setStock(\n db: ShopDatabase,\n sku: string,\n stock: number,\n updatedAt = Date.now(),\n): Promise<SetStockResult> {\n const result = await db\n .prepare(\"UPDATE inventory SET stock = ?, updated_at = ? WHERE sku = ?\")\n .bind(stock, updatedAt, sku)\n .run();\n return result.meta.changes === 0 ? \"not_found\" : \"ok\";\n}\n\nexport async function adjustStock(db: ShopDatabase, sku: string, delta: number): Promise<AdjustStockResult> {\n const existing = await db.prepare(\"SELECT stock FROM inventory WHERE sku = ?\").bind(sku).first<{ stock: number }>();\n if (!existing) return \"not_found\";\n\n const result = await db\n .prepare(\"UPDATE inventory SET stock = stock + ?, updated_at = ? WHERE sku = ? AND stock + ? >= 0\")\n .bind(delta, Date.now(), sku, delta)\n .run();\n\n if (result.meta.changes === 0) return \"insufficient\";\n return \"ok\";\n}\n","import type { WebhookItem, WebhookOrder, WebhookRecordedOrder } from \"../webhook.js\";\nimport type { ShopDatabase } from \"./d1.js\";\nimport type { OrdersRow } from \"./types.generated.js\";\n\nexport type UnshippedOrder = Pick<OrdersRow, \"id\" | \"name\" | \"items_json\" | \"amount\" | \"created_at\">;\n\nexport type Carrier = \"yamato\" | \"sagawa\" | \"japanpost\" | \"other\";\n\nexport type MarkShippedResult = \"ok\" | \"not_pending\";\nexport type UpdateTrackingResult = \"ok\" | \"not_found\";\n\nfunction parseCheckoutItems(items: string | null | undefined): WebhookItem[] {\n if (!items) return [];\n\n try {\n const parsed: unknown = JSON.parse(items);\n if (!Array.isArray(parsed)) return [];\n\n return parsed.filter((item): item is WebhookItem => {\n if (typeof item !== \"object\" || item === null) return false;\n const { sku, quantity } = item as { sku?: unknown; quantity?: unknown };\n return typeof sku === \"string\" && typeof quantity === \"number\" && Number.isSafeInteger(quantity) && quantity > 0;\n });\n } catch {\n return [];\n }\n}\n\nexport async function recordOrder(\n db: ShopDatabase,\n order: WebhookOrder,\n extrasJson: string | null = null,\n): Promise<void> {\n await db\n .prepare(\n `INSERT INTO orders (\n id, email, name, phone, items_json, amount, shipping, address_json,\n payment_intent, custom_fields_json, run_id, extras_json, created_at\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(\n order.id,\n order.email,\n order.name,\n order.phone,\n JSON.stringify(order.items),\n order.amount,\n order.shippingCost,\n order.shippingAddress ? JSON.stringify(order.shippingAddress) : null,\n order.paymentIntent,\n order.customFields ? JSON.stringify(order.customFields) : null,\n order.metadata?.run_id ?? null,\n extrasJson,\n order.createdAt,\n )\n .run();\n}\n\nexport async function recordRefund(\n db: ShopDatabase,\n paymentIntentId: string,\n): Promise<WebhookRecordedOrder | null> {\n await db.prepare(\"UPDATE orders SET status = ? WHERE payment_intent = ?\").bind(\"refunded\", paymentIntentId).run();\n const orderRow = await db\n .prepare(\"SELECT items_json FROM orders WHERE payment_intent = ?\")\n .bind(paymentIntentId)\n .first<{ items_json: string | null }>();\n if (!orderRow) return null;\n return { items: parseCheckoutItems(orderRow.items_json), offerSlug: null };\n}\n\nexport async function recordExpiry(\n db: ShopDatabase,\n sessionId: string,\n): Promise<WebhookRecordedOrder | null> {\n await db.prepare(\"UPDATE orders SET status = ? WHERE id = ?\").bind(\"cancelled\", sessionId).run();\n const orderRow = await db\n .prepare(\"SELECT items_json FROM orders WHERE id = ?\")\n .bind(sessionId)\n .first<{ items_json: string | null }>();\n if (!orderRow) return null;\n return { items: parseCheckoutItems(orderRow.items_json), offerSlug: null };\n}\n\nexport async function recordEmailResult(\n db: ShopDatabase,\n sessionId: string,\n status: \"sent\" | \"failed\",\n): Promise<void> {\n await db.prepare(\"UPDATE orders SET email_status = ? WHERE id = ?\").bind(status, sessionId).run();\n}\n\nexport async function listUnshipped(db: ShopDatabase, cutoff: number): Promise<UnshippedOrder[]> {\n const result = await db\n .prepare(\n `SELECT id, name, items_json, amount, created_at\n FROM orders\n WHERE status = ? AND created_at < ?\n ORDER BY created_at ASC`,\n )\n .bind(\"pending\", cutoff)\n .all<UnshippedOrder>();\n return result.results ?? [];\n}\n\nexport async function getOrder(\n db: ShopDatabase,\n id: string,\n): Promise<Pick<OrdersRow, \"id\" | \"status\" | \"tracking_number\" | \"carrier\"> | null> {\n const row = await db\n .prepare(\"SELECT id, status, tracking_number, carrier FROM orders WHERE id = ?\")\n .bind(id)\n .first<Pick<OrdersRow, \"id\" | \"status\" | \"tracking_number\" | \"carrier\">>();\n return row ?? null;\n}\n\nexport async function markShipped(\n db: ShopDatabase,\n id: string,\n options: { trackingNumber?: string | null; carrier?: Carrier | null; shippedAt?: number } = {},\n): Promise<MarkShippedResult> {\n const result = await db\n .prepare(\n `UPDATE orders\n SET status = ?, shipped_at = ?, tracking_number = COALESCE(?, tracking_number), carrier = COALESCE(?, carrier)\n WHERE id = ? AND status = ?`,\n )\n .bind(\"shipped\", options.shippedAt ?? Date.now(), options.trackingNumber ?? null, options.carrier ?? null, id, \"pending\")\n .run();\n return result.meta.changes === 0 ? \"not_pending\" : \"ok\";\n}\n\nexport async function updateTracking(\n db: ShopDatabase,\n id: string,\n trackingNumber: string,\n carrier: string | null,\n): Promise<UpdateTrackingResult> {\n const result = await db\n .prepare(\"UPDATE orders SET tracking_number = ?, carrier = ? WHERE id = ?\")\n .bind(trackingNumber, carrier, id)\n .run();\n return result.meta.changes === 0 ? \"not_found\" : \"ok\";\n}\n","import type { ShopDatabase } from \"./d1.js\";\nimport type { ProductsRow } from \"./types.generated.js\";\n\nexport type ProductWriteFields = {\n name: string;\n description: string | null;\n price: number;\n shippingUnits: number;\n images: string[];\n gtin: string | null;\n category: string | null;\n active: 0 | 1;\n lowStockThreshold: number | null;\n};\n\nexport type CreateProductResult = \"ok\" | \"conflict\";\nexport type UpdateProductResult = \"ok\" | \"not_found\";\nexport type DiscontinueProductResult = \"ok\" | \"not_found\" | \"already_inactive\";\n\nexport async function listProductRows(\n db: ShopDatabase,\n options: { includeInactive?: boolean } = {},\n): Promise<ProductsRow[]> {\n const sql = options.includeInactive\n ? `SELECT sku, name, description, price, shipping_units, images_json, gtin, category,\n active, low_stock_threshold, created_at, updated_at, updated_by\n FROM products\n ORDER BY sku ASC`\n : `SELECT sku, name, description, price, shipping_units, images_json, gtin, category,\n active, low_stock_threshold, created_at, updated_at, updated_by\n FROM products\n WHERE active = 1\n ORDER BY sku ASC`;\n\n const result = await db.prepare(sql).all<ProductsRow>();\n return result.results ?? [];\n}\n\nexport async function getProductRow(db: ShopDatabase, sku: string): Promise<ProductsRow | null> {\n const row = await db\n .prepare(\n `SELECT sku, name, description, price, shipping_units, images_json, gtin, category,\n active, low_stock_threshold, created_at, updated_at, updated_by\n FROM products\n WHERE sku = ?`,\n )\n .bind(sku)\n .first<ProductsRow>();\n return row ?? null;\n}\n\nexport async function createProduct(\n db: ShopDatabase,\n sku: string,\n fields: ProductWriteFields,\n updatedBy: string | null,\n now = Date.now(),\n): Promise<CreateProductResult> {\n const existing = await db.prepare(\"SELECT sku FROM products WHERE sku = ?\").bind(sku).first<{ sku: string }>();\n if (existing) return \"conflict\";\n\n const imagesJson = fields.images.length > 0 ? JSON.stringify(fields.images) : null;\n await db.batch([\n db\n .prepare(\n `INSERT INTO products (\n sku, name, description, price, shipping_units, images_json, gtin, category,\n active, low_stock_threshold, created_at, updated_at, updated_by\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(\n sku,\n fields.name,\n fields.description,\n fields.price,\n fields.shippingUnits,\n imagesJson,\n fields.gtin,\n fields.category,\n fields.active,\n fields.lowStockThreshold,\n now,\n now,\n updatedBy,\n ),\n db.prepare(\"INSERT OR IGNORE INTO inventory (sku, stock, updated_at) VALUES (?, ?, ?)\").bind(sku, 0, now),\n ]);\n return \"ok\";\n}\n\nexport async function updateProduct(\n db: ShopDatabase,\n sku: string,\n fields: Partial<ProductWriteFields>,\n updatedBy: string | null,\n now = Date.now(),\n): Promise<{ result: UpdateProductResult; before: ProductsRow | null; after: ProductsRow | null }> {\n const existing = await getProductRow(db, sku);\n if (!existing) return { result: \"not_found\", before: null, after: null };\n\n const next = {\n name: fields.name ?? existing.name,\n description: fields.description !== undefined ? fields.description : existing.description,\n price: fields.price ?? existing.price,\n shippingUnits: fields.shippingUnits ?? existing.shipping_units,\n imagesJson:\n fields.images !== undefined\n ? fields.images.length > 0\n ? JSON.stringify(fields.images)\n : null\n : existing.images_json,\n gtin: fields.gtin !== undefined ? fields.gtin : existing.gtin,\n category: fields.category !== undefined ? fields.category : existing.category,\n active: fields.active ?? existing.active,\n lowStockThreshold:\n fields.lowStockThreshold !== undefined ? fields.lowStockThreshold : existing.low_stock_threshold,\n };\n\n await db\n .prepare(\n `UPDATE products\n SET name = ?, description = ?, price = ?, shipping_units = ?, images_json = ?,\n gtin = ?, category = ?, active = ?, low_stock_threshold = ?, updated_at = ?, updated_by = ?\n WHERE sku = ?`,\n )\n .bind(\n next.name,\n next.description,\n next.price,\n next.shippingUnits,\n next.imagesJson,\n next.gtin,\n next.category,\n next.active,\n next.lowStockThreshold,\n now,\n updatedBy,\n sku,\n )\n .run();\n\n const after = await getProductRow(db, sku);\n return { result: \"ok\", before: existing, after };\n}\n\nexport async function discontinueProduct(\n db: ShopDatabase,\n sku: string,\n updatedBy: string | null,\n now = Date.now(),\n): Promise<DiscontinueProductResult> {\n const existing = await db\n .prepare(\"SELECT sku, active FROM products WHERE sku = ?\")\n .bind(sku)\n .first<{ sku: string; active: number }>();\n if (!existing) return \"not_found\";\n if (existing.active === 0) return \"already_inactive\";\n\n await db\n .prepare(\"UPDATE products SET active = 0, updated_at = ?, updated_by = ? WHERE sku = ?\")\n .bind(now, updatedBy, sku)\n .run();\n return \"ok\";\n}\n","// Generated from schema/snapshot.json. Do not edit by hand.\nexport const SNAPSHOT_SCHEMA_VERSION = 7 as const;\n\nexport type AdminAuditLogRow = {\n id: number;\n entity_type: string;\n entity_id: string;\n action: string;\n delta: number | null;\n reason: string | null;\n admin_email: string | null;\n actor: string;\n detail_json: string | null;\n created_at: number;\n};\nexport type InventoryRow = {\n sku: string;\n stock: number;\n updated_at: number;\n};\nexport type OrdersRow = {\n id: string;\n email: string;\n name: string | null;\n phone: string | null;\n items_json: string;\n amount: number;\n shipping: number | null;\n address_json: string | null;\n payment_intent: string | null;\n custom_fields_json: string | null;\n status: string;\n run_id: string | null;\n email_status: string | null;\n created_at: number;\n shipped_at: number | null;\n tracking_number: string | null;\n carrier: string | null;\n extras_json: string | null;\n};\nexport type ProcessedEventsRow = {\n event_id: string;\n processed_at: number;\n};\nexport type ProductsRow = {\n sku: string;\n name: string;\n description: string | null;\n price: number;\n shipping_units: number;\n images_json: string | null;\n gtin: string | null;\n category: string | null;\n active: number;\n low_stock_threshold: number | null;\n created_at: number;\n updated_at: number;\n updated_by: string | null;\n};\n"],"mappings":"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","names":[]}
@@ -0,0 +1,255 @@
1
+ import {
2
+ currentSchemaVersion
3
+ } from "./chunk-T2PO3YLZ.js";
4
+
5
+ // src/db/snapshot.ts
6
+ var CORE_TABLES = [
7
+ "processed_events",
8
+ "orders",
9
+ "inventory",
10
+ "admin_audit_log",
11
+ "products"
12
+ ];
13
+
14
+ // src/db/drift.ts
15
+ function columnKey(column) {
16
+ return [
17
+ column.name,
18
+ column.type.toUpperCase(),
19
+ String(column.notnull),
20
+ String(column.pk),
21
+ column.dflt_value ?? ""
22
+ ].join("|");
23
+ }
24
+ function requiredColumns(table) {
25
+ return table.columns;
26
+ }
27
+ function classifyDrift(expected, actual) {
28
+ const compatible = [];
29
+ const incompatible = [];
30
+ for (const extraTable of Object.keys(actual.tables)) {
31
+ if (!CORE_TABLES.includes(extraTable) && !(extraTable in expected.tables)) {
32
+ compatible.push({
33
+ kind: "compatible",
34
+ table: extraTable,
35
+ message: `instance table "${extraTable}" is not owned by core`
36
+ });
37
+ }
38
+ }
39
+ for (const tableName of CORE_TABLES) {
40
+ const want = expected.tables[tableName];
41
+ const got = actual.tables[tableName];
42
+ if (!want) continue;
43
+ if (!got) {
44
+ incompatible.push({
45
+ kind: "incompatible",
46
+ table: tableName,
47
+ message: `required table "${tableName}" is missing`
48
+ });
49
+ continue;
50
+ }
51
+ const gotByName = new Map(got.columns.map((column) => [column.name, column]));
52
+ for (const column of requiredColumns(want)) {
53
+ const actualColumn = gotByName.get(column.name);
54
+ if (!actualColumn) {
55
+ incompatible.push({
56
+ kind: "incompatible",
57
+ table: tableName,
58
+ message: `required column "${tableName}.${column.name}" is missing`
59
+ });
60
+ continue;
61
+ }
62
+ if (columnKey(column) !== columnKey(actualColumn)) {
63
+ incompatible.push({
64
+ kind: "incompatible",
65
+ table: tableName,
66
+ message: `column "${tableName}.${column.name}" changed (type/null/pk/default)`
67
+ });
68
+ }
69
+ }
70
+ const wantNames = new Set(want.columns.map((column) => column.name));
71
+ for (const column of got.columns) {
72
+ if (!wantNames.has(column.name)) {
73
+ compatible.push({
74
+ kind: "compatible",
75
+ table: tableName,
76
+ message: `instance column "${tableName}.${column.name}" is not owned by core`
77
+ });
78
+ }
79
+ }
80
+ const wantPk = want.columns.filter((column) => column.pk > 0).sort((a, b) => a.pk - b.pk).map((column) => column.name).join(",");
81
+ const gotPk = got.columns.filter((column) => column.pk > 0).sort((a, b) => a.pk - b.pk).map((column) => column.name).join(",");
82
+ if (wantPk !== gotPk) {
83
+ incompatible.push({
84
+ kind: "incompatible",
85
+ table: tableName,
86
+ message: `primary key of "${tableName}" is ${gotPk || "(none)"}; expected ${wantPk}`
87
+ });
88
+ }
89
+ const wantSql = normalizeSql(want.sql);
90
+ const gotSql = normalizeSql(got.sql);
91
+ if (wantSql && gotSql && extractChecks(wantSql) !== extractChecks(gotSql)) {
92
+ incompatible.push({
93
+ kind: "incompatible",
94
+ table: tableName,
95
+ message: `CHECK constraint on "${tableName}" does not match snapshot`
96
+ });
97
+ }
98
+ const wantIndexes = new Set(want.indexes.map((index) => index.name));
99
+ for (const index of got.indexes) {
100
+ if (!wantIndexes.has(index.name)) {
101
+ compatible.push({
102
+ kind: "compatible",
103
+ table: tableName,
104
+ message: `instance index "${index.name}" on "${tableName}" is not owned by core`
105
+ });
106
+ }
107
+ }
108
+ for (const index of want.indexes) {
109
+ if (!got.indexes.some((actualIndex) => actualIndex.name === index.name)) {
110
+ incompatible.push({
111
+ kind: "incompatible",
112
+ table: tableName,
113
+ message: `required index "${index.name}" on "${tableName}" is missing`
114
+ });
115
+ }
116
+ }
117
+ }
118
+ return { compatible, incompatible };
119
+ }
120
+ function normalizeSql(sql) {
121
+ return (sql ?? "").replace(/\s+/g, " ").trim().toUpperCase();
122
+ }
123
+ function extractChecks(sql) {
124
+ const matches = sql.match(/CHECK\s*\((?:[^()]|\([^()]*\))*\)/g);
125
+ return (matches ?? []).slice().sort().join(";");
126
+ }
127
+
128
+ // src/db/introspect.ts
129
+ function asResults(value) {
130
+ return Array.isArray(value) ? value : value.results;
131
+ }
132
+ async function introspectSnapshot(db, version, tableNames = CORE_TABLES) {
133
+ const tables = {};
134
+ for (const tableName of tableNames) {
135
+ const columns = asResults(
136
+ await db.prepare(`PRAGMA table_info(${quoteIdent(tableName)})`).all()
137
+ ).map(
138
+ (column) => ({
139
+ cid: column.cid,
140
+ name: column.name,
141
+ type: column.type,
142
+ notnull: column.notnull,
143
+ dflt_value: column.dflt_value,
144
+ pk: column.pk
145
+ })
146
+ );
147
+ if (columns.length === 0) continue;
148
+ const indexRows = asResults(
149
+ await db.prepare(`PRAGMA index_list(${quoteIdent(tableName)})`).all()
150
+ );
151
+ const indexes = [];
152
+ for (const index of indexRows) {
153
+ if (index.origin === "pk") continue;
154
+ const indexColumns = asResults(
155
+ await db.prepare(`PRAGMA index_info(${quoteIdent(index.name)})`).all()
156
+ ).map((info) => info.name).filter((name) => typeof name === "string");
157
+ indexes.push({
158
+ name: index.name,
159
+ unique: index.unique,
160
+ origin: index.origin,
161
+ columns: indexColumns
162
+ });
163
+ }
164
+ indexes.sort((a, b) => a.name.localeCompare(b.name));
165
+ const foreignKeys = asResults(
166
+ await db.prepare(`PRAGMA foreign_key_list(${quoteIdent(tableName)})`).all()
167
+ ).map(
168
+ (fk) => ({
169
+ id: fk.id,
170
+ table: fk.table,
171
+ from: fk.from,
172
+ to: fk.to,
173
+ on_update: fk.on_update,
174
+ on_delete: fk.on_delete
175
+ })
176
+ );
177
+ const master = await db.prepare("SELECT name, sql FROM sqlite_master WHERE type = 'table' AND name = ?").bind(tableName).first();
178
+ tables[tableName] = {
179
+ name: tableName,
180
+ sql: master?.sql ?? null,
181
+ columns,
182
+ indexes,
183
+ foreignKeys
184
+ };
185
+ }
186
+ return { version, tables };
187
+ }
188
+ function quoteIdent(value) {
189
+ return `"${value.replaceAll('"', '""')}"`;
190
+ }
191
+
192
+ // src/db/schema-version.ts
193
+ var TRACKING_TABLES_SQL = `
194
+ CREATE TABLE IF NOT EXISTS umec_schema_version (
195
+ id INTEGER PRIMARY KEY CHECK (id = 1),
196
+ version INTEGER NOT NULL,
197
+ core_version TEXT NOT NULL,
198
+ applied_at INTEGER NOT NULL,
199
+ time_travel_bookmark TEXT
200
+ );
201
+
202
+ CREATE TABLE IF NOT EXISTS umec_migrations (
203
+ name TEXT PRIMARY KEY,
204
+ checksum TEXT NOT NULL,
205
+ kind TEXT NOT NULL CHECK (kind IN ('sql', 'upgrade')),
206
+ applied_at INTEGER NOT NULL
207
+ );
208
+ `;
209
+ var SchemaVersionError = class extends Error {
210
+ dbVersion;
211
+ installedVersion;
212
+ constructor(dbVersion, installedVersion) {
213
+ super(
214
+ `DB \u306F schema v${dbVersion}\u3002\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u4E2D\u306E @umec/core \u306F v${installedVersion} \u307E\u3067\u3057\u304B\u77E5\u3089\u306A\u3044\u3002\u623B\u3059\u306B\u306F wrangler d1 time-travel restore --bookmark=<\u8A18\u9332\u6E08\u307F> \u306E\u3042\u3068\u540C\u3058 core \u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u5165\u308C\u76F4\u3059\u3053\u3068\u3002down migration \u306F\u7121\u3044\u3002`
215
+ );
216
+ this.name = "SchemaVersionError";
217
+ this.dbVersion = dbVersion;
218
+ this.installedVersion = installedVersion;
219
+ }
220
+ };
221
+ async function readSchemaVersion(db) {
222
+ try {
223
+ const row = await db.prepare("SELECT version, core_version, applied_at, time_travel_bookmark FROM umec_schema_version WHERE id = 1").first();
224
+ return row ?? null;
225
+ } catch {
226
+ return null;
227
+ }
228
+ }
229
+ async function listAppliedMigrations(db) {
230
+ try {
231
+ const result = await db.prepare("SELECT name, checksum, kind, applied_at FROM umec_migrations ORDER BY name").all();
232
+ return result.results ?? [];
233
+ } catch {
234
+ return [];
235
+ }
236
+ }
237
+ async function assertSchemaCompatible(db, installedVersion = currentSchemaVersion()) {
238
+ const row = await readSchemaVersion(db);
239
+ if (!row) return;
240
+ if (row.version > installedVersion) {
241
+ throw new SchemaVersionError(row.version, installedVersion);
242
+ }
243
+ }
244
+
245
+ export {
246
+ CORE_TABLES,
247
+ classifyDrift,
248
+ introspectSnapshot,
249
+ TRACKING_TABLES_SQL,
250
+ SchemaVersionError,
251
+ readSchemaVersion,
252
+ listAppliedMigrations,
253
+ assertSchemaCompatible
254
+ };
255
+ //# sourceMappingURL=chunk-PNICEHSO.js.map