@form-engine-ts/storage-sqlite 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 +67 -5
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +71 -6
- package/package.json +5 -2
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(`SQLite 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(`SQLite schema row ${index} is invalid.`, { cause });
|
|
59
59
|
}
|
|
@@ -111,10 +111,72 @@ function createSqliteStorage(options) {
|
|
|
111
111
|
})();
|
|
112
112
|
await migration;
|
|
113
113
|
};
|
|
114
|
+
function lifecycleBackend(client) {
|
|
115
|
+
const query = async (sql, params) => {
|
|
116
|
+
return await client.all(sql, params);
|
|
117
|
+
};
|
|
118
|
+
const marker = (_index) => "?";
|
|
119
|
+
return {
|
|
120
|
+
resources: ["schema", "submission"],
|
|
121
|
+
async list(formId) {
|
|
122
|
+
await ensureReady();
|
|
123
|
+
const records = [];
|
|
124
|
+
for (const kind of ["schema", "submission"]) {
|
|
125
|
+
const table = kind === "schema" ? schemasTable : responsesTable;
|
|
126
|
+
const rows = await query(`SELECT * FROM ${table} WHERE form_id = ${marker(1)}`, [formId]);
|
|
127
|
+
for (const row of rows) {
|
|
128
|
+
if (!isRecord(row)) throw new TypeError("Invalid lifecycle row.");
|
|
129
|
+
const payload = row[kind === "schema" ? "schema_json" : "submission_json"];
|
|
130
|
+
const value = parseJson(payload, "lifecycle");
|
|
131
|
+
const id = kind === "schema" ? JSON.stringify([row.form_id, row.form_version]) : String(row.response_id);
|
|
132
|
+
records.push({ kind, id, value: { row, value } });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return records;
|
|
136
|
+
},
|
|
137
|
+
async remove(resource) {
|
|
138
|
+
if (!isRecord(resource.value) || !isRecord(resource.value.row))
|
|
139
|
+
throw new TypeError("Invalid lifecycle resource.");
|
|
140
|
+
const row = resource.value.row;
|
|
141
|
+
const schema = resource.kind === "schema";
|
|
142
|
+
const table = schema ? schemasTable : responsesTable;
|
|
143
|
+
const payloadColumn = schema ? "schema_json" : "submission_json";
|
|
144
|
+
const payload = row[payloadColumn];
|
|
145
|
+
const params = schema ? [row.form_id, row.form_version] : [row.response_id, row.form_id];
|
|
146
|
+
const identity = schema ? `form_id = ${marker(1)} AND form_version = ${marker(2)}` : `response_id = ${marker(1)} AND form_id = ${marker(2)}`;
|
|
147
|
+
params.push(typeof payload === "string" ? payload : JSON.stringify(payload));
|
|
148
|
+
const deleted = await query(
|
|
149
|
+
`DELETE FROM ${table} WHERE ${identity} AND ${payloadColumn} = ${marker(3)} RETURNING ${schema ? "form_id" : "response_id"}`,
|
|
150
|
+
params
|
|
151
|
+
);
|
|
152
|
+
if (deleted.length === 0) throw new Error("Deletion revision conflict or record disappeared.");
|
|
153
|
+
return deleted.length;
|
|
154
|
+
},
|
|
155
|
+
...client.transaction === void 0 ? {} : {
|
|
156
|
+
transaction: (operation) => {
|
|
157
|
+
if (client.transaction === void 0) throw new Error("Transaction unavailable.");
|
|
158
|
+
return client.transaction((operationClient) => operation(lifecycleBackend(operationClient)));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const lifecycle = (0, import_core.createFormLifecycleAdapter)(
|
|
164
|
+
lifecycleBackend(options.db),
|
|
165
|
+
options.lifecycle === void 0 ? {} : {
|
|
166
|
+
...options.lifecycle,
|
|
167
|
+
...options.lifecycle.scope === void 0 ? {} : {
|
|
168
|
+
scope: (resource) => {
|
|
169
|
+
if (!isRecord(resource.value)) throw new TypeError("Invalid lifecycle resource.");
|
|
170
|
+
return options.lifecycle?.scope?.({ ...resource, value: resource.value.value }) ?? {};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
);
|
|
114
175
|
return {
|
|
176
|
+
...lifecycle,
|
|
115
177
|
async saveSchema(schema) {
|
|
116
178
|
await ensureReady();
|
|
117
|
-
(0, import_core.assertValidFormSchema)(schema);
|
|
179
|
+
(0, import_core.assertValidFormSchema)(schema, options.schemaValidation);
|
|
118
180
|
await options.db.run(
|
|
119
181
|
`INSERT INTO ${schemasTable} (form_id, form_version, schema_json, updated_at)
|
|
120
182
|
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
|
|
@@ -129,14 +191,14 @@ function createSqliteStorage(options) {
|
|
|
129
191
|
`SELECT form_id, form_version, schema_json FROM ${schemasTable} WHERE form_id = ? AND form_version = ?`,
|
|
130
192
|
[formId, formVersion]
|
|
131
193
|
);
|
|
132
|
-
return row === void 0 ? null : parseSchemaRow(row, 0);
|
|
194
|
+
return row === void 0 ? null : parseSchemaRow(row, 0, options.schemaValidation);
|
|
133
195
|
},
|
|
134
196
|
async listSchemas() {
|
|
135
197
|
await ensureReady();
|
|
136
198
|
const rows = await options.db.all(
|
|
137
199
|
`SELECT form_id, form_version, schema_json FROM ${schemasTable} ORDER BY form_id, form_version`
|
|
138
200
|
);
|
|
139
|
-
return rows.map(parseSchemaRow);
|
|
201
|
+
return rows.map((row, index) => parseSchemaRow(row, index, options.schemaValidation));
|
|
140
202
|
},
|
|
141
203
|
async deleteSchema(formId, formVersion) {
|
|
142
204
|
await ensureReady();
|
package/dist/index.d.cts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { FormStorageAdapter } from '@form-engine-ts/core';
|
|
1
|
+
import { ValidateFormSchemaOptions, FormLifecycleOptions, FormStorageAdapter } from '@form-engine-ts/core';
|
|
2
2
|
|
|
3
3
|
interface SqliteExecutor {
|
|
4
|
+
readonly transaction?: <T>(operation: (db: SqliteExecutor) => Promise<T>) => Promise<T>;
|
|
4
5
|
run(sql: string, params?: readonly unknown[]): Promise<void> | void;
|
|
5
6
|
get<T>(sql: string, params?: readonly unknown[]): Promise<T | undefined> | T | undefined;
|
|
6
7
|
all<T>(sql: string, params?: readonly unknown[]): Promise<readonly T[]> | readonly T[];
|
|
7
8
|
}
|
|
8
9
|
interface SqliteStorageOptions {
|
|
10
|
+
readonly schemaValidation?: ValidateFormSchemaOptions;
|
|
11
|
+
readonly lifecycle?: FormLifecycleOptions;
|
|
9
12
|
readonly db: SqliteExecutor;
|
|
10
13
|
readonly schemasTable?: string;
|
|
11
14
|
readonly responsesTable?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { FormStorageAdapter } from '@form-engine-ts/core';
|
|
1
|
+
import { ValidateFormSchemaOptions, FormLifecycleOptions, FormStorageAdapter } from '@form-engine-ts/core';
|
|
2
2
|
|
|
3
3
|
interface SqliteExecutor {
|
|
4
|
+
readonly transaction?: <T>(operation: (db: SqliteExecutor) => Promise<T>) => Promise<T>;
|
|
4
5
|
run(sql: string, params?: readonly unknown[]): Promise<void> | void;
|
|
5
6
|
get<T>(sql: string, params?: readonly unknown[]): Promise<T | undefined> | T | undefined;
|
|
6
7
|
all<T>(sql: string, params?: readonly unknown[]): Promise<readonly T[]> | readonly T[];
|
|
7
8
|
}
|
|
8
9
|
interface SqliteStorageOptions {
|
|
10
|
+
readonly schemaValidation?: ValidateFormSchemaOptions;
|
|
11
|
+
readonly lifecycle?: FormLifecycleOptions;
|
|
9
12
|
readonly db: SqliteExecutor;
|
|
10
13
|
readonly schemasTable?: string;
|
|
11
14
|
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(`SQLite 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(`SQLite schema row ${index} is invalid.`, { cause });
|
|
35
38
|
}
|
|
@@ -87,10 +90,72 @@ function createSqliteStorage(options) {
|
|
|
87
90
|
})();
|
|
88
91
|
await migration;
|
|
89
92
|
};
|
|
93
|
+
function lifecycleBackend(client) {
|
|
94
|
+
const query = async (sql, params) => {
|
|
95
|
+
return await client.all(sql, params);
|
|
96
|
+
};
|
|
97
|
+
const marker = (_index) => "?";
|
|
98
|
+
return {
|
|
99
|
+
resources: ["schema", "submission"],
|
|
100
|
+
async list(formId) {
|
|
101
|
+
await ensureReady();
|
|
102
|
+
const records = [];
|
|
103
|
+
for (const kind of ["schema", "submission"]) {
|
|
104
|
+
const table = kind === "schema" ? schemasTable : responsesTable;
|
|
105
|
+
const rows = await query(`SELECT * FROM ${table} WHERE form_id = ${marker(1)}`, [formId]);
|
|
106
|
+
for (const row of rows) {
|
|
107
|
+
if (!isRecord(row)) throw new TypeError("Invalid lifecycle row.");
|
|
108
|
+
const payload = row[kind === "schema" ? "schema_json" : "submission_json"];
|
|
109
|
+
const value = parseJson(payload, "lifecycle");
|
|
110
|
+
const id = kind === "schema" ? JSON.stringify([row.form_id, row.form_version]) : String(row.response_id);
|
|
111
|
+
records.push({ kind, id, value: { row, value } });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return records;
|
|
115
|
+
},
|
|
116
|
+
async remove(resource) {
|
|
117
|
+
if (!isRecord(resource.value) || !isRecord(resource.value.row))
|
|
118
|
+
throw new TypeError("Invalid lifecycle resource.");
|
|
119
|
+
const row = resource.value.row;
|
|
120
|
+
const schema = resource.kind === "schema";
|
|
121
|
+
const table = schema ? schemasTable : responsesTable;
|
|
122
|
+
const payloadColumn = schema ? "schema_json" : "submission_json";
|
|
123
|
+
const payload = row[payloadColumn];
|
|
124
|
+
const params = schema ? [row.form_id, row.form_version] : [row.response_id, row.form_id];
|
|
125
|
+
const identity = schema ? `form_id = ${marker(1)} AND form_version = ${marker(2)}` : `response_id = ${marker(1)} AND form_id = ${marker(2)}`;
|
|
126
|
+
params.push(typeof payload === "string" ? payload : JSON.stringify(payload));
|
|
127
|
+
const deleted = await query(
|
|
128
|
+
`DELETE FROM ${table} WHERE ${identity} AND ${payloadColumn} = ${marker(3)} RETURNING ${schema ? "form_id" : "response_id"}`,
|
|
129
|
+
params
|
|
130
|
+
);
|
|
131
|
+
if (deleted.length === 0) throw new Error("Deletion revision conflict or record disappeared.");
|
|
132
|
+
return deleted.length;
|
|
133
|
+
},
|
|
134
|
+
...client.transaction === void 0 ? {} : {
|
|
135
|
+
transaction: (operation) => {
|
|
136
|
+
if (client.transaction === void 0) throw new Error("Transaction unavailable.");
|
|
137
|
+
return client.transaction((operationClient) => operation(lifecycleBackend(operationClient)));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
const lifecycle = createFormLifecycleAdapter(
|
|
143
|
+
lifecycleBackend(options.db),
|
|
144
|
+
options.lifecycle === void 0 ? {} : {
|
|
145
|
+
...options.lifecycle,
|
|
146
|
+
...options.lifecycle.scope === void 0 ? {} : {
|
|
147
|
+
scope: (resource) => {
|
|
148
|
+
if (!isRecord(resource.value)) throw new TypeError("Invalid lifecycle resource.");
|
|
149
|
+
return options.lifecycle?.scope?.({ ...resource, value: resource.value.value }) ?? {};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
);
|
|
90
154
|
return {
|
|
155
|
+
...lifecycle,
|
|
91
156
|
async saveSchema(schema) {
|
|
92
157
|
await ensureReady();
|
|
93
|
-
assertValidFormSchema(schema);
|
|
158
|
+
assertValidFormSchema(schema, options.schemaValidation);
|
|
94
159
|
await options.db.run(
|
|
95
160
|
`INSERT INTO ${schemasTable} (form_id, form_version, schema_json, updated_at)
|
|
96
161
|
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
|
|
@@ -105,14 +170,14 @@ function createSqliteStorage(options) {
|
|
|
105
170
|
`SELECT form_id, form_version, schema_json FROM ${schemasTable} WHERE form_id = ? AND form_version = ?`,
|
|
106
171
|
[formId, formVersion]
|
|
107
172
|
);
|
|
108
|
-
return row === void 0 ? null : parseSchemaRow(row, 0);
|
|
173
|
+
return row === void 0 ? null : parseSchemaRow(row, 0, options.schemaValidation);
|
|
109
174
|
},
|
|
110
175
|
async listSchemas() {
|
|
111
176
|
await ensureReady();
|
|
112
177
|
const rows = await options.db.all(
|
|
113
178
|
`SELECT form_id, form_version, schema_json FROM ${schemasTable} ORDER BY form_id, form_version`
|
|
114
179
|
);
|
|
115
|
-
return rows.map(parseSchemaRow);
|
|
180
|
+
return rows.map((row, index) => parseSchemaRow(row, index, options.schemaValidation));
|
|
116
181
|
},
|
|
117
182
|
async deleteSchema(formId, formVersion) {
|
|
118
183
|
await ensureReady();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@form-engine-ts/storage-sqlite",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.18.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -40,7 +40,10 @@
|
|
|
40
40
|
"typescript"
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@form-engine-ts/core": "7.
|
|
43
|
+
"@form-engine-ts/core": "7.18.1"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@form-engine-ts/storage": "7.18.1"
|
|
44
47
|
},
|
|
45
48
|
"scripts": {
|
|
46
49
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean --external @form-engine-ts/core",
|