@form-engine-ts/storage-d1 7.17.2 → 7.18.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.
- package/dist/index.cjs +64 -5
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +68 -6
- package/package.json +4 -3
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 {
|
|
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.
|
|
3
|
+
"version": "7.18.0",
|
|
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.
|
|
44
|
+
"@form-engine-ts/core": "7.18.0"
|
|
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.0"
|
|
56
57
|
},
|
|
57
58
|
"scripts": {
|
|
58
59
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean --external @form-engine-ts/core",
|