@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 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 { 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(`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.17.3",
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.17.3"
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",