@aigne/afs-sqlite 1.0.1-beta
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/CHANGELOG.md +29 -0
- package/LICENSE.md +93 -0
- package/README.md +277 -0
- package/lib/cjs/actions/built-in.d.ts +5 -0
- package/lib/cjs/actions/built-in.js +165 -0
- package/lib/cjs/actions/registry.d.ts +49 -0
- package/lib/cjs/actions/registry.js +102 -0
- package/lib/cjs/actions/types.d.ts +51 -0
- package/lib/cjs/actions/types.js +2 -0
- package/lib/cjs/config.d.ts +89 -0
- package/lib/cjs/config.js +33 -0
- package/lib/cjs/index.d.ts +13 -0
- package/lib/cjs/index.js +47 -0
- package/lib/cjs/node/builder.d.ts +43 -0
- package/lib/cjs/node/builder.js +187 -0
- package/lib/cjs/operations/crud.d.ts +64 -0
- package/lib/cjs/operations/crud.js +225 -0
- package/lib/cjs/operations/query-builder.d.ts +37 -0
- package/lib/cjs/operations/query-builder.js +102 -0
- package/lib/cjs/operations/search.d.ts +75 -0
- package/lib/cjs/operations/search.js +172 -0
- package/lib/cjs/package.json +3 -0
- package/lib/cjs/router/path-router.d.ts +38 -0
- package/lib/cjs/router/path-router.js +90 -0
- package/lib/cjs/router/types.d.ts +30 -0
- package/lib/cjs/router/types.js +2 -0
- package/lib/cjs/schema/introspector.d.ts +48 -0
- package/lib/cjs/schema/introspector.js +186 -0
- package/lib/cjs/schema/types.d.ts +104 -0
- package/lib/cjs/schema/types.js +13 -0
- package/lib/cjs/sqlite-afs.d.ts +144 -0
- package/lib/cjs/sqlite-afs.js +337 -0
- package/lib/dts/actions/built-in.d.ts +5 -0
- package/lib/dts/actions/registry.d.ts +49 -0
- package/lib/dts/actions/types.d.ts +51 -0
- package/lib/dts/config.d.ts +89 -0
- package/lib/dts/index.d.ts +13 -0
- package/lib/dts/node/builder.d.ts +43 -0
- package/lib/dts/operations/crud.d.ts +64 -0
- package/lib/dts/operations/query-builder.d.ts +37 -0
- package/lib/dts/operations/search.d.ts +75 -0
- package/lib/dts/router/path-router.d.ts +38 -0
- package/lib/dts/router/types.d.ts +30 -0
- package/lib/dts/schema/introspector.d.ts +48 -0
- package/lib/dts/schema/types.d.ts +104 -0
- package/lib/dts/sqlite-afs.d.ts +144 -0
- package/lib/esm/actions/built-in.d.ts +5 -0
- package/lib/esm/actions/built-in.js +162 -0
- package/lib/esm/actions/registry.d.ts +49 -0
- package/lib/esm/actions/registry.js +98 -0
- package/lib/esm/actions/types.d.ts +51 -0
- package/lib/esm/actions/types.js +1 -0
- package/lib/esm/config.d.ts +89 -0
- package/lib/esm/config.js +30 -0
- package/lib/esm/index.d.ts +13 -0
- package/lib/esm/index.js +17 -0
- package/lib/esm/node/builder.d.ts +43 -0
- package/lib/esm/node/builder.js +177 -0
- package/lib/esm/operations/crud.d.ts +64 -0
- package/lib/esm/operations/crud.js +221 -0
- package/lib/esm/operations/query-builder.d.ts +37 -0
- package/lib/esm/operations/query-builder.js +92 -0
- package/lib/esm/operations/search.d.ts +75 -0
- package/lib/esm/operations/search.js +167 -0
- package/lib/esm/package.json +3 -0
- package/lib/esm/router/path-router.d.ts +38 -0
- package/lib/esm/router/path-router.js +83 -0
- package/lib/esm/router/types.d.ts +30 -0
- package/lib/esm/router/types.js +1 -0
- package/lib/esm/schema/introspector.d.ts +48 -0
- package/lib/esm/schema/introspector.js +182 -0
- package/lib/esm/schema/types.d.ts +104 -0
- package/lib/esm/schema/types.js +10 -0
- package/lib/esm/sqlite-afs.d.ts +144 -0
- package/lib/esm/sqlite-afs.js +333 -0
- package/package.json +71 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { initDatabase } from "@aigne/sqlite";
|
|
2
|
+
import { registerBuiltInActions } from "./actions/built-in.js";
|
|
3
|
+
import { ActionsRegistry } from "./actions/registry.js";
|
|
4
|
+
import { sqliteAFSConfigSchema } from "./config.js";
|
|
5
|
+
import { buildActionsListEntry } from "./node/builder.js";
|
|
6
|
+
import { CRUDOperations } from "./operations/crud.js";
|
|
7
|
+
import { createFTSConfig, FTSSearch } from "./operations/search.js";
|
|
8
|
+
import { createPathRouter, matchPath } from "./router/path-router.js";
|
|
9
|
+
import { SchemaIntrospector } from "./schema/introspector.js";
|
|
10
|
+
/**
|
|
11
|
+
* SQLite AFS Module
|
|
12
|
+
*
|
|
13
|
+
* Exposes SQLite databases as AFS nodes with full CRUD support,
|
|
14
|
+
* schema introspection, FTS5 search, and virtual paths (@attr, @meta, @actions).
|
|
15
|
+
*/
|
|
16
|
+
export class SQLiteAFS {
|
|
17
|
+
options;
|
|
18
|
+
name;
|
|
19
|
+
description;
|
|
20
|
+
accessMode;
|
|
21
|
+
db;
|
|
22
|
+
schemas = new Map();
|
|
23
|
+
router;
|
|
24
|
+
crud;
|
|
25
|
+
ftsSearch;
|
|
26
|
+
actions;
|
|
27
|
+
ftsConfig;
|
|
28
|
+
initialized = false;
|
|
29
|
+
constructor(options) {
|
|
30
|
+
this.options = options;
|
|
31
|
+
this.name = options.name ?? "sqlite-afs";
|
|
32
|
+
this.description = options.description ?? `SQLite database: ${options.url}`;
|
|
33
|
+
this.accessMode = options.accessMode ?? "readwrite";
|
|
34
|
+
this.ftsConfig = createFTSConfig(options.fts);
|
|
35
|
+
this.actions = new ActionsRegistry();
|
|
36
|
+
registerBuiltInActions(this.actions);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Returns the Zod schema for configuration validation
|
|
40
|
+
*/
|
|
41
|
+
static schema() {
|
|
42
|
+
return sqliteAFSConfigSchema;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Loads a module instance from configuration
|
|
46
|
+
*/
|
|
47
|
+
static async load({ parsed }) {
|
|
48
|
+
const validated = sqliteAFSConfigSchema.parse(parsed);
|
|
49
|
+
return new SQLiteAFS(validated);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Called when the module is mounted to AFS
|
|
53
|
+
*/
|
|
54
|
+
async onMount(_afs) {
|
|
55
|
+
await this.initialize();
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Initializes the database connection and introspects schema
|
|
59
|
+
*/
|
|
60
|
+
async initialize() {
|
|
61
|
+
if (this.initialized)
|
|
62
|
+
return;
|
|
63
|
+
// Initialize database connection
|
|
64
|
+
this.db = await initDatabase({
|
|
65
|
+
url: this.options.url,
|
|
66
|
+
wal: this.options.wal ?? true,
|
|
67
|
+
});
|
|
68
|
+
// Cast db to LibSQLDatabase for operations
|
|
69
|
+
const db = this.db;
|
|
70
|
+
// Introspect database schema
|
|
71
|
+
const introspector = new SchemaIntrospector();
|
|
72
|
+
this.schemas = await introspector.introspect(db, {
|
|
73
|
+
tables: this.options.tables,
|
|
74
|
+
excludeTables: this.options.excludeTables,
|
|
75
|
+
});
|
|
76
|
+
// Initialize components
|
|
77
|
+
this.router = createPathRouter();
|
|
78
|
+
this.crud = new CRUDOperations(db, this.schemas, "");
|
|
79
|
+
this.ftsSearch = new FTSSearch(db, this.schemas, this.ftsConfig, "");
|
|
80
|
+
this.initialized = true;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Ensures the module is initialized
|
|
84
|
+
*/
|
|
85
|
+
async ensureInitialized() {
|
|
86
|
+
if (!this.initialized) {
|
|
87
|
+
await this.initialize();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Lists entries at a path
|
|
92
|
+
*/
|
|
93
|
+
async list(path, options) {
|
|
94
|
+
await this.ensureInitialized();
|
|
95
|
+
const match = matchPath(this.router, path);
|
|
96
|
+
if (!match) {
|
|
97
|
+
return { data: [] };
|
|
98
|
+
}
|
|
99
|
+
switch (match.action) {
|
|
100
|
+
case "listTables":
|
|
101
|
+
return this.crud.listTables();
|
|
102
|
+
case "listTable":
|
|
103
|
+
if (!match.params.table)
|
|
104
|
+
return { data: [] };
|
|
105
|
+
return this.crud.listTable(match.params.table, options);
|
|
106
|
+
case "listAttributes":
|
|
107
|
+
if (!match.params.table || !match.params.pk)
|
|
108
|
+
return { data: [] };
|
|
109
|
+
return this.crud.listAttributes(match.params.table, match.params.pk);
|
|
110
|
+
case "listActions":
|
|
111
|
+
if (!match.params.table || !match.params.pk)
|
|
112
|
+
return { data: [] };
|
|
113
|
+
return this.listActions(match.params.table, match.params.pk);
|
|
114
|
+
default:
|
|
115
|
+
return { data: [] };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Reads an entry at a path
|
|
120
|
+
*/
|
|
121
|
+
async read(path, _options) {
|
|
122
|
+
await this.ensureInitialized();
|
|
123
|
+
const match = matchPath(this.router, path);
|
|
124
|
+
if (!match) {
|
|
125
|
+
return {};
|
|
126
|
+
}
|
|
127
|
+
switch (match.action) {
|
|
128
|
+
case "readRow":
|
|
129
|
+
if (!match.params.table || !match.params.pk)
|
|
130
|
+
return {};
|
|
131
|
+
return this.crud.readRow(match.params.table, match.params.pk);
|
|
132
|
+
case "getSchema":
|
|
133
|
+
if (!match.params.table)
|
|
134
|
+
return {};
|
|
135
|
+
return this.crud.getSchema(match.params.table);
|
|
136
|
+
case "getAttribute":
|
|
137
|
+
if (!match.params.table || !match.params.pk || !match.params.column)
|
|
138
|
+
return {};
|
|
139
|
+
return this.crud.getAttribute(match.params.table, match.params.pk, match.params.column);
|
|
140
|
+
case "getMeta":
|
|
141
|
+
if (!match.params.table || !match.params.pk)
|
|
142
|
+
return {};
|
|
143
|
+
return this.crud.getMeta(match.params.table, match.params.pk);
|
|
144
|
+
default:
|
|
145
|
+
return {};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Writes an entry at a path
|
|
150
|
+
*/
|
|
151
|
+
async write(path, content, _options) {
|
|
152
|
+
await this.ensureInitialized();
|
|
153
|
+
if (this.accessMode === "readonly") {
|
|
154
|
+
throw new Error("Module is in readonly mode");
|
|
155
|
+
}
|
|
156
|
+
const match = matchPath(this.router, path);
|
|
157
|
+
if (!match) {
|
|
158
|
+
throw new Error(`Invalid path: ${path}`);
|
|
159
|
+
}
|
|
160
|
+
switch (match.action) {
|
|
161
|
+
case "createRow":
|
|
162
|
+
if (!match.params.table) {
|
|
163
|
+
throw new Error("Table name required for create");
|
|
164
|
+
}
|
|
165
|
+
return this.crud.createRow(match.params.table, content.content ?? content);
|
|
166
|
+
case "readRow":
|
|
167
|
+
if (!match.params.table || !match.params.pk) {
|
|
168
|
+
throw new Error("Table and primary key required for update");
|
|
169
|
+
}
|
|
170
|
+
return this.crud.updateRow(match.params.table, match.params.pk, content.content ?? content);
|
|
171
|
+
case "executeAction":
|
|
172
|
+
if (!match.params.table || !match.params.action) {
|
|
173
|
+
throw new Error("Table and action name required");
|
|
174
|
+
}
|
|
175
|
+
return this.executeAction(match.params.table, match.params.pk, match.params.action, content.content ?? content);
|
|
176
|
+
default:
|
|
177
|
+
throw new Error(`Write not supported for path: ${path}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Deletes an entry at a path
|
|
182
|
+
*/
|
|
183
|
+
async delete(path, _options) {
|
|
184
|
+
await this.ensureInitialized();
|
|
185
|
+
if (this.accessMode === "readonly") {
|
|
186
|
+
throw new Error("Module is in readonly mode");
|
|
187
|
+
}
|
|
188
|
+
const match = matchPath(this.router, path);
|
|
189
|
+
if (!match || match.action !== "readRow") {
|
|
190
|
+
throw new Error(`Delete not supported for path: ${path}`);
|
|
191
|
+
}
|
|
192
|
+
if (!match.params.table || !match.params.pk) {
|
|
193
|
+
throw new Error("Table and primary key required for delete");
|
|
194
|
+
}
|
|
195
|
+
return this.crud.deleteRow(match.params.table, match.params.pk);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Searches for entries matching a query
|
|
199
|
+
*/
|
|
200
|
+
async search(path, query, options) {
|
|
201
|
+
await this.ensureInitialized();
|
|
202
|
+
// If path specifies a table, search only that table
|
|
203
|
+
const match = matchPath(this.router, path);
|
|
204
|
+
if (match?.params.table) {
|
|
205
|
+
return this.ftsSearch.searchTable(match.params.table, query, options);
|
|
206
|
+
}
|
|
207
|
+
// Otherwise search all FTS-enabled tables
|
|
208
|
+
return this.ftsSearch.search(query, options);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Executes a module operation
|
|
212
|
+
*/
|
|
213
|
+
async exec(path, args, _options) {
|
|
214
|
+
await this.ensureInitialized();
|
|
215
|
+
const match = matchPath(this.router, path);
|
|
216
|
+
if (match?.action === "executeAction" && match.params.table && match.params.action) {
|
|
217
|
+
const result = await this.executeAction(match.params.table, match.params.pk, match.params.action, args);
|
|
218
|
+
return { data: result.data };
|
|
219
|
+
}
|
|
220
|
+
throw new Error(`Exec not supported for path: ${path}`);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Lists available actions for a row
|
|
224
|
+
*/
|
|
225
|
+
listActions(table, pk) {
|
|
226
|
+
const actionNames = this.actions.listNames({ rowLevel: true });
|
|
227
|
+
return {
|
|
228
|
+
data: buildActionsListEntry(table, pk, actionNames, { basePath: "" }),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Executes an action
|
|
233
|
+
*/
|
|
234
|
+
async executeAction(table, pk, actionName, params) {
|
|
235
|
+
const schema = this.schemas.get(table);
|
|
236
|
+
if (!schema) {
|
|
237
|
+
throw new Error(`Table '${table}' not found`);
|
|
238
|
+
}
|
|
239
|
+
// Get row data if pk is provided
|
|
240
|
+
let row;
|
|
241
|
+
if (pk) {
|
|
242
|
+
const readResult = await this.crud.readRow(table, pk);
|
|
243
|
+
row = readResult.data?.content;
|
|
244
|
+
}
|
|
245
|
+
const ctx = {
|
|
246
|
+
db: this.db,
|
|
247
|
+
schemas: this.schemas,
|
|
248
|
+
table,
|
|
249
|
+
pk,
|
|
250
|
+
row,
|
|
251
|
+
module: {
|
|
252
|
+
refreshSchema: () => this.refreshSchema(),
|
|
253
|
+
exportTable: (t, f) => this.exportTable(t, f),
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
const result = await this.actions.execute(actionName, ctx, params);
|
|
257
|
+
if (!result.success) {
|
|
258
|
+
throw new Error(result.message ?? "Action failed");
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
data: {
|
|
262
|
+
id: `${table}:${pk ?? ""}:@actions:${actionName}`,
|
|
263
|
+
path: pk ? `/${table}/${pk}/@actions/${actionName}` : `/${table}/@actions/${actionName}`,
|
|
264
|
+
content: result.data,
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Refreshes the schema cache
|
|
270
|
+
*/
|
|
271
|
+
async refreshSchema() {
|
|
272
|
+
const db = this.db;
|
|
273
|
+
const introspector = new SchemaIntrospector();
|
|
274
|
+
this.schemas = await introspector.introspect(db, {
|
|
275
|
+
tables: this.options.tables,
|
|
276
|
+
excludeTables: this.options.excludeTables,
|
|
277
|
+
});
|
|
278
|
+
this.crud.setSchemas(this.schemas);
|
|
279
|
+
this.ftsSearch.setSchemas(this.schemas);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Exports table data in specified format
|
|
283
|
+
*/
|
|
284
|
+
async exportTable(table, format) {
|
|
285
|
+
const listResult = await this.crud.listTable(table, { limit: 10000 });
|
|
286
|
+
if (format === "csv") {
|
|
287
|
+
const schema = this.schemas.get(table);
|
|
288
|
+
if (!schema)
|
|
289
|
+
throw new Error(`Table '${table}' not found`);
|
|
290
|
+
const headers = schema.columns.map((c) => c.name).join(",");
|
|
291
|
+
const rows = listResult.data.map((entry) => {
|
|
292
|
+
const content = entry.content;
|
|
293
|
+
return schema.columns
|
|
294
|
+
.map((c) => {
|
|
295
|
+
const val = content[c.name];
|
|
296
|
+
if (val === null || val === undefined)
|
|
297
|
+
return "";
|
|
298
|
+
if (typeof val === "string" && (val.includes(",") || val.includes('"'))) {
|
|
299
|
+
return `"${val.replace(/"/g, '""')}"`;
|
|
300
|
+
}
|
|
301
|
+
return String(val);
|
|
302
|
+
})
|
|
303
|
+
.join(",");
|
|
304
|
+
});
|
|
305
|
+
return `${headers}\n${rows.join("\n")}`;
|
|
306
|
+
}
|
|
307
|
+
// Default: JSON
|
|
308
|
+
return listResult.data.map((entry) => entry.content);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Registers a custom action
|
|
312
|
+
*/
|
|
313
|
+
registerAction(name, handler, options) {
|
|
314
|
+
this.actions.registerSimple(name, async (ctx, params) => ({
|
|
315
|
+
success: true,
|
|
316
|
+
data: await handler(ctx, params),
|
|
317
|
+
}), options);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Gets table schemas (for external access)
|
|
321
|
+
*/
|
|
322
|
+
getSchemas() {
|
|
323
|
+
return this.schemas;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Gets the database instance (for advanced operations)
|
|
327
|
+
*/
|
|
328
|
+
getDatabase() {
|
|
329
|
+
return this.db;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
// Type check to ensure SQLiteAFS implements AFSModuleClass
|
|
333
|
+
const _typeCheck = SQLiteAFS;
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aigne/afs-sqlite",
|
|
3
|
+
"version": "1.0.1-beta",
|
|
4
|
+
"description": "AIGNE AFS module for SQLite database storage with schema introspection",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"author": "Arcblock <blocklet@arcblock.io> https://github.com/blocklet",
|
|
9
|
+
"homepage": "https://www.aigne.io/framework",
|
|
10
|
+
"license": "Elastic-2.0",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/AIGNE-io/aigne-framework"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/AIGNE-io/aigne-framework/issues"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"lib/cjs",
|
|
20
|
+
"lib/dts",
|
|
21
|
+
"lib/esm",
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"README.md",
|
|
24
|
+
"CHANGELOG.md"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "./lib/cjs/index.js",
|
|
28
|
+
"module": "./lib/esm/index.js",
|
|
29
|
+
"types": "./lib/dts/index.d.ts",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"import": "./lib/esm/index.js",
|
|
33
|
+
"require": "./lib/cjs/index.js",
|
|
34
|
+
"types": "./lib/dts/index.d.ts"
|
|
35
|
+
},
|
|
36
|
+
"./*": {
|
|
37
|
+
"import": "./lib/esm/*",
|
|
38
|
+
"require": "./lib/cjs/*",
|
|
39
|
+
"types": "./lib/dts/*"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"typesVersions": {
|
|
43
|
+
"*": {
|
|
44
|
+
".": [
|
|
45
|
+
"./lib/dts/index.d.ts"
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"radix3": "^1.1.2",
|
|
51
|
+
"zod": "^3.25.67",
|
|
52
|
+
"@aigne/afs": "^1.4.0-beta.9",
|
|
53
|
+
"@aigne/sqlite": "^0.4.9-beta.2"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/bun": "^1.2.22",
|
|
57
|
+
"drizzle-orm": "^0.44.2",
|
|
58
|
+
"npm-run-all": "^4.1.5",
|
|
59
|
+
"rimraf": "^6.0.1",
|
|
60
|
+
"typescript": "^5.9.2",
|
|
61
|
+
"@aigne/test-utils": "^0.5.69-beta.23"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"lint": "tsc --noEmit",
|
|
65
|
+
"build": "tsc --build scripts/tsconfig.build.json",
|
|
66
|
+
"clean": "rimraf lib test/coverage",
|
|
67
|
+
"test": "bun test",
|
|
68
|
+
"test:coverage": "bun test --coverage --coverage-reporter=lcov --coverage-reporter=text",
|
|
69
|
+
"postbuild": "node ../../scripts/post-build-lib.mjs"
|
|
70
|
+
}
|
|
71
|
+
}
|