@form-engine-ts/storage-d1 7.17.3 → 7.18.1

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.
package/dist/index.cjs CHANGED
@@ -48,12 +48,12 @@ function parseSubmission(value, location) {
48
48
  }
49
49
  return cloneJson(parsed);
50
50
  }
51
- function parseSchemaRow(value, index) {
51
+ function parseSchemaRow(value, index, validation = {}) {
52
52
  if (!isRecord(value)) throw new Error(`D1 schema row ${index} is invalid.`);
53
53
  const row = value;
54
54
  const schema = parseJson(row.schema_json, `schema row ${index}`);
55
55
  try {
56
- (0, import_core.assertValidFormSchema)(schema);
56
+ (0, import_core.assertValidFormSchema)(schema, validation);
57
57
  } catch (cause) {
58
58
  throw new Error(`D1 schema row ${index} is invalid.`, { cause });
59
59
  }
@@ -139,10 +139,69 @@ function createD1Storage(options) {
139
139
  })();
140
140
  await migration;
141
141
  };
142
+ function lifecycleBackend(client) {
143
+ const query = async (sql, params) => {
144
+ const result = await boundStatement(client, sql, params).all();
145
+ assertSuccessful(result, "lifecycle");
146
+ if (result.results === void 0) throw new Error("D1 lifecycle result missing rows.");
147
+ return result.results;
148
+ };
149
+ const marker = (_index) => "?";
150
+ return {
151
+ resources: ["schema", "submission"],
152
+ async list(formId) {
153
+ await ensureReady();
154
+ const records = [];
155
+ for (const kind of ["schema", "submission"]) {
156
+ const table = kind === "schema" ? schemasTable : responsesTable;
157
+ const rows = await query(`SELECT * FROM ${table} WHERE form_id = ${marker(1)}`, [formId]);
158
+ for (const row of rows) {
159
+ if (!isRecord(row)) throw new TypeError("Invalid lifecycle row.");
160
+ const payload = row[kind === "schema" ? "schema_json" : "submission_json"];
161
+ const value = parseJson(payload, "lifecycle");
162
+ const id = kind === "schema" ? JSON.stringify([row.form_id, row.form_version]) : String(row.response_id);
163
+ records.push({ kind, id, value: { row, value } });
164
+ }
165
+ }
166
+ return records;
167
+ },
168
+ async remove(resource) {
169
+ if (!isRecord(resource.value) || !isRecord(resource.value.row))
170
+ throw new TypeError("Invalid lifecycle resource.");
171
+ const row = resource.value.row;
172
+ const schema = resource.kind === "schema";
173
+ const table = schema ? schemasTable : responsesTable;
174
+ const payloadColumn = schema ? "schema_json" : "submission_json";
175
+ const payload = row[payloadColumn];
176
+ const params = schema ? [row.form_id, row.form_version] : [row.response_id, row.form_id];
177
+ const identity = schema ? `form_id = ${marker(1)} AND form_version = ${marker(2)}` : `response_id = ${marker(1)} AND form_id = ${marker(2)}`;
178
+ params.push(typeof payload === "string" ? payload : JSON.stringify(payload));
179
+ const deleted = await query(
180
+ `DELETE FROM ${table} WHERE ${identity} AND ${payloadColumn} = ${marker(3)} RETURNING ${schema ? "form_id" : "response_id"}`,
181
+ params
182
+ );
183
+ if (deleted.length === 0) throw new Error("Deletion revision conflict or record disappeared.");
184
+ return deleted.length;
185
+ }
186
+ };
187
+ }
188
+ const lifecycle = (0, import_core.createFormLifecycleAdapter)(
189
+ lifecycleBackend(options.db),
190
+ options.lifecycle === void 0 ? {} : {
191
+ ...options.lifecycle,
192
+ ...options.lifecycle.scope === void 0 ? {} : {
193
+ scope: (resource) => {
194
+ if (!isRecord(resource.value)) throw new TypeError("Invalid lifecycle resource.");
195
+ return options.lifecycle?.scope?.({ ...resource, value: resource.value.value }) ?? {};
196
+ }
197
+ }
198
+ }
199
+ );
142
200
  return {
201
+ ...lifecycle,
143
202
  async saveSchema(schema) {
144
203
  await ensureReady();
145
- (0, import_core.assertValidFormSchema)(schema);
204
+ (0, import_core.assertValidFormSchema)(schema, options.schemaValidation);
146
205
  await run(
147
206
  `INSERT INTO ${schemasTable} (form_id, form_version, schema_json, updated_at)
148
207
  VALUES (?, ?, ?, CURRENT_TIMESTAMP)
@@ -158,14 +217,14 @@ function createD1Storage(options) {
158
217
  `SELECT form_id, form_version, schema_json FROM ${schemasTable} WHERE form_id = ? AND form_version = ? LIMIT 1`,
159
218
  [formId, formVersion]
160
219
  ).first();
161
- return row === null ? null : parseSchemaRow(row, 0);
220
+ return row === null ? null : parseSchemaRow(row, 0, options.schemaValidation);
162
221
  },
163
222
  async listSchemas() {
164
223
  await ensureReady();
165
224
  const rows = await all(
166
225
  `SELECT form_id, form_version, schema_json FROM ${schemasTable} ORDER BY form_id, form_version`
167
226
  );
168
- return rows.map(parseSchemaRow);
227
+ return rows.map((row, index) => parseSchemaRow(row, index, options.schemaValidation));
169
228
  },
170
229
  async deleteSchema(formId, formVersion) {
171
230
  await ensureReady();
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { FormStorageAdapter } from '@form-engine-ts/core';
1
+ import { ValidateFormSchemaOptions, FormLifecycleOptions, FormStorageAdapter } from '@form-engine-ts/core';
2
2
 
3
3
  interface D1ResultLike<T = Record<string, unknown>> {
4
4
  readonly success: boolean;
@@ -15,6 +15,8 @@ interface D1DatabaseLike {
15
15
  batch(statements: readonly D1PreparedStatementLike[]): Promise<readonly D1ResultLike[]>;
16
16
  }
17
17
  interface D1StorageOptions {
18
+ readonly schemaValidation?: ValidateFormSchemaOptions;
19
+ readonly lifecycle?: FormLifecycleOptions;
18
20
  readonly db: D1DatabaseLike;
19
21
  readonly schemasTable?: string;
20
22
  readonly responsesTable?: string;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { FormStorageAdapter } from '@form-engine-ts/core';
1
+ import { ValidateFormSchemaOptions, FormLifecycleOptions, FormStorageAdapter } from '@form-engine-ts/core';
2
2
 
3
3
  interface D1ResultLike<T = Record<string, unknown>> {
4
4
  readonly success: boolean;
@@ -15,6 +15,8 @@ interface D1DatabaseLike {
15
15
  batch(statements: readonly D1PreparedStatementLike[]): Promise<readonly D1ResultLike[]>;
16
16
  }
17
17
  interface D1StorageOptions {
18
+ readonly schemaValidation?: ValidateFormSchemaOptions;
19
+ readonly lifecycle?: FormLifecycleOptions;
18
20
  readonly db: D1DatabaseLike;
19
21
  readonly schemasTable?: string;
20
22
  readonly responsesTable?: string;
package/dist/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  // src/index.ts
2
- import { assertValidFormSchema } from "@form-engine-ts/core";
2
+ import {
3
+ assertValidFormSchema,
4
+ createFormLifecycleAdapter
5
+ } from "@form-engine-ts/core";
3
6
  function isRecord(value) {
4
7
  return typeof value === "object" && value !== null && !Array.isArray(value);
5
8
  }
@@ -24,12 +27,12 @@ function parseSubmission(value, location) {
24
27
  }
25
28
  return cloneJson(parsed);
26
29
  }
27
- function parseSchemaRow(value, index) {
30
+ function parseSchemaRow(value, index, validation = {}) {
28
31
  if (!isRecord(value)) throw new Error(`D1 schema row ${index} is invalid.`);
29
32
  const row = value;
30
33
  const schema = parseJson(row.schema_json, `schema row ${index}`);
31
34
  try {
32
- assertValidFormSchema(schema);
35
+ assertValidFormSchema(schema, validation);
33
36
  } catch (cause) {
34
37
  throw new Error(`D1 schema row ${index} is invalid.`, { cause });
35
38
  }
@@ -115,10 +118,69 @@ function createD1Storage(options) {
115
118
  })();
116
119
  await migration;
117
120
  };
121
+ function lifecycleBackend(client) {
122
+ const query = async (sql, params) => {
123
+ const result = await boundStatement(client, sql, params).all();
124
+ assertSuccessful(result, "lifecycle");
125
+ if (result.results === void 0) throw new Error("D1 lifecycle result missing rows.");
126
+ return result.results;
127
+ };
128
+ const marker = (_index) => "?";
129
+ return {
130
+ resources: ["schema", "submission"],
131
+ async list(formId) {
132
+ await ensureReady();
133
+ const records = [];
134
+ for (const kind of ["schema", "submission"]) {
135
+ const table = kind === "schema" ? schemasTable : responsesTable;
136
+ const rows = await query(`SELECT * FROM ${table} WHERE form_id = ${marker(1)}`, [formId]);
137
+ for (const row of rows) {
138
+ if (!isRecord(row)) throw new TypeError("Invalid lifecycle row.");
139
+ const payload = row[kind === "schema" ? "schema_json" : "submission_json"];
140
+ const value = parseJson(payload, "lifecycle");
141
+ const id = kind === "schema" ? JSON.stringify([row.form_id, row.form_version]) : String(row.response_id);
142
+ records.push({ kind, id, value: { row, value } });
143
+ }
144
+ }
145
+ return records;
146
+ },
147
+ async remove(resource) {
148
+ if (!isRecord(resource.value) || !isRecord(resource.value.row))
149
+ throw new TypeError("Invalid lifecycle resource.");
150
+ const row = resource.value.row;
151
+ const schema = resource.kind === "schema";
152
+ const table = schema ? schemasTable : responsesTable;
153
+ const payloadColumn = schema ? "schema_json" : "submission_json";
154
+ const payload = row[payloadColumn];
155
+ const params = schema ? [row.form_id, row.form_version] : [row.response_id, row.form_id];
156
+ const identity = schema ? `form_id = ${marker(1)} AND form_version = ${marker(2)}` : `response_id = ${marker(1)} AND form_id = ${marker(2)}`;
157
+ params.push(typeof payload === "string" ? payload : JSON.stringify(payload));
158
+ const deleted = await query(
159
+ `DELETE FROM ${table} WHERE ${identity} AND ${payloadColumn} = ${marker(3)} RETURNING ${schema ? "form_id" : "response_id"}`,
160
+ params
161
+ );
162
+ if (deleted.length === 0) throw new Error("Deletion revision conflict or record disappeared.");
163
+ return deleted.length;
164
+ }
165
+ };
166
+ }
167
+ const lifecycle = createFormLifecycleAdapter(
168
+ lifecycleBackend(options.db),
169
+ options.lifecycle === void 0 ? {} : {
170
+ ...options.lifecycle,
171
+ ...options.lifecycle.scope === void 0 ? {} : {
172
+ scope: (resource) => {
173
+ if (!isRecord(resource.value)) throw new TypeError("Invalid lifecycle resource.");
174
+ return options.lifecycle?.scope?.({ ...resource, value: resource.value.value }) ?? {};
175
+ }
176
+ }
177
+ }
178
+ );
118
179
  return {
180
+ ...lifecycle,
119
181
  async saveSchema(schema) {
120
182
  await ensureReady();
121
- assertValidFormSchema(schema);
183
+ assertValidFormSchema(schema, options.schemaValidation);
122
184
  await run(
123
185
  `INSERT INTO ${schemasTable} (form_id, form_version, schema_json, updated_at)
124
186
  VALUES (?, ?, ?, CURRENT_TIMESTAMP)
@@ -134,14 +196,14 @@ function createD1Storage(options) {
134
196
  `SELECT form_id, form_version, schema_json FROM ${schemasTable} WHERE form_id = ? AND form_version = ? LIMIT 1`,
135
197
  [formId, formVersion]
136
198
  ).first();
137
- return row === null ? null : parseSchemaRow(row, 0);
199
+ return row === null ? null : parseSchemaRow(row, 0, options.schemaValidation);
138
200
  },
139
201
  async listSchemas() {
140
202
  await ensureReady();
141
203
  const rows = await all(
142
204
  `SELECT form_id, form_version, schema_json FROM ${schemasTable} ORDER BY form_id, form_version`
143
205
  );
144
- return rows.map(parseSchemaRow);
206
+ return rows.map((row, index) => parseSchemaRow(row, index, options.schemaValidation));
145
207
  },
146
208
  async deleteSchema(formId, formVersion) {
147
209
  await ensureReady();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@form-engine-ts/storage-d1",
3
- "version": "7.17.3",
3
+ "version": "7.18.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -41,7 +41,7 @@
41
41
  "typescript"
42
42
  ],
43
43
  "dependencies": {
44
- "@form-engine-ts/core": "7.17.3"
44
+ "@form-engine-ts/core": "7.18.1"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "@cloudflare/workers-types": "^4.20240000.0"
@@ -52,7 +52,8 @@
52
52
  }
53
53
  },
54
54
  "devDependencies": {
55
- "@cloudflare/workers-types": "^4.20240000.0"
55
+ "@cloudflare/workers-types": "^4.20240000.0",
56
+ "@form-engine-ts/storage": "7.18.1"
56
57
  },
57
58
  "scripts": {
58
59
  "build": "tsup src/index.ts --format esm,cjs --dts --clean --external @form-engine-ts/core",