@umec/core 0.1.0-alpha.0 → 0.1.0-alpha.10

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 (53) hide show
  1. package/dist/catalog-5BPLMAW2.js +15 -0
  2. package/dist/catalog-5BPLMAW2.js.map +1 -0
  3. package/dist/checkout.d.ts +10 -3
  4. package/dist/checkout.js +1 -1
  5. package/dist/{chunk-2NYFCO5V.js → chunk-2EBXJT3M.js} +30 -30
  6. package/dist/chunk-2EBXJT3M.js.map +1 -0
  7. package/dist/{chunk-WZFM6R5J.js → chunk-4V4GYE7E.js} +4 -10
  8. package/dist/chunk-4V4GYE7E.js.map +1 -0
  9. package/dist/chunk-CYKUAS3D.js +9 -0
  10. package/dist/chunk-CYKUAS3D.js.map +1 -0
  11. package/dist/chunk-DN3J2NQC.js +298 -0
  12. package/dist/chunk-DN3J2NQC.js.map +1 -0
  13. package/dist/chunk-PNICEHSO.js +255 -0
  14. package/dist/chunk-PNICEHSO.js.map +1 -0
  15. package/dist/{chunk-QRFFBMFN.js → chunk-RFEX32LI.js} +12 -11
  16. package/dist/chunk-RFEX32LI.js.map +1 -0
  17. package/dist/chunk-T2PO3YLZ.js +64 -0
  18. package/dist/chunk-T2PO3YLZ.js.map +1 -0
  19. package/dist/{chunk-REUSLO5I.js → chunk-ZP7IGPJW.js} +26 -4
  20. package/dist/chunk-ZP7IGPJW.js.map +1 -0
  21. package/dist/cli.js +823 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/db/index.d.ts +130 -0
  24. package/dist/db/index.js +81 -0
  25. package/dist/db/index.js.map +1 -0
  26. package/dist/index.d.ts +6 -4
  27. package/dist/index.js +15 -4
  28. package/dist/middleware.d.ts +7 -2
  29. package/dist/middleware.js +1 -1
  30. package/dist/notify.d.ts +7 -8
  31. package/dist/notify.js +1 -1
  32. package/dist/orders-DRZvcL3s.d.ts +99 -0
  33. package/dist/schema-version-F-YYC1UG.d.ts +25 -0
  34. package/dist/sql-J4YBS7N7.js +7 -0
  35. package/dist/sql-J4YBS7N7.js.map +1 -0
  36. package/dist/webhook.d.ts +10 -2
  37. package/dist/webhook.js +1 -1
  38. package/package.json +10 -2
  39. package/schema/frozen-checksums.json +11 -0
  40. package/schema/migrations/0001_processed_events.sql +4 -0
  41. package/schema/migrations/0002_orders.sql +19 -0
  42. package/schema/migrations/0003_inventory.sql +5 -0
  43. package/schema/migrations/0004_admin_audit_log.sql +15 -0
  44. package/schema/migrations/0005_orders_tracking.sql +3 -0
  45. package/schema/migrations/0006_products.sql +15 -0
  46. package/schema/migrations/0007_orders_extras.sql +1 -0
  47. package/schema/snapshot.json +433 -0
  48. package/schema/upgrades/types.ts +16 -0
  49. package/schema/upgrades/v2-to-v6.ts +205 -0
  50. package/dist/chunk-2NYFCO5V.js.map +0 -1
  51. package/dist/chunk-QRFFBMFN.js.map +0 -1
  52. package/dist/chunk-REUSLO5I.js.map +0 -1
  53. package/dist/chunk-WZFM6R5J.js.map +0 -1
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/db/snapshot.ts","../src/db/drift.ts","../src/db/introspect.ts","../src/db/schema-version.ts"],"sourcesContent":["export type SnapshotColumn = {\n cid: number;\n name: string;\n type: string;\n notnull: number;\n dflt_value: string | null;\n pk: number;\n};\n\nexport type SnapshotIndex = {\n name: string;\n unique: number;\n origin: string;\n columns: string[];\n};\n\nexport type SnapshotForeignKey = {\n id: number;\n table: string;\n from: string;\n to: string;\n on_update: string;\n on_delete: string;\n};\n\nexport type SnapshotTable = {\n name: string;\n sql: string | null;\n columns: SnapshotColumn[];\n indexes: SnapshotIndex[];\n foreignKeys: SnapshotForeignKey[];\n};\n\nexport type SchemaSnapshot = {\n version: number;\n tables: Record<string, SnapshotTable>;\n};\n\nexport const CORE_TABLES = [\n \"processed_events\",\n \"orders\",\n \"inventory\",\n \"admin_audit_log\",\n \"products\",\n] as const;\n\nexport type CoreTableName = (typeof CORE_TABLES)[number];\n","import { CORE_TABLES, type SchemaSnapshot, type SnapshotColumn, type SnapshotTable } from \"./snapshot.js\";\n\nexport type DriftKind = \"compatible\" | \"incompatible\";\n\nexport type DriftFinding = {\n kind: DriftKind;\n table: string;\n message: string;\n};\n\nexport type DriftReport = {\n compatible: DriftFinding[];\n incompatible: DriftFinding[];\n};\n\nfunction columnKey(column: SnapshotColumn): string {\n return [\n column.name,\n column.type.toUpperCase(),\n String(column.notnull),\n String(column.pk),\n column.dflt_value ?? \"\",\n ].join(\"|\");\n}\n\nfunction requiredColumns(table: SnapshotTable): SnapshotColumn[] {\n return table.columns;\n}\n\nexport function classifyDrift(expected: SchemaSnapshot, actual: SchemaSnapshot): DriftReport {\n const compatible: DriftFinding[] = [];\n const incompatible: DriftFinding[] = [];\n\n for (const extraTable of Object.keys(actual.tables)) {\n if (!CORE_TABLES.includes(extraTable as (typeof CORE_TABLES)[number]) && !(extraTable in expected.tables)) {\n compatible.push({\n kind: \"compatible\",\n table: extraTable,\n message: `instance table \"${extraTable}\" is not owned by core`,\n });\n }\n }\n\n for (const tableName of CORE_TABLES) {\n const want = expected.tables[tableName];\n const got = actual.tables[tableName];\n if (!want) continue;\n if (!got) {\n incompatible.push({\n kind: \"incompatible\",\n table: tableName,\n message: `required table \"${tableName}\" is missing`,\n });\n continue;\n }\n\n const gotByName = new Map(got.columns.map((column) => [column.name, column]));\n for (const column of requiredColumns(want)) {\n const actualColumn = gotByName.get(column.name);\n if (!actualColumn) {\n incompatible.push({\n kind: \"incompatible\",\n table: tableName,\n message: `required column \"${tableName}.${column.name}\" is missing`,\n });\n continue;\n }\n if (columnKey(column) !== columnKey(actualColumn)) {\n incompatible.push({\n kind: \"incompatible\",\n table: tableName,\n message: `column \"${tableName}.${column.name}\" changed (type/null/pk/default)`,\n });\n }\n }\n\n const wantNames = new Set(want.columns.map((column) => column.name));\n for (const column of got.columns) {\n if (!wantNames.has(column.name)) {\n compatible.push({\n kind: \"compatible\",\n table: tableName,\n message: `instance column \"${tableName}.${column.name}\" is not owned by core`,\n });\n }\n }\n\n const wantPk = want.columns\n .filter((column) => column.pk > 0)\n .sort((a, b) => a.pk - b.pk)\n .map((column) => column.name)\n .join(\",\");\n const gotPk = got.columns\n .filter((column) => column.pk > 0)\n .sort((a, b) => a.pk - b.pk)\n .map((column) => column.name)\n .join(\",\");\n if (wantPk !== gotPk) {\n incompatible.push({\n kind: \"incompatible\",\n table: tableName,\n message: `primary key of \"${tableName}\" is ${gotPk || \"(none)\"}; expected ${wantPk}`,\n });\n }\n\n const wantSql = normalizeSql(want.sql);\n const gotSql = normalizeSql(got.sql);\n if (wantSql && gotSql && extractChecks(wantSql) !== extractChecks(gotSql)) {\n incompatible.push({\n kind: \"incompatible\",\n table: tableName,\n message: `CHECK constraint on \"${tableName}\" does not match snapshot`,\n });\n }\n\n const wantIndexes = new Set(want.indexes.map((index) => index.name));\n for (const index of got.indexes) {\n if (!wantIndexes.has(index.name)) {\n compatible.push({\n kind: \"compatible\",\n table: tableName,\n message: `instance index \"${index.name}\" on \"${tableName}\" is not owned by core`,\n });\n }\n }\n for (const index of want.indexes) {\n if (!got.indexes.some((actualIndex) => actualIndex.name === index.name)) {\n incompatible.push({\n kind: \"incompatible\",\n table: tableName,\n message: `required index \"${index.name}\" on \"${tableName}\" is missing`,\n });\n }\n }\n }\n\n return { compatible, incompatible };\n}\n\nfunction normalizeSql(sql: string | null): string {\n return (sql ?? \"\").replace(/\\s+/g, \" \").trim().toUpperCase();\n}\n\nfunction extractChecks(sql: string): string {\n const matches = sql.match(/CHECK\\s*\\((?:[^()]|\\([^()]*\\))*\\)/g);\n return (matches ?? []).slice().sort().join(\";\");\n}\n","import type { ShopDatabase } from \"./d1.js\";\nimport {\n CORE_TABLES,\n type SchemaSnapshot,\n type SnapshotColumn,\n type SnapshotForeignKey,\n type SnapshotIndex,\n type SnapshotTable,\n} from \"./snapshot.js\";\n\ntype PragmaColumn = {\n cid: number;\n name: string;\n type: string;\n notnull: number;\n dflt_value: string | null;\n pk: number;\n};\n\ntype PragmaIndex = {\n name: string;\n unique: number;\n origin: string;\n};\n\ntype PragmaIndexInfo = {\n name: string | null;\n};\n\ntype PragmaForeignKey = {\n id: number;\n table: string;\n from: string;\n to: string;\n on_update: string;\n on_delete: string;\n};\n\ntype MasterRow = {\n name: string;\n sql: string | null;\n};\n\nfunction asResults<T>(value: { results: T[] } | T[]): T[] {\n return Array.isArray(value) ? value : value.results;\n}\n\nexport async function introspectSnapshot(\n db: ShopDatabase,\n version: number,\n tableNames: readonly string[] = CORE_TABLES,\n): Promise<SchemaSnapshot> {\n const tables: Record<string, SnapshotTable> = {};\n\n for (const tableName of tableNames) {\n const columns = asResults(\n await db.prepare(`PRAGMA table_info(${quoteIdent(tableName)})`).all<PragmaColumn>(),\n ).map(\n (column): SnapshotColumn => ({\n cid: column.cid,\n name: column.name,\n type: column.type,\n notnull: column.notnull,\n dflt_value: column.dflt_value,\n pk: column.pk,\n }),\n );\n if (columns.length === 0) continue;\n\n const indexRows = asResults(\n await db.prepare(`PRAGMA index_list(${quoteIdent(tableName)})`).all<PragmaIndex>(),\n );\n const indexes: SnapshotIndex[] = [];\n for (const index of indexRows) {\n if (index.origin === \"pk\") continue;\n const indexColumns = asResults(\n await db.prepare(`PRAGMA index_info(${quoteIdent(index.name)})`).all<PragmaIndexInfo>(),\n )\n .map((info) => info.name)\n .filter((name): name is string => typeof name === \"string\");\n indexes.push({\n name: index.name,\n unique: index.unique,\n origin: index.origin,\n columns: indexColumns,\n });\n }\n indexes.sort((a, b) => a.name.localeCompare(b.name));\n\n const foreignKeys = asResults(\n await db.prepare(`PRAGMA foreign_key_list(${quoteIdent(tableName)})`).all<PragmaForeignKey>(),\n ).map(\n (fk): SnapshotForeignKey => ({\n id: fk.id,\n table: fk.table,\n from: fk.from,\n to: fk.to,\n on_update: fk.on_update,\n on_delete: fk.on_delete,\n }),\n );\n\n const master = await db\n .prepare(\"SELECT name, sql FROM sqlite_master WHERE type = 'table' AND name = ?\")\n .bind(tableName)\n .first<MasterRow>();\n\n tables[tableName] = {\n name: tableName,\n sql: master?.sql ?? null,\n columns,\n indexes,\n foreignKeys,\n };\n }\n\n return { version, tables };\n}\n\nfunction quoteIdent(value: string): string {\n return `\"${value.replaceAll('\"', '\"\"')}\"`;\n}\n","import type { ShopDatabase } from \"./d1.js\";\nimport { currentSchemaVersion } from \"./catalog.js\";\n\nexport const TRACKING_TABLES_SQL = `\nCREATE TABLE IF NOT EXISTS umec_schema_version (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n version INTEGER NOT NULL,\n core_version TEXT NOT NULL,\n applied_at INTEGER NOT NULL,\n time_travel_bookmark TEXT\n);\n\nCREATE TABLE IF NOT EXISTS umec_migrations (\n name TEXT PRIMARY KEY,\n checksum TEXT NOT NULL,\n kind TEXT NOT NULL CHECK (kind IN ('sql', 'upgrade')),\n applied_at INTEGER NOT NULL\n);\n`;\n\nexport type SchemaVersionRow = {\n version: number;\n core_version: string;\n applied_at: number;\n time_travel_bookmark: string | null;\n};\n\nexport type AppliedMigrationRow = {\n name: string;\n checksum: string;\n kind: \"sql\" | \"upgrade\";\n applied_at: number;\n};\n\nexport class SchemaVersionError extends Error {\n readonly dbVersion: number;\n readonly installedVersion: number;\n\n constructor(dbVersion: number, installedVersion: number) {\n super(\n `DB は schema v${dbVersion}。インストール中の @umec/core は v${installedVersion} までしか知らない。戻すには wrangler d1 time-travel restore --bookmark=<記録済み> のあと同じ core バージョンを入れ直すこと。down migration は無い。`,\n );\n this.name = \"SchemaVersionError\";\n this.dbVersion = dbVersion;\n this.installedVersion = installedVersion;\n }\n}\n\nexport async function readSchemaVersion(db: ShopDatabase): Promise<SchemaVersionRow | null> {\n try {\n const row = await db\n .prepare(\"SELECT version, core_version, applied_at, time_travel_bookmark FROM umec_schema_version WHERE id = 1\")\n .first<SchemaVersionRow>();\n return row ?? null;\n } catch {\n return null;\n }\n}\n\nexport async function listAppliedMigrations(db: ShopDatabase): Promise<AppliedMigrationRow[]> {\n try {\n const result = await db\n .prepare(\"SELECT name, checksum, kind, applied_at FROM umec_migrations ORDER BY name\")\n .all<AppliedMigrationRow>();\n return result.results ?? [];\n } catch {\n return [];\n }\n}\n\nexport async function assertSchemaCompatible(\n db: ShopDatabase,\n installedVersion = currentSchemaVersion(),\n): Promise<void> {\n const row = await readSchemaVersion(db);\n if (!row) return;\n if (row.version > installedVersion) {\n throw new SchemaVersionError(row.version, installedVersion);\n }\n}\n"],"mappings":"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","names":[]}
@@ -49,19 +49,20 @@ function formatOrderLine(order, products, now = Date.now()) {
49
49
  const name = order.name?.trim() || "\u304A\u5BA2\u69D8";
50
50
  return `${name} | ${parseItems(order.items_json, products)} | ${formatYen(order.amount)} | ${elapsedDays}\u65E5`;
51
51
  }
52
- async function notifyUnshipped(orders, env, { alertFrom, alertTo, products = {} }) {
52
+ async function notifyUnshipped(orders, env, { alertFrom, alertTo, alertSubject, products = {} }) {
53
53
  if (orders.length === 0) return;
54
- if (!env.DISCORD_WEBHOOK_URL) throw new Error("DISCORD_WEBHOOK_URL is not configured");
55
54
  if (!env.RESEND_API_KEY) throw new Error("RESEND_API_KEY is not configured");
56
55
  const lines = orders.map((order) => formatOrderLine(order, products));
57
56
  const content = ["\u672A\u767A\u9001\u30A2\u30E9\u30FC\u30C8: pending \u306E\u6CE8\u6587\u304C\u3042\u308A\u307E\u3059\u3002", "", ...lines].join("\n");
58
- const discordResponse = await fetch(env.DISCORD_WEBHOOK_URL, {
59
- method: "POST",
60
- headers: { "Content-Type": "application/json" },
61
- body: JSON.stringify({ content, allowed_mentions: { parse: [] } })
62
- });
63
- if (!discordResponse.ok) {
64
- throw new Error(`Discord notification failed: ${discordResponse.status}`);
57
+ if (env.DISCORD_WEBHOOK_URL) {
58
+ const discordResponse = await fetch(env.DISCORD_WEBHOOK_URL, {
59
+ method: "POST",
60
+ headers: { "Content-Type": "application/json" },
61
+ body: JSON.stringify({ content, allowed_mentions: { parse: [] } })
62
+ });
63
+ if (!discordResponse.ok) {
64
+ throw new Error(`Discord notification failed: ${discordResponse.status}`);
65
+ }
65
66
  }
66
67
  const rows = lines.map((line) => {
67
68
  const [name, items, amount, elapsedDays] = line.split(" | ");
@@ -76,7 +77,7 @@ async function notifyUnshipped(orders, env, { alertFrom, alertTo, products = {}
76
77
  const { error } = await resend.emails.send({
77
78
  from: alertFrom,
78
79
  to: alertTo,
79
- subject: "\u3010emu\u3011\u672A\u767A\u9001\u30A2\u30E9\u30FC\u30C8",
80
+ subject: alertSubject,
80
81
  html: `<!DOCTYPE html>
81
82
  <html lang="ja">
82
83
  <head><meta charset="UTF-8"></head>
@@ -106,4 +107,4 @@ async function notifyUnshipped(orders, env, { alertFrom, alertTo, products = {}
106
107
  export {
107
108
  notifyUnshipped
108
109
  };
109
- //# sourceMappingURL=chunk-QRFFBMFN.js.map
110
+ //# sourceMappingURL=chunk-RFEX32LI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/notify.ts"],"sourcesContent":["import { Resend } from \"resend\";\nimport type { UnshippedOrder as SnapshotUnshippedOrder } from \"./db/orders.js\";\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\n\nexport type UnshippedOrder = SnapshotUnshippedOrder;\n\nexport type NotifyEnv = {\n DISCORD_WEBHOOK_URL?: string;\n RESEND_API_KEY?: string;\n};\n\ntype CheckoutItem = {\n sku?: unknown;\n quantity?: unknown;\n};\n\nexport type NotifyProduct = {\n name: string;\n};\n\nexport type NotifyProductsMap = Record<string, NotifyProduct | undefined>;\n\nexport type NotifyUnshippedOptions = {\n alertFrom: string;\n alertTo: string;\n alertSubject: string;\n products?: NotifyProductsMap;\n};\n\nfunction formatYen(value: number | null) {\n if (value == null) return \"-\";\n return new Intl.NumberFormat(\"ja-JP\", {\n style: \"currency\",\n currency: \"JPY\",\n maximumFractionDigits: 0,\n }).format(value);\n}\n\nfunction parseTimestamp(value: number | string | null) {\n if (value == null || value === \"\") return null;\n const timestamp = typeof value === \"string\" ? Number(value) : value;\n return Number.isFinite(timestamp) ? timestamp : null;\n}\n\nfunction parseItems(itemsJson: string | null, products: NotifyProductsMap) {\n if (!itemsJson) return \"-\";\n\n try {\n const parsed: unknown = JSON.parse(itemsJson);\n if (!Array.isArray(parsed) || parsed.length === 0) return \"-\";\n\n return parsed\n .map((item) => {\n if (typeof item !== \"object\" || item === null) return null;\n const { sku, quantity } = item as CheckoutItem;\n const productSku = typeof sku === \"string\" ? sku : \"\";\n const productName = productSku && products[productSku] ? products[productSku].name : productSku || \"商品\";\n const itemQuantity = typeof quantity === \"number\" && Number.isFinite(quantity) ? quantity : 0;\n return `${productName} x ${itemQuantity}`;\n })\n .filter((item): item is string => Boolean(item))\n .join(\", \");\n } catch {\n return \"-\";\n }\n}\n\nfunction escapeHtml(value: string) {\n return value\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"'\", \"&#39;\");\n}\n\nfunction notificationErrorSuffix(error: unknown) {\n if (typeof error !== \"object\" || error === null) return \"\";\n const value = error as { name?: unknown; statusCode?: unknown };\n const details = [\n typeof value.name === \"string\" ? `name=${value.name}` : null,\n typeof value.statusCode === \"number\" ? `statusCode=${value.statusCode}` : null,\n ].filter((detail): detail is string => detail !== null);\n\n return details.length > 0 ? ` (${details.join(\", \")})` : \"\";\n}\n\nfunction formatOrderLine(order: UnshippedOrder, products: NotifyProductsMap, now = Date.now()) {\n const createdAt = parseTimestamp(order.created_at);\n const elapsedDays = createdAt == null ? \"-\" : String(Math.floor((now - createdAt) / DAY_MS));\n const name = order.name?.trim() || \"お客様\";\n return `${name} | ${parseItems(order.items_json, products)} | ${formatYen(order.amount)} | ${elapsedDays}日`;\n}\n\nexport async function notifyUnshipped(\n orders: UnshippedOrder[],\n env: NotifyEnv,\n { alertFrom, alertTo, alertSubject, products = {} }: NotifyUnshippedOptions,\n) {\n if (orders.length === 0) return;\n if (!env.RESEND_API_KEY) throw new Error(\"RESEND_API_KEY is not configured\");\n\n const lines = orders.map((order) => formatOrderLine(order, products));\n const content = [\"未発送アラート: pending の注文があります。\", \"\", ...lines].join(\"\\n\");\n\n if (env.DISCORD_WEBHOOK_URL) {\n const discordResponse = await fetch(env.DISCORD_WEBHOOK_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ content, allowed_mentions: { parse: [] } }),\n });\n if (!discordResponse.ok) {\n throw new Error(`Discord notification failed: ${discordResponse.status}`);\n }\n }\n\n const rows = lines\n .map((line) => {\n const [name, items, amount, elapsedDays] = line.split(\" | \");\n return `<tr>\n <td style=\"padding:8px;border-bottom:1px solid #eee;\">${escapeHtml(name ?? \"\")}</td>\n <td style=\"padding:8px;border-bottom:1px solid #eee;\">${escapeHtml(items ?? \"\")}</td>\n <td style=\"padding:8px;border-bottom:1px solid #eee;text-align:right;\">${escapeHtml(amount ?? \"\")}</td>\n <td style=\"padding:8px;border-bottom:1px solid #eee;text-align:right;\">${escapeHtml(elapsedDays ?? \"\")}</td>\n </tr>`;\n })\n .join(\"\");\n\n const resend = new Resend(env.RESEND_API_KEY);\n const { error } = await resend.emails.send({\n from: alertFrom,\n to: alertTo,\n subject: alertSubject,\n html: `<!DOCTYPE html>\n<html lang=\"ja\">\n<head><meta charset=\"UTF-8\"></head>\n<body style=\"font-family:sans-serif;color:#222;max-width:720px;margin:0 auto;padding:32px 16px;\">\n <h1 style=\"font-size:20px;margin:0 0 16px;\">未発送アラート</h1>\n <p style=\"font-size:14px;line-height:1.7;color:#555;\">pending のまま threshold を超えた注文があります。</p>\n <table style=\"width:100%;border-collapse:collapse;font-size:14px;\">\n <thead>\n <tr>\n <th style=\"padding:8px;border-bottom:2px solid #222;text-align:left;\">名前</th>\n <th style=\"padding:8px;border-bottom:2px solid #222;text-align:left;\">商品</th>\n <th style=\"padding:8px;border-bottom:2px solid #222;text-align:right;\">金額</th>\n <th style=\"padding:8px;border-bottom:2px solid #222;text-align:right;\">経過日数</th>\n </tr>\n </thead>\n <tbody>${rows}</tbody>\n </table>\n</body>\n</html>`,\n text: content,\n });\n if (error) {\n throw new Error(`Resend notification failed${notificationErrorSuffix(error)}`);\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,64 @@
1
+ // src/db/catalog.ts
2
+ import { readdirSync, readFileSync } from "fs";
3
+ import { join as join2 } from "path";
4
+
5
+ // src/package-root.ts
6
+ import { existsSync } from "fs";
7
+ import { dirname, join } from "path";
8
+ import { fileURLToPath } from "url";
9
+ function findPackageRoot(startHref = import.meta.url) {
10
+ let dir = fileURLToPath(new URL(".", startHref));
11
+ for (; ; ) {
12
+ if (existsSync(join(dir, "package.json")) && existsSync(join(dir, "schema"))) {
13
+ return dir;
14
+ }
15
+ const parent = dirname(dir);
16
+ if (parent === dir) {
17
+ throw new Error("Cannot locate @umec/core package root (package.json + schema/)");
18
+ }
19
+ dir = parent;
20
+ }
21
+ }
22
+
23
+ // src/db/catalog.ts
24
+ var MIGRATION_NAME = /^(\d{4})_.+\.sql$/;
25
+ function schemaDir(root = findPackageRoot()) {
26
+ return join2(root, "schema");
27
+ }
28
+ function migrationsDir(root = findPackageRoot()) {
29
+ return join2(schemaDir(root), "migrations");
30
+ }
31
+ function listCoreMigrations(root = findPackageRoot()) {
32
+ const dir = migrationsDir(root);
33
+ return readdirSync(dir).filter((name) => MIGRATION_NAME.test(name)).sort().map((name) => {
34
+ const match = MIGRATION_NAME.exec(name);
35
+ if (!match) throw new Error(`Invalid migration name: ${name}`);
36
+ const version = Number(match[1]);
37
+ const path = join2(dir, name);
38
+ return {
39
+ name,
40
+ version,
41
+ path,
42
+ sql: readFileSync(path, "utf8")
43
+ };
44
+ });
45
+ }
46
+ function currentSchemaVersion(root = findPackageRoot()) {
47
+ const migrations = listCoreMigrations(root);
48
+ const last = migrations[migrations.length - 1];
49
+ if (!last) throw new Error("No core migrations found");
50
+ return last.version;
51
+ }
52
+ function loadSnapshotJson(root = findPackageRoot()) {
53
+ return JSON.parse(readFileSync(join2(schemaDir(root), "snapshot.json"), "utf8"));
54
+ }
55
+
56
+ export {
57
+ findPackageRoot,
58
+ schemaDir,
59
+ migrationsDir,
60
+ listCoreMigrations,
61
+ currentSchemaVersion,
62
+ loadSnapshotJson
63
+ };
64
+ //# sourceMappingURL=chunk-T2PO3YLZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/db/catalog.ts","../src/package-root.ts"],"sourcesContent":["import { readdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { findPackageRoot } from \"../package-root.js\";\n\nexport type CoreMigration = {\n name: string;\n version: number;\n path: string;\n sql: string;\n};\n\nconst MIGRATION_NAME = /^(\\d{4})_.+\\.sql$/;\n\nexport function schemaDir(root = findPackageRoot()): string {\n return join(root, \"schema\");\n}\n\nexport function migrationsDir(root = findPackageRoot()): string {\n return join(schemaDir(root), \"migrations\");\n}\n\nexport function listCoreMigrations(root = findPackageRoot()): CoreMigration[] {\n const dir = migrationsDir(root);\n return readdirSync(dir)\n .filter((name) => MIGRATION_NAME.test(name))\n .sort()\n .map((name) => {\n const match = MIGRATION_NAME.exec(name);\n if (!match) throw new Error(`Invalid migration name: ${name}`);\n const version = Number(match[1]);\n const path = join(dir, name);\n return {\n name,\n version,\n path,\n sql: readFileSync(path, \"utf8\"),\n };\n });\n}\n\nexport function currentSchemaVersion(root = findPackageRoot()): number {\n const migrations = listCoreMigrations(root);\n const last = migrations[migrations.length - 1];\n if (!last) throw new Error(\"No core migrations found\");\n return last.version;\n}\n\nexport function loadSnapshotJson(root = findPackageRoot()): unknown {\n return JSON.parse(readFileSync(join(schemaDir(root), \"snapshot.json\"), \"utf8\"));\n}\n","import { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nexport function findPackageRoot(startHref = import.meta.url): string {\n let dir = fileURLToPath(new URL(\".\", startHref));\n for (;;) {\n if (existsSync(join(dir, \"package.json\")) && existsSync(join(dir, \"schema\"))) {\n return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) {\n throw new Error(\"Cannot locate @umec/core package root (package.json + schema/)\");\n }\n dir = parent;\n }\n}\n"],"mappings":";AAAA,SAAS,aAAa,oBAAoB;AAC1C,SAAS,QAAAA,aAAY;;;ACDrB,SAAS,kBAAkB;AAC3B,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;AAEvB,SAAS,gBAAgB,YAAY,YAAY,KAAa;AACnE,MAAI,MAAM,cAAc,IAAI,IAAI,KAAK,SAAS,CAAC;AAC/C,aAAS;AACP,QAAI,WAAW,KAAK,KAAK,cAAc,CAAC,KAAK,WAAW,KAAK,KAAK,QAAQ,CAAC,GAAG;AAC5E,aAAO;AAAA,IACT;AACA,UAAM,SAAS,QAAQ,GAAG;AAC1B,QAAI,WAAW,KAAK;AAClB,YAAM,IAAI,MAAM,gEAAgE;AAAA,IAClF;AACA,UAAM;AAAA,EACR;AACF;;;ADLA,IAAM,iBAAiB;AAEhB,SAAS,UAAU,OAAO,gBAAgB,GAAW;AAC1D,SAAOC,MAAK,MAAM,QAAQ;AAC5B;AAEO,SAAS,cAAc,OAAO,gBAAgB,GAAW;AAC9D,SAAOA,MAAK,UAAU,IAAI,GAAG,YAAY;AAC3C;AAEO,SAAS,mBAAmB,OAAO,gBAAgB,GAAoB;AAC5E,QAAM,MAAM,cAAc,IAAI;AAC9B,SAAO,YAAY,GAAG,EACnB,OAAO,CAAC,SAAS,eAAe,KAAK,IAAI,CAAC,EAC1C,KAAK,EACL,IAAI,CAAC,SAAS;AACb,UAAM,QAAQ,eAAe,KAAK,IAAI;AACtC,QAAI,CAAC,MAAO,OAAM,IAAI,MAAM,2BAA2B,IAAI,EAAE;AAC7D,UAAM,UAAU,OAAO,MAAM,CAAC,CAAC;AAC/B,UAAM,OAAOA,MAAK,KAAK,IAAI;AAC3B,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK,aAAa,MAAM,MAAM;AAAA,IAChC;AAAA,EACF,CAAC;AACL;AAEO,SAAS,qBAAqB,OAAO,gBAAgB,GAAW;AACrE,QAAM,aAAa,mBAAmB,IAAI;AAC1C,QAAM,OAAO,WAAW,WAAW,SAAS,CAAC;AAC7C,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,0BAA0B;AACrD,SAAO,KAAK;AACd;AAEO,SAAS,iBAAiB,OAAO,gBAAgB,GAAY;AAClE,SAAO,KAAK,MAAM,aAAaA,MAAK,UAAU,IAAI,GAAG,eAAe,GAAG,MAAM,CAAC;AAChF;","names":["join","join"]}
@@ -52,13 +52,15 @@ function createWebhookHandler({
52
52
  recordRefund,
53
53
  recordExpiry,
54
54
  recordOrder,
55
+ recordEmailResult,
55
56
  restoreStock,
56
57
  decrementStock,
57
58
  incrementOfferSold,
58
59
  decrementOfferSold,
59
60
  renderOrderEmail,
60
61
  orderEmailFrom,
61
- orderEmailBcc
62
+ orderEmailBcc,
63
+ orderEmailReplyTo
62
64
  } = {}) {
63
65
  return async ({ request }) => {
64
66
  console.log("[webhook] POST received, method:", request.method);
@@ -122,6 +124,8 @@ function createWebhookHandler({
122
124
  if (typeof v === "string" && v.trim() !== "") cf[f.key] = v.trim();
123
125
  }
124
126
  const customFields = Object.keys(cf).length > 0 ? cf : null;
127
+ const { items: _items, offer_slug: _offerSlug, ...externalMetadata } = session.metadata ?? {};
128
+ const metadata = Object.keys(externalMetadata).length > 0 ? externalMetadata : null;
125
129
  const shipping = session.collected_information?.shipping_details ?? session.shipping_details;
126
130
  const offerSlugFromMeta = session.metadata?.offer_slug ?? null;
127
131
  if (recordOrder) {
@@ -137,6 +141,7 @@ function createWebhookHandler({
137
141
  paymentIntent,
138
142
  offerSlug: offerSlugFromMeta,
139
143
  customFields,
144
+ metadata,
140
145
  createdAt: Date.now()
141
146
  });
142
147
  if (checkoutItems.length > 0 && decrementStock) await decrementStock(checkoutItems);
@@ -173,21 +178,38 @@ function createWebhookHandler({
173
178
  shipping,
174
179
  customFields: cf
175
180
  });
181
+ let emailResult;
176
182
  try {
177
183
  const resend = new Resend(resendKey);
178
184
  const { error } = await resend.emails.send({
179
185
  from: orderEmailFrom,
180
186
  to: email,
181
187
  ...orderEmailBcc ? { bcc: orderEmailBcc } : {},
188
+ ...orderEmailReplyTo ? { reply_to: orderEmailReplyTo } : {},
182
189
  subject,
183
190
  html
184
191
  });
185
192
  if (error) {
186
- console.error("[webhook] Resend error:", providerErrorDetails(error));
193
+ const details = providerErrorDetails(error);
194
+ console.error("[webhook] Resend error:", details);
195
+ emailResult = {
196
+ sessionId: session.id,
197
+ status: "failed",
198
+ ...details.message ? { errorMessage: details.message } : {}
199
+ };
200
+ } else {
201
+ emailResult = { sessionId: session.id, status: "sent" };
187
202
  }
188
203
  } catch (e) {
189
- console.error("[webhook] Resend exception:", providerErrorDetails(e));
204
+ const details = providerErrorDetails(e);
205
+ console.error("[webhook] Resend exception:", details);
206
+ emailResult = {
207
+ sessionId: session.id,
208
+ status: "failed",
209
+ ...details.message ? { errorMessage: details.message } : {}
210
+ };
190
211
  }
212
+ if (recordEmailResult) await recordEmailResult(emailResult);
191
213
  return new Response("OK", { status: 200 });
192
214
  };
193
215
  }
@@ -196,4 +218,4 @@ export {
196
218
  GET,
197
219
  createWebhookHandler
198
220
  };
199
- //# sourceMappingURL=chunk-REUSLO5I.js.map
221
+ //# sourceMappingURL=chunk-ZP7IGPJW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/webhook.ts"],"sourcesContent":["import { Resend } from \"resend\";\nimport Stripe from \"stripe\";\nimport { getServerEnv } from \"./env.js\";\n\nconst STRIPE_API_VERSION = \"2026-05-27.dahlia\";\n\ntype ShippingDetails = NonNullable<\n NonNullable<Stripe.Checkout.Session[\"collected_information\"]>[\"shipping_details\"]\n>;\ntype SessionWithLegacyShipping = Stripe.Checkout.Session & {\n shipping_details?: ShippingDetails | null;\n};\nexport type WebhookItem = {\n sku: string;\n quantity: number;\n};\nexport type SupportedWebhookEvent =\n | \"checkout.session.completed\"\n | \"checkout.session.expired\"\n | \"charge.refunded\";\n\nexport type OrderEmailLineItem = {\n description: string | null;\n quantity: number | null;\n amount: number | null;\n};\n\nexport type OrderEmailData = {\n name: string;\n email: string;\n phone: string | null;\n items: OrderEmailLineItem[];\n totalAmount: number;\n shippingCostAmount: number | null;\n shipping: ShippingDetails | null | undefined;\n customFields: Record<string, string>;\n};\n\nexport type RenderOrderEmail = (order: OrderEmailData) => { subject: string; html: string };\n\nexport type WebhookOrder = {\n id: string;\n email: string;\n name: string | null;\n phone: string | null;\n items: WebhookItem[];\n amount: number;\n shippingCost: number | null;\n shippingAddress: ShippingDetails | null | undefined;\n paymentIntent: string | null;\n offerSlug: string | null;\n customFields: Record<string, string> | null;\n metadata: Record<string, string> | null;\n createdAt: number;\n};\n\nexport type WebhookRecordedOrder = {\n items: WebhookItem[];\n offerSlug: string | null;\n};\n\nexport type WebhookEmailResult = {\n sessionId: string;\n status: \"sent\" | \"failed\";\n errorMessage?: string;\n};\n\nexport type WebhookHandlerOptions = {\n claimEvent?: (eventId: string) => Promise<boolean>;\n recordRefund?: (paymentIntentId: string) => Promise<WebhookRecordedOrder | null>;\n recordExpiry?: (sessionId: string) => Promise<WebhookRecordedOrder | null>;\n recordOrder?: (order: WebhookOrder) => Promise<void>;\n recordEmailResult?: (result: WebhookEmailResult) => Promise<void>;\n restoreStock?: (items: WebhookItem[]) => Promise<void>;\n decrementStock?: (items: WebhookItem[]) => Promise<void>;\n incrementOfferSold?: (offerSlug: string, items: WebhookItem[]) => Promise<void>;\n decrementOfferSold?: (offerSlug: string, items: WebhookItem[]) => Promise<void>;\n renderOrderEmail?: RenderOrderEmail;\n orderEmailFrom?: string;\n orderEmailBcc?: string | string[];\n orderEmailReplyTo?: string;\n};\n\nexport type WebhookHandler = ({ request }: { request: Request }) => Promise<Response>;\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\nfunction isSupportedWebhookEvent(type: string): type is SupportedWebhookEvent {\n return (\n type === \"checkout.session.completed\" ||\n type === \"checkout.session.expired\" ||\n type === \"charge.refunded\"\n );\n}\n\nfunction getPaymentIntentId(paymentIntent: string | Stripe.PaymentIntent | null) {\n if (typeof paymentIntent === \"string\") return paymentIntent;\n return paymentIntent?.id ?? null;\n}\n\nfunction providerErrorDetails(error: unknown) {\n if (typeof error !== \"object\" || error === null) return { message: String(error) };\n const value = error as {\n name?: unknown;\n type?: unknown;\n code?: unknown;\n statusCode?: unknown;\n requestId?: unknown;\n message?: unknown;\n };\n\n return {\n name: typeof value.name === \"string\" ? value.name : undefined,\n type: typeof value.type === \"string\" ? value.type : undefined,\n code: typeof value.code === \"string\" ? value.code : undefined,\n statusCode: typeof value.statusCode === \"number\" ? value.statusCode : undefined,\n requestId: typeof value.requestId === \"string\" ? value.requestId : undefined,\n message: typeof value.message === \"string\" ? value.message : undefined,\n };\n}\n\nasync function restoreRecordedOrder(\n order: WebhookRecordedOrder | null,\n restoreStock?: (items: WebhookItem[]) => Promise<void>,\n decrementOfferSold?: (offerSlug: string, items: WebhookItem[]) => Promise<void>,\n) {\n const items = order?.items ?? [];\n if (items.length > 0 && restoreStock) await restoreStock(items);\n if (order?.offerSlug && decrementOfferSold) {\n await decrementOfferSold(order.offerSlug, items);\n }\n}\n\nexport const GET: WebhookHandler = async () => new Response(\"OK\", { status: 200 });\n\nexport function createWebhookHandler({\n claimEvent,\n recordRefund,\n recordExpiry,\n recordOrder,\n recordEmailResult,\n restoreStock,\n decrementStock,\n incrementOfferSold,\n decrementOfferSold,\n renderOrderEmail,\n orderEmailFrom,\n orderEmailBcc,\n orderEmailReplyTo,\n}: WebhookHandlerOptions = {}): WebhookHandler {\n return async ({ request }) => {\n console.log(\"[webhook] POST received, method:\", request.method);\n\n const {\n STRIPE_SECRET_KEY: stripeKey,\n STRIPE_WEBHOOK_SECRET: webhookSecret,\n RESEND_API_KEY: resendKey,\n } = getServerEnv();\n\n if (!stripeKey || !webhookSecret) {\n console.error(\"[webhook] Stripe env not configured\");\n return new Response(\"Server error\", { status: 500 });\n }\n\n const sig = request.headers.get(\"stripe-signature\");\n if (!sig) return new Response(\"Bad request\", { status: 400 });\n\n const payload = await request.text();\n\n const stripe = new Stripe(stripeKey, { apiVersion: STRIPE_API_VERSION });\n\n let event: Stripe.Event;\n try {\n event = await stripe.webhooks.constructEventAsync(payload, sig, webhookSecret);\n } catch {\n return new Response(\"Invalid signature\", { status: 400 });\n }\n\n if (!isSupportedWebhookEvent(event.type)) {\n return new Response(\"OK\", { status: 200 });\n }\n\n if (claimEvent) {\n const claim = await claimEvent(event.id);\n if (!claim) {\n console.log(\"[webhook] duplicate event skipped:\", event.id);\n return new Response(\"OK\", { status: 200 });\n }\n }\n\n if (event.type === \"charge.refunded\") {\n const charge = event.data.object as Stripe.Charge;\n const paymentIntent = getPaymentIntentId(charge.payment_intent);\n\n if (paymentIntent && recordRefund) {\n const order = await recordRefund(paymentIntent);\n await restoreRecordedOrder(order, restoreStock, decrementOfferSold);\n }\n\n return new Response(\"OK\", { status: 200 });\n }\n\n const session = event.data.object as Stripe.Checkout.Session;\n\n if (event.type === \"checkout.session.expired\") {\n if (recordExpiry) {\n const order = await recordExpiry(session.id);\n await restoreRecordedOrder(order, restoreStock, decrementOfferSold);\n }\n\n return new Response(\"OK\", { status: 200 });\n }\n\n const email = session.customer_details?.email;\n const customerName = session.customer_details?.name ?? null;\n const name = customerName ?? \"お客様\";\n const phone = session.customer_details?.phone ?? null;\n const totalAmount = session.amount_total ?? 0;\n const shippingCostAmount = session.shipping_cost?.amount_total ?? null;\n const paymentIntent = getPaymentIntentId(session.payment_intent);\n const checkoutItems = parseCheckoutItems(session.metadata?.items);\n const cf: Record<string, string> = {};\n for (const f of (session.custom_fields ?? []) as Array<{\n key: string;\n type: string;\n text?: { value?: string | null };\n dropdown?: { value?: string | null };\n }>) {\n const v = f.type === \"dropdown\" ? f.dropdown?.value : f.text?.value;\n if (typeof v === \"string\" && v.trim() !== \"\") cf[f.key] = v.trim();\n }\n const customFields = Object.keys(cf).length > 0 ? cf : null;\n const { items: _items, offer_slug: _offerSlug, ...externalMetadata } = session.metadata ?? {};\n const metadata = Object.keys(externalMetadata).length > 0 ? externalMetadata : null;\n\n const shipping =\n session.collected_information?.shipping_details ??\n (session as SessionWithLegacyShipping).shipping_details;\n\n const offerSlugFromMeta = session.metadata?.offer_slug ?? null;\n\n if (recordOrder) {\n await recordOrder({\n id: session.id,\n email: email ?? \"\",\n name: customerName,\n phone,\n items: checkoutItems,\n amount: totalAmount,\n shippingCost: shippingCostAmount,\n shippingAddress: shipping,\n paymentIntent,\n offerSlug: offerSlugFromMeta,\n customFields,\n metadata,\n createdAt: Date.now(),\n });\n if (checkoutItems.length > 0 && decrementStock) await decrementStock(checkoutItems);\n if (offerSlugFromMeta && checkoutItems.length > 0 && incrementOfferSold) {\n await incrementOfferSold(offerSlugFromMeta, checkoutItems);\n }\n }\n\n let lineItems: Stripe.LineItem[] = [];\n try {\n const result = await stripe.checkout.sessions.listLineItems(session.id, { limit: 100 });\n lineItems = result.data;\n } catch (e) {\n console.error(\"[webhook] listLineItems failed:\", providerErrorDetails(e));\n }\n\n if (!email) {\n console.error(\"[webhook] no customer email in session:\", session.id);\n return new Response(\"OK\", { status: 200 });\n }\n\n if (!resendKey || !renderOrderEmail || !orderEmailFrom) {\n if (!resendKey) console.error(\"[webhook] RESEND_API_KEY not set\");\n return new Response(\"OK\", { status: 200 });\n }\n\n const { subject, html } = renderOrderEmail({\n name,\n email,\n phone,\n items: lineItems.map((item) => ({\n description: item.description,\n quantity: item.quantity,\n amount: item.amount_total,\n })),\n totalAmount,\n shippingCostAmount,\n shipping,\n customFields: cf,\n });\n\n let emailResult: WebhookEmailResult;\n try {\n const resend = new Resend(resendKey);\n const { error } = await resend.emails.send({\n from: orderEmailFrom,\n to: email,\n ...(orderEmailBcc ? { bcc: orderEmailBcc } : {}),\n ...(orderEmailReplyTo ? { reply_to: orderEmailReplyTo } : {}),\n subject,\n html,\n });\n if (error) {\n const details = providerErrorDetails(error);\n console.error(\"[webhook] Resend error:\", details);\n emailResult = {\n sessionId: session.id,\n status: \"failed\",\n ...(details.message ? { errorMessage: details.message } : {}),\n };\n } else {\n emailResult = { sessionId: session.id, status: \"sent\" };\n }\n } catch (e) {\n const details = providerErrorDetails(e);\n console.error(\"[webhook] Resend exception:\", details);\n emailResult = {\n sessionId: session.id,\n status: \"failed\",\n ...(details.message ? { errorMessage: details.message } : {}),\n };\n }\n\n if (recordEmailResult) await recordEmailResult(emailResult);\n\n return new Response(\"OK\", { status: 200 });\n };\n}\n"],"mappings":"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","names":["paymentIntent"]}