@graypirate/tabula 1.0.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.
Files changed (71) hide show
  1. package/README.md +141 -0
  2. package/dist/API/create.d.ts +33 -0
  3. package/dist/API/create.js +91 -0
  4. package/dist/API/delete.d.ts +31 -0
  5. package/dist/API/delete.js +38 -0
  6. package/dist/API/index.d.ts +13 -0
  7. package/dist/API/index.js +9 -0
  8. package/dist/API/init.d.ts +3 -0
  9. package/dist/API/init.js +8 -0
  10. package/dist/API/read.d.ts +60 -0
  11. package/dist/API/read.js +95 -0
  12. package/dist/API/search.d.ts +4 -0
  13. package/dist/API/search.js +4 -0
  14. package/dist/API/types.d.ts +23 -0
  15. package/dist/API/types.js +0 -0
  16. package/dist/API/validation.d.ts +8 -0
  17. package/dist/API/validation.js +103 -0
  18. package/dist/API/write.d.ts +30 -0
  19. package/dist/API/write.js +90 -0
  20. package/dist/CLI/arguments.d.ts +45 -0
  21. package/dist/CLI/arguments.js +263 -0
  22. package/dist/CLI/dispatch.d.ts +3 -0
  23. package/dist/CLI/dispatch.js +143 -0
  24. package/dist/CLI/errors.d.ts +10 -0
  25. package/dist/CLI/errors.js +20 -0
  26. package/dist/CLI/index.d.ts +6 -0
  27. package/dist/CLI/index.js +70 -0
  28. package/dist/CLI/io.d.ts +6 -0
  29. package/dist/CLI/io.js +28 -0
  30. package/dist/CLI/json.d.ts +9 -0
  31. package/dist/CLI/json.js +42 -0
  32. package/dist/CLI/properties.d.ts +3 -0
  33. package/dist/CLI/properties.js +22 -0
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.js +1 -0
  36. package/dist/src/storage/db/blocks.d.ts +68 -0
  37. package/dist/src/storage/db/blocks.js +185 -0
  38. package/dist/src/storage/db/edges.d.ts +61 -0
  39. package/dist/src/storage/db/edges.js +306 -0
  40. package/dist/src/storage/db/init.d.ts +22 -0
  41. package/dist/src/storage/db/init.js +108 -0
  42. package/dist/src/storage/db/nodes.d.ts +34 -0
  43. package/dist/src/storage/db/nodes.js +91 -0
  44. package/dist/src/storage/db/objects.d.ts +55 -0
  45. package/dist/src/storage/db/objects.js +123 -0
  46. package/dist/src/storage/db/schema.sql +59 -0
  47. package/dist/src/storage/index.d.ts +47 -0
  48. package/dist/src/storage/index.js +315 -0
  49. package/dist/src/storage/types.d.ts +15 -0
  50. package/dist/src/storage/types.js +1 -0
  51. package/dist/src/types/block.d.ts +12 -0
  52. package/dist/src/types/block.js +0 -0
  53. package/dist/src/types/database.d.ts +6 -0
  54. package/dist/src/types/database.js +0 -0
  55. package/dist/src/types/graph.d.ts +11 -0
  56. package/dist/src/types/graph.js +0 -0
  57. package/dist/src/types/json.d.ts +7 -0
  58. package/dist/src/types/json.js +0 -0
  59. package/dist/src/types/object.d.ts +12 -0
  60. package/dist/src/types/object.js +0 -0
  61. package/dist/src/types/workspace.d.ts +6 -0
  62. package/dist/src/types/workspace.js +0 -0
  63. package/dist/src/utils/id.d.ts +6 -0
  64. package/dist/src/utils/id.js +18 -0
  65. package/dist/src/utils/yaml.d.ts +3 -0
  66. package/dist/src/utils/yaml.js +26 -0
  67. package/dist/src/workspace/index.d.ts +1 -0
  68. package/dist/src/workspace/index.js +1 -0
  69. package/dist/src/workspace/resolution.d.ts +20 -0
  70. package/dist/src/workspace/resolution.js +90 -0
  71. package/package.json +43 -0
@@ -0,0 +1,306 @@
1
+ import { BlockPrefix, DatabasePrefix, ObjectPrefix } from "../../utils/id";
2
+ import { getStoredNodeType, isStoredNode, } from "./nodes";
3
+ /**
4
+ * Reads the current direct parent for an object or block node.
5
+ * @param db - The database containing the child node
6
+ * @param childID - The object or block ID whose parent should be read
7
+ * @returns The parent reference, or undefined when the child is unattached
8
+ */
9
+ export function getEntityParent(db, childID) {
10
+ const childType = getStoredNodeType(db, childID);
11
+ if (childType === undefined) {
12
+ throw new Error(`Entity not found: ${childID}`);
13
+ }
14
+ if (childType === "database") {
15
+ throw new Error(`Database cannot be a child entity: ${childID}`);
16
+ }
17
+ const row = db.query(`
18
+ SELECT
19
+ edges.parent_id AS id,
20
+ nodes.type
21
+ FROM edges
22
+ JOIN nodes ON nodes.id = edges.parent_id
23
+ WHERE edges.child_id = $childID
24
+ `).get({ $childID: childID });
25
+ return row === null
26
+ ? undefined
27
+ : {
28
+ type: row.type,
29
+ id: row.id,
30
+ };
31
+ }
32
+ /**
33
+ * Adds an object as a root child of the database.
34
+ * @param db - The database containing the object
35
+ * @param databaseID - The database parent ID
36
+ * @param objectID - The object child ID
37
+ */
38
+ export function appendDatabaseRootObject(db, databaseID, objectID) {
39
+ appendEntityChild(db, databaseID, { type: "object", id: objectID });
40
+ }
41
+ /**
42
+ * Appends an existing object or block under a database, object, or block parent.
43
+ * If the child already has a parent, it is moved to the new parent.
44
+ * @param db - The database containing the parent and child
45
+ * @param parentID - The database, object, or block parent ID
46
+ * @param child - The object or block child to attach
47
+ */
48
+ export function appendEntityChild(db, parentID, child) {
49
+ validateParent(db, parentID);
50
+ validateChildBatch(db, parentID, [child]);
51
+ if (hasEntityChild(db, parentID, child.id)) {
52
+ return;
53
+ }
54
+ const append = db.transaction(() => {
55
+ detachEntityParent(db, child.id);
56
+ const row = db.query(`
57
+ SELECT COALESCE(MAX(position), -1) AS maxPosition
58
+ FROM edges
59
+ WHERE parent_id = $parentID
60
+ `).get({ $parentID: parentID });
61
+ db.query(`
62
+ INSERT INTO edges (parent_id, child_id, position)
63
+ VALUES ($parentID, $childID, $position)
64
+ `).run({
65
+ $parentID: parentID,
66
+ $childID: child.id,
67
+ $position: row.maxPosition + 1,
68
+ });
69
+ validateNoEntityCycles(db);
70
+ });
71
+ append();
72
+ }
73
+ /**
74
+ * Reads root object IDs ordered under the database.
75
+ * @param db - The database to read
76
+ * @param databaseID - The database ID whose root objects should be listed
77
+ * @returns Ordered root object IDs
78
+ */
79
+ export function getDatabaseRootObjects(db, databaseID) {
80
+ validateDatabaseParent(db, databaseID);
81
+ const rows = db.query(`
82
+ SELECT child_id AS id
83
+ FROM edges
84
+ JOIN nodes ON nodes.id = edges.child_id
85
+ WHERE parent_id = $databaseID
86
+ AND nodes.type = 'object'
87
+ ORDER BY position
88
+ `).all({ $databaseID: databaseID });
89
+ return rows.map((row) => row.id);
90
+ }
91
+ /**
92
+ * Reads direct child entity references for a database, object, or block parent.
93
+ * @param db - The database containing the parent
94
+ * @param parentID - The database or entity parent ID
95
+ * @returns Ordered child entity references
96
+ */
97
+ export function getDirectEntityChildren(db, parentID) {
98
+ validateParent(db, parentID);
99
+ const rows = db.query(`
100
+ SELECT
101
+ child_id AS id,
102
+ nodes.type,
103
+ position
104
+ FROM edges
105
+ JOIN nodes ON nodes.id = edges.child_id
106
+ WHERE parent_id = $parentID
107
+ ORDER BY position
108
+ `).all({ $parentID: parentID });
109
+ return rows.map((row) => {
110
+ if (row.type === "database") {
111
+ throw new Error(`Database cannot be a child entity: ${row.id}`);
112
+ }
113
+ return {
114
+ type: row.type,
115
+ id: row.id,
116
+ };
117
+ });
118
+ }
119
+ /**
120
+ * Replaces direct children for one or more parents.
121
+ * @param db - The database containing the parents and children
122
+ * @param desiredChildrenByParent - Complete desired child lists keyed by parent ID
123
+ */
124
+ export function replaceEntityChildren(db, desiredChildrenByParent) {
125
+ if (desiredChildrenByParent.size === 0) {
126
+ return;
127
+ }
128
+ const replace = db.transaction(() => {
129
+ const incomingChildIDs = new Set();
130
+ for (const [parentID, children] of desiredChildrenByParent) {
131
+ validateParent(db, parentID);
132
+ validateChildBatch(db, parentID, children);
133
+ for (const child of children) {
134
+ if (incomingChildIDs.has(child.id)) {
135
+ throw new Error(`Duplicate child entity: ${child.id}`);
136
+ }
137
+ incomingChildIDs.add(child.id);
138
+ }
139
+ }
140
+ for (const parentID of desiredChildrenByParent.keys()) {
141
+ db.query(`
142
+ DELETE FROM edges
143
+ WHERE parent_id = $parentID
144
+ `).run({ $parentID: parentID });
145
+ }
146
+ const insert = db.query(`
147
+ INSERT INTO edges (parent_id, child_id, position)
148
+ VALUES ($parentID, $childID, $position)
149
+ `);
150
+ for (const [parentID, children] of desiredChildrenByParent) {
151
+ children.forEach((child, position) => {
152
+ detachEntityParent(db, child.id);
153
+ insert.run({
154
+ $parentID: parentID,
155
+ $childID: child.id,
156
+ $position: position,
157
+ });
158
+ });
159
+ }
160
+ validateNoEntityCycles(db);
161
+ });
162
+ replace();
163
+ }
164
+ /**
165
+ * Reads direct child IDs without joining subtype rows.
166
+ * @param db - The database containing the parent
167
+ * @param parentID - The parent ID to read
168
+ * @returns Ordered child IDs
169
+ */
170
+ export function getDirectEntityChildIDs(db, parentID) {
171
+ return getDirectEntityChildren(db, parentID).map((child) => child.id);
172
+ }
173
+ /**
174
+ * Removes the current parent edge for a child so it can be reparented or detached.
175
+ * @param db - The database containing the edge
176
+ * @param childID - The child ID to detach
177
+ */
178
+ export function detachEntityParent(db, childID) {
179
+ db.query(`
180
+ DELETE FROM edges
181
+ WHERE child_id = $childID
182
+ `).run({ $childID: childID });
183
+ }
184
+ /** Checks whether any parent edge references an entity. */
185
+ export function hasEntityParent(db, id) {
186
+ return db.query(`
187
+ SELECT 1
188
+ FROM edges
189
+ WHERE child_id = $id
190
+ `).get({ $id: id }) !== null;
191
+ }
192
+ /** Checks whether a direct parent/child edge already exists. */
193
+ function hasEntityChild(db, parentID, childID) {
194
+ return db.query(`
195
+ SELECT 1
196
+ FROM edges
197
+ WHERE parent_id = $parentID
198
+ AND child_id = $childID
199
+ `).get({
200
+ $parentID: parentID,
201
+ $childID: childID,
202
+ }) !== null;
203
+ }
204
+ /** Validates that an entity reference uses the correct ID prefix for its type. */
205
+ function validateEntityReference(reference) {
206
+ if (reference.type === "database") {
207
+ throw new Error(`Database cannot be a child entity: ${reference.id}`);
208
+ }
209
+ if (reference.type === "object" && !reference.id.startsWith(`${ObjectPrefix}_`)) {
210
+ throw new Error(`Invalid object id: ${reference.id}`);
211
+ }
212
+ if (reference.type === "block" && !reference.id.startsWith(`${BlockPrefix}_`)) {
213
+ throw new Error(`Invalid block id: ${reference.id}`);
214
+ }
215
+ }
216
+ /** Validates that an entity exists and matches the requested type. */
217
+ function validateExistingEntity(db, reference) {
218
+ validateEntityReference(reference);
219
+ const type = getStoredNodeType(db, reference.id);
220
+ if (type === undefined) {
221
+ throw new Error(`Entity not found: ${reference.id}`);
222
+ }
223
+ if (type !== reference.type) {
224
+ throw new Error(`Entity ${reference.id} is a ${type}, not a ${reference.type}`);
225
+ }
226
+ }
227
+ /** Validates one direct child list for parent/child type rules and duplicates. */
228
+ function validateChildBatch(db, parentID, children) {
229
+ const childIDs = new Set();
230
+ for (const child of children) {
231
+ validateExistingEntity(db, child);
232
+ if (parentID === child.id) {
233
+ throw new Error(`Entity ${child.id} cannot parent itself`);
234
+ }
235
+ if (parentID.startsWith(`${DatabasePrefix}_`) && child.type === "block") {
236
+ throw new Error(`Database children must be objects: ${child.id}`);
237
+ }
238
+ if (childIDs.has(child.id)) {
239
+ throw new Error(`Duplicate child entity: ${child.id}`);
240
+ }
241
+ childIDs.add(child.id);
242
+ }
243
+ }
244
+ /** Validates that a database, object, or block parent exists. */
245
+ function validateParent(db, parentID) {
246
+ if (parentID.startsWith(`${DatabasePrefix}_`)) {
247
+ validateDatabaseParent(db, parentID);
248
+ return;
249
+ }
250
+ if (!isStoredNode(db, parentID)) {
251
+ throw new Error(`Parent entity not found: ${parentID}`);
252
+ }
253
+ }
254
+ /** Validates that a database parent exists. */
255
+ function validateDatabaseParent(db, databaseID) {
256
+ if (!databaseID.startsWith(`${DatabasePrefix}_`)) {
257
+ throw new Error(`Invalid database id: ${databaseID}`);
258
+ }
259
+ const row = db.query(`
260
+ SELECT nodes.type
261
+ FROM "database"
262
+ JOIN nodes ON nodes.id = "database".id
263
+ WHERE "database".id = $id
264
+ `).get({ $id: databaseID });
265
+ if (!row) {
266
+ throw new Error(`Database not found: ${databaseID}`);
267
+ }
268
+ if (row.type !== "database") {
269
+ throw new Error(`Entity ${databaseID} is a ${row.type}, not a database`);
270
+ }
271
+ }
272
+ /** Validates that global containment edges do not contain object/block cycles. */
273
+ function validateNoEntityCycles(db) {
274
+ const rows = db.query(`
275
+ SELECT parent_id AS parentID, child_id AS childID
276
+ FROM edges
277
+ `).all();
278
+ const childrenByParent = new Map();
279
+ for (const row of rows) {
280
+ if (row.parentID.startsWith(`${DatabasePrefix}_`)) {
281
+ continue;
282
+ }
283
+ const children = childrenByParent.get(row.parentID) ?? [];
284
+ children.push(row.childID);
285
+ childrenByParent.set(row.parentID, children);
286
+ }
287
+ const visiting = new Set();
288
+ const visited = new Set();
289
+ const visit = (id) => {
290
+ if (visiting.has(id)) {
291
+ throw new Error(`Entity cycle detected at ${id}`);
292
+ }
293
+ if (visited.has(id)) {
294
+ return;
295
+ }
296
+ visiting.add(id);
297
+ for (const childID of childrenByParent.get(id) ?? []) {
298
+ visit(childID);
299
+ }
300
+ visiting.delete(id);
301
+ visited.add(id);
302
+ };
303
+ for (const parentID of childrenByParent.keys()) {
304
+ visit(parentID);
305
+ }
306
+ }
@@ -0,0 +1,22 @@
1
+ import { Database } from "bun:sqlite";
2
+ import type { DBMetadata } from "../../types/database";
3
+ export declare const SchemaVersion = "0.0.3";
4
+ /**
5
+ * Opens a database, initializes its schema, and creates its metadata when needed.
6
+ * @param path - The path of the SQLite database
7
+ * @param name - The optional database name
8
+ * @returns The initialized SQLite database connection
9
+ */
10
+ export declare function initDatabase(path: string, name?: string): Database;
11
+ /**
12
+ * Opens an existing initialized database without re-running schema setup.
13
+ * @param path - The path of the SQLite database
14
+ * @returns The opened SQLite database connection
15
+ */
16
+ export declare function openDatabase(path: string): Database;
17
+ /**
18
+ * Reads the metadata associated with a database.
19
+ * @param db - The database to read
20
+ * @returns The database metadata
21
+ */
22
+ export declare function getDatabaseMetadata(db: Database): DBMetadata;
@@ -0,0 +1,108 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { createDatabaseID } from "../../utils/id";
3
+ import schema from "./schema.sql" with { type: "text" };
4
+ export const SchemaVersion = "0.0.3";
5
+ /**
6
+ * Opens a database, initializes its schema, and creates its metadata when needed.
7
+ * @param path - The path of the SQLite database
8
+ * @param name - The optional database name
9
+ * @returns The initialized SQLite database connection
10
+ */
11
+ export function initDatabase(path, name) {
12
+ const db = new Database(path);
13
+ try {
14
+ db.exec("PRAGMA busy_timeout = 5000;");
15
+ db.exec("PRAGMA foreign_keys = ON;");
16
+ const isFresh = isEmptyDatabase(db);
17
+ if (!isFresh) {
18
+ validateDatabaseMetadata(db);
19
+ }
20
+ db.exec("PRAGMA journal_mode = WAL;");
21
+ db.exec(schema);
22
+ if (isFresh) {
23
+ const insertMetadata = db.transaction(() => {
24
+ const databaseID = createDatabaseID();
25
+ db.query(`
26
+ INSERT INTO nodes (id, type)
27
+ VALUES ($id, 'database')
28
+ `).run({ $id: databaseID });
29
+ db.query(`
30
+ INSERT INTO "database" (id, name, schema_version)
31
+ VALUES ($id, $name, $schemaVersion)
32
+ `).run({
33
+ $id: databaseID,
34
+ $name: name ?? null,
35
+ $schemaVersion: SchemaVersion,
36
+ });
37
+ });
38
+ insertMetadata();
39
+ }
40
+ }
41
+ catch (error) {
42
+ db.close();
43
+ throw error;
44
+ }
45
+ return db;
46
+ }
47
+ /**
48
+ * Opens an existing initialized database without re-running schema setup.
49
+ * @param path - The path of the SQLite database
50
+ * @returns The opened SQLite database connection
51
+ */
52
+ export function openDatabase(path) {
53
+ const db = new Database(path, { create: false, readwrite: true });
54
+ try {
55
+ db.exec("PRAGMA busy_timeout = 5000;");
56
+ db.exec("PRAGMA foreign_keys = ON;");
57
+ validateDatabaseMetadata(db);
58
+ }
59
+ catch (error) {
60
+ db.close();
61
+ throw error;
62
+ }
63
+ return db;
64
+ }
65
+ function isEmptyDatabase(db) {
66
+ const row = db.query(`
67
+ SELECT COUNT(*) AS count
68
+ FROM sqlite_master
69
+ WHERE name NOT LIKE 'sqlite_%'
70
+ `).get();
71
+ return row.count === 0;
72
+ }
73
+ /**
74
+ * Reads the metadata associated with a database.
75
+ * @param db - The database to read
76
+ * @returns The database metadata
77
+ */
78
+ export function getDatabaseMetadata(db) {
79
+ const rows = db.query(`
80
+ SELECT
81
+ id,
82
+ name,
83
+ schema_version AS schemaVersion
84
+ FROM "database"
85
+ LIMIT 2
86
+ `).all();
87
+ if (rows.length !== 1) {
88
+ throw new Error("Database metadata not found");
89
+ }
90
+ const row = rows[0];
91
+ return {
92
+ id: row.id,
93
+ name: row.name ?? undefined,
94
+ schemaVersion: row.schemaVersion,
95
+ };
96
+ }
97
+ function validateDatabaseMetadata(db) {
98
+ let metadata;
99
+ try {
100
+ metadata = getDatabaseMetadata(db);
101
+ }
102
+ catch {
103
+ throw new Error(`Incompatible database schema; expected valid version ${SchemaVersion} metadata`);
104
+ }
105
+ if (metadata.schemaVersion !== SchemaVersion) {
106
+ throw new Error(`Unsupported database schema version ${metadata.schemaVersion}; expected ${SchemaVersion}`);
107
+ }
108
+ }
@@ -0,0 +1,34 @@
1
+ import type { Database } from "bun:sqlite";
2
+ import type { StoredEntityReference, StoredEntityType } from "../types";
3
+ /**
4
+ * Inserts one graph node row.
5
+ * @param db - The database containing the node
6
+ * @param reference - The node type and ID to insert
7
+ */
8
+ export declare function insertStoredNode(db: Database, reference: StoredEntityReference): void;
9
+ /**
10
+ * Inserts one graph node row, or validates that an existing row has the same type.
11
+ * @param db - The database containing the node
12
+ * @param reference - The node type and ID to persist
13
+ */
14
+ export declare function upsertStoredNode(db: Database, reference: StoredEntityReference): void;
15
+ /**
16
+ * Checks whether a graph node exists.
17
+ * @param db - The database to check
18
+ * @param id - The node ID to check
19
+ * @returns True if the node exists
20
+ */
21
+ export declare function isStoredNode(db: Database, id: string): boolean;
22
+ /**
23
+ * Reads the stored node type for an ID.
24
+ * @param db - The database containing the node
25
+ * @param id - The node ID to read
26
+ * @returns The node type, or undefined when no node exists
27
+ */
28
+ export declare function getStoredNodeType(db: Database, id: string): StoredEntityType | undefined;
29
+ /**
30
+ * Deletes graph nodes by ID.
31
+ * @param db - The database containing the nodes
32
+ * @param ids - The node IDs to delete
33
+ */
34
+ export declare function deleteStoredNodes(db: Database, ids: string[]): void;
@@ -0,0 +1,91 @@
1
+ import { BlockPrefix, DatabasePrefix, ObjectPrefix } from "../../utils/id";
2
+ /**
3
+ * Inserts one graph node row.
4
+ * @param db - The database containing the node
5
+ * @param reference - The node type and ID to insert
6
+ */
7
+ export function insertStoredNode(db, reference) {
8
+ validateNodeReference(reference);
9
+ const existingType = getStoredNodeType(db, reference.id);
10
+ if (existingType !== undefined) {
11
+ throw new Error(`Entity already exists: ${reference.id}`);
12
+ }
13
+ db.query(`
14
+ INSERT INTO nodes (id, type)
15
+ VALUES ($id, $type)
16
+ `).run({
17
+ $id: reference.id,
18
+ $type: reference.type,
19
+ });
20
+ }
21
+ /**
22
+ * Inserts one graph node row, or validates that an existing row has the same type.
23
+ * @param db - The database containing the node
24
+ * @param reference - The node type and ID to persist
25
+ */
26
+ export function upsertStoredNode(db, reference) {
27
+ validateNodeReference(reference);
28
+ const existingType = getStoredNodeType(db, reference.id);
29
+ if (existingType !== undefined) {
30
+ if (existingType !== reference.type) {
31
+ throw new Error(`Entity ${reference.id} is a ${existingType}, not a ${reference.type}`);
32
+ }
33
+ return;
34
+ }
35
+ db.query(`
36
+ INSERT INTO nodes (id, type)
37
+ VALUES ($id, $type)
38
+ `).run({
39
+ $id: reference.id,
40
+ $type: reference.type,
41
+ });
42
+ }
43
+ /**
44
+ * Checks whether a graph node exists.
45
+ * @param db - The database to check
46
+ * @param id - The node ID to check
47
+ * @returns True if the node exists
48
+ */
49
+ export function isStoredNode(db, id) {
50
+ return getStoredNodeType(db, id) !== undefined;
51
+ }
52
+ /**
53
+ * Reads the stored node type for an ID.
54
+ * @param db - The database containing the node
55
+ * @param id - The node ID to read
56
+ * @returns The node type, or undefined when no node exists
57
+ */
58
+ export function getStoredNodeType(db, id) {
59
+ const row = db.query(`
60
+ SELECT type
61
+ FROM nodes
62
+ WHERE id = $id
63
+ `).get({ $id: id });
64
+ return row?.type;
65
+ }
66
+ /**
67
+ * Deletes graph nodes by ID.
68
+ * @param db - The database containing the nodes
69
+ * @param ids - The node IDs to delete
70
+ */
71
+ export function deleteStoredNodes(db, ids) {
72
+ if (ids.length === 0) {
73
+ return;
74
+ }
75
+ db.query(`
76
+ DELETE FROM nodes
77
+ WHERE id IN (SELECT value FROM json_each($ids))
78
+ `).run({ $ids: JSON.stringify(ids) });
79
+ }
80
+ /** Validates that a node reference uses the correct ID prefix for its type. */
81
+ function validateNodeReference(reference) {
82
+ if (reference.type === "database" && !reference.id.startsWith(`${DatabasePrefix}_`)) {
83
+ throw new Error(`Invalid database id: ${reference.id}`);
84
+ }
85
+ if (reference.type === "object" && !reference.id.startsWith(`${ObjectPrefix}_`)) {
86
+ throw new Error(`Invalid object id: ${reference.id}`);
87
+ }
88
+ if (reference.type === "block" && !reference.id.startsWith(`${BlockPrefix}_`)) {
89
+ throw new Error(`Invalid block id: ${reference.id}`);
90
+ }
91
+ }
@@ -0,0 +1,55 @@
1
+ import type { Database } from "bun:sqlite";
2
+ import type { ObjID, ObjMetadata } from "../../types/object";
3
+ import type { StoredObject } from "../types";
4
+ /**
5
+ * Inserts an object row without adding database-root or child containment edges.
6
+ * @param db - The database containing the object
7
+ * @param metadata - The object metadata to insert
8
+ */
9
+ export declare function insertStoredObject(db: Database, metadata: StoredObject): void;
10
+ /**
11
+ * Inserts or updates an object row without changing containment edges.
12
+ * @param db - The database containing the object
13
+ * @param metadata - The object metadata to persist
14
+ */
15
+ export declare function upsertStoredObject(db: Database, metadata: StoredObject): void;
16
+ /**
17
+ * Reads object metadata without recursive children.
18
+ * @param db - The database containing the object
19
+ * @param objectID - The object ID to read
20
+ * @returns The object metadata
21
+ */
22
+ export declare function getObjectMetadata(db: Database, objectID: ObjID): ObjMetadata;
23
+ /**
24
+ * Reads the stored object row without recursive children.
25
+ * @param db - The database containing the object
26
+ * @param objectID - The object ID to read
27
+ * @returns The stored object metadata
28
+ */
29
+ export declare function getStoredObject(db: Database, objectID: ObjID): StoredObject;
30
+ /**
31
+ * Updates object metadata without changing containment edges.
32
+ * @param db - The database containing the object
33
+ * @param metadata - The object metadata to update
34
+ */
35
+ export declare function updateObjectMetadata(db: Database, metadata: StoredObject): void;
36
+ /**
37
+ * Updates the stored object row without changing recursive children.
38
+ * @param db - The database containing the object
39
+ * @param object - The object metadata to update
40
+ */
41
+ export declare function updateStoredObject(db: Database, object: StoredObject): void;
42
+ /**
43
+ * Deletes an object row without deleting its graph node or containment subtree.
44
+ * @param db - The database containing the object
45
+ * @param objectID - The object ID to delete
46
+ * @returns True if the object existed and was deleted
47
+ */
48
+ export declare function deleteStoredObject(db: Database, objectID: ObjID): boolean;
49
+ /**
50
+ * Checks whether an object row exists.
51
+ * @param db - The database to check
52
+ * @param objectID - The object ID to check
53
+ * @returns True if the object exists
54
+ */
55
+ export declare function isStoredObject(db: Database, objectID: ObjID): boolean;