@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,123 @@
1
+ import { ObjectPrefix } from "../../utils/id";
2
+ /**
3
+ * Inserts an object row without adding database-root or child containment edges.
4
+ * @param db - The database containing the object
5
+ * @param metadata - The object metadata to insert
6
+ */
7
+ export function insertStoredObject(db, metadata) {
8
+ validateObjectMetadata(metadata);
9
+ db.query(`
10
+ INSERT INTO objects (id, name, properties)
11
+ VALUES ($id, $name, $properties)
12
+ `).run(mapObjectParameters(metadata));
13
+ }
14
+ /**
15
+ * Inserts or updates an object row without changing containment edges.
16
+ * @param db - The database containing the object
17
+ * @param metadata - The object metadata to persist
18
+ */
19
+ export function upsertStoredObject(db, metadata) {
20
+ validateObjectMetadata(metadata);
21
+ if (isStoredObject(db, metadata.id)) {
22
+ db.query(`
23
+ UPDATE objects
24
+ SET name = $name,
25
+ properties = $properties
26
+ WHERE id = $id
27
+ `).run(mapObjectParameters(metadata));
28
+ return;
29
+ }
30
+ insertStoredObject(db, metadata);
31
+ }
32
+ /**
33
+ * Reads object metadata without recursive children.
34
+ * @param db - The database containing the object
35
+ * @param objectID - The object ID to read
36
+ * @returns The object metadata
37
+ */
38
+ export function getObjectMetadata(db, objectID) {
39
+ const row = db.query(`
40
+ SELECT id, name, properties
41
+ FROM objects
42
+ WHERE id = $id
43
+ `).get({ $id: objectID });
44
+ if (!row) {
45
+ throw new Error(`Object not found: ${objectID}`);
46
+ }
47
+ return {
48
+ id: row.id,
49
+ type: "object",
50
+ name: row.name,
51
+ properties: JSON.parse(row.properties),
52
+ };
53
+ }
54
+ /**
55
+ * Reads the stored object row without recursive children.
56
+ * @param db - The database containing the object
57
+ * @param objectID - The object ID to read
58
+ * @returns The stored object metadata
59
+ */
60
+ export function getStoredObject(db, objectID) {
61
+ return getObjectMetadata(db, objectID);
62
+ }
63
+ /**
64
+ * Updates object metadata without changing containment edges.
65
+ * @param db - The database containing the object
66
+ * @param metadata - The object metadata to update
67
+ */
68
+ export function updateObjectMetadata(db, metadata) {
69
+ validateObjectMetadata(metadata);
70
+ if (!isStoredObject(db, metadata.id)) {
71
+ throw new Error(`Object not found: ${metadata.id}`);
72
+ }
73
+ db.query(`
74
+ UPDATE objects
75
+ SET name = $name,
76
+ properties = $properties
77
+ WHERE id = $id
78
+ `).run(mapObjectParameters(metadata));
79
+ }
80
+ /**
81
+ * Updates the stored object row without changing recursive children.
82
+ * @param db - The database containing the object
83
+ * @param object - The object metadata to update
84
+ */
85
+ export function updateStoredObject(db, object) {
86
+ updateObjectMetadata(db, object);
87
+ }
88
+ /**
89
+ * Deletes an object row without deleting its graph node or containment subtree.
90
+ * @param db - The database containing the object
91
+ * @param objectID - The object ID to delete
92
+ * @returns True if the object existed and was deleted
93
+ */
94
+ export function deleteStoredObject(db, objectID) {
95
+ const result = db.query(`
96
+ DELETE FROM objects
97
+ WHERE id = $id
98
+ `).run({ $id: objectID });
99
+ return result.changes > 0;
100
+ }
101
+ /**
102
+ * Checks whether an object row exists.
103
+ * @param db - The database to check
104
+ * @param objectID - The object ID to check
105
+ * @returns True if the object exists
106
+ */
107
+ export function isStoredObject(db, objectID) {
108
+ return db.query("SELECT 1 FROM objects WHERE id = $id").get({ $id: objectID }) !== null;
109
+ }
110
+ /** Validates an object ID. */
111
+ function validateObjectMetadata(metadata) {
112
+ if (!metadata.id.startsWith(`${ObjectPrefix}_`)) {
113
+ throw new Error(`Invalid object id: ${metadata.id}`);
114
+ }
115
+ }
116
+ /** Maps object metadata to SQLite named parameters. */
117
+ function mapObjectParameters(metadata) {
118
+ return {
119
+ $id: metadata.id,
120
+ $name: metadata.name,
121
+ $properties: JSON.stringify(metadata.properties ?? {}),
122
+ };
123
+ }
@@ -0,0 +1,59 @@
1
+ CREATE TABLE IF NOT EXISTS "database" (
2
+ id TEXT PRIMARY KEY
3
+ REFERENCES nodes(id)
4
+ ON DELETE RESTRICT
5
+ DEFERRABLE INITIALLY DEFERRED,
6
+ name TEXT,
7
+ schema_version TEXT NOT NULL
8
+ );
9
+
10
+ CREATE TRIGGER IF NOT EXISTS database_singleton_insert
11
+ BEFORE INSERT ON "database"
12
+ WHEN (SELECT COUNT(*) FROM "database") >= 1
13
+ BEGIN
14
+ SELECT RAISE(ABORT, 'database metadata already exists');
15
+ END;
16
+
17
+ CREATE TABLE IF NOT EXISTS nodes (
18
+ id TEXT PRIMARY KEY,
19
+ type TEXT NOT NULL CHECK (type IN ('database', 'object', 'block'))
20
+ );
21
+
22
+ CREATE TABLE IF NOT EXISTS objects (
23
+ id TEXT PRIMARY KEY
24
+ REFERENCES nodes(id)
25
+ ON DELETE CASCADE
26
+ DEFERRABLE INITIALLY DEFERRED,
27
+ name TEXT NOT NULL,
28
+ properties TEXT NOT NULL DEFAULT '{}'
29
+ CHECK (json_valid(properties))
30
+ );
31
+
32
+ CREATE TABLE IF NOT EXISTS blocks (
33
+ id TEXT PRIMARY KEY
34
+ REFERENCES nodes(id)
35
+ ON DELETE CASCADE
36
+ DEFERRABLE INITIALLY DEFERRED,
37
+ content TEXT NOT NULL,
38
+ properties TEXT NOT NULL DEFAULT '{}'
39
+ CHECK (json_valid(properties))
40
+ );
41
+
42
+ CREATE TABLE IF NOT EXISTS edges (
43
+ parent_id TEXT NOT NULL
44
+ REFERENCES nodes(id)
45
+ ON DELETE CASCADE
46
+ DEFERRABLE INITIALLY DEFERRED,
47
+ child_id TEXT NOT NULL
48
+ REFERENCES nodes(id)
49
+ ON DELETE CASCADE
50
+ DEFERRABLE INITIALLY DEFERRED,
51
+ position INTEGER NOT NULL CHECK (position >= 0),
52
+ PRIMARY KEY (parent_id, child_id),
53
+ UNIQUE (child_id),
54
+ UNIQUE (parent_id, position),
55
+ CHECK (parent_id <> child_id)
56
+ );
57
+
58
+ CREATE INDEX IF NOT EXISTS edges_parent_id_idx
59
+ ON edges(parent_id);
@@ -0,0 +1,47 @@
1
+ import type { Database } from "bun:sqlite";
2
+ import type { Block, BlockID, BlockMetadata } from "../types/block";
3
+ import type { DBMetadata } from "../types/database";
4
+ import type { Entity, EntityReference } from "../types/graph";
5
+ import type { Obj, ObjID, ObjMetadata } from "../types/object";
6
+ import type { StoredEntity, StoredEntityID, StoredEntityReference } from "./types";
7
+ export type { StoredBlock, StoredEntity, StoredEntityID, StoredEntityReference, StoredEntityType, StoredObject, } from "./types";
8
+ export type SearchType = "object" | "block";
9
+ export interface SearchResult {
10
+ type: SearchType;
11
+ id: string;
12
+ label: string;
13
+ }
14
+ export declare function initializeStorage(path: string, name?: string): Database;
15
+ export declare function openStorage(path: string): Database;
16
+ export declare function readDatabaseMetadata(db: Database): DBMetadata;
17
+ /**
18
+ * Create an entity with parent placement.
19
+ *
20
+ * Objects: parentID defaults to database root if unspecified.
21
+ * Blocks require an explicit object or block parent.
22
+ * @param db
23
+ * @param entity
24
+ * @param parentID
25
+ */
26
+ export declare function createEntity(db: Database, entity: StoredEntity, parentID?: StoredEntityID): void;
27
+ export declare function objectExists(db: Database, objectID: ObjID): boolean;
28
+ export declare function blockExists(db: Database, blockID: BlockID): boolean;
29
+ export declare function entityExists(db: Database, id: string): boolean;
30
+ export declare function readObjectTree(db: Database, objectID: ObjID): Obj;
31
+ export declare function readBlockTree(db: Database, blockID: BlockID): Block;
32
+ export declare function readEntityTree(db: Database, entityID: string, visited?: Set<string>): Entity;
33
+ export declare function readDatabaseRootObjects(db: Database): ObjID[];
34
+ export declare function readObjectMetadata(db: Database, objectID: ObjID): ObjMetadata;
35
+ export declare function readBlockMetadata(db: Database, blockID: BlockID): BlockMetadata;
36
+ export declare function readDirectEntityChildIDs(db: Database, parentID: string): StoredEntityID[];
37
+ export declare function readEntityParent(db: Database, entityID: string): StoredEntityReference | undefined;
38
+ export declare function readEntityParentID(db: Database, entityID: string): StoredEntityID | null;
39
+ export declare function persistEntityTree(db: Database, root: Entity): void;
40
+ export declare function writeEntityTree(db: Database, root: Entity, parentID?: StoredEntityID): Entity;
41
+ export declare function moveObjectToDatabaseRoot(db: Database, objectID: ObjID): void;
42
+ export declare function attachEntityChild(db: Database, parentID: StoredEntityID, child: EntityReference): void;
43
+ export declare function replaceDirectEntityChildren(db: Database, desiredChildrenByParent: Map<string, EntityReference[]>): void;
44
+ export declare function deleteObjectTree(db: Database, objectID: ObjID): boolean;
45
+ export declare function deleteBlockTree(db: Database, blockID: BlockID): boolean;
46
+ export declare function deleteEntityTree(db: Database, entityID: string): boolean;
47
+ export declare function searchEntities(db: Database, query: string, type?: SearchType): SearchResult[];
@@ -0,0 +1,315 @@
1
+ import { getBlockMetadata, getStoredBlock, insertStoredBlock, isStoredBlock, upsertStoredBlock, } from "./db/blocks";
2
+ import { appendEntityChild, appendDatabaseRootObject, getDatabaseRootObjects, getDirectEntityChildren, getDirectEntityChildIDs, getEntityParent, replaceEntityChildren, } from "./db/edges";
3
+ import { getDatabaseMetadata, initDatabase, openDatabase as openStoredDatabase, } from "./db/init";
4
+ import { deleteStoredNodes, getStoredNodeType, insertStoredNode, isStoredNode, upsertStoredNode, } from "./db/nodes";
5
+ import { getObjectMetadata, getStoredObject, insertStoredObject, isStoredObject, upsertStoredObject, } from "./db/objects";
6
+ // DB functionality
7
+ export function initializeStorage(path, name) {
8
+ return initDatabase(path, name);
9
+ }
10
+ export function openStorage(path) {
11
+ return openStoredDatabase(path);
12
+ }
13
+ export function readDatabaseMetadata(db) {
14
+ return getDatabaseMetadata(db);
15
+ }
16
+ // Creation and existence
17
+ /**
18
+ * Create an entity with parent placement.
19
+ *
20
+ * Objects: parentID defaults to database root if unspecified.
21
+ * Blocks require an explicit object or block parent.
22
+ * @param db
23
+ * @param entity
24
+ * @param parentID
25
+ */
26
+ export function createEntity(db, entity, parentID) {
27
+ const create = db.transaction(() => {
28
+ insertStoredNode(db, {
29
+ type: entity.type,
30
+ id: entity.id,
31
+ });
32
+ if (entity.type === "object") {
33
+ insertStoredObject(db, entity);
34
+ }
35
+ else {
36
+ insertStoredBlock(db, entity);
37
+ }
38
+ appendEntityChild(db, resolveRootParentID(db, entity.type, parentID), {
39
+ type: entity.type,
40
+ id: entity.id,
41
+ });
42
+ });
43
+ create();
44
+ }
45
+ export function objectExists(db, objectID) {
46
+ return isStoredObject(db, objectID);
47
+ }
48
+ export function blockExists(db, blockID) {
49
+ return isStoredBlock(db, blockID);
50
+ }
51
+ export function entityExists(db, id) {
52
+ return isStoredNode(db, id);
53
+ }
54
+ // Read
55
+ export function readObjectTree(db, objectID) {
56
+ const type = getStoredNodeType(db, objectID);
57
+ if (type === undefined || type !== "object") {
58
+ throw new Error(`Object not found: ${objectID}`);
59
+ }
60
+ const entity = readEntityTree(db, objectID);
61
+ if (entity.type !== "object") {
62
+ throw new Error(`Object not found: ${objectID}`);
63
+ }
64
+ return entity;
65
+ }
66
+ export function readBlockTree(db, blockID) {
67
+ const type = getStoredNodeType(db, blockID);
68
+ if (type === undefined || type !== "block") {
69
+ throw new Error(`Block not found: ${blockID}`);
70
+ }
71
+ const entity = readEntityTree(db, blockID);
72
+ if (entity.type !== "block") {
73
+ throw new Error(`Block not found: ${blockID}`);
74
+ }
75
+ return entity;
76
+ }
77
+ export function readEntityTree(db, entityID, visited = new Set()) {
78
+ if (visited.has(entityID)) {
79
+ throw new Error(`Entity cycle detected at ${entityID}`);
80
+ }
81
+ visited.add(entityID);
82
+ const type = getStoredNodeType(db, entityID);
83
+ if (type === undefined) {
84
+ throw new Error(`Entity not found: ${entityID}`);
85
+ }
86
+ if (type === "database") {
87
+ throw new Error(`Database cannot be read as a public entity: ${entityID}`);
88
+ }
89
+ const children = getDirectEntityChildren(db, entityID).map((child) => readEntityTree(db, child.id, new Set(visited)));
90
+ if (type === "object") {
91
+ return {
92
+ ...getStoredObject(db, entityID),
93
+ children,
94
+ };
95
+ }
96
+ return {
97
+ ...getStoredBlock(db, entityID),
98
+ children,
99
+ };
100
+ }
101
+ export function readDatabaseRootObjects(db) {
102
+ return getDatabaseRootObjects(db, getDatabaseMetadata(db).id);
103
+ }
104
+ export function readObjectMetadata(db, objectID) {
105
+ return getObjectMetadata(db, objectID);
106
+ }
107
+ export function readBlockMetadata(db, blockID) {
108
+ return getBlockMetadata(db, blockID);
109
+ }
110
+ export function readDirectEntityChildIDs(db, parentID) {
111
+ return getDirectEntityChildIDs(db, parentID);
112
+ }
113
+ export function readEntityParent(db, entityID) {
114
+ return getEntityParent(db, entityID);
115
+ }
116
+ export function readEntityParentID(db, entityID) {
117
+ return readEntityParent(db, entityID)?.id ?? null;
118
+ }
119
+ // Insert and write
120
+ export function persistEntityTree(db, root) {
121
+ const visit = (entity) => {
122
+ upsertStoredNode(db, {
123
+ type: entity.type,
124
+ id: entity.id,
125
+ });
126
+ if (entity.type === "object") {
127
+ upsertStoredObject(db, {
128
+ id: entity.id,
129
+ type: "object",
130
+ name: entity.name,
131
+ properties: entity.properties ?? {},
132
+ });
133
+ }
134
+ else {
135
+ upsertStoredBlock(db, {
136
+ id: entity.id,
137
+ type: "block",
138
+ content: entity.content,
139
+ properties: entity.properties ?? {},
140
+ });
141
+ }
142
+ entity.children.forEach(visit);
143
+ };
144
+ visit(root);
145
+ }
146
+ export function writeEntityTree(db, root, parentID) {
147
+ const write = db.transaction(() => {
148
+ persistEntityTree(db, root);
149
+ appendEntityChild(db, resolveRootParentID(db, root.type, parentID), {
150
+ type: root.type,
151
+ id: root.id,
152
+ });
153
+ const desiredChildrenByParent = buildChildMap(root);
154
+ const submittedIDs = collectSubmittedEntityIDs(root);
155
+ const omittedChildIDs = collectOmittedChildIDs(db, desiredChildrenByParent, submittedIDs);
156
+ replaceEntityChildren(db, desiredChildrenByParent);
157
+ deleteOmittedEntitySubtrees(db, omittedChildIDs);
158
+ });
159
+ write();
160
+ return readEntityTree(db, root.id);
161
+ }
162
+ export function moveObjectToDatabaseRoot(db, objectID) {
163
+ appendDatabaseRootObject(db, getDatabaseMetadata(db).id, objectID);
164
+ }
165
+ export function attachEntityChild(db, parentID, child) {
166
+ appendEntityChild(db, parentID, child);
167
+ }
168
+ export function replaceDirectEntityChildren(db, desiredChildrenByParent) {
169
+ replaceEntityChildren(db, desiredChildrenByParent);
170
+ }
171
+ // Deletion
172
+ export function deleteObjectTree(db, objectID) {
173
+ const type = getStoredNodeType(db, objectID);
174
+ if (type === undefined) {
175
+ return false;
176
+ }
177
+ if (type !== "object") {
178
+ throw new Error(`Object not found: ${objectID}`);
179
+ }
180
+ return deleteEntityTree(db, objectID);
181
+ }
182
+ export function deleteBlockTree(db, blockID) {
183
+ const type = getStoredNodeType(db, blockID);
184
+ if (type === undefined) {
185
+ return false;
186
+ }
187
+ if (type !== "block") {
188
+ throw new Error(`Block not found: ${blockID}`);
189
+ }
190
+ return deleteEntityTree(db, blockID);
191
+ }
192
+ export function deleteEntityTree(db, entityID) {
193
+ const type = getStoredNodeType(db, entityID);
194
+ if (type === undefined) {
195
+ return false;
196
+ }
197
+ if (type === "database") {
198
+ throw new Error(`Database cannot be deleted as a public entity: ${entityID}`);
199
+ }
200
+ const ids = collectEntitySubtreeIDs(db, entityID);
201
+ deleteStoredNodes(db, ids);
202
+ return true;
203
+ }
204
+ // Search functionality
205
+ export function searchEntities(db, query, type) {
206
+ const types = type === undefined
207
+ ? ["object", "block"]
208
+ : [type];
209
+ const rows = [];
210
+ const parameters = { $query: query };
211
+ if (types.includes("object")) {
212
+ rows.push(...db.query(`
213
+ SELECT 'object' AS type, id, name AS label
214
+ FROM objects
215
+ WHERE instr(lower(name), lower($query)) > 0
216
+ OR instr(lower(properties), lower($query)) > 0
217
+ `).all(parameters));
218
+ }
219
+ if (types.includes("block")) {
220
+ rows.push(...db.query(`
221
+ SELECT 'block' AS type, id, content AS label
222
+ FROM blocks
223
+ WHERE instr(lower(content), lower($query)) > 0
224
+ OR instr(lower(properties), lower($query)) > 0
225
+ `).all(parameters));
226
+ }
227
+ return rows
228
+ .map((row) => ({
229
+ ...row,
230
+ label: row.type === "block" ? blockLabel(row.label) : row.label,
231
+ }))
232
+ .sort((left, right) => left.type.localeCompare(right.type)
233
+ || left.label.localeCompare(right.label)
234
+ || left.id.localeCompare(right.id));
235
+ }
236
+ /** Converts block content into a compact single-line search label. */
237
+ function blockLabel(content) {
238
+ const normalized = content.replace(/\s+/g, " ").trim();
239
+ return normalized.length <= 80 ? normalized : `${normalized.slice(0, 77)}...`;
240
+ }
241
+ /** Builds complete direct-child replacement lists for every entity in a tree. */
242
+ function buildChildMap(root) {
243
+ const map = new Map();
244
+ const visit = (entity) => {
245
+ map.set(entity.id, entity.children.map((child) => ({
246
+ type: child.type,
247
+ id: child.id,
248
+ })));
249
+ entity.children.forEach(visit);
250
+ };
251
+ visit(root);
252
+ return map;
253
+ }
254
+ /** Resolves public root placement. */
255
+ function resolveRootParentID(db, type, parentID) {
256
+ if (parentID !== undefined) {
257
+ return parentID;
258
+ }
259
+ if (type === "object") {
260
+ return getDatabaseMetadata(db).id;
261
+ }
262
+ throw new Error("Block parent is required");
263
+ }
264
+ /** Collects every entity ID explicitly present in a submitted tree. */
265
+ function collectSubmittedEntityIDs(root) {
266
+ const ids = new Set();
267
+ const visit = (entity) => {
268
+ ids.add(entity.id);
269
+ entity.children.forEach(visit);
270
+ };
271
+ visit(root);
272
+ return ids;
273
+ }
274
+ /** Finds existing direct children that a replacement write omitted. */
275
+ function collectOmittedChildIDs(db, desiredChildrenByParent, submittedIDs) {
276
+ const omitted = new Set();
277
+ for (const [parentID, desiredChildren] of desiredChildrenByParent) {
278
+ const desiredChildIDs = new Set(desiredChildren.map((child) => child.id));
279
+ for (const child of getDirectEntityChildren(db, parentID)) {
280
+ if (!desiredChildIDs.has(child.id) && !submittedIDs.has(child.id)) {
281
+ omitted.add(child.id);
282
+ }
283
+ }
284
+ }
285
+ return [...omitted];
286
+ }
287
+ /** Deletes omitted children after moved descendants have been reparented. */
288
+ function deleteOmittedEntitySubtrees(db, rootIDs) {
289
+ const deleted = new Set();
290
+ for (const rootID of rootIDs) {
291
+ if (deleted.has(rootID) || getStoredNodeType(db, rootID) === undefined) {
292
+ continue;
293
+ }
294
+ const ids = collectEntitySubtreeIDs(db, rootID);
295
+ deleteStoredNodes(db, ids);
296
+ ids.forEach((id) => deleted.add(id));
297
+ }
298
+ }
299
+ /** Collects one entity and all of its recursive containment descendants. */
300
+ function collectEntitySubtreeIDs(db, rootID) {
301
+ const ids = [];
302
+ const visited = new Set();
303
+ const visit = (id) => {
304
+ if (visited.has(id)) {
305
+ throw new Error(`Entity cycle detected at ${id}`);
306
+ }
307
+ visited.add(id);
308
+ ids.push(id);
309
+ for (const child of getDirectEntityChildren(db, id)) {
310
+ visit(child.id);
311
+ }
312
+ };
313
+ visit(rootID);
314
+ return ids;
315
+ }
@@ -0,0 +1,15 @@
1
+ import type { BlockMetadata } from "../types/block";
2
+ import type { DatabaseID } from "../types/database";
3
+ import type { EntityID, EntityType } from "../types/graph";
4
+ import type { ObjMetadata } from "../types/object";
5
+ export type StoredEntityType = "database" | EntityType;
6
+ export type StoredEntityID = DatabaseID | EntityID;
7
+ export interface StoredEntityReference {
8
+ readonly type: StoredEntityType;
9
+ readonly id: StoredEntityID;
10
+ }
11
+ export type StoredObject = ObjMetadata;
12
+ export interface StoredBlock extends BlockMetadata {
13
+ content: string;
14
+ }
15
+ export type StoredEntity = StoredObject | StoredBlock;
@@ -0,0 +1 @@
1
+ // This file defines the internal stored rows for concrete entity tables.
@@ -0,0 +1,12 @@
1
+ import type { Entity } from "./graph";
2
+ import type { JSONRecord } from "./json";
3
+ export type BlockID = string;
4
+ export interface BlockMetadata {
5
+ readonly id: BlockID;
6
+ readonly type: "block";
7
+ properties?: JSONRecord;
8
+ }
9
+ export interface Block extends BlockMetadata {
10
+ content: string;
11
+ children: Entity[];
12
+ }
File without changes
@@ -0,0 +1,6 @@
1
+ export type DatabaseID = string;
2
+ export interface DBMetadata {
3
+ readonly id: DatabaseID;
4
+ name?: string;
5
+ schemaVersion: string;
6
+ }
File without changes
@@ -0,0 +1,11 @@
1
+ import type { Block, BlockID } from "./block";
2
+ import type { Obj, ObjID } from "./object";
3
+ import type { WorkspaceID } from "./workspace";
4
+ export type EntityType = "object" | "block";
5
+ export type EntityID = ObjID | BlockID;
6
+ export type Entity = Obj | Block;
7
+ export interface EntityReference {
8
+ readonly type: EntityType;
9
+ readonly id: EntityID;
10
+ }
11
+ export type EntityParentID = WorkspaceID | EntityID | null;
File without changes
@@ -0,0 +1,7 @@
1
+ export type JSONPrimitive = string | number | boolean | null;
2
+ export type JSONValue = JSONPrimitive | JSONValue[] | {
3
+ [key: string]: JSONValue;
4
+ };
5
+ export type JSONRecord = {
6
+ [key: string]: JSONValue;
7
+ };
File without changes
@@ -0,0 +1,12 @@
1
+ import type { Entity } from "./graph";
2
+ import type { JSONRecord } from "./json";
3
+ export type ObjID = string;
4
+ export interface ObjMetadata {
5
+ readonly id: ObjID;
6
+ readonly type: "object";
7
+ name: string;
8
+ properties?: JSONRecord;
9
+ }
10
+ export interface Obj extends ObjMetadata {
11
+ children: Entity[];
12
+ }
File without changes
@@ -0,0 +1,6 @@
1
+ export type WorkspaceID = string;
2
+ export interface WorkspaceMetadata {
3
+ readonly id: WorkspaceID;
4
+ name?: string;
5
+ schemaVersion: string;
6
+ }
File without changes
@@ -0,0 +1,6 @@
1
+ export declare const ObjectPrefix: string;
2
+ export declare const BlockPrefix: string;
3
+ export declare const DatabasePrefix: string;
4
+ export declare function createObjID(): string;
5
+ export declare function createBlockID(): string;
6
+ export declare function createDatabaseID(): string;
@@ -0,0 +1,18 @@
1
+ import { randomUUID } from "node:crypto";
2
+ export const ObjectPrefix = "o";
3
+ export const BlockPrefix = "b";
4
+ export const DatabasePrefix = "d";
5
+ // HELPER: Creates a unique ID with the given prefix
6
+ function createID(prefix) {
7
+ const random = randomUUID().replaceAll("-", "");
8
+ return `${prefix}_${random}`;
9
+ }
10
+ export function createObjID() {
11
+ return createID(ObjectPrefix);
12
+ }
13
+ export function createBlockID() {
14
+ return createID(BlockPrefix);
15
+ }
16
+ export function createDatabaseID() {
17
+ return createID(DatabasePrefix);
18
+ }
@@ -0,0 +1,3 @@
1
+ import type { ObjMetadata } from "../types/object";
2
+ export declare function renderFrontmatter(frontmatter: ObjMetadata): string;
3
+ export declare function parseFrontmatter<T>(markdown: string): T;