@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 @@
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":[]}
@@ -107,4 +107,4 @@ async function notifyUnshipped(orders, env, { alertFrom, alertTo, alertSubject,
107
107
  export {
108
108
  notifyUnshipped
109
109
  };
110
- //# sourceMappingURL=chunk-JWTXQAOL.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":";AAAA,SAAS,cAAc;AAGvB,IAAM,SAAS,KAAK,KAAK,KAAK;AA2B9B,SAAS,UAAU,OAAsB;AACvC,MAAI,SAAS,KAAM,QAAO;AAC1B,SAAO,IAAI,KAAK,aAAa,SAAS;AAAA,IACpC,OAAO;AAAA,IACP,UAAU;AAAA,IACV,uBAAuB;AAAA,EACzB,CAAC,EAAE,OAAO,KAAK;AACjB;AAEA,SAAS,eAAe,OAA+B;AACrD,MAAI,SAAS,QAAQ,UAAU,GAAI,QAAO;AAC1C,QAAM,YAAY,OAAO,UAAU,WAAW,OAAO,KAAK,IAAI;AAC9D,SAAO,OAAO,SAAS,SAAS,IAAI,YAAY;AAClD;AAEA,SAAS,WAAW,WAA0B,UAA6B;AACzE,MAAI,CAAC,UAAW,QAAO;AAEvB,MAAI;AACF,UAAM,SAAkB,KAAK,MAAM,SAAS;AAC5C,QAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,OAAO,WAAW,EAAG,QAAO;AAE1D,WAAO,OACJ,IAAI,CAAC,SAAS;AACb,UAAI,OAAO,SAAS,YAAY,SAAS,KAAM,QAAO;AACtD,YAAM,EAAE,KAAK,SAAS,IAAI;AAC1B,YAAM,aAAa,OAAO,QAAQ,WAAW,MAAM;AACnD,YAAM,cAAc,cAAc,SAAS,UAAU,IAAI,SAAS,UAAU,EAAE,OAAO,cAAc;AACnG,YAAM,eAAe,OAAO,aAAa,YAAY,OAAO,SAAS,QAAQ,IAAI,WAAW;AAC5F,aAAO,GAAG,WAAW,MAAM,YAAY;AAAA,IACzC,CAAC,EACA,OAAO,CAAC,SAAyB,QAAQ,IAAI,CAAC,EAC9C,KAAK,IAAI;AAAA,EACd,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,WAAW,OAAe;AACjC,SAAO,MACJ,WAAW,KAAK,OAAO,EACvB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,QAAQ,EACxB,WAAW,KAAK,OAAO;AAC5B;AAEA,SAAS,wBAAwB,OAAgB;AAC/C,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,QAAQ;AACd,QAAM,UAAU;AAAA,IACd,OAAO,MAAM,SAAS,WAAW,QAAQ,MAAM,IAAI,KAAK;AAAA,IACxD,OAAO,MAAM,eAAe,WAAW,cAAc,MAAM,UAAU,KAAK;AAAA,EAC5E,EAAE,OAAO,CAAC,WAA6B,WAAW,IAAI;AAEtD,SAAO,QAAQ,SAAS,IAAI,KAAK,QAAQ,KAAK,IAAI,CAAC,MAAM;AAC3D;AAEA,SAAS,gBAAgB,OAAuB,UAA6B,MAAM,KAAK,IAAI,GAAG;AAC7F,QAAM,YAAY,eAAe,MAAM,UAAU;AACjD,QAAM,cAAc,aAAa,OAAO,MAAM,OAAO,KAAK,OAAO,MAAM,aAAa,MAAM,CAAC;AAC3F,QAAM,OAAO,MAAM,MAAM,KAAK,KAAK;AACnC,SAAO,GAAG,IAAI,MAAM,WAAW,MAAM,YAAY,QAAQ,CAAC,MAAM,UAAU,MAAM,MAAM,CAAC,MAAM,WAAW;AAC1G;AAEA,eAAsB,gBACpB,QACA,KACA,EAAE,WAAW,SAAS,cAAc,WAAW,CAAC,EAAE,GAClD;AACA,MAAI,OAAO,WAAW,EAAG;AACzB,MAAI,CAAC,IAAI,eAAgB,OAAM,IAAI,MAAM,kCAAkC;AAE3E,QAAM,QAAQ,OAAO,IAAI,CAAC,UAAU,gBAAgB,OAAO,QAAQ,CAAC;AACpE,QAAM,UAAU,CAAC,8GAA8B,IAAI,GAAG,KAAK,EAAE,KAAK,IAAI;AAEtE,MAAI,IAAI,qBAAqB;AAC3B,UAAM,kBAAkB,MAAM,MAAM,IAAI,qBAAqB;AAAA,MAC3D,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,SAAS,kBAAkB,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC;AAAA,IACnE,CAAC;AACD,QAAI,CAAC,gBAAgB,IAAI;AACvB,YAAM,IAAI,MAAM,gCAAgC,gBAAgB,MAAM,EAAE;AAAA,IAC1E;AAAA,EACF;AAEA,QAAM,OAAO,MACV,IAAI,CAAC,SAAS;AACb,UAAM,CAAC,MAAM,OAAO,QAAQ,WAAW,IAAI,KAAK,MAAM,KAAK;AAC3D,WAAO;AAAA,gEACmD,WAAW,QAAQ,EAAE,CAAC;AAAA,gEACtB,WAAW,SAAS,EAAE,CAAC;AAAA,iFACN,WAAW,UAAU,EAAE,CAAC;AAAA,iFACxB,WAAW,eAAe,EAAE,CAAC;AAAA;AAAA,EAE1G,CAAC,EACA,KAAK,EAAE;AAEV,QAAM,SAAS,IAAI,OAAO,IAAI,cAAc;AAC5C,QAAM,EAAE,MAAM,IAAI,MAAM,OAAO,OAAO,KAAK;AAAA,IACzC,MAAM;AAAA,IACN,IAAI;AAAA,IACJ,SAAS;AAAA,IACT,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,aAeG,IAAI;AAAA;AAAA;AAAA;AAAA,IAIb,MAAM;AAAA,EACR,CAAC;AACD,MAAI,OAAO;AACT,UAAM,IAAI,MAAM,6BAA6B,wBAAwB,KAAK,CAAC,EAAE;AAAA,EAC/E;AACF;","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"]}