@form-engine-ts/storage-sqlite 8.1.6 → 8.2.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/README.md +2 -1
- package/dist/index.cjs +43 -12
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +44 -12
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -30,4 +30,5 @@ const storage = createSqliteStorage({ db: executor, autoMigrate: true });
|
|
|
30
30
|
`autoMigrate` defaults to `false`; when enabled, idempotent table/index DDL runs lazily once. Schemas use a `(form_id, form_version)` primary key. Complete schemas and submissions are stored as JSON text alongside searchable response metadata. `listSubmissions` accepts inclusive `since`/`until` ISO 8601 boundaries and orders by timestamp then ID. Schema deletion does not cascade; form-scoped and full clears retain the configured tables.
|
|
31
31
|
|
|
32
32
|
`saveSubmissionWithinLimit(submission, maxResponses, options?)` requires `SqliteExecutor.transaction` so the capacity
|
|
33
|
-
check and insert commit atomically.
|
|
33
|
+
check and insert commit atomically. `responseLimitScope: "form"` checks the total across versions. Without a transaction,
|
|
34
|
+
the method throws an error with `code: "transaction_unsupported"`.
|
package/dist/index.cjs
CHANGED
|
@@ -81,6 +81,11 @@ function createSqliteStorage(options) {
|
|
|
81
81
|
}
|
|
82
82
|
const schemasTable = identifier(options.schemasTable, "form_schemas", "schemasTable");
|
|
83
83
|
const responsesTableName = options.responsesTable ?? "form_responses";
|
|
84
|
+
const runtimeSettingsTable = identifier(
|
|
85
|
+
options.runtimeSettingsTable,
|
|
86
|
+
"form_runtime_settings",
|
|
87
|
+
"runtimeSettingsTable"
|
|
88
|
+
);
|
|
84
89
|
const responsesTable = identifier(responsesTableName, "form_responses", "responsesTable");
|
|
85
90
|
const responsesIndex = identifier(`${responsesTableName}_lookup_idx`, "form_responses_lookup_idx", "responsesTable");
|
|
86
91
|
let migration;
|
|
@@ -102,6 +107,10 @@ function createSqliteStorage(options) {
|
|
|
102
107
|
submitted_at TEXT NOT NULL,
|
|
103
108
|
submission_json TEXT NOT NULL
|
|
104
109
|
)`);
|
|
110
|
+
await options.db.run(`CREATE TABLE IF NOT EXISTS ${runtimeSettingsTable} (
|
|
111
|
+
form_id TEXT PRIMARY KEY,
|
|
112
|
+
settings_json TEXT NOT NULL
|
|
113
|
+
)`);
|
|
105
114
|
await options.db.run(
|
|
106
115
|
`CREATE INDEX IF NOT EXISTS ${responsesIndex} ON ${responsesTable} (form_id, submitted_at, response_id)`
|
|
107
116
|
);
|
|
@@ -114,18 +123,18 @@ function createSqliteStorage(options) {
|
|
|
114
123
|
};
|
|
115
124
|
const marker = (_index) => "?";
|
|
116
125
|
return {
|
|
117
|
-
resources: ["schema", "submission"],
|
|
126
|
+
resources: ["schema", "submission", "runtimeSettings"],
|
|
118
127
|
async list(formId) {
|
|
119
128
|
await ensureReady();
|
|
120
129
|
const records = [];
|
|
121
|
-
for (const kind of ["schema", "submission"]) {
|
|
122
|
-
const table = kind === "schema" ? schemasTable : responsesTable;
|
|
130
|
+
for (const kind of ["schema", "submission", "runtimeSettings"]) {
|
|
131
|
+
const table = kind === "schema" ? schemasTable : kind === "submission" ? responsesTable : runtimeSettingsTable;
|
|
123
132
|
const rows = await query(`SELECT * FROM ${table} WHERE form_id = ${marker(1)}`, [formId]);
|
|
124
133
|
for (const row of rows) {
|
|
125
134
|
if (!isRecord(row)) throw new TypeError("Invalid lifecycle row.");
|
|
126
|
-
const payload = row[kind === "schema" ? "schema_json" : "submission_json"];
|
|
135
|
+
const payload = row[kind === "schema" ? "schema_json" : kind === "submission" ? "submission_json" : "settings_json"];
|
|
127
136
|
const value = parseJson(payload, "lifecycle");
|
|
128
|
-
const id = kind === "schema" ? JSON.stringify([row.form_id, row.form_version]) : String(row.response_id);
|
|
137
|
+
const id = kind === "schema" ? JSON.stringify([row.form_id, row.form_version]) : kind === "submission" ? String(row.response_id) : formId;
|
|
129
138
|
records.push({ kind, id, value: { row, value } });
|
|
130
139
|
}
|
|
131
140
|
}
|
|
@@ -136,14 +145,15 @@ function createSqliteStorage(options) {
|
|
|
136
145
|
throw new TypeError("Invalid lifecycle resource.");
|
|
137
146
|
const row = resource.value.row;
|
|
138
147
|
const schema = resource.kind === "schema";
|
|
139
|
-
const
|
|
140
|
-
const
|
|
148
|
+
const settings = resource.kind === "runtimeSettings";
|
|
149
|
+
const table = schema ? schemasTable : settings ? runtimeSettingsTable : responsesTable;
|
|
150
|
+
const payloadColumn = schema ? "schema_json" : settings ? "settings_json" : "submission_json";
|
|
141
151
|
const payload = row[payloadColumn];
|
|
142
|
-
const params = schema ? [row.form_id, row.form_version] : [row.response_id, row.form_id];
|
|
143
|
-
const identity = schema ? `form_id = ${marker(1)} AND form_version = ${marker(2)}` : `response_id = ${marker(1)} AND form_id = ${marker(2)}`;
|
|
152
|
+
const params = schema ? [row.form_id, row.form_version] : settings ? [row.form_id] : [row.response_id, row.form_id];
|
|
153
|
+
const identity = schema ? `form_id = ${marker(1)} AND form_version = ${marker(2)}` : settings ? `form_id = ${marker(1)}` : `response_id = ${marker(1)} AND form_id = ${marker(2)}`;
|
|
144
154
|
params.push(typeof payload === "string" ? payload : JSON.stringify(payload));
|
|
145
155
|
const deleted = await query(
|
|
146
|
-
`DELETE FROM ${table} WHERE ${identity} AND ${payloadColumn} = ${marker(
|
|
156
|
+
`DELETE FROM ${table} WHERE ${identity} AND ${payloadColumn} = ${marker(params.length)} RETURNING ${schema ? "form_id" : settings ? "form_id" : "response_id"}`,
|
|
147
157
|
params
|
|
148
158
|
);
|
|
149
159
|
if (deleted.length === 0) throw new Error("Deletion revision conflict or record disappeared.");
|
|
@@ -190,6 +200,26 @@ function createSqliteStorage(options) {
|
|
|
190
200
|
);
|
|
191
201
|
return row === void 0 ? null : parseSchemaRow(row, 0, options.schemaValidation);
|
|
192
202
|
},
|
|
203
|
+
async getRuntimeSettings(formId) {
|
|
204
|
+
await ensureReady();
|
|
205
|
+
const row = await options.db.get(
|
|
206
|
+
`SELECT settings_json FROM ${runtimeSettingsTable} WHERE form_id = ?`,
|
|
207
|
+
[formId]
|
|
208
|
+
);
|
|
209
|
+
if (row === void 0) return null;
|
|
210
|
+
const settings = parseJson(row.settings_json, "runtime settings");
|
|
211
|
+
(0, import_core.assertValidFormRuntimeSettings)(settings);
|
|
212
|
+
return cloneJson(settings);
|
|
213
|
+
},
|
|
214
|
+
async saveRuntimeSettings(settings) {
|
|
215
|
+
await ensureReady();
|
|
216
|
+
(0, import_core.assertValidFormRuntimeSettings)(settings);
|
|
217
|
+
await options.db.run(
|
|
218
|
+
`INSERT INTO ${runtimeSettingsTable} (form_id, settings_json) VALUES (?, ?)
|
|
219
|
+
ON CONFLICT (form_id) DO UPDATE SET settings_json = excluded.settings_json`,
|
|
220
|
+
[settings.formId, JSON.stringify(settings)]
|
|
221
|
+
);
|
|
222
|
+
},
|
|
193
223
|
async listSchemas() {
|
|
194
224
|
await ensureReady();
|
|
195
225
|
const rows = await options.db.all(
|
|
@@ -235,8 +265,8 @@ function createSqliteStorage(options) {
|
|
|
235
265
|
return { status: "conflict", submissionId: stored.id, payloadHash, existingPayloadHash };
|
|
236
266
|
}
|
|
237
267
|
const row = await db.get(
|
|
238
|
-
`SELECT COUNT(*) AS count FROM ${responsesTable} WHERE form_id = ? AND form_version =
|
|
239
|
-
[stored.formId, stored.formVersion]
|
|
268
|
+
`SELECT COUNT(*) AS count FROM ${responsesTable} WHERE form_id = ?${saveOptions.responseLimitScope === "form" ? "" : " AND form_version = ?"}`,
|
|
269
|
+
saveOptions.responseLimitScope === "form" ? [stored.formId] : [stored.formId, stored.formVersion]
|
|
240
270
|
);
|
|
241
271
|
if (Number(row?.count ?? 0) >= maxResponses) return { status: "limit_reached" };
|
|
242
272
|
await db.run(
|
|
@@ -305,6 +335,7 @@ function createSqliteStorage(options) {
|
|
|
305
335
|
await ensureReady();
|
|
306
336
|
await options.db.run(`DELETE FROM ${responsesTable}`);
|
|
307
337
|
await options.db.run(`DELETE FROM ${schemasTable}`);
|
|
338
|
+
await options.db.run(`DELETE FROM ${runtimeSettingsTable}`);
|
|
308
339
|
}
|
|
309
340
|
};
|
|
310
341
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -12,6 +12,7 @@ interface SqliteStorageOptions {
|
|
|
12
12
|
readonly db: SqliteExecutor;
|
|
13
13
|
readonly schemasTable?: string;
|
|
14
14
|
readonly responsesTable?: string;
|
|
15
|
+
readonly runtimeSettingsTable?: string;
|
|
15
16
|
readonly autoMigrate?: boolean;
|
|
16
17
|
}
|
|
17
18
|
declare function createSqliteStorage(options: SqliteStorageOptions): FormStorageAdapter;
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ interface SqliteStorageOptions {
|
|
|
12
12
|
readonly db: SqliteExecutor;
|
|
13
13
|
readonly schemasTable?: string;
|
|
14
14
|
readonly responsesTable?: string;
|
|
15
|
+
readonly runtimeSettingsTable?: string;
|
|
15
16
|
readonly autoMigrate?: boolean;
|
|
16
17
|
}
|
|
17
18
|
declare function createSqliteStorage(options: SqliteStorageOptions): FormStorageAdapter;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import {
|
|
3
|
+
assertValidFormRuntimeSettings,
|
|
3
4
|
assertValidFormSchema,
|
|
4
5
|
createFormLifecycleAdapter,
|
|
5
6
|
hashFormSubmissionPayload,
|
|
@@ -62,6 +63,11 @@ function createSqliteStorage(options) {
|
|
|
62
63
|
}
|
|
63
64
|
const schemasTable = identifier(options.schemasTable, "form_schemas", "schemasTable");
|
|
64
65
|
const responsesTableName = options.responsesTable ?? "form_responses";
|
|
66
|
+
const runtimeSettingsTable = identifier(
|
|
67
|
+
options.runtimeSettingsTable,
|
|
68
|
+
"form_runtime_settings",
|
|
69
|
+
"runtimeSettingsTable"
|
|
70
|
+
);
|
|
65
71
|
const responsesTable = identifier(responsesTableName, "form_responses", "responsesTable");
|
|
66
72
|
const responsesIndex = identifier(`${responsesTableName}_lookup_idx`, "form_responses_lookup_idx", "responsesTable");
|
|
67
73
|
let migration;
|
|
@@ -83,6 +89,10 @@ function createSqliteStorage(options) {
|
|
|
83
89
|
submitted_at TEXT NOT NULL,
|
|
84
90
|
submission_json TEXT NOT NULL
|
|
85
91
|
)`);
|
|
92
|
+
await options.db.run(`CREATE TABLE IF NOT EXISTS ${runtimeSettingsTable} (
|
|
93
|
+
form_id TEXT PRIMARY KEY,
|
|
94
|
+
settings_json TEXT NOT NULL
|
|
95
|
+
)`);
|
|
86
96
|
await options.db.run(
|
|
87
97
|
`CREATE INDEX IF NOT EXISTS ${responsesIndex} ON ${responsesTable} (form_id, submitted_at, response_id)`
|
|
88
98
|
);
|
|
@@ -95,18 +105,18 @@ function createSqliteStorage(options) {
|
|
|
95
105
|
};
|
|
96
106
|
const marker = (_index) => "?";
|
|
97
107
|
return {
|
|
98
|
-
resources: ["schema", "submission"],
|
|
108
|
+
resources: ["schema", "submission", "runtimeSettings"],
|
|
99
109
|
async list(formId) {
|
|
100
110
|
await ensureReady();
|
|
101
111
|
const records = [];
|
|
102
|
-
for (const kind of ["schema", "submission"]) {
|
|
103
|
-
const table = kind === "schema" ? schemasTable : responsesTable;
|
|
112
|
+
for (const kind of ["schema", "submission", "runtimeSettings"]) {
|
|
113
|
+
const table = kind === "schema" ? schemasTable : kind === "submission" ? responsesTable : runtimeSettingsTable;
|
|
104
114
|
const rows = await query(`SELECT * FROM ${table} WHERE form_id = ${marker(1)}`, [formId]);
|
|
105
115
|
for (const row of rows) {
|
|
106
116
|
if (!isRecord(row)) throw new TypeError("Invalid lifecycle row.");
|
|
107
|
-
const payload = row[kind === "schema" ? "schema_json" : "submission_json"];
|
|
117
|
+
const payload = row[kind === "schema" ? "schema_json" : kind === "submission" ? "submission_json" : "settings_json"];
|
|
108
118
|
const value = parseJson(payload, "lifecycle");
|
|
109
|
-
const id = kind === "schema" ? JSON.stringify([row.form_id, row.form_version]) : String(row.response_id);
|
|
119
|
+
const id = kind === "schema" ? JSON.stringify([row.form_id, row.form_version]) : kind === "submission" ? String(row.response_id) : formId;
|
|
110
120
|
records.push({ kind, id, value: { row, value } });
|
|
111
121
|
}
|
|
112
122
|
}
|
|
@@ -117,14 +127,15 @@ function createSqliteStorage(options) {
|
|
|
117
127
|
throw new TypeError("Invalid lifecycle resource.");
|
|
118
128
|
const row = resource.value.row;
|
|
119
129
|
const schema = resource.kind === "schema";
|
|
120
|
-
const
|
|
121
|
-
const
|
|
130
|
+
const settings = resource.kind === "runtimeSettings";
|
|
131
|
+
const table = schema ? schemasTable : settings ? runtimeSettingsTable : responsesTable;
|
|
132
|
+
const payloadColumn = schema ? "schema_json" : settings ? "settings_json" : "submission_json";
|
|
122
133
|
const payload = row[payloadColumn];
|
|
123
|
-
const params = schema ? [row.form_id, row.form_version] : [row.response_id, row.form_id];
|
|
124
|
-
const identity = schema ? `form_id = ${marker(1)} AND form_version = ${marker(2)}` : `response_id = ${marker(1)} AND form_id = ${marker(2)}`;
|
|
134
|
+
const params = schema ? [row.form_id, row.form_version] : settings ? [row.form_id] : [row.response_id, row.form_id];
|
|
135
|
+
const identity = schema ? `form_id = ${marker(1)} AND form_version = ${marker(2)}` : settings ? `form_id = ${marker(1)}` : `response_id = ${marker(1)} AND form_id = ${marker(2)}`;
|
|
125
136
|
params.push(typeof payload === "string" ? payload : JSON.stringify(payload));
|
|
126
137
|
const deleted = await query(
|
|
127
|
-
`DELETE FROM ${table} WHERE ${identity} AND ${payloadColumn} = ${marker(
|
|
138
|
+
`DELETE FROM ${table} WHERE ${identity} AND ${payloadColumn} = ${marker(params.length)} RETURNING ${schema ? "form_id" : settings ? "form_id" : "response_id"}`,
|
|
128
139
|
params
|
|
129
140
|
);
|
|
130
141
|
if (deleted.length === 0) throw new Error("Deletion revision conflict or record disappeared.");
|
|
@@ -171,6 +182,26 @@ function createSqliteStorage(options) {
|
|
|
171
182
|
);
|
|
172
183
|
return row === void 0 ? null : parseSchemaRow(row, 0, options.schemaValidation);
|
|
173
184
|
},
|
|
185
|
+
async getRuntimeSettings(formId) {
|
|
186
|
+
await ensureReady();
|
|
187
|
+
const row = await options.db.get(
|
|
188
|
+
`SELECT settings_json FROM ${runtimeSettingsTable} WHERE form_id = ?`,
|
|
189
|
+
[formId]
|
|
190
|
+
);
|
|
191
|
+
if (row === void 0) return null;
|
|
192
|
+
const settings = parseJson(row.settings_json, "runtime settings");
|
|
193
|
+
assertValidFormRuntimeSettings(settings);
|
|
194
|
+
return cloneJson(settings);
|
|
195
|
+
},
|
|
196
|
+
async saveRuntimeSettings(settings) {
|
|
197
|
+
await ensureReady();
|
|
198
|
+
assertValidFormRuntimeSettings(settings);
|
|
199
|
+
await options.db.run(
|
|
200
|
+
`INSERT INTO ${runtimeSettingsTable} (form_id, settings_json) VALUES (?, ?)
|
|
201
|
+
ON CONFLICT (form_id) DO UPDATE SET settings_json = excluded.settings_json`,
|
|
202
|
+
[settings.formId, JSON.stringify(settings)]
|
|
203
|
+
);
|
|
204
|
+
},
|
|
174
205
|
async listSchemas() {
|
|
175
206
|
await ensureReady();
|
|
176
207
|
const rows = await options.db.all(
|
|
@@ -216,8 +247,8 @@ function createSqliteStorage(options) {
|
|
|
216
247
|
return { status: "conflict", submissionId: stored.id, payloadHash, existingPayloadHash };
|
|
217
248
|
}
|
|
218
249
|
const row = await db.get(
|
|
219
|
-
`SELECT COUNT(*) AS count FROM ${responsesTable} WHERE form_id = ? AND form_version =
|
|
220
|
-
[stored.formId, stored.formVersion]
|
|
250
|
+
`SELECT COUNT(*) AS count FROM ${responsesTable} WHERE form_id = ?${saveOptions.responseLimitScope === "form" ? "" : " AND form_version = ?"}`,
|
|
251
|
+
saveOptions.responseLimitScope === "form" ? [stored.formId] : [stored.formId, stored.formVersion]
|
|
221
252
|
);
|
|
222
253
|
if (Number(row?.count ?? 0) >= maxResponses) return { status: "limit_reached" };
|
|
223
254
|
await db.run(
|
|
@@ -286,6 +317,7 @@ function createSqliteStorage(options) {
|
|
|
286
317
|
await ensureReady();
|
|
287
318
|
await options.db.run(`DELETE FROM ${responsesTable}`);
|
|
288
319
|
await options.db.run(`DELETE FROM ${schemasTable}`);
|
|
320
|
+
await options.db.run(`DELETE FROM ${runtimeSettingsTable}`);
|
|
289
321
|
}
|
|
290
322
|
};
|
|
291
323
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@form-engine-ts/storage-sqlite",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.2.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
"typescript"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@form-engine-ts/core": "8.
|
|
43
|
+
"@form-engine-ts/core": "8.2.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@form-engine-ts/storage": "8.
|
|
46
|
+
"@form-engine-ts/storage": "8.2.0"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean --external @form-engine-ts/core",
|