@xnetjs/data 0.0.2

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 Chris Smothers
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,229 @@
1
+ # @xnetjs/data
2
+
3
+ Schema system, NodeStore, and Yjs CRDT engine for xNet. This is the central data package -- it defines how structured data and rich text documents are created, stored, and synced.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @xnetjs/data
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **Schema system** -- `defineSchema()` with 16 typed property helpers
14
+ - **NodeStore** -- Event-sourced LWW (Last-Writer-Wins) storage engine
15
+ - **Built-in schemas** -- Page, Database, Task, Canvas, Comment
16
+ - **Yjs CRDT** -- Y.Doc helpers for signed updates and merge/state-vector workflows
17
+ - **Awareness/presence** -- Real-time user presence
18
+ - **Blob service** -- File upload/download with content addressing
19
+ - **Storage adapters** -- SQLite and in-memory NodeStore adapters
20
+ - **Temp ID mapping** -- Optimistic creates with server-assigned IDs
21
+
22
+ ## Usage
23
+
24
+ ### Define a Schema
25
+
26
+ ```typescript
27
+ import { defineSchema, text, number, select, date, checkbox, relation } from '@xnetjs/data'
28
+
29
+ const TaskSchema = defineSchema({
30
+ name: 'Task',
31
+ namespace: 'myapp://',
32
+ document: 'yjs', // Enable rich text body
33
+ properties: {
34
+ title: text({ required: true }),
35
+ priority: number(),
36
+ done: checkbox(),
37
+ dueDate: date(),
38
+ status: select({
39
+ options: [
40
+ { id: 'todo', name: 'To Do' },
41
+ { id: 'in-progress', name: 'In Progress' },
42
+ { id: 'done', name: 'Done' }
43
+ ] as const
44
+ }),
45
+ assignee: relation({ schema: 'myapp://Person' })
46
+ }
47
+ })
48
+ ```
49
+
50
+ ### Property Helpers
51
+
52
+ | Type | Import | Description |
53
+ | ------------- | --------------- | --------------------------- |
54
+ | `text` | `text()` | String values |
55
+ | `number` | `number()` | Numeric values |
56
+ | `checkbox` | `checkbox()` | Boolean toggle |
57
+ | `date` | `date()` | Single date |
58
+ | `dateRange` | `dateRange()` | Start + end date |
59
+ | `select` | `select()` | Single choice |
60
+ | `multiSelect` | `multiSelect()` | Multiple choices |
61
+ | `person` | `person()` | DID reference |
62
+ | `relation` | `relation()` | Link to another node |
63
+ | `url` | `url()` | URL string |
64
+ | `email` | `email()` | Email address |
65
+ | `phone` | `phone()` | Phone number |
66
+ | `file` | `file()` | File attachment |
67
+ | `created` | `created()` | Auto-set creation timestamp |
68
+ | `updated` | `updated()` | Auto-set update timestamp |
69
+ | `createdBy` | `createdBy()` | Auto-set author DID |
70
+
71
+ ### NodeStore
72
+
73
+ ```typescript
74
+ import { NodeStore, MemoryNodeStorageAdapter } from '@xnetjs/data'
75
+
76
+ const store = new NodeStore({
77
+ storage: new MemoryNodeStorageAdapter(),
78
+ authorDID: identity.did,
79
+ signingKey: privateKey
80
+ })
81
+
82
+ // Create
83
+ const task = await store.create(TaskSchema, { title: 'Buy milk', status: 'todo' })
84
+
85
+ // Read
86
+ const loaded = await store.get(TaskSchema, task.id)
87
+
88
+ // Update
89
+ await store.update(TaskSchema, task.id, { status: 'done' })
90
+
91
+ // List
92
+ const tasks = await store.list(TaskSchema)
93
+
94
+ // Delete (soft)
95
+ await store.remove(task.id)
96
+ ```
97
+
98
+ ### Yjs Documents
99
+
100
+ ```typescript
101
+ import { YDoc, captureUpdate, applySignedUpdate, signUpdate, mergeDocuments } from '@xnetjs/data'
102
+
103
+ const authorDID = identity.did
104
+ const signingKey = keyBundle.signingKey
105
+
106
+ const docA = new YDoc()
107
+ const docB = new YDoc()
108
+
109
+ // Edit rich text content
110
+ docA.getText('content').insert(0, 'Hello world')
111
+
112
+ // Capture and sign update bytes
113
+ const update = captureUpdate(docA)
114
+ const signed = signUpdate(update, { authorDID, signingKey })
115
+
116
+ // Verify/apply to another replica
117
+ applySignedUpdate(docB, signed)
118
+
119
+ // Merge full state from one doc into another
120
+ mergeDocuments(docA, docB)
121
+ ```
122
+
123
+ ## Architecture
124
+
125
+ ```mermaid
126
+ flowchart TD
127
+ Schema["defineSchema()<br/><small>16 property helpers</small>"]
128
+ Store["NodeStore<br/><small>Event-sourced LWW</small>"]
129
+ Doc["Yjs Documents<br/><small>CRDT rich text</small>"]
130
+ Blob["BlobService<br/><small>File attachments</small>"]
131
+ Builtin["Built-in Schemas<br/><small>Page, Database, Task,<br/>Canvas, Comment</small>"]
132
+
133
+ Schema --> Store
134
+ Schema --> Builtin
135
+ Store --> Doc
136
+ Store --> Blob
137
+ Builtin --> Store
138
+
139
+ subgraph Adapters["Storage Adapters"]
140
+ SQLite["SQLite<br/>Adapter"]
141
+ Mem["Memory<br/>Adapter"]
142
+ end
143
+
144
+ Store --> Adapters
145
+ ```
146
+
147
+ ### Storage Adapters
148
+
149
+ | Adapter | Platform | Description |
150
+ | -------------------------- | -------- | -------------------------------------- |
151
+ | `SQLiteNodeStorageAdapter` | All | Primary adapter using `@xnetjs/sqlite` |
152
+ | `MemoryNodeStorageAdapter` | All | In-memory storage for testing |
153
+
154
+ **Recommended:** Use `SQLiteNodeStorageAdapter` with the appropriate `@xnetjs/sqlite` adapter for your platform:
155
+
156
+ ```typescript
157
+ import { NodeStore, SQLiteNodeStorageAdapter } from '@xnetjs/data'
158
+ import { ElectronSQLiteAdapter } from '@xnetjs/sqlite/electron' // or /web, /expo
159
+
160
+ const sqliteAdapter = new ElectronSQLiteAdapter()
161
+ await sqliteAdapter.open({ path: 'xnet.db' })
162
+
163
+ const storage = new SQLiteNodeStorageAdapter(sqliteAdapter)
164
+ await storage.open()
165
+
166
+ const store = new NodeStore({
167
+ storage,
168
+ authorDID: identity.did,
169
+ signingKey: privateKey
170
+ })
171
+ ```
172
+
173
+ ### Telemetry Integration
174
+
175
+ NodeStore supports optional telemetry for tracking CRUD operations, performance, and errors:
176
+
177
+ ```typescript
178
+ import { NodeStore } from '@xnetjs/data'
179
+ import { TelemetryCollector, ConsentManager } from '@xnetjs/telemetry'
180
+
181
+ const consent = new ConsentManager()
182
+ const telemetry = new TelemetryCollector({ consent })
183
+
184
+ const store = new NodeStore({
185
+ storage,
186
+ authorDID: identity.did,
187
+ signingKey: privateKey,
188
+ telemetry // <-- Add telemetry collector
189
+ })
190
+ ```
191
+
192
+ When telemetry is enabled, NodeStore automatically reports:
193
+
194
+ - **Performance metrics**: `data.create`, `data.update`, `data.delete`, `data.list`, `data.applyRemoteChange`
195
+ - **Usage metrics**: Operation counts
196
+ - **Security events**: Hash/signature verification failures, unauthorized remote changes
197
+ - **Crash reports**: Errors with context
198
+
199
+ All telemetry respects user consent settings and privacy buckets (no exact values, no PII).
200
+
201
+ ## Modules
202
+
203
+ | Module | Description |
204
+ | ------------------------- | -------------------------------------- |
205
+ | `schema/define.ts` | `defineSchema()` factory |
206
+ | `schema/node.ts` | FlatNode type (flattened properties) |
207
+ | `schema/registry.ts` | Schema registry |
208
+ | `store/store.ts` | NodeStore engine |
209
+ | `store/sqlite-adapter.ts` | SQLite NodeStore adapter (recommended) |
210
+ | `store/memory-adapter.ts` | In-memory NodeStore adapter |
211
+ | `store/tempids.ts` | Temp ID mapping for optimistic creates |
212
+ | `updates.ts` | Signed update and merge utilities |
213
+ | `blob/blob-service.ts` | File blob storage |
214
+ | `sync/awareness.ts` | Presence awareness |
215
+ | `blocks/registry.ts` | Block type registry |
216
+
217
+ ## Dependencies
218
+
219
+ - `@xnetjs/core`, `@xnetjs/crypto`, `@xnetjs/identity`, `@xnetjs/storage`, `@xnetjs/sync`, `@xnetjs/sqlite`
220
+ - `yjs`, `y-protocols` -- CRDT engine
221
+ - `nanoid` -- ID generation
222
+
223
+ ## Testing
224
+
225
+ ```bash
226
+ pnpm --filter @xnetjs/data test
227
+ ```
228
+
229
+ 10 test files covering schema system, NodeStore, comments, and blob service.
@@ -0,0 +1,7 @@
1
+ import {
2
+ CanvasSchema
3
+ } from "./chunk-4MTS5KAQ.js";
4
+ import "./chunk-SZC345Z2.js";
5
+ export {
6
+ CanvasSchema
7
+ };
@@ -0,0 +1,53 @@
1
+ import {
2
+ checkbox,
3
+ date,
4
+ defineSchema,
5
+ person,
6
+ relation,
7
+ select,
8
+ text
9
+ } from "./chunk-SZC345Z2.js";
10
+
11
+ // src/schema/schemas/task.ts
12
+ var TaskSchema = defineSchema({
13
+ name: "Task",
14
+ namespace: "xnet://xnet.fyi/",
15
+ properties: {
16
+ /** Task title */
17
+ title: text({ required: true, maxLength: 500 }),
18
+ /** Whether the task is completed */
19
+ completed: checkbox({ default: false }),
20
+ /** Task status */
21
+ status: select({
22
+ options: [
23
+ { id: "todo", name: "To Do", color: "gray" },
24
+ { id: "in-progress", name: "In Progress", color: "blue" },
25
+ { id: "done", name: "Done", color: "green" },
26
+ { id: "cancelled", name: "Cancelled", color: "red" }
27
+ ],
28
+ default: "todo"
29
+ }),
30
+ /** Task priority */
31
+ priority: select({
32
+ options: [
33
+ { id: "low", name: "Low", color: "gray" },
34
+ { id: "medium", name: "Medium", color: "yellow" },
35
+ { id: "high", name: "High", color: "orange" },
36
+ { id: "urgent", name: "Urgent", color: "red" }
37
+ ],
38
+ default: "medium"
39
+ }),
40
+ /** Due date */
41
+ dueDate: date({}),
42
+ /** Assigned person */
43
+ assignee: person({}),
44
+ /** Parent task (for subtasks) */
45
+ parent: relation({ target: "xnet://xnet.fyi/Task" })
46
+ },
47
+ document: "yjs"
48
+ // Collaborative Y.Doc for description
49
+ });
50
+
51
+ export {
52
+ TaskSchema
53
+ };
@@ -0,0 +1,22 @@
1
+ import {
2
+ defineSchema,
3
+ text
4
+ } from "./chunk-SZC345Z2.js";
5
+
6
+ // src/schema/schemas/canvas.ts
7
+ var CanvasSchema = defineSchema({
8
+ name: "Canvas",
9
+ namespace: "xnet://xnet.fyi/",
10
+ properties: {
11
+ /** Canvas title */
12
+ title: text({ required: true, maxLength: 500 }),
13
+ /** Emoji or icon URL */
14
+ icon: text({})
15
+ },
16
+ document: "yjs"
17
+ // Collaborative Y.Doc for canvas data (nodes, edges)
18
+ });
19
+
20
+ export {
21
+ CanvasSchema
22
+ };
@@ -0,0 +1,76 @@
1
+ import {
2
+ checkbox,
3
+ created,
4
+ createdBy,
5
+ date,
6
+ defineSchema,
7
+ file,
8
+ person,
9
+ relation,
10
+ select,
11
+ text
12
+ } from "./chunk-SZC345Z2.js";
13
+
14
+ // src/schema/schemas/comment.ts
15
+ var CommentSchema = defineSchema({
16
+ name: "Comment",
17
+ namespace: "xnet://xnet.fyi/",
18
+ properties: {
19
+ // ─── Universal Targeting (schema-agnostic relation) ────────────────────────
20
+ /** The Node this comment is on (any schema - Page, Task, Database, Canvas, etc.) */
21
+ target: relation({ required: true }),
22
+ /** Schema IRI of the target Node (optimization hint, not enforced) */
23
+ targetSchema: text({}),
24
+ // ─── Threading (flat - all replies point to root) ──────────────────────────
25
+ /** Root comment ID for threading (null = this IS the root) */
26
+ inReplyTo: relation({}),
27
+ // ─── Anchor Data (polymorphic positioning) ─────────────────────────────────
28
+ /** Type of anchor point */
29
+ anchorType: select({
30
+ options: [
31
+ { id: "text", name: "Text Selection", color: "blue" },
32
+ { id: "cell", name: "Database Cell", color: "green" },
33
+ { id: "row", name: "Database Row", color: "green" },
34
+ { id: "column", name: "Database Column", color: "green" },
35
+ { id: "canvas-position", name: "Canvas Position", color: "purple" },
36
+ { id: "canvas-object", name: "Canvas Object", color: "purple" },
37
+ { id: "node", name: "Whole Node", color: "gray" }
38
+ ],
39
+ required: true,
40
+ default: "node"
41
+ }),
42
+ /** JSON-encoded anchor position data */
43
+ anchorData: text({ required: true }),
44
+ // ─── Content ───────────────────────────────────────────────────────────────
45
+ /** Comment body in GitHub-flavored markdown (stored as plain text) */
46
+ content: text({ required: true, maxLength: 1e4 }),
47
+ /** Optional file attachments */
48
+ attachments: file({ multiple: true }),
49
+ // ─── Pseudo Reply-To (for UI, not structural threading) ────────────────────
50
+ /** DID of user being replied to (UI hint) */
51
+ replyToUser: person({}),
52
+ /** Comment ID being referenced (for "in reply to" display) */
53
+ replyToCommentId: relation({}),
54
+ // ─── Thread State (on root comment only) ───────────────────────────────────
55
+ /** Whether the thread has been resolved */
56
+ resolved: checkbox({ default: false }),
57
+ /** Who resolved the thread */
58
+ resolvedBy: person({}),
59
+ /** When the thread was resolved */
60
+ resolvedAt: date({}),
61
+ // ─── Edit State ────────────────────────────────────────────────────────────
62
+ /** Whether the comment has been edited */
63
+ edited: checkbox({ default: false }),
64
+ /** When the comment was last edited */
65
+ editedAt: date({}),
66
+ // ─── Auto-populated Metadata ───────────────────────────────────────────────
67
+ createdAt: created(),
68
+ createdBy: createdBy()
69
+ },
70
+ // Comments are plain text + markdown, no collaborative Y.Doc needed
71
+ document: void 0
72
+ });
73
+
74
+ export {
75
+ CommentSchema
76
+ };
@@ -0,0 +1,25 @@
1
+ import {
2
+ defineSchema,
3
+ file,
4
+ text
5
+ } from "./chunk-SZC345Z2.js";
6
+
7
+ // src/schema/schemas/page.ts
8
+ var PageSchema = defineSchema({
9
+ name: "Page",
10
+ namespace: "xnet://xnet.fyi/",
11
+ properties: {
12
+ /** Page title */
13
+ title: text({ required: true, maxLength: 500 }),
14
+ /** Emoji or icon URL */
15
+ icon: text({}),
16
+ /** Cover image */
17
+ cover: file({ accept: ["image/*"] })
18
+ },
19
+ document: "yjs"
20
+ // Collaborative Y.Doc for rich text
21
+ });
22
+
23
+ export {
24
+ PageSchema
25
+ };
@@ -0,0 +1,29 @@
1
+ import {
2
+ defineSchema,
3
+ number,
4
+ person,
5
+ text
6
+ } from "./chunk-SZC345Z2.js";
7
+
8
+ // src/schema/schemas/grant.ts
9
+ var GrantSchema = defineSchema({
10
+ name: "Grant",
11
+ namespace: "xnet://xnet.fyi/",
12
+ properties: {
13
+ issuer: person({ required: true }),
14
+ grantee: person({ required: true }),
15
+ resource: text({ required: true }),
16
+ resourceSchema: text({ required: true }),
17
+ actions: text({ required: true }),
18
+ expiresAt: number({}),
19
+ revokedAt: number({}),
20
+ revokedBy: person({}),
21
+ ucanToken: text({}),
22
+ proofDepth: number({}),
23
+ parentGrantId: text({})
24
+ }
25
+ });
26
+
27
+ export {
28
+ GrantSchema
29
+ };