digital-objects 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.
- package/.turbo/turbo-build.log +4 -0
- package/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/README.md +476 -0
- package/dist/ai-database-adapter.d.ts +49 -0
- package/dist/ai-database-adapter.d.ts.map +1 -0
- package/dist/ai-database-adapter.js +89 -0
- package/dist/ai-database-adapter.js.map +1 -0
- package/dist/errors.d.ts +47 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +72 -0
- package/dist/errors.js.map +1 -0
- package/dist/http-schemas.d.ts +165 -0
- package/dist/http-schemas.d.ts.map +1 -0
- package/dist/http-schemas.js +55 -0
- package/dist/http-schemas.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/linguistic.d.ts +54 -0
- package/dist/linguistic.d.ts.map +1 -0
- package/dist/linguistic.js +226 -0
- package/dist/linguistic.js.map +1 -0
- package/dist/memory-provider.d.ts +46 -0
- package/dist/memory-provider.d.ts.map +1 -0
- package/dist/memory-provider.js +279 -0
- package/dist/memory-provider.js.map +1 -0
- package/dist/ns-client.d.ts +88 -0
- package/dist/ns-client.d.ts.map +1 -0
- package/dist/ns-client.js +253 -0
- package/dist/ns-client.js.map +1 -0
- package/dist/ns-exports.d.ts +23 -0
- package/dist/ns-exports.d.ts.map +1 -0
- package/dist/ns-exports.js +21 -0
- package/dist/ns-exports.js.map +1 -0
- package/dist/ns.d.ts +60 -0
- package/dist/ns.d.ts.map +1 -0
- package/dist/ns.js +818 -0
- package/dist/ns.js.map +1 -0
- package/dist/r2-persistence.d.ts +112 -0
- package/dist/r2-persistence.d.ts.map +1 -0
- package/dist/r2-persistence.js +252 -0
- package/dist/r2-persistence.js.map +1 -0
- package/dist/schema-validation.d.ts +80 -0
- package/dist/schema-validation.d.ts.map +1 -0
- package/dist/schema-validation.js +233 -0
- package/dist/schema-validation.js.map +1 -0
- package/dist/types.d.ts +184 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +26 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -0
- package/src/ai-database-adapter.test.ts +610 -0
- package/src/ai-database-adapter.ts +189 -0
- package/src/benchmark.test.ts +109 -0
- package/src/errors.ts +91 -0
- package/src/http-schemas.ts +67 -0
- package/src/index.ts +87 -0
- package/src/linguistic.test.ts +1107 -0
- package/src/linguistic.ts +253 -0
- package/src/memory-provider.ts +470 -0
- package/src/ns-client.test.ts +1360 -0
- package/src/ns-client.ts +342 -0
- package/src/ns-exports.ts +23 -0
- package/src/ns.test.ts +1381 -0
- package/src/ns.ts +1215 -0
- package/src/provider.test.ts +675 -0
- package/src/r2-persistence.test.ts +263 -0
- package/src/r2-persistence.ts +367 -0
- package/src/schema-validation.test.ts +167 -0
- package/src/schema-validation.ts +330 -0
- package/src/types.ts +252 -0
- package/test/action-status.test.ts +42 -0
- package/test/batch-limits.test.ts +165 -0
- package/test/docs.test.ts +48 -0
- package/test/errors.test.ts +148 -0
- package/test/http-validation.test.ts +401 -0
- package/test/ns-client-errors.test.ts +208 -0
- package/test/ns-namespace.test.ts +307 -0
- package/test/performance.test.ts +168 -0
- package/test/schema-validation-error.test.ts +213 -0
- package/test/schema-validation.test.ts +440 -0
- package/test/search-escaping.test.ts +359 -0
- package/test/security.test.ts +322 -0
- package/tsconfig.json +10 -0
- package/wrangler.jsonc +16 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ai-database Adapter
|
|
3
|
+
*
|
|
4
|
+
* Wraps a DigitalObjectsProvider to provide the ai-database DBProvider interface.
|
|
5
|
+
* This enables ai-database to use digital-objects as its storage backend.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { DigitalObjectsProvider, Thing, ListOptions as DOListOptions } from './types.js'
|
|
9
|
+
|
|
10
|
+
// These types match ai-database's DBProvider interface
|
|
11
|
+
export interface ListOptions {
|
|
12
|
+
limit?: number
|
|
13
|
+
offset?: number
|
|
14
|
+
where?: Record<string, unknown>
|
|
15
|
+
orderBy?: string
|
|
16
|
+
order?: 'asc' | 'desc'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SearchOptions extends ListOptions {
|
|
20
|
+
fields?: string[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SemanticSearchOptions extends SearchOptions {
|
|
24
|
+
embedding?: number[]
|
|
25
|
+
minScore?: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface HybridSearchOptions extends SearchOptions {
|
|
29
|
+
semanticWeight?: number
|
|
30
|
+
ftsWeight?: number
|
|
31
|
+
minScore?: number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* ai-database DBProvider interface (simplified)
|
|
36
|
+
*/
|
|
37
|
+
export interface DBProvider {
|
|
38
|
+
// Entity operations
|
|
39
|
+
get(type: string, id: string): Promise<Record<string, unknown> | null>
|
|
40
|
+
list(type: string, options?: ListOptions): Promise<Record<string, unknown>[]>
|
|
41
|
+
search(type: string, query: string, options?: SearchOptions): Promise<Record<string, unknown>[]>
|
|
42
|
+
create(
|
|
43
|
+
type: string,
|
|
44
|
+
id: string | undefined,
|
|
45
|
+
data: Record<string, unknown>
|
|
46
|
+
): Promise<Record<string, unknown>>
|
|
47
|
+
update(type: string, id: string, data: Record<string, unknown>): Promise<Record<string, unknown>>
|
|
48
|
+
delete(type: string, id: string): Promise<boolean>
|
|
49
|
+
|
|
50
|
+
// Relation operations
|
|
51
|
+
related(type: string, id: string, relation: string): Promise<Record<string, unknown>[]>
|
|
52
|
+
relate(
|
|
53
|
+
fromType: string,
|
|
54
|
+
fromId: string,
|
|
55
|
+
relation: string,
|
|
56
|
+
toType: string,
|
|
57
|
+
toId: string,
|
|
58
|
+
metadata?: { matchMode?: 'exact' | 'fuzzy'; similarity?: number; matchedType?: string }
|
|
59
|
+
): Promise<void>
|
|
60
|
+
unrelate(
|
|
61
|
+
fromType: string,
|
|
62
|
+
fromId: string,
|
|
63
|
+
relation: string,
|
|
64
|
+
toType: string,
|
|
65
|
+
toId: string
|
|
66
|
+
): Promise<void>
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Convert Thing to entity format (with $id, $type)
|
|
71
|
+
*/
|
|
72
|
+
function thingToEntity<T>(
|
|
73
|
+
thing: Thing<T>
|
|
74
|
+
): Record<string, unknown> & { $id: string; $type: string } {
|
|
75
|
+
return {
|
|
76
|
+
$id: thing.id,
|
|
77
|
+
$type: thing.noun,
|
|
78
|
+
...thing.data,
|
|
79
|
+
} as Record<string, unknown> & { $id: string; $type: string }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Extract data from entity (remove $id, $type)
|
|
84
|
+
*/
|
|
85
|
+
function entityToData(entity: Record<string, unknown>): Record<string, unknown> {
|
|
86
|
+
const { $id, $type, ...data } = entity
|
|
87
|
+
return data
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create a DBProvider adapter from a DigitalObjectsProvider
|
|
92
|
+
*/
|
|
93
|
+
export function createDBProviderAdapter(provider: DigitalObjectsProvider): DBProvider {
|
|
94
|
+
return {
|
|
95
|
+
async get(type: string, id: string): Promise<Record<string, unknown> | null> {
|
|
96
|
+
const thing = await provider.get(id)
|
|
97
|
+
if (!thing || thing.noun !== type) return null
|
|
98
|
+
return thingToEntity(thing)
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
async list(type: string, options?: ListOptions): Promise<Record<string, unknown>[]> {
|
|
102
|
+
const things = await provider.list(type, options as DOListOptions)
|
|
103
|
+
return things.map(thingToEntity)
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
async search(
|
|
107
|
+
type: string,
|
|
108
|
+
query: string,
|
|
109
|
+
options?: SearchOptions
|
|
110
|
+
): Promise<Record<string, unknown>[]> {
|
|
111
|
+
const things = await provider.search(query, { ...options, where: { ...options?.where } })
|
|
112
|
+
// Filter by type since search is global
|
|
113
|
+
return things.filter((t) => t.noun === type).map(thingToEntity)
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
async create(
|
|
117
|
+
type: string,
|
|
118
|
+
id: string | undefined,
|
|
119
|
+
data: Record<string, unknown>
|
|
120
|
+
): Promise<Record<string, unknown>> {
|
|
121
|
+
// Ensure noun is defined
|
|
122
|
+
const existingNoun = await provider.getNoun(type)
|
|
123
|
+
if (!existingNoun) {
|
|
124
|
+
await provider.defineNoun({ name: type })
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const thing = await provider.create(type, entityToData(data), id)
|
|
128
|
+
return thingToEntity(thing)
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
async update(
|
|
132
|
+
_type: string,
|
|
133
|
+
id: string,
|
|
134
|
+
data: Record<string, unknown>
|
|
135
|
+
): Promise<Record<string, unknown>> {
|
|
136
|
+
const thing = await provider.update(id, entityToData(data))
|
|
137
|
+
return thingToEntity(thing)
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
async delete(_type: string, id: string): Promise<boolean> {
|
|
141
|
+
return provider.delete(id)
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
async related(type: string, id: string, relation: string): Promise<Record<string, unknown>[]> {
|
|
145
|
+
// ai-database expects related entities of a specific type via a relation
|
|
146
|
+
// digital-objects uses verb as the relation type
|
|
147
|
+
const things = await provider.related(id, relation, 'both')
|
|
148
|
+
// Filter by expected type
|
|
149
|
+
return things.filter((t) => t.noun === type).map(thingToEntity)
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
async relate(
|
|
153
|
+
_fromType: string,
|
|
154
|
+
fromId: string,
|
|
155
|
+
relation: string,
|
|
156
|
+
_toType: string,
|
|
157
|
+
toId: string,
|
|
158
|
+
metadata?: { matchMode?: 'exact' | 'fuzzy'; similarity?: number; matchedType?: string }
|
|
159
|
+
): Promise<void> {
|
|
160
|
+
// Ensure verb is defined
|
|
161
|
+
const existingVerb = await provider.getVerb(relation)
|
|
162
|
+
if (!existingVerb) {
|
|
163
|
+
await provider.defineVerb({ name: relation })
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
await provider.perform(relation, fromId, toId, metadata)
|
|
167
|
+
},
|
|
168
|
+
|
|
169
|
+
async unrelate(
|
|
170
|
+
_fromType: string,
|
|
171
|
+
fromId: string,
|
|
172
|
+
relation: string,
|
|
173
|
+
_toType: string,
|
|
174
|
+
toId: string
|
|
175
|
+
): Promise<void> {
|
|
176
|
+
// Find the action(s) matching this relation and delete them
|
|
177
|
+
const actions = await provider.listActions({
|
|
178
|
+
verb: relation,
|
|
179
|
+
subject: fromId,
|
|
180
|
+
object: toId,
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
// Delete all matching actions (for GDPR compliance)
|
|
184
|
+
for (const action of actions) {
|
|
185
|
+
await provider.deleteAction(action.id)
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest'
|
|
2
|
+
import { createMemoryProvider } from './memory-provider.js'
|
|
3
|
+
import type { DigitalObjectsProvider } from './types.js'
|
|
4
|
+
|
|
5
|
+
describe('Performance Benchmarks', () => {
|
|
6
|
+
let provider: DigitalObjectsProvider
|
|
7
|
+
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
provider = createMemoryProvider()
|
|
10
|
+
await provider.defineNoun({ name: 'Item' })
|
|
11
|
+
await provider.defineVerb({ name: 'link' })
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
describe('create() performance', () => {
|
|
15
|
+
it('should create 1000 things in under 100ms', async () => {
|
|
16
|
+
const start = performance.now()
|
|
17
|
+
|
|
18
|
+
for (let i = 0; i < 1000; i++) {
|
|
19
|
+
await provider.create('Item', { index: i, name: `Item ${i}` })
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const elapsed = performance.now() - start
|
|
23
|
+
console.log(
|
|
24
|
+
`create() 1000 items: ${elapsed.toFixed(2)}ms (${((1000 / elapsed) * 1000).toFixed(
|
|
25
|
+
0
|
|
26
|
+
)} ops/sec)`
|
|
27
|
+
)
|
|
28
|
+
expect(elapsed).toBeLessThan(100)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
describe('list() performance', () => {
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
// Create 10k items
|
|
35
|
+
for (let i = 0; i < 10000; i++) {
|
|
36
|
+
await provider.create('Item', { index: i })
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('should list 100 items from 10k in under 10ms', async () => {
|
|
41
|
+
const start = performance.now()
|
|
42
|
+
|
|
43
|
+
const items = await provider.list('Item', { limit: 100 })
|
|
44
|
+
|
|
45
|
+
const elapsed = performance.now() - start
|
|
46
|
+
console.log(`list() 100 from 10k: ${elapsed.toFixed(2)}ms`)
|
|
47
|
+
expect(items.length).toBe(100)
|
|
48
|
+
expect(elapsed).toBeLessThan(10)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should list with where filter in under 50ms', async () => {
|
|
52
|
+
const start = performance.now()
|
|
53
|
+
|
|
54
|
+
const items = await provider.list('Item', { where: { index: 5000 } })
|
|
55
|
+
|
|
56
|
+
const elapsed = performance.now() - start
|
|
57
|
+
console.log(`list() with where: ${elapsed.toFixed(2)}ms`)
|
|
58
|
+
expect(elapsed).toBeLessThan(50)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
describe('related() performance', () => {
|
|
63
|
+
beforeEach(async () => {
|
|
64
|
+
// Create graph: 100 items with 10 relations each
|
|
65
|
+
const items = []
|
|
66
|
+
for (let i = 0; i < 100; i++) {
|
|
67
|
+
items.push(await provider.create('Item', { index: i }))
|
|
68
|
+
}
|
|
69
|
+
for (let i = 0; i < 100; i++) {
|
|
70
|
+
for (let j = 0; j < 10; j++) {
|
|
71
|
+
const target = (i + j + 1) % 100
|
|
72
|
+
await provider.perform('link', items[i].id, items[target].id)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('should traverse relations in under 20ms', async () => {
|
|
78
|
+
const items = await provider.list('Item', { limit: 1 })
|
|
79
|
+
const start = performance.now()
|
|
80
|
+
|
|
81
|
+
const related = await provider.related(items[0].id, 'link', 'out')
|
|
82
|
+
|
|
83
|
+
const elapsed = performance.now() - start
|
|
84
|
+
console.log(`related() traversal: ${elapsed.toFixed(2)}ms, found ${related.length} items`)
|
|
85
|
+
expect(elapsed).toBeLessThan(20)
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
describe('search() performance', () => {
|
|
90
|
+
beforeEach(async () => {
|
|
91
|
+
for (let i = 0; i < 1000; i++) {
|
|
92
|
+
await provider.create('Item', {
|
|
93
|
+
name: `Item ${i}`,
|
|
94
|
+
description: `This is a searchable description for item number ${i}`,
|
|
95
|
+
})
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('should search 1000 items in under 50ms', async () => {
|
|
100
|
+
const start = performance.now()
|
|
101
|
+
|
|
102
|
+
const results = await provider.search('searchable', { limit: 100 })
|
|
103
|
+
|
|
104
|
+
const elapsed = performance.now() - start
|
|
105
|
+
console.log(`search() 1000 items: ${elapsed.toFixed(2)}ms, found ${results.length} results`)
|
|
106
|
+
expect(elapsed).toBeLessThan(50)
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
})
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standardized error classes for digital-objects
|
|
3
|
+
*
|
|
4
|
+
* These errors provide consistent error handling across all providers
|
|
5
|
+
* (MemoryProvider, NS Durable Object) with proper HTTP status codes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Base error class for all digital-objects errors
|
|
10
|
+
*/
|
|
11
|
+
export class DigitalObjectsError extends Error {
|
|
12
|
+
constructor(message: string, public code: string, public statusCode: number = 500) {
|
|
13
|
+
super(message)
|
|
14
|
+
this.name = 'DigitalObjectsError'
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Thrown when a resource is not found
|
|
20
|
+
*/
|
|
21
|
+
export class NotFoundError extends DigitalObjectsError {
|
|
22
|
+
constructor(type: string, id: string) {
|
|
23
|
+
super(`${type} not found: ${id}`, 'NOT_FOUND', 404)
|
|
24
|
+
this.name = 'NotFoundError'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Thrown when validation fails
|
|
30
|
+
*/
|
|
31
|
+
export class ValidationError extends DigitalObjectsError {
|
|
32
|
+
constructor(message: string, public errors: Array<{ field: string; message: string }>) {
|
|
33
|
+
super(message, 'VALIDATION_ERROR', 400)
|
|
34
|
+
this.name = 'ValidationError'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Thrown when there's a conflict (e.g., duplicate ID)
|
|
40
|
+
*/
|
|
41
|
+
export class ConflictError extends DigitalObjectsError {
|
|
42
|
+
constructor(message: string) {
|
|
43
|
+
super(message, 'CONFLICT', 409)
|
|
44
|
+
this.name = 'ConflictError'
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Thrown when a server error occurs (5xx status codes)
|
|
50
|
+
*/
|
|
51
|
+
export class ServerError extends DigitalObjectsError {
|
|
52
|
+
constructor(message: string, statusCode: number = 500) {
|
|
53
|
+
super(message, 'SERVER_ERROR', statusCode)
|
|
54
|
+
this.name = 'ServerError'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Thrown when a network error occurs (connection refused, timeout, etc.)
|
|
60
|
+
*/
|
|
61
|
+
export class NetworkError extends DigitalObjectsError {
|
|
62
|
+
constructor(message: string, public cause?: Error) {
|
|
63
|
+
super(message, 'NETWORK_ERROR', 503)
|
|
64
|
+
this.name = 'NetworkError'
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Convert an error to an HTTP-safe JSON response body
|
|
70
|
+
*/
|
|
71
|
+
export function errorToResponse(error: unknown): { body: object; status: number } {
|
|
72
|
+
if (error instanceof DigitalObjectsError) {
|
|
73
|
+
return {
|
|
74
|
+
body: {
|
|
75
|
+
error: error.code,
|
|
76
|
+
message: error.message,
|
|
77
|
+
...(error instanceof ValidationError ? { errors: error.errors } : {}),
|
|
78
|
+
},
|
|
79
|
+
status: error.statusCode,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Don't expose internal error details to clients
|
|
84
|
+
return {
|
|
85
|
+
body: {
|
|
86
|
+
error: 'INTERNAL_ERROR',
|
|
87
|
+
message: 'An internal error occurred',
|
|
88
|
+
},
|
|
89
|
+
status: 500,
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const NounDefinitionSchema = z.object({
|
|
4
|
+
name: z.string().min(1),
|
|
5
|
+
singular: z.string().optional(),
|
|
6
|
+
plural: z.string().optional(),
|
|
7
|
+
description: z.string().optional(),
|
|
8
|
+
schema: z.record(z.any()).optional(),
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
export const VerbDefinitionSchema = z.object({
|
|
12
|
+
name: z.string().min(1),
|
|
13
|
+
action: z.string().optional(),
|
|
14
|
+
act: z.string().optional(),
|
|
15
|
+
activity: z.string().optional(),
|
|
16
|
+
event: z.string().optional(),
|
|
17
|
+
reverseBy: z.string().optional(),
|
|
18
|
+
inverse: z.string().optional(),
|
|
19
|
+
description: z.string().optional(),
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
export const CreateThingSchema = z.object({
|
|
23
|
+
noun: z.string().min(1),
|
|
24
|
+
data: z.record(z.any()),
|
|
25
|
+
id: z.string().optional(),
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
export const UpdateThingSchema = z.object({
|
|
29
|
+
data: z.record(z.any()),
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
export const PerformActionSchema = z.object({
|
|
33
|
+
verb: z.string().min(1),
|
|
34
|
+
subject: z.string().optional(),
|
|
35
|
+
object: z.string().optional(),
|
|
36
|
+
data: z.any().optional(),
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// Batch operation schemas
|
|
40
|
+
export const BatchCreateThingsSchema = z.object({
|
|
41
|
+
noun: z.string().min(1),
|
|
42
|
+
items: z.array(z.record(z.any())),
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
export const BatchUpdateThingsSchema = z.object({
|
|
46
|
+
updates: z.array(
|
|
47
|
+
z.object({
|
|
48
|
+
id: z.string().min(1),
|
|
49
|
+
data: z.record(z.any()),
|
|
50
|
+
})
|
|
51
|
+
),
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
export const BatchDeleteThingsSchema = z.object({
|
|
55
|
+
ids: z.array(z.string().min(1)),
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
export const BatchPerformActionsSchema = z.object({
|
|
59
|
+
actions: z.array(
|
|
60
|
+
z.object({
|
|
61
|
+
verb: z.string().min(1),
|
|
62
|
+
subject: z.string().optional(),
|
|
63
|
+
object: z.string().optional(),
|
|
64
|
+
data: z.any().optional(),
|
|
65
|
+
})
|
|
66
|
+
),
|
|
67
|
+
})
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* digital-objects - Unified storage primitive for AI primitives
|
|
3
|
+
*
|
|
4
|
+
* Core concepts:
|
|
5
|
+
* - **Nouns**: Entity type definitions (singular/plural/schema)
|
|
6
|
+
* - **Verbs**: Action definitions (conjugations, reverse forms)
|
|
7
|
+
* - **Things**: Entity instances (the actual data)
|
|
8
|
+
* - **Actions**: Events + Relationships + Audit Trail (unified graph edges)
|
|
9
|
+
*
|
|
10
|
+
* Providers:
|
|
11
|
+
* - `MemoryProvider`: In-memory for tests
|
|
12
|
+
* - `NS`: SQLite in Cloudflare Durable Objects (import from 'digital-objects/ns')
|
|
13
|
+
*
|
|
14
|
+
* @packageDocumentation
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// Types
|
|
18
|
+
export type {
|
|
19
|
+
Noun,
|
|
20
|
+
NounDefinition,
|
|
21
|
+
Verb,
|
|
22
|
+
VerbDefinition,
|
|
23
|
+
Thing,
|
|
24
|
+
Action,
|
|
25
|
+
ActionStatusType,
|
|
26
|
+
FieldDefinition,
|
|
27
|
+
PrimitiveType,
|
|
28
|
+
ListOptions,
|
|
29
|
+
ActionOptions,
|
|
30
|
+
DigitalObjectsProvider,
|
|
31
|
+
Direction,
|
|
32
|
+
} from './types.js'
|
|
33
|
+
|
|
34
|
+
// Validation utilities and constants
|
|
35
|
+
export { validateDirection, MAX_BATCH_SIZE, ActionStatus } from './types.js'
|
|
36
|
+
|
|
37
|
+
// Memory Provider
|
|
38
|
+
export { MemoryProvider, createMemoryProvider } from './memory-provider.js'
|
|
39
|
+
|
|
40
|
+
// Linguistic utilities
|
|
41
|
+
export { deriveNoun, deriveVerb, pluralize, singularize } from './linguistic.js'
|
|
42
|
+
|
|
43
|
+
// NS Client (for HTTP access to NS Durable Object)
|
|
44
|
+
export { NSClient, createNSClient } from './ns-client.js'
|
|
45
|
+
export type { NSClientOptions } from './ns-client.js'
|
|
46
|
+
|
|
47
|
+
// R2 Persistence (backup/restore to Cloudflare R2)
|
|
48
|
+
export {
|
|
49
|
+
createSnapshot,
|
|
50
|
+
restoreSnapshot,
|
|
51
|
+
appendWAL,
|
|
52
|
+
replayWAL,
|
|
53
|
+
compactWAL,
|
|
54
|
+
exportJSONL,
|
|
55
|
+
importJSONL,
|
|
56
|
+
exportToR2,
|
|
57
|
+
importFromR2,
|
|
58
|
+
} from './r2-persistence.js'
|
|
59
|
+
export type { Snapshot, WALEntry, SnapshotOptions, SnapshotResult } from './r2-persistence.js'
|
|
60
|
+
|
|
61
|
+
// ai-database Adapter
|
|
62
|
+
export { createDBProviderAdapter } from './ai-database-adapter.js'
|
|
63
|
+
export type {
|
|
64
|
+
DBProvider,
|
|
65
|
+
ListOptions as DBListOptions,
|
|
66
|
+
SearchOptions,
|
|
67
|
+
SemanticSearchOptions,
|
|
68
|
+
HybridSearchOptions,
|
|
69
|
+
} from './ai-database-adapter.js'
|
|
70
|
+
|
|
71
|
+
// Errors
|
|
72
|
+
export {
|
|
73
|
+
DigitalObjectsError,
|
|
74
|
+
NotFoundError,
|
|
75
|
+
ValidationError,
|
|
76
|
+
ConflictError,
|
|
77
|
+
errorToResponse,
|
|
78
|
+
} from './errors.js'
|
|
79
|
+
|
|
80
|
+
// Schema Validation
|
|
81
|
+
export { validateOnly, validateData } from './schema-validation.js'
|
|
82
|
+
export type {
|
|
83
|
+
SchemaValidationError,
|
|
84
|
+
ValidationErrorCode,
|
|
85
|
+
ValidationResult,
|
|
86
|
+
ValidationOptions,
|
|
87
|
+
} from './schema-validation.js'
|