@puzzle-ui/storage-d1 0.0.0-stage → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1.adapter.js","names":[],"sources":["../src/d1.adapter.ts"],"sourcesContent":["import {\n StorageAtomicityError,\n StorageConflictError,\n StorageDuplicateError,\n StorageValidationError,\n type StorageAdapter,\n type StorageBatchOperation,\n type StorageBatchResult,\n type StorageCollection,\n type StorageCollectionName,\n type StorageCondition,\n type StorageCursor,\n type StorageCursorQuery,\n type StoragePage,\n type StorageQuery,\n type StorageRecord,\n type StorageUpdate,\n type StorageWhere,\n type StorageCapabilities,\n type StorageCapabilityReport,\n validateQuery,\n} from \"@puzzle-ui/storage\";\n\nimport type {\n D1DatabaseLike,\n D1PreparedStatementLike,\n D1ResultLike,\n D1StorageAdapterOptions,\n} from \"./d1.types\";\n\ntype D1Row = {\n id: string;\n created_at: string;\n updated_at: string;\n revision: number;\n data: string;\n};\n\ntype CursorPayload = {\n readonly values: readonly unknown[];\n};\n\nconst DATE_MARKER = \"__puzzle_storage_date__\";\n\nconst serialize = (value: unknown): string =>\n JSON.stringify(value, (_key, current: unknown) =>\n current instanceof Date\n ? { [DATE_MARKER]: current.toISOString() }\n : current,\n );\n\nconst deserialize = <T>(value: string): T =>\n JSON.parse(value, (_key, current: unknown) => {\n if (\n typeof current === \"object\" &&\n current !== null &&\n DATE_MARKER in current\n ) {\n const date = current[DATE_MARKER];\n\n if (typeof date !== \"string\") {\n throw new StorageValidationError(\"Invalid serialized D1 date.\");\n }\n\n return new Date(date);\n }\n\n return current;\n }) as T;\n\nconst encodeCursor = (payload: CursorPayload): StorageCursor => ({\n value: btoa(encodeURIComponent(JSON.stringify(payload))),\n});\n\nconst decodeCursor = (cursor: StorageCursor): CursorPayload => {\n try {\n return JSON.parse(decodeURIComponent(atob(cursor.value))) as CursorPayload;\n } catch {\n throw new StorageValidationError(\"Invalid D1 cursor.\");\n }\n};\n\nconst quoteIdentifier = (value: string): string => {\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {\n throw new StorageValidationError(`Invalid SQL identifier: ${value}`);\n }\n\n return `\"${value}\"`;\n};\n\nconst jsonPath = (field: string): string => {\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) {\n throw new StorageValidationError(`Unsupported JSON field name: ${field}`);\n }\n\n return `$.\"${field}\"`;\n};\n\nconst sqlField = (field: string): string => {\n if (field === \"id\") return \"id\";\n if (field === \"createdAt\") return \"created_at\";\n if (field === \"updatedAt\") return \"updated_at\";\n if (field === \"revision\") return \"revision\";\n\n return `json_extract(data, '${jsonPath(field)}')`;\n};\n\nconst sqlValue = (value: unknown): unknown =>\n value instanceof Date ? value.toISOString() : value;\n\nconst buildComparison = (\n field: string,\n comparison: {\n readonly operator: string;\n readonly value: unknown;\n },\n): { sql: string; values: unknown[] } => {\n const column = sqlField(field);\n const value = comparison.value;\n\n switch (comparison.operator) {\n case \"eq\":\n return value === null\n ? { sql: `${column} IS NULL`, values: [] }\n : { sql: `${column} = ?`, values: [sqlValue(value)] };\n\n case \"ne\":\n return value === null\n ? { sql: `${column} IS NOT NULL`, values: [] }\n : { sql: `${column} != ?`, values: [sqlValue(value)] };\n\n case \"gt\":\n return { sql: `${column} > ?`, values: [sqlValue(value)] };\n\n case \"gte\":\n return { sql: `${column} >= ?`, values: [sqlValue(value)] };\n\n case \"lt\":\n return { sql: `${column} < ?`, values: [sqlValue(value)] };\n\n case \"lte\":\n return { sql: `${column} <= ?`, values: [sqlValue(value)] };\n\n case \"exists\":\n return {\n sql: comparison.value ? `${column} IS NOT NULL` : `${column} IS NULL`,\n values: [],\n };\n\n case \"in\":\n case \"notIn\": {\n const values = value as readonly unknown[];\n\n if (values.length === 0) {\n return {\n sql: comparison.operator === \"in\" ? \"0 = 1\" : \"1 = 1\",\n values: [],\n };\n }\n\n const placeholders = values.map(() => \"?\").join(\", \");\n const operator = comparison.operator === \"in\" ? \"IN\" : \"NOT IN\";\n\n return {\n sql: `${column} ${operator} (${placeholders})`,\n values: values.map(sqlValue),\n };\n }\n\n default:\n throw new StorageValidationError(\n `Unsupported query operator: ${comparison.operator}`,\n );\n }\n};\n\nconst buildWhere = <T extends object>(\n where?: StorageWhere<T>,\n): { sql: string; values: unknown[] } => {\n if (!where) return { sql: \"1 = 1\", values: [] };\n\n const parts: string[] = [];\n const values: unknown[] = [];\n\n for (const [field, comparison] of Object.entries(where)) {\n if (field === \"and\" || field === \"or\") {\n const nested = (comparison as readonly StorageWhere<T>[]).map((item) =>\n buildWhere(item),\n );\n\n if (nested.length === 0) continue;\n\n parts.push(\n `(${nested\n .map((item) => item.sql)\n .join(field === \"and\" ? \" AND \" : \" OR \")})`,\n );\n\n for (const item of nested) values.push(...item.values);\n\n continue;\n }\n\n const value: unknown = comparison;\n\n if (\n typeof value !== \"object\" ||\n value === null ||\n !(\"operator\" in value) ||\n typeof value.operator !== \"string\" ||\n !(\"value\" in value)\n ) {\n throw new StorageValidationError(\n `Invalid query comparison for field: ${field}`,\n );\n }\n\n const result = buildComparison(field, {\n operator: value.operator,\n value: value.value,\n });\n\n parts.push(result.sql);\n values.push(...result.values);\n }\n\n return {\n sql: parts.length ? parts.join(\" AND \") : \"1 = 1\",\n values,\n };\n};\n\nconst buildOrderBy = <T extends StorageRecord>(\n orderBy?: StorageQuery<T>[\"orderBy\"],\n): string => {\n const items = (orderBy ?? []).map(\n (item) =>\n `${sqlField(String(item.field))} ${\n item.direction === \"desc\" ? \"DESC\" : \"ASC\"\n }`,\n );\n\n if (!items.some((item) => item.startsWith(\"id \"))) {\n items.push(\"id ASC\");\n }\n\n return items.join(\", \");\n};\n\nconst buildCursorWhere = <T extends StorageRecord>(\n orderBy: NonNullable<StorageQuery<T>[\"orderBy\"]>,\n values: readonly unknown[],\n): { sql: string; values: unknown[] } => {\n const fields = [\n ...orderBy.map((item) => ({\n field: String(item.field),\n direction: item.direction,\n })),\n ];\n\n if (!fields.some((item) => item.field === \"id\")) {\n fields.push({ field: \"id\", direction: \"asc\" });\n }\n\n const branches: string[] = [];\n const parameters: unknown[] = [];\n\n for (const [index, field] of fields.entries()) {\n const conditions: string[] = [];\n\n for (const [previous, prefix] of fields.slice(0, index).entries()) {\n conditions.push(`${sqlField(prefix.field)} = ?`);\n parameters.push(sqlValue(values[previous]));\n }\n\n const operator = field.direction === \"desc\" ? \"<\" : \">\";\n\n conditions.push(`${sqlField(field.field)} ${operator} ?`);\n parameters.push(sqlValue(values[index]));\n\n branches.push(`(${conditions.join(\" AND \")})`);\n }\n\n return {\n sql: `(${branches.join(\" OR \")})`,\n values: parameters,\n };\n};\n\nconst cleanChanges = <T extends StorageRecord>(\n changes: StorageUpdate<T>,\n): Record<string, unknown> => {\n const result = { ...changes } as Record<string, unknown>;\n\n delete result.id;\n delete result.createdAt;\n delete result.updatedAt;\n delete result.revision;\n\n return result;\n};\n\nconst toRecord = <T extends StorageRecord>(row: D1Row): T => {\n const data = deserialize<Record<string, unknown>>(row.data);\n\n return {\n ...data,\n id: row.id,\n createdAt: new Date(row.created_at),\n updatedAt: new Date(row.updated_at),\n revision: row.revision,\n } as T;\n};\n\nconst getChanges = (result: D1ResultLike): number =>\n Number(result.meta?.changes ?? 0);\n\nconst mapD1Error = (error: unknown): never => {\n const message = error instanceof Error ? error.message.toLowerCase() : \"\";\n\n if (\n message.includes(\"unique constraint\") ||\n message.includes(\"primary key\")\n ) {\n throw new StorageDuplicateError(\"d1_unique_constraint\");\n }\n\n throw error;\n};\n\nexport class D1StorageAdapter implements StorageAdapter {\n readonly capabilities: StorageCapabilities = {\n transactions: true,\n atomicBatch: true,\n conditionalWrites: true,\n uniqueConstraints: true,\n upsert: true,\n cursorPagination: true,\n migrations: true,\n ttlIndexes: false,\n };\n\n private readonly database: D1DatabaseLike;\n private readonly prefix: string;\n\n constructor(options: D1StorageAdapterOptions) {\n this.database = options.database;\n this.prefix = options.tablePrefix ?? \"puzzle_\";\n }\n\n getCapabilities(): StorageCapabilityReport {\n return {\n capabilities: this.capabilities,\n limitations: [\n \"D1 does not provide native TTL indexes. Expiration cleanup must be handled by an application or scheduled job.\",\n ],\n };\n }\n\n private table(collection: StorageCollectionName): string {\n return quoteIdentifier(`${this.prefix}${collection}`);\n }\n\n private prepare(\n sql: string,\n values: readonly unknown[] = [],\n ): D1PreparedStatementLike {\n return this.database.prepare(sql).bind(...values);\n }\n\n private async run(\n sql: string,\n values: readonly unknown[] = [],\n ): Promise<D1ResultLike> {\n return this.prepare(sql, values).run();\n }\n\n private async first<T>(\n sql: string,\n values: readonly unknown[] = [],\n ): Promise<T | null> {\n return this.prepare(sql, values).first<T>();\n }\n\n private async all<T>(\n sql: string,\n values: readonly unknown[] = [],\n ): Promise<readonly T[]> {\n const result = await this.prepare(sql, values).all<T>();\n\n return result.results ?? [];\n }\n\n private insertStatement<T extends StorageRecord>(\n collection: StorageCollectionName,\n record: Parameters<StorageAdapter[\"insert\"]>[1],\n ): {\n sql: string;\n values: readonly unknown[];\n record: T;\n } {\n const input = record as StorageRecord & {\n id: string;\n [key: string]: unknown;\n };\n\n const now = new Date();\n const createdAt = input.createdAt ?? now;\n const updatedAt = input.updatedAt ?? now;\n const revision = input.revision ?? 1;\n\n const {\n id,\n createdAt: _createdAt,\n updatedAt: _updatedAt,\n revision: _revision,\n ...data\n } = input;\n\n const result = {\n id,\n createdAt,\n updatedAt,\n revision,\n ...data,\n } as T;\n\n return {\n sql: `INSERT INTO ${this.table(collection)}\n (id, created_at, updated_at, revision, data)\n VALUES (?, ?, ?, ?, ?)`,\n values: [\n id,\n createdAt.toISOString(),\n updatedAt.toISOString(),\n revision,\n serialize(data),\n ],\n record: result,\n };\n }\n\n async registerCollection<T extends StorageRecord>(\n definition: StorageCollection<T>,\n ): Promise<void> {\n const table = this.table(definition.name);\n\n await this.run(`\n CREATE TABLE IF NOT EXISTS ${table} (\n id TEXT PRIMARY KEY NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n revision INTEGER NOT NULL CHECK (revision > 0),\n data TEXT NOT NULL CHECK (json_valid(data))\n )\n `);\n\n for (const index of definition.indexes ?? []) {\n const fields = index.fields.map((field) => {\n const fieldName = String(field);\n return sqlField(fieldName);\n });\n\n const unique = index.unique ? \"UNIQUE \" : \"\";\n const name = quoteIdentifier(\n `${this.prefix}${definition.name}_${index.name}`,\n );\n\n await this.run(`\n CREATE ${unique}INDEX IF NOT EXISTS ${name}\n ON ${table} (${fields.join(\", \")})\n `);\n }\n }\n\n async findById<T extends StorageRecord>(\n collection: StorageCollectionName,\n id: string,\n ): Promise<T | null> {\n const row = await this.first<D1Row>(\n `SELECT * FROM ${this.table(collection)} WHERE id = ? LIMIT 1`,\n [id],\n );\n\n return row ? toRecord<T>(row) : null;\n }\n\n async findOne<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T>,\n ): Promise<T | null> {\n validateQuery(query);\n\n const where = buildWhere(query.where);\n\n const row = await this.first<D1Row>(\n `SELECT * FROM ${this.table(collection)}\n WHERE ${where.sql}\n ORDER BY ${buildOrderBy(query.orderBy)}\n LIMIT 1`,\n where.values,\n );\n\n return row ? toRecord<T>(row) : null;\n }\n\n async findMany<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T> = {},\n ): Promise<readonly T[]> {\n validateQuery(query);\n\n const where = buildWhere(query.where);\n\n const values = [...where.values];\n let pagination = \"\";\n\n if (query.limit !== undefined) {\n pagination += \" LIMIT ?\";\n values.push(query.limit);\n } else if (query.offset !== undefined) {\n pagination += \" LIMIT -1\";\n }\n\n if (query.offset !== undefined) {\n pagination += \" OFFSET ?\";\n values.push(query.offset);\n }\n\n const rows = await this.all<D1Row>(\n `SELECT * FROM ${this.table(collection)}\n WHERE ${where.sql}\n ORDER BY ${buildOrderBy(query.orderBy)}\n ${pagination}`,\n values,\n );\n\n return rows.map((row) => toRecord<T>(row));\n }\n\n async findPage<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageCursorQuery<T>,\n ): Promise<StoragePage<T>> {\n if (!Number.isInteger(query.first) || query.first < 1) {\n throw new StorageValidationError(\n \"Cursor page size must be a positive integer.\",\n );\n }\n\n const orderBy = query.orderBy ?? [\n { field: \"id\" as keyof T & string, direction: \"asc\" as const },\n ];\n\n const base = buildWhere(query.where);\n let whereSql = base.sql;\n const values = [...base.values];\n\n if (query.after) {\n const cursor = decodeCursor(query.after);\n const after = buildCursorWhere(orderBy, cursor.values);\n\n whereSql = `(${whereSql}) AND ${after.sql}`;\n values.push(...after.values);\n }\n\n values.push(query.first + 1);\n\n const rows = await this.all<D1Row>(\n `SELECT * FROM ${this.table(collection)}\n WHERE ${whereSql}\n ORDER BY ${buildOrderBy(orderBy)}\n LIMIT ?`,\n values,\n );\n\n const hasNextPage = rows.length > query.first;\n const items = rows.slice(0, query.first);\n const last = items.at(-1);\n\n const cursorFields = [...orderBy.map((item) => String(item.field))];\n\n if (!cursorFields.includes(\"id\")) cursorFields.push(\"id\");\n\n const nextCursor =\n hasNextPage && last\n ? encodeCursor({\n values: cursorFields.map((field) => {\n if (field === \"id\") return last.id;\n if (field === \"createdAt\") return last.created_at;\n if (field === \"updatedAt\") return last.updated_at;\n if (field === \"revision\") return last.revision;\n\n return deserialize<Record<string, unknown>>(last.data)[field];\n }),\n })\n : null;\n\n return {\n items: items.map((row) => toRecord<T>(row)),\n hasNextPage,\n nextCursor,\n };\n }\n\n async insert<T extends StorageRecord>(\n collection: StorageCollectionName,\n record: Parameters<StorageAdapter[\"insert\"]>[1],\n ): Promise<T> {\n const statement = this.insertStatement<T>(collection, record);\n\n try {\n await this.run(statement.sql, statement.values);\n return statement.record;\n } catch (error) {\n return mapD1Error(error);\n }\n }\n\n async upsert<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T>,\n record: Parameters<StorageAdapter[\"insert\"]>[1],\n ): Promise<T> {\n const existing = await this.findOne<T>(collection, query);\n\n if (existing) {\n const changes = {\n ...(record as Record<string, unknown>),\n } as StorageUpdate<T>;\n\n const updated = await this.update<T>(collection, existing.id, changes);\n\n if (!updated) throw new StorageConflictError();\n\n return updated;\n }\n\n return this.insert<T>(collection, record);\n }\n\n private buildUpdate(\n collection: StorageCollectionName,\n id: string,\n changes: Record<string, unknown>,\n condition?: StorageCondition,\n ): { sql: string; values: unknown[] } {\n const entries = Object.entries(changes);\n const expressions = entries.map(\n ([field]) => `data = json_set(data, ?, json(?))`,\n );\n\n const assignments: string[] = [];\n\n if (entries.length > 0) {\n assignments.push(\n `data = ${entries.reduce(\n (expression, _entry, index) => `json_set(${expression}, ?, json(?))`,\n \"data\",\n )}`,\n );\n }\n\n assignments.push(\"updated_at = ?\");\n assignments.push(\"revision = revision + 1\");\n\n const where = [\"id = ?\"];\n const values: unknown[] = [];\n\n for (const [field, value] of entries) {\n values.push(jsonPath(field), serialize(value));\n }\n\n values.push(new Date().toISOString(), id);\n\n if (condition?.expectedRevision !== undefined) {\n where.push(\"revision = ?\");\n values.push(condition.expectedRevision);\n }\n\n if (condition?.expectedUpdatedAt !== undefined) {\n where.push(\"updated_at = ?\");\n values.push(condition.expectedUpdatedAt.toISOString());\n }\n\n return {\n sql: `UPDATE ${this.table(collection)}\n SET ${assignments.join(\", \")}\n WHERE ${where.join(\" AND \")}`,\n values,\n };\n }\n\n async update<T extends StorageRecord>(\n collection: StorageCollectionName,\n id: string,\n changes: StorageUpdate<T>,\n condition?: StorageCondition,\n ): Promise<T | null> {\n const clean = cleanChanges(changes);\n const statement = this.buildUpdate(collection, id, clean, condition);\n\n const result = await this.run(statement.sql, statement.values);\n\n if (getChanges(result) === 0) {\n if (condition && (await this.findById(collection, id))) {\n throw new StorageConflictError();\n }\n\n return null;\n }\n\n return this.findById<T>(collection, id);\n }\n\n async updateMany<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T>,\n changes: StorageUpdate<T>,\n ): Promise<number> {\n validateQuery(query);\n\n const clean = cleanChanges(changes);\n const entries = Object.entries(clean);\n const assignments = entries.length\n ? `data = ${entries.reduce(\n (expression) => `json_set(${expression}, ?, json(?))`,\n \"data\",\n )}, `\n : \"\";\n\n const values: unknown[] = [];\n\n for (const [field, value] of entries) {\n values.push(jsonPath(field), serialize(value));\n }\n\n values.push(new Date().toISOString());\n\n const where = buildWhere(query.where);\n values.push(...where.values);\n\n const result = await this.run(\n `UPDATE ${this.table(collection)}\n SET ${assignments}updated_at = ?, revision = revision + 1\n WHERE ${where.sql}`,\n values,\n );\n\n return getChanges(result);\n }\n\n async delete<T extends StorageRecord>(\n collection: StorageCollectionName,\n id: string,\n condition?: StorageCondition,\n ): Promise<boolean> {\n const where = [\"id = ?\"];\n const values: unknown[] = [id];\n\n if (condition?.expectedRevision !== undefined) {\n where.push(\"revision = ?\");\n values.push(condition.expectedRevision);\n }\n\n if (condition?.expectedUpdatedAt !== undefined) {\n where.push(\"updated_at = ?\");\n values.push(condition.expectedUpdatedAt.toISOString());\n }\n\n const result = await this.run(\n `DELETE FROM ${this.table(collection)}\n WHERE ${where.join(\" AND \")}`,\n values,\n );\n\n if (getChanges(result) > 0) return true;\n\n if (condition && (await this.findById(collection, id))) {\n throw new StorageConflictError();\n }\n\n return false;\n }\n\n async deleteMany<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T>,\n ): Promise<number> {\n validateQuery(query);\n\n const where = buildWhere(query.where);\n const result = await this.run(\n `DELETE FROM ${this.table(collection)} WHERE ${where.sql}`,\n where.values,\n );\n\n return getChanges(result);\n }\n\n async exists<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T>,\n ): Promise<boolean> {\n validateQuery(query);\n\n const where = buildWhere(query.where);\n const result = await this.first<{ found: number }>(\n `SELECT 1 AS found FROM ${this.table(collection)}\n WHERE ${where.sql} LIMIT 1`,\n where.values,\n );\n\n return result !== null;\n }\n\n async count<T extends StorageRecord>(\n collection: StorageCollectionName,\n query: StorageQuery<T> = {},\n ): Promise<number> {\n validateQuery(query);\n\n const where = buildWhere(query.where);\n const result = await this.first<{ total: number }>(\n `SELECT COUNT(*) AS total FROM ${this.table(collection)}\n WHERE ${where.sql}`,\n where.values,\n );\n\n return Number(result?.total ?? 0);\n }\n\n async atomicBatch<T extends StorageRecord>(\n operations: readonly StorageBatchOperation<T>[],\n ): Promise<StorageBatchResult<T>> {\n const assertionTable = quoteIdentifier(`${this.prefix}atomic_assertions`);\n\n await this.run(`\n CREATE TABLE IF NOT EXISTS ${assertionTable} (\n value INTEGER NOT NULL CHECK (value = 1)\n )\n `);\n\n const statements: D1PreparedStatementLike[] = [];\n const resultIndexes: number[] = [];\n\n for (const operation of operations) {\n const table = this.table(operation.collection);\n\n if (\n operation.type !== \"insert\" &&\n (operation.condition?.expectedRevision !== undefined ||\n operation.condition?.expectedUpdatedAt !== undefined)\n ) {\n const conditions = [\"id = ?\"];\n const values: unknown[] = [operation.id];\n\n if (operation.condition.expectedRevision !== undefined) {\n conditions.push(\"revision = ?\");\n values.push(operation.condition.expectedRevision);\n }\n\n if (operation.condition.expectedUpdatedAt !== undefined) {\n conditions.push(\"updated_at = ?\");\n values.push(operation.condition.expectedUpdatedAt.toISOString());\n }\n\n statements.push(\n this.prepare(\n `INSERT INTO ${assertionTable}(value)\n SELECT CASE WHEN EXISTS (\n SELECT 1 FROM ${table}\n WHERE ${conditions.join(\" AND \")}\n ) THEN 1 ELSE 0 END`,\n values,\n ),\n );\n\n statements.push(this.prepare(`DELETE FROM ${assertionTable}`));\n }\n\n if (operation.type === \"insert\") {\n const statement = this.insertStatement<T>(\n operation.collection,\n operation.record,\n );\n\n statements.push(this.prepare(statement.sql, statement.values));\n\n resultIndexes.push(statements.length - 1);\n continue;\n }\n\n if (operation.type === \"update\") {\n const statement = this.buildUpdate(\n operation.collection,\n operation.id,\n cleanChanges(operation.changes),\n operation.condition,\n );\n\n statements.push(this.prepare(statement.sql, statement.values));\n\n resultIndexes.push(statements.length - 1);\n continue;\n }\n\n const conditions = [\"id = ?\"];\n const values: unknown[] = [operation.id];\n\n if (operation.condition?.expectedRevision !== undefined) {\n conditions.push(\"revision = ?\");\n values.push(operation.condition.expectedRevision);\n }\n\n if (operation.condition?.expectedUpdatedAt !== undefined) {\n conditions.push(\"updated_at = ?\");\n values.push(operation.condition.expectedUpdatedAt.toISOString());\n }\n\n statements.push(\n this.prepare(\n `DELETE FROM ${table} WHERE ${conditions.join(\" AND \")}`,\n values,\n ),\n );\n\n resultIndexes.push(statements.length - 1);\n }\n\n try {\n const batchResults = await this.database.batch(statements);\n const results: StorageBatchResult<T>[\"results\"][number][] = [];\n\n for (const [index, operation] of operations.entries()) {\n const resultIndex = resultIndexes[index];\n const result =\n resultIndex === undefined ? undefined : batchResults[resultIndex];\n\n if (!result) {\n throw new StorageAtomicityError(\n \"D1 batch did not return a result for every operation.\",\n );\n }\n\n if (operation.type === \"insert\") {\n const input = operation.record as StorageRecord & {\n id: string;\n };\n\n const inserted = await this.findById<T>(\n operation.collection,\n input.id,\n );\n\n if (!inserted) {\n throw new StorageAtomicityError(\n \"D1 batch committed but the inserted record could not be read.\",\n );\n }\n\n results.push({ type: \"insert\", record: inserted });\n continue;\n }\n\n if (operation.type === \"update\") {\n results.push({\n type: \"update\",\n record: await this.findById<T>(operation.collection, operation.id),\n });\n continue;\n }\n\n results.push({\n type: \"delete\",\n deleted: getChanges(result) > 0,\n });\n }\n\n return { results };\n } catch (error) {\n const message = error instanceof Error ? error.message.toLowerCase() : \"\";\n\n if (message.includes(\"check constraint\")) {\n throw new StorageConflictError();\n }\n\n return mapD1Error(error);\n }\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,194 @@
1
+ require("@puzzle-ui/storage");
2
+ // src/d1.migrations.ts
3
+ const sleep = (duration) => new Promise((resolve) => setTimeout(resolve, duration));
4
+ const quote = (value) => {
5
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) throw new Error(`Invalid SQL identifier: ${value}`);
6
+ return `"${value}"`;
7
+ };
8
+ var D1StorageMigrationAdapter = class {
9
+ database;
10
+ prefix;
11
+ lockDurationMs;
12
+ constructor(options) {
13
+ this.database = options.database;
14
+ this.prefix = options.tablePrefix ?? "puzzle_";
15
+ this.lockDurationMs = options.lockDurationMs ?? 6e5;
16
+ }
17
+ get migrationsTable() {
18
+ return quote(`${this.prefix}migrations`);
19
+ }
20
+ get lockTable() {
21
+ return quote(`${this.prefix}migration_lock`);
22
+ }
23
+ async run(sql, values = []) {
24
+ return this.database.prepare(sql).bind(...values).run();
25
+ }
26
+ async ensureTables() {
27
+ await this.run(`
28
+ CREATE TABLE IF NOT EXISTS ${this.migrationsTable} (
29
+ id TEXT PRIMARY KEY NOT NULL,
30
+ version INTEGER NOT NULL,
31
+ description TEXT NOT NULL,
32
+ status TEXT NOT NULL,
33
+ started_at TEXT,
34
+ completed_at TEXT,
35
+ error TEXT
36
+ )
37
+ `);
38
+ await this.run(`
39
+ CREATE TABLE IF NOT EXISTS ${this.lockTable} (
40
+ id INTEGER PRIMARY KEY CHECK (id = 1),
41
+ owner TEXT NOT NULL,
42
+ expires_at INTEGER NOT NULL
43
+ )
44
+ `);
45
+ await this.run(`
46
+ INSERT OR IGNORE INTO ${this.lockTable}
47
+ (id, owner, expires_at)
48
+ VALUES (1, '', 0)
49
+ `);
50
+ }
51
+ async getMigrationStatus() {
52
+ await this.ensureTables();
53
+ const migrations = ((await this.database.prepare(`SELECT id, version, description, status,
54
+ started_at, completed_at, error
55
+ FROM ${this.migrationsTable}
56
+ ORDER BY version ASC`).all()).results ?? []).map((row) => ({
57
+ id: String(row.id),
58
+ version: Number(row.version),
59
+ description: String(row.description),
60
+ status: String(row.status),
61
+ ...row.started_at ? { startedAt: new Date(String(row.started_at)) } : {},
62
+ ...row.completed_at ? { completedAt: new Date(String(row.completed_at)) } : {},
63
+ ...row.error ? { error: String(row.error) } : {}
64
+ }));
65
+ return {
66
+ currentVersion: migrations.reduce((version, migration) => migration.status === "completed" ? Math.max(version, migration.version) : version, 0),
67
+ migrations
68
+ };
69
+ }
70
+ async acquireLock(owner) {
71
+ const deadline = Date.now() + this.lockDurationMs;
72
+ while (Date.now() < deadline) {
73
+ const now = Date.now();
74
+ const expiresAt = now + this.lockDurationMs;
75
+ const result = await this.run(`UPDATE ${this.lockTable}
76
+ SET owner = ?, expires_at = ?
77
+ WHERE id = 1 AND expires_at < ?`, [
78
+ owner,
79
+ expiresAt,
80
+ now
81
+ ]);
82
+ if (Number(result.meta?.changes ?? 0) === 1) return;
83
+ await sleep(50);
84
+ }
85
+ throw new Error("Timed out while acquiring the D1 migration lock.");
86
+ }
87
+ async execute(operation) {
88
+ switch (operation.type) {
89
+ case "createCollection": {
90
+ const table = quote(`${this.prefix}${operation.collection}`);
91
+ await this.run(`
92
+ CREATE TABLE IF NOT EXISTS ${table} (
93
+ id TEXT PRIMARY KEY NOT NULL,
94
+ created_at TEXT NOT NULL,
95
+ updated_at TEXT NOT NULL,
96
+ revision INTEGER NOT NULL CHECK (revision > 0),
97
+ data TEXT NOT NULL CHECK (json_valid(data))
98
+ )
99
+ `);
100
+ return;
101
+ }
102
+ case "dropCollection":
103
+ await this.run(`DROP TABLE IF EXISTS ${quote(`${this.prefix}${operation.collection}`)}`);
104
+ return;
105
+ case "createIndex": {
106
+ const table = quote(`${this.prefix}${operation.collection}`);
107
+ const name = quote(`${this.prefix}${operation.name}`);
108
+ const fields = operation.fields.map((field) => {
109
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) throw new Error(`Invalid index field: ${field}`);
110
+ if (field === "id" || field === "createdAt" || field === "updatedAt" || field === "revision") return {
111
+ id: "id",
112
+ createdAt: "created_at",
113
+ updatedAt: "updated_at",
114
+ revision: "revision"
115
+ }[field];
116
+ return `json_extract(data, '$."${field}"')`;
117
+ });
118
+ await this.run(`
119
+ CREATE ${operation.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${name}
120
+ ON ${table} (${fields.join(", ")})
121
+ `);
122
+ return;
123
+ }
124
+ case "dropIndex":
125
+ await this.run(`DROP INDEX IF EXISTS ${quote(`${this.prefix}${operation.name}`)}`);
126
+ return;
127
+ }
128
+ }
129
+ async runMigrations(migrations, options = {}) {
130
+ await this.ensureTables();
131
+ const owner = crypto.randomUUID();
132
+ await this.acquireLock(owner);
133
+ const applied = [];
134
+ const skipped = [];
135
+ try {
136
+ const sorted = [...migrations].sort((a, b) => a.version - b.version);
137
+ for (const migration of sorted) {
138
+ if (options.targetVersion !== void 0 && migration.version > options.targetVersion) {
139
+ skipped.push(migration.version);
140
+ continue;
141
+ }
142
+ if ((await this.database.prepare(`SELECT status FROM ${this.migrationsTable}
143
+ WHERE id = ? LIMIT 1`).bind(migration.id).first())?.status === "completed") {
144
+ skipped.push(migration.version);
145
+ continue;
146
+ }
147
+ if (options.dryRun) {
148
+ applied.push(migration.version);
149
+ continue;
150
+ }
151
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
152
+ await this.run(`INSERT INTO ${this.migrationsTable}
153
+ (id, version, description, status, started_at)
154
+ VALUES (?, ?, ?, 'running', ?)
155
+ ON CONFLICT(id) DO UPDATE SET
156
+ version = excluded.version,
157
+ description = excluded.description,
158
+ status = 'running',
159
+ started_at = excluded.started_at,
160
+ completed_at = NULL,
161
+ error = NULL`, [
162
+ migration.id,
163
+ migration.version,
164
+ migration.description,
165
+ startedAt
166
+ ]);
167
+ try {
168
+ await migration.up({ execute: (operation) => this.execute(operation) });
169
+ await this.run(`UPDATE ${this.migrationsTable}
170
+ SET status = 'completed', completed_at = ?, error = NULL
171
+ WHERE id = ?`, [(/* @__PURE__ */ new Date()).toISOString(), migration.id]);
172
+ applied.push(migration.version);
173
+ } catch (error) {
174
+ await this.run(`UPDATE ${this.migrationsTable}
175
+ SET status = 'failed', error = ?
176
+ WHERE id = ?`, [error instanceof Error ? error.message : String(error), migration.id]);
177
+ throw error;
178
+ }
179
+ }
180
+ } finally {
181
+ await this.run(`UPDATE ${this.lockTable}
182
+ SET owner = '', expires_at = 0
183
+ WHERE id = 1 AND owner = ?`, [owner]);
184
+ }
185
+ return {
186
+ applied,
187
+ skipped,
188
+ currentVersion: (await this.getMigrationStatus()).currentVersion
189
+ };
190
+ }
191
+ };
192
+ exports.D1StorageMigrationAdapter = D1StorageMigrationAdapter;
193
+
194
+ //# sourceMappingURL=d1.migrations.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1.migrations.cjs","names":[],"sources":["../src/d1.migrations.ts"],"sourcesContent":["import {\n type StorageMigration,\n type StorageMigrationAdapter,\n type StorageMigrationOperation,\n type StorageMigrationOptions,\n type StorageMigrationRecord,\n type StorageMigrationResult,\n} from \"@puzzle-ui/storage\";\n\nimport type { D1DatabaseLike, D1StorageMigrationOptions } from \"./d1.types\";\n\nconst sleep = (duration: number) =>\n new Promise((resolve) => setTimeout(resolve, duration));\n\nconst quote = (value: string) => {\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {\n throw new Error(`Invalid SQL identifier: ${value}`);\n }\n\n return `\"${value}\"`;\n};\n\nexport class D1StorageMigrationAdapter implements StorageMigrationAdapter {\n private readonly database: D1DatabaseLike;\n private readonly prefix: string;\n private readonly lockDurationMs: number;\n\n constructor(options: D1StorageMigrationOptions) {\n this.database = options.database;\n this.prefix = options.tablePrefix ?? \"puzzle_\";\n this.lockDurationMs = options.lockDurationMs ?? 600_000;\n }\n\n private get migrationsTable() {\n return quote(`${this.prefix}migrations`);\n }\n\n private get lockTable() {\n return quote(`${this.prefix}migration_lock`);\n }\n\n private async run(sql: string, values: readonly unknown[] = []) {\n return this.database\n .prepare(sql)\n .bind(...values)\n .run();\n }\n\n private async ensureTables() {\n await this.run(`\n CREATE TABLE IF NOT EXISTS ${this.migrationsTable} (\n id TEXT PRIMARY KEY NOT NULL,\n version INTEGER NOT NULL,\n description TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n completed_at TEXT,\n error TEXT\n )\n `);\n\n await this.run(`\n CREATE TABLE IF NOT EXISTS ${this.lockTable} (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n owner TEXT NOT NULL,\n expires_at INTEGER NOT NULL\n )\n `);\n\n await this.run(`\n INSERT OR IGNORE INTO ${this.lockTable}\n (id, owner, expires_at)\n VALUES (1, '', 0)\n `);\n }\n\n async getMigrationStatus(): Promise<{\n currentVersion: number;\n migrations: readonly StorageMigrationRecord[];\n }> {\n await this.ensureTables();\n\n const result = await this.database\n .prepare(\n `SELECT id, version, description, status,\n started_at, completed_at, error\n FROM ${this.migrationsTable}\n ORDER BY version ASC`,\n )\n .all<Record<string, unknown>>();\n\n const migrations = (result.results ?? []).map((row) => ({\n id: String(row.id),\n version: Number(row.version),\n description: String(row.description),\n status: String(row.status) as StorageMigrationRecord[\"status\"],\n ...(row.started_at\n ? { startedAt: new Date(String(row.started_at)) }\n : {}),\n ...(row.completed_at\n ? { completedAt: new Date(String(row.completed_at)) }\n : {}),\n ...(row.error ? { error: String(row.error) } : {}),\n }));\n\n return {\n currentVersion: migrations.reduce(\n (version, migration) =>\n migration.status === \"completed\"\n ? Math.max(version, migration.version)\n : version,\n 0,\n ),\n migrations,\n };\n }\n\n private async acquireLock(owner: string): Promise<void> {\n const deadline = Date.now() + this.lockDurationMs;\n\n while (Date.now() < deadline) {\n const now = Date.now();\n const expiresAt = now + this.lockDurationMs;\n\n const result = await this.run(\n `UPDATE ${this.lockTable}\n SET owner = ?, expires_at = ?\n WHERE id = 1 AND expires_at < ?`,\n [owner, expiresAt, now],\n );\n\n if (Number(result.meta?.changes ?? 0) === 1) return;\n\n await sleep(50);\n }\n\n throw new Error(\"Timed out while acquiring the D1 migration lock.\");\n }\n\n private async execute(operation: StorageMigrationOperation): Promise<void> {\n switch (operation.type) {\n case \"createCollection\": {\n const table = quote(`${this.prefix}${operation.collection}`);\n\n await this.run(`\n CREATE TABLE IF NOT EXISTS ${table} (\n id TEXT PRIMARY KEY NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n revision INTEGER NOT NULL CHECK (revision > 0),\n data TEXT NOT NULL CHECK (json_valid(data))\n )\n `);\n\n return;\n }\n\n case \"dropCollection\":\n await this.run(\n `DROP TABLE IF EXISTS ${quote(\n `${this.prefix}${operation.collection}`,\n )}`,\n );\n return;\n\n case \"createIndex\": {\n const table = quote(`${this.prefix}${operation.collection}`);\n const name = quote(`${this.prefix}${operation.name}`);\n\n const fields = operation.fields.map((field) => {\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) {\n throw new Error(`Invalid index field: ${field}`);\n }\n\n if (\n field === \"id\" ||\n field === \"createdAt\" ||\n field === \"updatedAt\" ||\n field === \"revision\"\n ) {\n const columns: Record<string, string> = {\n id: \"id\",\n createdAt: \"created_at\",\n updatedAt: \"updated_at\",\n revision: \"revision\",\n };\n\n return columns[field];\n }\n\n return `json_extract(data, '$.\"${field}\"')`;\n });\n\n await this.run(`\n CREATE ${operation.unique ? \"UNIQUE \" : \"\"}INDEX IF NOT EXISTS ${name}\n ON ${table} (${fields.join(\", \")})\n `);\n\n return;\n }\n\n case \"dropIndex\":\n await this.run(\n `DROP INDEX IF EXISTS ${quote(`${this.prefix}${operation.name}`)}`,\n );\n return;\n }\n }\n\n async runMigrations(\n migrations: readonly StorageMigration[],\n options: StorageMigrationOptions = {},\n ): Promise<StorageMigrationResult> {\n await this.ensureTables();\n\n const owner = crypto.randomUUID();\n await this.acquireLock(owner);\n\n const applied: number[] = [];\n const skipped: number[] = [];\n\n try {\n const sorted = [...migrations].sort((a, b) => a.version - b.version);\n\n for (const migration of sorted) {\n if (\n options.targetVersion !== undefined &&\n migration.version > options.targetVersion\n ) {\n skipped.push(migration.version);\n continue;\n }\n\n const existing = await this.database\n .prepare(\n `SELECT status FROM ${this.migrationsTable}\n WHERE id = ? LIMIT 1`,\n )\n .bind(migration.id)\n .first<{ status: string }>();\n\n if (existing?.status === \"completed\") {\n skipped.push(migration.version);\n continue;\n }\n\n if (options.dryRun) {\n applied.push(migration.version);\n continue;\n }\n\n const startedAt = new Date().toISOString();\n\n await this.run(\n `INSERT INTO ${this.migrationsTable}\n (id, version, description, status, started_at)\n VALUES (?, ?, ?, 'running', ?)\n ON CONFLICT(id) DO UPDATE SET\n version = excluded.version,\n description = excluded.description,\n status = 'running',\n started_at = excluded.started_at,\n completed_at = NULL,\n error = NULL`,\n [migration.id, migration.version, migration.description, startedAt],\n );\n\n try {\n await migration.up({\n execute: (operation) => this.execute(operation),\n });\n\n await this.run(\n `UPDATE ${this.migrationsTable}\n SET status = 'completed', completed_at = ?, error = NULL\n WHERE id = ?`,\n [new Date().toISOString(), migration.id],\n );\n\n applied.push(migration.version);\n } catch (error) {\n await this.run(\n `UPDATE ${this.migrationsTable}\n SET status = 'failed', error = ?\n WHERE id = ?`,\n [\n error instanceof Error ? error.message : String(error),\n migration.id,\n ],\n );\n\n throw error;\n }\n }\n } finally {\n await this.run(\n `UPDATE ${this.lockTable}\n SET owner = '', expires_at = 0\n WHERE id = 1 AND owner = ?`,\n [owner],\n );\n }\n\n const status = await this.getMigrationStatus();\n\n return {\n applied,\n skipped,\n currentVersion: status.currentVersion,\n };\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { D1StorageMigrationOptions } from "./d1.types.cjs";
2
+ import { StorageMigration, StorageMigrationAdapter, StorageMigrationOptions, StorageMigrationRecord, StorageMigrationResult } from "@puzzle-ui/storage";
3
+ export declare class D1StorageMigrationAdapter implements StorageMigrationAdapter {
4
+ private readonly database;
5
+ private readonly prefix;
6
+ private readonly lockDurationMs;
7
+ constructor(options: D1StorageMigrationOptions);
8
+ private get migrationsTable();
9
+ private get lockTable();
10
+ private run;
11
+ private ensureTables;
12
+ getMigrationStatus(): Promise<{
13
+ currentVersion: number;
14
+ migrations: readonly StorageMigrationRecord[];
15
+ }>;
16
+ private acquireLock;
17
+ private execute;
18
+ runMigrations(migrations: readonly StorageMigration[], options?: StorageMigrationOptions): Promise<StorageMigrationResult>;
19
+ }
@@ -0,0 +1,19 @@
1
+ import { D1StorageMigrationOptions } from "./d1.types.js";
2
+ import { StorageMigration, StorageMigrationAdapter, StorageMigrationOptions, StorageMigrationRecord, StorageMigrationResult } from "@puzzle-ui/storage";
3
+ export declare class D1StorageMigrationAdapter implements StorageMigrationAdapter {
4
+ private readonly database;
5
+ private readonly prefix;
6
+ private readonly lockDurationMs;
7
+ constructor(options: D1StorageMigrationOptions);
8
+ private get migrationsTable();
9
+ private get lockTable();
10
+ private run;
11
+ private ensureTables;
12
+ getMigrationStatus(): Promise<{
13
+ currentVersion: number;
14
+ migrations: readonly StorageMigrationRecord[];
15
+ }>;
16
+ private acquireLock;
17
+ private execute;
18
+ runMigrations(migrations: readonly StorageMigration[], options?: StorageMigrationOptions): Promise<StorageMigrationResult>;
19
+ }
@@ -0,0 +1,194 @@
1
+ import "@puzzle-ui/storage";
2
+ // src/d1.migrations.ts
3
+ const sleep = (duration) => new Promise((resolve) => setTimeout(resolve, duration));
4
+ const quote = (value) => {
5
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) throw new Error(`Invalid SQL identifier: ${value}`);
6
+ return `"${value}"`;
7
+ };
8
+ var D1StorageMigrationAdapter = class {
9
+ database;
10
+ prefix;
11
+ lockDurationMs;
12
+ constructor(options) {
13
+ this.database = options.database;
14
+ this.prefix = options.tablePrefix ?? "puzzle_";
15
+ this.lockDurationMs = options.lockDurationMs ?? 6e5;
16
+ }
17
+ get migrationsTable() {
18
+ return quote(`${this.prefix}migrations`);
19
+ }
20
+ get lockTable() {
21
+ return quote(`${this.prefix}migration_lock`);
22
+ }
23
+ async run(sql, values = []) {
24
+ return this.database.prepare(sql).bind(...values).run();
25
+ }
26
+ async ensureTables() {
27
+ await this.run(`
28
+ CREATE TABLE IF NOT EXISTS ${this.migrationsTable} (
29
+ id TEXT PRIMARY KEY NOT NULL,
30
+ version INTEGER NOT NULL,
31
+ description TEXT NOT NULL,
32
+ status TEXT NOT NULL,
33
+ started_at TEXT,
34
+ completed_at TEXT,
35
+ error TEXT
36
+ )
37
+ `);
38
+ await this.run(`
39
+ CREATE TABLE IF NOT EXISTS ${this.lockTable} (
40
+ id INTEGER PRIMARY KEY CHECK (id = 1),
41
+ owner TEXT NOT NULL,
42
+ expires_at INTEGER NOT NULL
43
+ )
44
+ `);
45
+ await this.run(`
46
+ INSERT OR IGNORE INTO ${this.lockTable}
47
+ (id, owner, expires_at)
48
+ VALUES (1, '', 0)
49
+ `);
50
+ }
51
+ async getMigrationStatus() {
52
+ await this.ensureTables();
53
+ const migrations = ((await this.database.prepare(`SELECT id, version, description, status,
54
+ started_at, completed_at, error
55
+ FROM ${this.migrationsTable}
56
+ ORDER BY version ASC`).all()).results ?? []).map((row) => ({
57
+ id: String(row.id),
58
+ version: Number(row.version),
59
+ description: String(row.description),
60
+ status: String(row.status),
61
+ ...row.started_at ? { startedAt: new Date(String(row.started_at)) } : {},
62
+ ...row.completed_at ? { completedAt: new Date(String(row.completed_at)) } : {},
63
+ ...row.error ? { error: String(row.error) } : {}
64
+ }));
65
+ return {
66
+ currentVersion: migrations.reduce((version, migration) => migration.status === "completed" ? Math.max(version, migration.version) : version, 0),
67
+ migrations
68
+ };
69
+ }
70
+ async acquireLock(owner) {
71
+ const deadline = Date.now() + this.lockDurationMs;
72
+ while (Date.now() < deadline) {
73
+ const now = Date.now();
74
+ const expiresAt = now + this.lockDurationMs;
75
+ const result = await this.run(`UPDATE ${this.lockTable}
76
+ SET owner = ?, expires_at = ?
77
+ WHERE id = 1 AND expires_at < ?`, [
78
+ owner,
79
+ expiresAt,
80
+ now
81
+ ]);
82
+ if (Number(result.meta?.changes ?? 0) === 1) return;
83
+ await sleep(50);
84
+ }
85
+ throw new Error("Timed out while acquiring the D1 migration lock.");
86
+ }
87
+ async execute(operation) {
88
+ switch (operation.type) {
89
+ case "createCollection": {
90
+ const table = quote(`${this.prefix}${operation.collection}`);
91
+ await this.run(`
92
+ CREATE TABLE IF NOT EXISTS ${table} (
93
+ id TEXT PRIMARY KEY NOT NULL,
94
+ created_at TEXT NOT NULL,
95
+ updated_at TEXT NOT NULL,
96
+ revision INTEGER NOT NULL CHECK (revision > 0),
97
+ data TEXT NOT NULL CHECK (json_valid(data))
98
+ )
99
+ `);
100
+ return;
101
+ }
102
+ case "dropCollection":
103
+ await this.run(`DROP TABLE IF EXISTS ${quote(`${this.prefix}${operation.collection}`)}`);
104
+ return;
105
+ case "createIndex": {
106
+ const table = quote(`${this.prefix}${operation.collection}`);
107
+ const name = quote(`${this.prefix}${operation.name}`);
108
+ const fields = operation.fields.map((field) => {
109
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) throw new Error(`Invalid index field: ${field}`);
110
+ if (field === "id" || field === "createdAt" || field === "updatedAt" || field === "revision") return {
111
+ id: "id",
112
+ createdAt: "created_at",
113
+ updatedAt: "updated_at",
114
+ revision: "revision"
115
+ }[field];
116
+ return `json_extract(data, '$."${field}"')`;
117
+ });
118
+ await this.run(`
119
+ CREATE ${operation.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${name}
120
+ ON ${table} (${fields.join(", ")})
121
+ `);
122
+ return;
123
+ }
124
+ case "dropIndex":
125
+ await this.run(`DROP INDEX IF EXISTS ${quote(`${this.prefix}${operation.name}`)}`);
126
+ return;
127
+ }
128
+ }
129
+ async runMigrations(migrations, options = {}) {
130
+ await this.ensureTables();
131
+ const owner = crypto.randomUUID();
132
+ await this.acquireLock(owner);
133
+ const applied = [];
134
+ const skipped = [];
135
+ try {
136
+ const sorted = [...migrations].sort((a, b) => a.version - b.version);
137
+ for (const migration of sorted) {
138
+ if (options.targetVersion !== void 0 && migration.version > options.targetVersion) {
139
+ skipped.push(migration.version);
140
+ continue;
141
+ }
142
+ if ((await this.database.prepare(`SELECT status FROM ${this.migrationsTable}
143
+ WHERE id = ? LIMIT 1`).bind(migration.id).first())?.status === "completed") {
144
+ skipped.push(migration.version);
145
+ continue;
146
+ }
147
+ if (options.dryRun) {
148
+ applied.push(migration.version);
149
+ continue;
150
+ }
151
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
152
+ await this.run(`INSERT INTO ${this.migrationsTable}
153
+ (id, version, description, status, started_at)
154
+ VALUES (?, ?, ?, 'running', ?)
155
+ ON CONFLICT(id) DO UPDATE SET
156
+ version = excluded.version,
157
+ description = excluded.description,
158
+ status = 'running',
159
+ started_at = excluded.started_at,
160
+ completed_at = NULL,
161
+ error = NULL`, [
162
+ migration.id,
163
+ migration.version,
164
+ migration.description,
165
+ startedAt
166
+ ]);
167
+ try {
168
+ await migration.up({ execute: (operation) => this.execute(operation) });
169
+ await this.run(`UPDATE ${this.migrationsTable}
170
+ SET status = 'completed', completed_at = ?, error = NULL
171
+ WHERE id = ?`, [(/* @__PURE__ */ new Date()).toISOString(), migration.id]);
172
+ applied.push(migration.version);
173
+ } catch (error) {
174
+ await this.run(`UPDATE ${this.migrationsTable}
175
+ SET status = 'failed', error = ?
176
+ WHERE id = ?`, [error instanceof Error ? error.message : String(error), migration.id]);
177
+ throw error;
178
+ }
179
+ }
180
+ } finally {
181
+ await this.run(`UPDATE ${this.lockTable}
182
+ SET owner = '', expires_at = 0
183
+ WHERE id = 1 AND owner = ?`, [owner]);
184
+ }
185
+ return {
186
+ applied,
187
+ skipped,
188
+ currentVersion: (await this.getMigrationStatus()).currentVersion
189
+ };
190
+ }
191
+ };
192
+ export { D1StorageMigrationAdapter };
193
+
194
+ //# sourceMappingURL=d1.migrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"d1.migrations.js","names":[],"sources":["../src/d1.migrations.ts"],"sourcesContent":["import {\n type StorageMigration,\n type StorageMigrationAdapter,\n type StorageMigrationOperation,\n type StorageMigrationOptions,\n type StorageMigrationRecord,\n type StorageMigrationResult,\n} from \"@puzzle-ui/storage\";\n\nimport type { D1DatabaseLike, D1StorageMigrationOptions } from \"./d1.types\";\n\nconst sleep = (duration: number) =>\n new Promise((resolve) => setTimeout(resolve, duration));\n\nconst quote = (value: string) => {\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {\n throw new Error(`Invalid SQL identifier: ${value}`);\n }\n\n return `\"${value}\"`;\n};\n\nexport class D1StorageMigrationAdapter implements StorageMigrationAdapter {\n private readonly database: D1DatabaseLike;\n private readonly prefix: string;\n private readonly lockDurationMs: number;\n\n constructor(options: D1StorageMigrationOptions) {\n this.database = options.database;\n this.prefix = options.tablePrefix ?? \"puzzle_\";\n this.lockDurationMs = options.lockDurationMs ?? 600_000;\n }\n\n private get migrationsTable() {\n return quote(`${this.prefix}migrations`);\n }\n\n private get lockTable() {\n return quote(`${this.prefix}migration_lock`);\n }\n\n private async run(sql: string, values: readonly unknown[] = []) {\n return this.database\n .prepare(sql)\n .bind(...values)\n .run();\n }\n\n private async ensureTables() {\n await this.run(`\n CREATE TABLE IF NOT EXISTS ${this.migrationsTable} (\n id TEXT PRIMARY KEY NOT NULL,\n version INTEGER NOT NULL,\n description TEXT NOT NULL,\n status TEXT NOT NULL,\n started_at TEXT,\n completed_at TEXT,\n error TEXT\n )\n `);\n\n await this.run(`\n CREATE TABLE IF NOT EXISTS ${this.lockTable} (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n owner TEXT NOT NULL,\n expires_at INTEGER NOT NULL\n )\n `);\n\n await this.run(`\n INSERT OR IGNORE INTO ${this.lockTable}\n (id, owner, expires_at)\n VALUES (1, '', 0)\n `);\n }\n\n async getMigrationStatus(): Promise<{\n currentVersion: number;\n migrations: readonly StorageMigrationRecord[];\n }> {\n await this.ensureTables();\n\n const result = await this.database\n .prepare(\n `SELECT id, version, description, status,\n started_at, completed_at, error\n FROM ${this.migrationsTable}\n ORDER BY version ASC`,\n )\n .all<Record<string, unknown>>();\n\n const migrations = (result.results ?? []).map((row) => ({\n id: String(row.id),\n version: Number(row.version),\n description: String(row.description),\n status: String(row.status) as StorageMigrationRecord[\"status\"],\n ...(row.started_at\n ? { startedAt: new Date(String(row.started_at)) }\n : {}),\n ...(row.completed_at\n ? { completedAt: new Date(String(row.completed_at)) }\n : {}),\n ...(row.error ? { error: String(row.error) } : {}),\n }));\n\n return {\n currentVersion: migrations.reduce(\n (version, migration) =>\n migration.status === \"completed\"\n ? Math.max(version, migration.version)\n : version,\n 0,\n ),\n migrations,\n };\n }\n\n private async acquireLock(owner: string): Promise<void> {\n const deadline = Date.now() + this.lockDurationMs;\n\n while (Date.now() < deadline) {\n const now = Date.now();\n const expiresAt = now + this.lockDurationMs;\n\n const result = await this.run(\n `UPDATE ${this.lockTable}\n SET owner = ?, expires_at = ?\n WHERE id = 1 AND expires_at < ?`,\n [owner, expiresAt, now],\n );\n\n if (Number(result.meta?.changes ?? 0) === 1) return;\n\n await sleep(50);\n }\n\n throw new Error(\"Timed out while acquiring the D1 migration lock.\");\n }\n\n private async execute(operation: StorageMigrationOperation): Promise<void> {\n switch (operation.type) {\n case \"createCollection\": {\n const table = quote(`${this.prefix}${operation.collection}`);\n\n await this.run(`\n CREATE TABLE IF NOT EXISTS ${table} (\n id TEXT PRIMARY KEY NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n revision INTEGER NOT NULL CHECK (revision > 0),\n data TEXT NOT NULL CHECK (json_valid(data))\n )\n `);\n\n return;\n }\n\n case \"dropCollection\":\n await this.run(\n `DROP TABLE IF EXISTS ${quote(\n `${this.prefix}${operation.collection}`,\n )}`,\n );\n return;\n\n case \"createIndex\": {\n const table = quote(`${this.prefix}${operation.collection}`);\n const name = quote(`${this.prefix}${operation.name}`);\n\n const fields = operation.fields.map((field) => {\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) {\n throw new Error(`Invalid index field: ${field}`);\n }\n\n if (\n field === \"id\" ||\n field === \"createdAt\" ||\n field === \"updatedAt\" ||\n field === \"revision\"\n ) {\n const columns: Record<string, string> = {\n id: \"id\",\n createdAt: \"created_at\",\n updatedAt: \"updated_at\",\n revision: \"revision\",\n };\n\n return columns[field];\n }\n\n return `json_extract(data, '$.\"${field}\"')`;\n });\n\n await this.run(`\n CREATE ${operation.unique ? \"UNIQUE \" : \"\"}INDEX IF NOT EXISTS ${name}\n ON ${table} (${fields.join(\", \")})\n `);\n\n return;\n }\n\n case \"dropIndex\":\n await this.run(\n `DROP INDEX IF EXISTS ${quote(`${this.prefix}${operation.name}`)}`,\n );\n return;\n }\n }\n\n async runMigrations(\n migrations: readonly StorageMigration[],\n options: StorageMigrationOptions = {},\n ): Promise<StorageMigrationResult> {\n await this.ensureTables();\n\n const owner = crypto.randomUUID();\n await this.acquireLock(owner);\n\n const applied: number[] = [];\n const skipped: number[] = [];\n\n try {\n const sorted = [...migrations].sort((a, b) => a.version - b.version);\n\n for (const migration of sorted) {\n if (\n options.targetVersion !== undefined &&\n migration.version > options.targetVersion\n ) {\n skipped.push(migration.version);\n continue;\n }\n\n const existing = await this.database\n .prepare(\n `SELECT status FROM ${this.migrationsTable}\n WHERE id = ? LIMIT 1`,\n )\n .bind(migration.id)\n .first<{ status: string }>();\n\n if (existing?.status === \"completed\") {\n skipped.push(migration.version);\n continue;\n }\n\n if (options.dryRun) {\n applied.push(migration.version);\n continue;\n }\n\n const startedAt = new Date().toISOString();\n\n await this.run(\n `INSERT INTO ${this.migrationsTable}\n (id, version, description, status, started_at)\n VALUES (?, ?, ?, 'running', ?)\n ON CONFLICT(id) DO UPDATE SET\n version = excluded.version,\n description = excluded.description,\n status = 'running',\n started_at = excluded.started_at,\n completed_at = NULL,\n error = NULL`,\n [migration.id, migration.version, migration.description, startedAt],\n );\n\n try {\n await migration.up({\n execute: (operation) => this.execute(operation),\n });\n\n await this.run(\n `UPDATE ${this.migrationsTable}\n SET status = 'completed', completed_at = ?, error = NULL\n WHERE id = ?`,\n [new Date().toISOString(), migration.id],\n );\n\n applied.push(migration.version);\n } catch (error) {\n await this.run(\n `UPDATE ${this.migrationsTable}\n SET status = 'failed', error = ?\n WHERE id = ?`,\n [\n error instanceof Error ? error.message : String(error),\n migration.id,\n ],\n );\n\n throw error;\n }\n }\n } finally {\n await this.run(\n `UPDATE ${this.lockTable}\n SET owner = '', expires_at = 0\n WHERE id = 1 AND owner = ?`,\n [owner],\n );\n }\n\n const status = await this.getMigrationStatus();\n\n return {\n applied,\n skipped,\n currentVersion: status.currentVersion,\n };\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ export type D1ResultLike<T = unknown> = {
2
+ readonly results?: readonly T[];
3
+ readonly success: boolean;
4
+ readonly meta?: Readonly<Record<string, unknown>>;
5
+ };
6
+ export type D1PreparedStatementLike = {
7
+ bind(...values: unknown[]): D1PreparedStatementLike;
8
+ first<T = unknown>(columnName?: string): Promise<T | null>;
9
+ all<T = unknown>(): Promise<D1ResultLike<T>>;
10
+ run<T = unknown>(): Promise<D1ResultLike<T>>;
11
+ };
12
+ export type D1DatabaseLike = {
13
+ prepare(query: string): D1PreparedStatementLike;
14
+ batch<T = unknown>(statements: readonly D1PreparedStatementLike[]): Promise<readonly D1ResultLike<T>[]>;
15
+ };
16
+ export type D1StorageAdapterOptions = {
17
+ readonly database: D1DatabaseLike;
18
+ readonly tablePrefix?: string;
19
+ };
20
+ export type D1StorageMigrationOptions = {
21
+ readonly database: D1DatabaseLike;
22
+ readonly tablePrefix?: string;
23
+ readonly lockDurationMs?: number;
24
+ };
@@ -0,0 +1,24 @@
1
+ export type D1ResultLike<T = unknown> = {
2
+ readonly results?: readonly T[];
3
+ readonly success: boolean;
4
+ readonly meta?: Readonly<Record<string, unknown>>;
5
+ };
6
+ export type D1PreparedStatementLike = {
7
+ bind(...values: unknown[]): D1PreparedStatementLike;
8
+ first<T = unknown>(columnName?: string): Promise<T | null>;
9
+ all<T = unknown>(): Promise<D1ResultLike<T>>;
10
+ run<T = unknown>(): Promise<D1ResultLike<T>>;
11
+ };
12
+ export type D1DatabaseLike = {
13
+ prepare(query: string): D1PreparedStatementLike;
14
+ batch<T = unknown>(statements: readonly D1PreparedStatementLike[]): Promise<readonly D1ResultLike<T>[]>;
15
+ };
16
+ export type D1StorageAdapterOptions = {
17
+ readonly database: D1DatabaseLike;
18
+ readonly tablePrefix?: string;
19
+ };
20
+ export type D1StorageMigrationOptions = {
21
+ readonly database: D1DatabaseLike;
22
+ readonly tablePrefix?: string;
23
+ readonly lockDurationMs?: number;
24
+ };
package/dist/index.cjs ADDED
@@ -0,0 +1,5 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_d1_adapter = require("./d1.adapter.cjs");
3
+ const require_d1_migrations = require("./d1.migrations.cjs");
4
+ exports.D1StorageAdapter = require_d1_adapter.D1StorageAdapter;
5
+ exports.D1StorageMigrationAdapter = require_d1_migrations.D1StorageMigrationAdapter;
@@ -0,0 +1,4 @@
1
+ import { D1StorageAdapter } from "./d1.adapter.cjs";
2
+ import { D1StorageMigrationAdapter } from "./d1.migrations.cjs";
3
+ import { D1DatabaseLike, D1PreparedStatementLike, D1ResultLike, D1StorageAdapterOptions, D1StorageMigrationOptions } from "./d1.types.cjs";
4
+ export { type D1DatabaseLike, type D1PreparedStatementLike, type D1ResultLike, D1StorageAdapter, type D1StorageAdapterOptions, D1StorageMigrationAdapter, type D1StorageMigrationOptions };
@@ -0,0 +1,4 @@
1
+ import { D1StorageAdapter } from "./d1.adapter.js";
2
+ import { D1StorageMigrationAdapter } from "./d1.migrations.js";
3
+ import { D1DatabaseLike, D1PreparedStatementLike, D1ResultLike, D1StorageAdapterOptions, D1StorageMigrationOptions } from "./d1.types.js";
4
+ export { type D1DatabaseLike, type D1PreparedStatementLike, type D1ResultLike, D1StorageAdapter, type D1StorageAdapterOptions, D1StorageMigrationAdapter, type D1StorageMigrationOptions };