@form-engine-ts/storage-sqlite 1.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nitta-a
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @form-engine-ts/storage-sqlite
2
+
3
+ SQLite implementation of the complete form-engine-ts storage contract using a small injected executor that supports synchronous or asynchronous drivers.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pnpm add @form-engine-ts/core @form-engine-ts/storage-sqlite
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```ts
14
+ import Database from "better-sqlite3";
15
+ import { createSqliteStorage, type SqliteExecutor } from "@form-engine-ts/storage-sqlite";
16
+
17
+ const db = new Database("forms.sqlite");
18
+ const executor: SqliteExecutor = {
19
+ run: (sql, params = []) => {
20
+ db.prepare(sql).run(...params);
21
+ },
22
+ get: (sql, params = []) => db.prepare(sql).get(...params),
23
+ all: (sql, params = []) => db.prepare(sql).all(...params)
24
+ };
25
+ const storage = createSqliteStorage({ db: executor, autoMigrate: true });
26
+ ```
27
+
28
+ `better-sqlite3` is only an example and is not a runtime dependency. Wrap `bun:sqlite`, libSQL, or another driver in the same `run/get/all` interface; return values may be synchronous or promises. The caller owns the database lifecycle and transaction policy.
29
+
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.
package/dist/index.cjs ADDED
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ createSqliteStorage: () => createSqliteStorage
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+ var import_core = require("@form-engine-ts/core");
27
+ function isRecord(value) {
28
+ return typeof value === "object" && value !== null && !Array.isArray(value);
29
+ }
30
+ function isFormValue(value) {
31
+ return value === void 0 || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) || Array.isArray(value) && value.every((item) => typeof item === "string");
32
+ }
33
+ function cloneJson(value) {
34
+ return JSON.parse(JSON.stringify(value));
35
+ }
36
+ function parseJson(value, location) {
37
+ if (typeof value !== "string") return cloneJson(value);
38
+ try {
39
+ return JSON.parse(value);
40
+ } catch (cause) {
41
+ throw new Error(`SQLite JSON at ${location} is invalid.`, { cause });
42
+ }
43
+ }
44
+ function parseSubmission(value, location) {
45
+ const parsed = parseJson(value, location);
46
+ if (!isRecord(parsed) || typeof parsed.id !== "string" || typeof parsed.formId !== "string" || !Number.isInteger(parsed.formVersion) || typeof parsed.locale !== "string" || typeof parsed.submittedAt !== "string" || !isRecord(parsed.values) || !Object.values(parsed.values).every(isFormValue)) {
47
+ throw new Error(`SQLite submission at ${location} is invalid.`);
48
+ }
49
+ return cloneJson(parsed);
50
+ }
51
+ function parseSchemaRow(value, index) {
52
+ if (!isRecord(value)) throw new Error(`SQLite schema row ${index} is invalid.`);
53
+ const row = value;
54
+ const schema = parseJson(row.schema_json, `schema row ${index}`);
55
+ try {
56
+ (0, import_core.assertValidFormSchema)(schema);
57
+ } catch (cause) {
58
+ throw new Error(`SQLite schema row ${index} is invalid.`, { cause });
59
+ }
60
+ if (row.form_id !== schema.id || row.form_version !== schema.version) {
61
+ throw new Error(`SQLite schema row ${index} has inconsistent metadata.`);
62
+ }
63
+ return cloneJson(schema);
64
+ }
65
+ function parseSubmissionRow(value, index) {
66
+ if (!isRecord(value)) throw new Error(`SQLite submission row ${index} is invalid.`);
67
+ const row = value;
68
+ const submission = parseSubmission(row.submission_json, `submission row ${index}`);
69
+ if (row.response_id !== submission.id || row.form_id !== submission.formId || row.form_version !== submission.formVersion || row.locale !== submission.locale || row.submitted_at !== submission.submittedAt) {
70
+ throw new Error(`SQLite submission row ${index} has inconsistent metadata.`);
71
+ }
72
+ return submission;
73
+ }
74
+ function identifier(value, fallback, optionName) {
75
+ const name = value ?? fallback;
76
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
77
+ throw new TypeError(`${optionName} must be a safe SQL identifier.`);
78
+ }
79
+ return `"${name}"`;
80
+ }
81
+ function createSqliteStorage(options) {
82
+ if (options?.db === void 0 || typeof options.db.run !== "function" || typeof options.db.get !== "function" || typeof options.db.all !== "function") {
83
+ throw new TypeError("db with run, get, and all functions is required.");
84
+ }
85
+ const schemasTable = identifier(options.schemasTable, "form_schemas", "schemasTable");
86
+ const responsesTableName = options.responsesTable ?? "form_responses";
87
+ const responsesTable = identifier(responsesTableName, "form_responses", "responsesTable");
88
+ const responsesIndex = identifier(`${responsesTableName}_lookup_idx`, "form_responses_lookup_idx", "responsesTable");
89
+ let migration;
90
+ const ensureReady = async () => {
91
+ if (options.autoMigrate !== true) return;
92
+ migration ??= (async () => {
93
+ await options.db.run(`CREATE TABLE IF NOT EXISTS ${schemasTable} (
94
+ form_id TEXT NOT NULL,
95
+ form_version INTEGER NOT NULL,
96
+ schema_json TEXT NOT NULL,
97
+ updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
98
+ PRIMARY KEY (form_id, form_version)
99
+ )`);
100
+ await options.db.run(`CREATE TABLE IF NOT EXISTS ${responsesTable} (
101
+ response_id TEXT PRIMARY KEY,
102
+ form_id TEXT NOT NULL,
103
+ form_version INTEGER NOT NULL,
104
+ locale TEXT NOT NULL,
105
+ submitted_at TEXT NOT NULL,
106
+ submission_json TEXT NOT NULL
107
+ )`);
108
+ await options.db.run(
109
+ `CREATE INDEX IF NOT EXISTS ${responsesIndex} ON ${responsesTable} (form_id, submitted_at, response_id)`
110
+ );
111
+ })();
112
+ await migration;
113
+ };
114
+ return {
115
+ async saveSchema(schema) {
116
+ await ensureReady();
117
+ (0, import_core.assertValidFormSchema)(schema);
118
+ await options.db.run(
119
+ `INSERT INTO ${schemasTable} (form_id, form_version, schema_json, updated_at)
120
+ VALUES (?, ?, ?, CURRENT_TIMESTAMP)
121
+ ON CONFLICT (form_id, form_version) DO UPDATE
122
+ SET schema_json = excluded.schema_json, updated_at = CURRENT_TIMESTAMP`,
123
+ [schema.id, schema.version, JSON.stringify(schema)]
124
+ );
125
+ },
126
+ async getSchema(formId, formVersion) {
127
+ await ensureReady();
128
+ const row = await options.db.get(
129
+ `SELECT form_id, form_version, schema_json FROM ${schemasTable} WHERE form_id = ? AND form_version = ?`,
130
+ [formId, formVersion]
131
+ );
132
+ return row === void 0 ? null : parseSchemaRow(row, 0);
133
+ },
134
+ async listSchemas() {
135
+ await ensureReady();
136
+ const rows = await options.db.all(
137
+ `SELECT form_id, form_version, schema_json FROM ${schemasTable} ORDER BY form_id, form_version`
138
+ );
139
+ return rows.map(parseSchemaRow);
140
+ },
141
+ async deleteSchema(formId, formVersion) {
142
+ await ensureReady();
143
+ await options.db.run(`DELETE FROM ${schemasTable} WHERE form_id = ? AND form_version = ?`, [formId, formVersion]);
144
+ },
145
+ async saveSubmission(submission) {
146
+ await ensureReady();
147
+ const stored = parseSubmission(submission, `input "${String(submission?.id)}"`);
148
+ await options.db.run(
149
+ `INSERT INTO ${responsesTable}
150
+ (response_id, form_id, form_version, locale, submitted_at, submission_json)
151
+ VALUES (?, ?, ?, ?, ?, ?)`,
152
+ [stored.id, stored.formId, stored.formVersion, stored.locale, stored.submittedAt, JSON.stringify(stored)]
153
+ );
154
+ },
155
+ async listSubmissions(formId, formVersion, queryOptions) {
156
+ await ensureReady();
157
+ const conditions = ["form_id = ?"];
158
+ const params = [formId];
159
+ if (formVersion !== void 0) {
160
+ conditions.push("form_version = ?");
161
+ params.push(formVersion);
162
+ }
163
+ if (queryOptions?.since !== void 0) {
164
+ conditions.push("submitted_at >= ?");
165
+ params.push(queryOptions.since);
166
+ }
167
+ if (queryOptions?.until !== void 0) {
168
+ conditions.push("submitted_at <= ?");
169
+ params.push(queryOptions.until);
170
+ }
171
+ const rows = await options.db.all(
172
+ `SELECT response_id, form_id, form_version, locale, submitted_at, submission_json
173
+ FROM ${responsesTable} WHERE ${conditions.join(" AND ")} ORDER BY submitted_at, response_id`,
174
+ params
175
+ );
176
+ return rows.map(parseSubmissionRow);
177
+ },
178
+ async deleteSubmission(submissionId) {
179
+ await ensureReady();
180
+ await options.db.run(`DELETE FROM ${responsesTable} WHERE response_id = ?`, [submissionId]);
181
+ },
182
+ async clearResponses(formId) {
183
+ await ensureReady();
184
+ await options.db.run(`DELETE FROM ${responsesTable} WHERE form_id = ?`, [formId]);
185
+ },
186
+ async clear() {
187
+ await ensureReady();
188
+ await options.db.run(`DELETE FROM ${responsesTable}`);
189
+ await options.db.run(`DELETE FROM ${schemasTable}`);
190
+ }
191
+ };
192
+ }
193
+ // Annotate the CommonJS export names for ESM import in node:
194
+ 0 && (module.exports = {
195
+ createSqliteStorage
196
+ });
@@ -0,0 +1,16 @@
1
+ import { FormStorageAdapter } from '@form-engine-ts/core';
2
+
3
+ interface SqliteExecutor {
4
+ run(sql: string, params?: readonly unknown[]): Promise<void> | void;
5
+ get<T>(sql: string, params?: readonly unknown[]): Promise<T | undefined> | T | undefined;
6
+ all<T>(sql: string, params?: readonly unknown[]): Promise<readonly T[]> | readonly T[];
7
+ }
8
+ interface SqliteStorageOptions {
9
+ readonly db: SqliteExecutor;
10
+ readonly schemasTable?: string;
11
+ readonly responsesTable?: string;
12
+ readonly autoMigrate?: boolean;
13
+ }
14
+ declare function createSqliteStorage(options: SqliteStorageOptions): FormStorageAdapter;
15
+
16
+ export { type SqliteExecutor, type SqliteStorageOptions, createSqliteStorage };
@@ -0,0 +1,16 @@
1
+ import { FormStorageAdapter } from '@form-engine-ts/core';
2
+
3
+ interface SqliteExecutor {
4
+ run(sql: string, params?: readonly unknown[]): Promise<void> | void;
5
+ get<T>(sql: string, params?: readonly unknown[]): Promise<T | undefined> | T | undefined;
6
+ all<T>(sql: string, params?: readonly unknown[]): Promise<readonly T[]> | readonly T[];
7
+ }
8
+ interface SqliteStorageOptions {
9
+ readonly db: SqliteExecutor;
10
+ readonly schemasTable?: string;
11
+ readonly responsesTable?: string;
12
+ readonly autoMigrate?: boolean;
13
+ }
14
+ declare function createSqliteStorage(options: SqliteStorageOptions): FormStorageAdapter;
15
+
16
+ export { type SqliteExecutor, type SqliteStorageOptions, createSqliteStorage };
package/dist/index.js ADDED
@@ -0,0 +1,171 @@
1
+ // src/index.ts
2
+ import { assertValidFormSchema } from "@form-engine-ts/core";
3
+ function isRecord(value) {
4
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5
+ }
6
+ function isFormValue(value) {
7
+ return value === void 0 || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) || Array.isArray(value) && value.every((item) => typeof item === "string");
8
+ }
9
+ function cloneJson(value) {
10
+ return JSON.parse(JSON.stringify(value));
11
+ }
12
+ function parseJson(value, location) {
13
+ if (typeof value !== "string") return cloneJson(value);
14
+ try {
15
+ return JSON.parse(value);
16
+ } catch (cause) {
17
+ throw new Error(`SQLite JSON at ${location} is invalid.`, { cause });
18
+ }
19
+ }
20
+ function parseSubmission(value, location) {
21
+ const parsed = parseJson(value, location);
22
+ if (!isRecord(parsed) || typeof parsed.id !== "string" || typeof parsed.formId !== "string" || !Number.isInteger(parsed.formVersion) || typeof parsed.locale !== "string" || typeof parsed.submittedAt !== "string" || !isRecord(parsed.values) || !Object.values(parsed.values).every(isFormValue)) {
23
+ throw new Error(`SQLite submission at ${location} is invalid.`);
24
+ }
25
+ return cloneJson(parsed);
26
+ }
27
+ function parseSchemaRow(value, index) {
28
+ if (!isRecord(value)) throw new Error(`SQLite schema row ${index} is invalid.`);
29
+ const row = value;
30
+ const schema = parseJson(row.schema_json, `schema row ${index}`);
31
+ try {
32
+ assertValidFormSchema(schema);
33
+ } catch (cause) {
34
+ throw new Error(`SQLite schema row ${index} is invalid.`, { cause });
35
+ }
36
+ if (row.form_id !== schema.id || row.form_version !== schema.version) {
37
+ throw new Error(`SQLite schema row ${index} has inconsistent metadata.`);
38
+ }
39
+ return cloneJson(schema);
40
+ }
41
+ function parseSubmissionRow(value, index) {
42
+ if (!isRecord(value)) throw new Error(`SQLite submission row ${index} is invalid.`);
43
+ const row = value;
44
+ const submission = parseSubmission(row.submission_json, `submission row ${index}`);
45
+ if (row.response_id !== submission.id || row.form_id !== submission.formId || row.form_version !== submission.formVersion || row.locale !== submission.locale || row.submitted_at !== submission.submittedAt) {
46
+ throw new Error(`SQLite submission row ${index} has inconsistent metadata.`);
47
+ }
48
+ return submission;
49
+ }
50
+ function identifier(value, fallback, optionName) {
51
+ const name = value ?? fallback;
52
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
53
+ throw new TypeError(`${optionName} must be a safe SQL identifier.`);
54
+ }
55
+ return `"${name}"`;
56
+ }
57
+ function createSqliteStorage(options) {
58
+ if (options?.db === void 0 || typeof options.db.run !== "function" || typeof options.db.get !== "function" || typeof options.db.all !== "function") {
59
+ throw new TypeError("db with run, get, and all functions is required.");
60
+ }
61
+ const schemasTable = identifier(options.schemasTable, "form_schemas", "schemasTable");
62
+ const responsesTableName = options.responsesTable ?? "form_responses";
63
+ const responsesTable = identifier(responsesTableName, "form_responses", "responsesTable");
64
+ const responsesIndex = identifier(`${responsesTableName}_lookup_idx`, "form_responses_lookup_idx", "responsesTable");
65
+ let migration;
66
+ const ensureReady = async () => {
67
+ if (options.autoMigrate !== true) return;
68
+ migration ??= (async () => {
69
+ await options.db.run(`CREATE TABLE IF NOT EXISTS ${schemasTable} (
70
+ form_id TEXT NOT NULL,
71
+ form_version INTEGER NOT NULL,
72
+ schema_json TEXT NOT NULL,
73
+ updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
74
+ PRIMARY KEY (form_id, form_version)
75
+ )`);
76
+ await options.db.run(`CREATE TABLE IF NOT EXISTS ${responsesTable} (
77
+ response_id TEXT PRIMARY KEY,
78
+ form_id TEXT NOT NULL,
79
+ form_version INTEGER NOT NULL,
80
+ locale TEXT NOT NULL,
81
+ submitted_at TEXT NOT NULL,
82
+ submission_json TEXT NOT NULL
83
+ )`);
84
+ await options.db.run(
85
+ `CREATE INDEX IF NOT EXISTS ${responsesIndex} ON ${responsesTable} (form_id, submitted_at, response_id)`
86
+ );
87
+ })();
88
+ await migration;
89
+ };
90
+ return {
91
+ async saveSchema(schema) {
92
+ await ensureReady();
93
+ assertValidFormSchema(schema);
94
+ await options.db.run(
95
+ `INSERT INTO ${schemasTable} (form_id, form_version, schema_json, updated_at)
96
+ VALUES (?, ?, ?, CURRENT_TIMESTAMP)
97
+ ON CONFLICT (form_id, form_version) DO UPDATE
98
+ SET schema_json = excluded.schema_json, updated_at = CURRENT_TIMESTAMP`,
99
+ [schema.id, schema.version, JSON.stringify(schema)]
100
+ );
101
+ },
102
+ async getSchema(formId, formVersion) {
103
+ await ensureReady();
104
+ const row = await options.db.get(
105
+ `SELECT form_id, form_version, schema_json FROM ${schemasTable} WHERE form_id = ? AND form_version = ?`,
106
+ [formId, formVersion]
107
+ );
108
+ return row === void 0 ? null : parseSchemaRow(row, 0);
109
+ },
110
+ async listSchemas() {
111
+ await ensureReady();
112
+ const rows = await options.db.all(
113
+ `SELECT form_id, form_version, schema_json FROM ${schemasTable} ORDER BY form_id, form_version`
114
+ );
115
+ return rows.map(parseSchemaRow);
116
+ },
117
+ async deleteSchema(formId, formVersion) {
118
+ await ensureReady();
119
+ await options.db.run(`DELETE FROM ${schemasTable} WHERE form_id = ? AND form_version = ?`, [formId, formVersion]);
120
+ },
121
+ async saveSubmission(submission) {
122
+ await ensureReady();
123
+ const stored = parseSubmission(submission, `input "${String(submission?.id)}"`);
124
+ await options.db.run(
125
+ `INSERT INTO ${responsesTable}
126
+ (response_id, form_id, form_version, locale, submitted_at, submission_json)
127
+ VALUES (?, ?, ?, ?, ?, ?)`,
128
+ [stored.id, stored.formId, stored.formVersion, stored.locale, stored.submittedAt, JSON.stringify(stored)]
129
+ );
130
+ },
131
+ async listSubmissions(formId, formVersion, queryOptions) {
132
+ await ensureReady();
133
+ const conditions = ["form_id = ?"];
134
+ const params = [formId];
135
+ if (formVersion !== void 0) {
136
+ conditions.push("form_version = ?");
137
+ params.push(formVersion);
138
+ }
139
+ if (queryOptions?.since !== void 0) {
140
+ conditions.push("submitted_at >= ?");
141
+ params.push(queryOptions.since);
142
+ }
143
+ if (queryOptions?.until !== void 0) {
144
+ conditions.push("submitted_at <= ?");
145
+ params.push(queryOptions.until);
146
+ }
147
+ const rows = await options.db.all(
148
+ `SELECT response_id, form_id, form_version, locale, submitted_at, submission_json
149
+ FROM ${responsesTable} WHERE ${conditions.join(" AND ")} ORDER BY submitted_at, response_id`,
150
+ params
151
+ );
152
+ return rows.map(parseSubmissionRow);
153
+ },
154
+ async deleteSubmission(submissionId) {
155
+ await ensureReady();
156
+ await options.db.run(`DELETE FROM ${responsesTable} WHERE response_id = ?`, [submissionId]);
157
+ },
158
+ async clearResponses(formId) {
159
+ await ensureReady();
160
+ await options.db.run(`DELETE FROM ${responsesTable} WHERE form_id = ?`, [formId]);
161
+ },
162
+ async clear() {
163
+ await ensureReady();
164
+ await options.db.run(`DELETE FROM ${responsesTable}`);
165
+ await options.db.run(`DELETE FROM ${schemasTable}`);
166
+ }
167
+ };
168
+ }
169
+ export {
170
+ createSqliteStorage
171
+ };
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@form-engine-ts/storage-sqlite",
3
+ "version": "1.1.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "type": "module",
8
+ "sideEffects": false,
9
+ "files": [
10
+ "dist",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "main": "./dist/index.cjs",
15
+ "module": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js",
21
+ "require": "./dist/index.cjs"
22
+ }
23
+ },
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/nitta-a/form-engine-ts.git",
28
+ "directory": "packages/storage-sqlite"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/nitta-a/form-engine-ts/issues"
32
+ },
33
+ "homepage": "https://github.com/nitta-a/form-engine-ts#readme",
34
+ "keywords": [
35
+ "form",
36
+ "survey",
37
+ "storage",
38
+ "sqlite",
39
+ "libsql",
40
+ "typescript"
41
+ ],
42
+ "dependencies": {
43
+ "@form-engine-ts/core": "1.1.0"
44
+ },
45
+ "scripts": {
46
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean --external @form-engine-ts/core",
47
+ "check": "biome check . && tsc --noEmit",
48
+ "test": "vitest run --globals",
49
+ "typecheck": "tsc --noEmit"
50
+ }
51
+ }