@durable-streams/state 0.2.9 → 0.3.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/README.md +12 -2
- package/dist/db.cjs +561 -0
- package/dist/db.d.cts +152 -0
- package/dist/db.d.ts +152 -0
- package/dist/db.js +364 -0
- package/dist/index-CqdIsdQy.d.cts +173 -0
- package/dist/index-D6Nak3Wl.d.ts +173 -0
- package/dist/index.cjs +5 -758
- package/dist/index.d.cts +2 -315
- package/dist/index.d.ts +2 -315
- package/dist/index.js +2 -561
- package/dist/src-AIE5IYwJ.cjs +228 -0
- package/dist/src-VTyL9Eij.js +203 -0
- package/package.json +16 -3
- package/skills/stream-db/SKILL.md +3 -3
- package/src/db.ts +61 -0
- package/src/index.ts +12 -55
- package/src/schema.ts +265 -0
- package/src/stream-db.ts +13 -254
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
//#region src/types.ts
|
|
4
|
+
/**
|
|
5
|
+
* Type guard to check if an event is a change event
|
|
6
|
+
*/
|
|
7
|
+
function isChangeEvent(event) {
|
|
8
|
+
return event != null && `operation` in event.headers;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Type guard to check if an event is a control event
|
|
12
|
+
*/
|
|
13
|
+
function isControlEvent(event) {
|
|
14
|
+
return event != null && `control` in event.headers;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/materialized-state.ts
|
|
19
|
+
/**
|
|
20
|
+
* MaterializedState maintains an in-memory view of state from change events.
|
|
21
|
+
*
|
|
22
|
+
* It organizes data by type, where each type contains a map of key -> value.
|
|
23
|
+
* This supports multi-type streams where different entity types can coexist.
|
|
24
|
+
*/
|
|
25
|
+
var MaterializedState = class {
|
|
26
|
+
data;
|
|
27
|
+
constructor() {
|
|
28
|
+
this.data = new Map();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Apply a single change event to update the materialized state
|
|
32
|
+
*/
|
|
33
|
+
apply(event) {
|
|
34
|
+
const { type, key, value, headers } = event;
|
|
35
|
+
let typeMap = this.data.get(type);
|
|
36
|
+
if (!typeMap) {
|
|
37
|
+
typeMap = new Map();
|
|
38
|
+
this.data.set(type, typeMap);
|
|
39
|
+
}
|
|
40
|
+
switch (headers.operation) {
|
|
41
|
+
case `insert`:
|
|
42
|
+
typeMap.set(key, value);
|
|
43
|
+
break;
|
|
44
|
+
case `update`:
|
|
45
|
+
typeMap.set(key, value);
|
|
46
|
+
break;
|
|
47
|
+
case `upsert`:
|
|
48
|
+
typeMap.set(key, value);
|
|
49
|
+
break;
|
|
50
|
+
case `delete`:
|
|
51
|
+
typeMap.delete(key);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Apply a batch of change events
|
|
57
|
+
*/
|
|
58
|
+
applyBatch(events) {
|
|
59
|
+
for (const event of events) this.apply(event);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get a specific value by type and key
|
|
63
|
+
*/
|
|
64
|
+
get(type, key) {
|
|
65
|
+
const typeMap = this.data.get(type);
|
|
66
|
+
if (!typeMap) return void 0;
|
|
67
|
+
return typeMap.get(key);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get all entries for a specific type
|
|
71
|
+
*/
|
|
72
|
+
getType(type) {
|
|
73
|
+
return this.data.get(type) || new Map();
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Clear all state
|
|
77
|
+
*/
|
|
78
|
+
clear() {
|
|
79
|
+
this.data.clear();
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get the number of types in the state
|
|
83
|
+
*/
|
|
84
|
+
get typeCount() {
|
|
85
|
+
return this.data.size;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get all type names
|
|
89
|
+
*/
|
|
90
|
+
get types() {
|
|
91
|
+
return Array.from(this.data.keys());
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/schema.ts
|
|
97
|
+
/**
|
|
98
|
+
* Reserved collection names that would collide with StreamDB properties
|
|
99
|
+
* (collections are now namespaced, but we still prevent internal name collisions)
|
|
100
|
+
*/
|
|
101
|
+
const RESERVED_COLLECTION_NAMES = new Set([
|
|
102
|
+
`collections`,
|
|
103
|
+
`preload`,
|
|
104
|
+
`close`,
|
|
105
|
+
`utils`,
|
|
106
|
+
`actions`
|
|
107
|
+
]);
|
|
108
|
+
/**
|
|
109
|
+
* Create helper functions for a collection
|
|
110
|
+
*/
|
|
111
|
+
function createCollectionHelpers(eventType, primaryKey, schema) {
|
|
112
|
+
return {
|
|
113
|
+
insert: ({ key, value, headers }) => {
|
|
114
|
+
const result = schema[`~standard`].validate(value);
|
|
115
|
+
if (`issues` in result) throw new Error(`Validation failed for ${eventType} insert: ${result.issues?.map((i) => i.message).join(`, `) ?? `Unknown validation error`}`);
|
|
116
|
+
const derived = value[primaryKey];
|
|
117
|
+
const finalKey = key ?? (derived != null && derived !== `` ? String(derived) : void 0);
|
|
118
|
+
if (finalKey == null || finalKey === ``) throw new Error(`Cannot create ${eventType} insert event: must provide either 'key' or a value with a non-empty '${primaryKey}' field`);
|
|
119
|
+
return {
|
|
120
|
+
type: eventType,
|
|
121
|
+
key: finalKey,
|
|
122
|
+
value,
|
|
123
|
+
headers: {
|
|
124
|
+
...headers,
|
|
125
|
+
operation: `insert`
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
update: ({ key, value, oldValue, headers }) => {
|
|
130
|
+
const result = schema[`~standard`].validate(value);
|
|
131
|
+
if (`issues` in result) throw new Error(`Validation failed for ${eventType} update: ${result.issues?.map((i) => i.message).join(`, `) ?? `Unknown validation error`}`);
|
|
132
|
+
if (oldValue !== void 0) {
|
|
133
|
+
const oldResult = schema[`~standard`].validate(oldValue);
|
|
134
|
+
if (`issues` in oldResult) throw new Error(`Validation failed for ${eventType} update (oldValue): ${oldResult.issues?.map((i) => i.message).join(`, `) ?? `Unknown validation error`}`);
|
|
135
|
+
}
|
|
136
|
+
const derived = value[primaryKey];
|
|
137
|
+
const finalKey = key ?? (derived != null && derived !== `` ? String(derived) : void 0);
|
|
138
|
+
if (finalKey == null || finalKey === ``) throw new Error(`Cannot create ${eventType} update event: must provide either 'key' or a value with a non-empty '${primaryKey}' field`);
|
|
139
|
+
return {
|
|
140
|
+
type: eventType,
|
|
141
|
+
key: finalKey,
|
|
142
|
+
value,
|
|
143
|
+
old_value: oldValue,
|
|
144
|
+
headers: {
|
|
145
|
+
...headers,
|
|
146
|
+
operation: `update`
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
delete: ({ key, oldValue, headers }) => {
|
|
151
|
+
if (oldValue !== void 0) {
|
|
152
|
+
const result = schema[`~standard`].validate(oldValue);
|
|
153
|
+
if (`issues` in result) throw new Error(`Validation failed for ${eventType} delete (oldValue): ${result.issues?.map((i) => i.message).join(`, `) ?? `Unknown validation error`}`);
|
|
154
|
+
}
|
|
155
|
+
const finalKey = key ?? (oldValue ? String(oldValue[primaryKey]) : void 0);
|
|
156
|
+
if (!finalKey) throw new Error(`Cannot create ${eventType} delete event: must provide either 'key' or 'oldValue' with a ${primaryKey} field`);
|
|
157
|
+
return {
|
|
158
|
+
type: eventType,
|
|
159
|
+
key: finalKey,
|
|
160
|
+
old_value: oldValue,
|
|
161
|
+
headers: {
|
|
162
|
+
...headers,
|
|
163
|
+
operation: `delete`
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
},
|
|
167
|
+
upsert: ({ key, value, headers }) => {
|
|
168
|
+
const result = schema[`~standard`].validate(value);
|
|
169
|
+
if (`issues` in result) throw new Error(`Validation failed for ${eventType} upsert: ${result.issues?.map((i) => i.message).join(`, `) ?? `Unknown validation error`}`);
|
|
170
|
+
const derived = value[primaryKey];
|
|
171
|
+
const finalKey = key ?? (derived != null && derived !== `` ? String(derived) : void 0);
|
|
172
|
+
if (finalKey == null || finalKey === ``) throw new Error(`Cannot create ${eventType} upsert event: must provide either 'key' or a value with a non-empty '${primaryKey}' field`);
|
|
173
|
+
return {
|
|
174
|
+
type: eventType,
|
|
175
|
+
key: finalKey,
|
|
176
|
+
value,
|
|
177
|
+
headers: {
|
|
178
|
+
...headers,
|
|
179
|
+
operation: `upsert`
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Create a state schema definition with typed collections and event helpers
|
|
187
|
+
*/
|
|
188
|
+
function createStateSchema(collections) {
|
|
189
|
+
for (const name of Object.keys(collections)) if (RESERVED_COLLECTION_NAMES.has(name)) throw new Error(`Reserved collection name "${name}" - this would collide with StreamDB properties (${Array.from(RESERVED_COLLECTION_NAMES).join(`, `)})`);
|
|
190
|
+
const typeToCollection = new Map();
|
|
191
|
+
for (const [collectionName, def] of Object.entries(collections)) {
|
|
192
|
+
const existing = typeToCollection.get(def.type);
|
|
193
|
+
if (existing) throw new Error(`Duplicate event type "${def.type}" - used by both "${existing}" and "${collectionName}" collections`);
|
|
194
|
+
typeToCollection.set(def.type, collectionName);
|
|
195
|
+
}
|
|
196
|
+
const enhancedCollections = {};
|
|
197
|
+
for (const [name, collectionDef] of Object.entries(collections)) enhancedCollections[name] = {
|
|
198
|
+
...collectionDef,
|
|
199
|
+
...createCollectionHelpers(collectionDef.type, collectionDef.primaryKey, collectionDef.schema)
|
|
200
|
+
};
|
|
201
|
+
return enhancedCollections;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
//#endregion
|
|
205
|
+
Object.defineProperty(exports, 'MaterializedState', {
|
|
206
|
+
enumerable: true,
|
|
207
|
+
get: function () {
|
|
208
|
+
return MaterializedState;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
Object.defineProperty(exports, 'createStateSchema', {
|
|
212
|
+
enumerable: true,
|
|
213
|
+
get: function () {
|
|
214
|
+
return createStateSchema;
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
Object.defineProperty(exports, 'isChangeEvent', {
|
|
218
|
+
enumerable: true,
|
|
219
|
+
get: function () {
|
|
220
|
+
return isChangeEvent;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
Object.defineProperty(exports, 'isControlEvent', {
|
|
224
|
+
enumerable: true,
|
|
225
|
+
get: function () {
|
|
226
|
+
return isControlEvent;
|
|
227
|
+
}
|
|
228
|
+
});
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
//#region src/types.ts
|
|
2
|
+
/**
|
|
3
|
+
* Type guard to check if an event is a change event
|
|
4
|
+
*/
|
|
5
|
+
function isChangeEvent(event) {
|
|
6
|
+
return event != null && `operation` in event.headers;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Type guard to check if an event is a control event
|
|
10
|
+
*/
|
|
11
|
+
function isControlEvent(event) {
|
|
12
|
+
return event != null && `control` in event.headers;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/materialized-state.ts
|
|
17
|
+
/**
|
|
18
|
+
* MaterializedState maintains an in-memory view of state from change events.
|
|
19
|
+
*
|
|
20
|
+
* It organizes data by type, where each type contains a map of key -> value.
|
|
21
|
+
* This supports multi-type streams where different entity types can coexist.
|
|
22
|
+
*/
|
|
23
|
+
var MaterializedState = class {
|
|
24
|
+
data;
|
|
25
|
+
constructor() {
|
|
26
|
+
this.data = new Map();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Apply a single change event to update the materialized state
|
|
30
|
+
*/
|
|
31
|
+
apply(event) {
|
|
32
|
+
const { type, key, value, headers } = event;
|
|
33
|
+
let typeMap = this.data.get(type);
|
|
34
|
+
if (!typeMap) {
|
|
35
|
+
typeMap = new Map();
|
|
36
|
+
this.data.set(type, typeMap);
|
|
37
|
+
}
|
|
38
|
+
switch (headers.operation) {
|
|
39
|
+
case `insert`:
|
|
40
|
+
typeMap.set(key, value);
|
|
41
|
+
break;
|
|
42
|
+
case `update`:
|
|
43
|
+
typeMap.set(key, value);
|
|
44
|
+
break;
|
|
45
|
+
case `upsert`:
|
|
46
|
+
typeMap.set(key, value);
|
|
47
|
+
break;
|
|
48
|
+
case `delete`:
|
|
49
|
+
typeMap.delete(key);
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Apply a batch of change events
|
|
55
|
+
*/
|
|
56
|
+
applyBatch(events) {
|
|
57
|
+
for (const event of events) this.apply(event);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get a specific value by type and key
|
|
61
|
+
*/
|
|
62
|
+
get(type, key) {
|
|
63
|
+
const typeMap = this.data.get(type);
|
|
64
|
+
if (!typeMap) return void 0;
|
|
65
|
+
return typeMap.get(key);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get all entries for a specific type
|
|
69
|
+
*/
|
|
70
|
+
getType(type) {
|
|
71
|
+
return this.data.get(type) || new Map();
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Clear all state
|
|
75
|
+
*/
|
|
76
|
+
clear() {
|
|
77
|
+
this.data.clear();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get the number of types in the state
|
|
81
|
+
*/
|
|
82
|
+
get typeCount() {
|
|
83
|
+
return this.data.size;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get all type names
|
|
87
|
+
*/
|
|
88
|
+
get types() {
|
|
89
|
+
return Array.from(this.data.keys());
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/schema.ts
|
|
95
|
+
/**
|
|
96
|
+
* Reserved collection names that would collide with StreamDB properties
|
|
97
|
+
* (collections are now namespaced, but we still prevent internal name collisions)
|
|
98
|
+
*/
|
|
99
|
+
const RESERVED_COLLECTION_NAMES = new Set([
|
|
100
|
+
`collections`,
|
|
101
|
+
`preload`,
|
|
102
|
+
`close`,
|
|
103
|
+
`utils`,
|
|
104
|
+
`actions`
|
|
105
|
+
]);
|
|
106
|
+
/**
|
|
107
|
+
* Create helper functions for a collection
|
|
108
|
+
*/
|
|
109
|
+
function createCollectionHelpers(eventType, primaryKey, schema) {
|
|
110
|
+
return {
|
|
111
|
+
insert: ({ key, value, headers }) => {
|
|
112
|
+
const result = schema[`~standard`].validate(value);
|
|
113
|
+
if (`issues` in result) throw new Error(`Validation failed for ${eventType} insert: ${result.issues?.map((i) => i.message).join(`, `) ?? `Unknown validation error`}`);
|
|
114
|
+
const derived = value[primaryKey];
|
|
115
|
+
const finalKey = key ?? (derived != null && derived !== `` ? String(derived) : void 0);
|
|
116
|
+
if (finalKey == null || finalKey === ``) throw new Error(`Cannot create ${eventType} insert event: must provide either 'key' or a value with a non-empty '${primaryKey}' field`);
|
|
117
|
+
return {
|
|
118
|
+
type: eventType,
|
|
119
|
+
key: finalKey,
|
|
120
|
+
value,
|
|
121
|
+
headers: {
|
|
122
|
+
...headers,
|
|
123
|
+
operation: `insert`
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
update: ({ key, value, oldValue, headers }) => {
|
|
128
|
+
const result = schema[`~standard`].validate(value);
|
|
129
|
+
if (`issues` in result) throw new Error(`Validation failed for ${eventType} update: ${result.issues?.map((i) => i.message).join(`, `) ?? `Unknown validation error`}`);
|
|
130
|
+
if (oldValue !== void 0) {
|
|
131
|
+
const oldResult = schema[`~standard`].validate(oldValue);
|
|
132
|
+
if (`issues` in oldResult) throw new Error(`Validation failed for ${eventType} update (oldValue): ${oldResult.issues?.map((i) => i.message).join(`, `) ?? `Unknown validation error`}`);
|
|
133
|
+
}
|
|
134
|
+
const derived = value[primaryKey];
|
|
135
|
+
const finalKey = key ?? (derived != null && derived !== `` ? String(derived) : void 0);
|
|
136
|
+
if (finalKey == null || finalKey === ``) throw new Error(`Cannot create ${eventType} update event: must provide either 'key' or a value with a non-empty '${primaryKey}' field`);
|
|
137
|
+
return {
|
|
138
|
+
type: eventType,
|
|
139
|
+
key: finalKey,
|
|
140
|
+
value,
|
|
141
|
+
old_value: oldValue,
|
|
142
|
+
headers: {
|
|
143
|
+
...headers,
|
|
144
|
+
operation: `update`
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
delete: ({ key, oldValue, headers }) => {
|
|
149
|
+
if (oldValue !== void 0) {
|
|
150
|
+
const result = schema[`~standard`].validate(oldValue);
|
|
151
|
+
if (`issues` in result) throw new Error(`Validation failed for ${eventType} delete (oldValue): ${result.issues?.map((i) => i.message).join(`, `) ?? `Unknown validation error`}`);
|
|
152
|
+
}
|
|
153
|
+
const finalKey = key ?? (oldValue ? String(oldValue[primaryKey]) : void 0);
|
|
154
|
+
if (!finalKey) throw new Error(`Cannot create ${eventType} delete event: must provide either 'key' or 'oldValue' with a ${primaryKey} field`);
|
|
155
|
+
return {
|
|
156
|
+
type: eventType,
|
|
157
|
+
key: finalKey,
|
|
158
|
+
old_value: oldValue,
|
|
159
|
+
headers: {
|
|
160
|
+
...headers,
|
|
161
|
+
operation: `delete`
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
upsert: ({ key, value, headers }) => {
|
|
166
|
+
const result = schema[`~standard`].validate(value);
|
|
167
|
+
if (`issues` in result) throw new Error(`Validation failed for ${eventType} upsert: ${result.issues?.map((i) => i.message).join(`, `) ?? `Unknown validation error`}`);
|
|
168
|
+
const derived = value[primaryKey];
|
|
169
|
+
const finalKey = key ?? (derived != null && derived !== `` ? String(derived) : void 0);
|
|
170
|
+
if (finalKey == null || finalKey === ``) throw new Error(`Cannot create ${eventType} upsert event: must provide either 'key' or a value with a non-empty '${primaryKey}' field`);
|
|
171
|
+
return {
|
|
172
|
+
type: eventType,
|
|
173
|
+
key: finalKey,
|
|
174
|
+
value,
|
|
175
|
+
headers: {
|
|
176
|
+
...headers,
|
|
177
|
+
operation: `upsert`
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Create a state schema definition with typed collections and event helpers
|
|
185
|
+
*/
|
|
186
|
+
function createStateSchema(collections) {
|
|
187
|
+
for (const name of Object.keys(collections)) if (RESERVED_COLLECTION_NAMES.has(name)) throw new Error(`Reserved collection name "${name}" - this would collide with StreamDB properties (${Array.from(RESERVED_COLLECTION_NAMES).join(`, `)})`);
|
|
188
|
+
const typeToCollection = new Map();
|
|
189
|
+
for (const [collectionName, def] of Object.entries(collections)) {
|
|
190
|
+
const existing = typeToCollection.get(def.type);
|
|
191
|
+
if (existing) throw new Error(`Duplicate event type "${def.type}" - used by both "${existing}" and "${collectionName}" collections`);
|
|
192
|
+
typeToCollection.set(def.type, collectionName);
|
|
193
|
+
}
|
|
194
|
+
const enhancedCollections = {};
|
|
195
|
+
for (const [name, collectionDef] of Object.entries(collections)) enhancedCollections[name] = {
|
|
196
|
+
...collectionDef,
|
|
197
|
+
...createCollectionHelpers(collectionDef.type, collectionDef.primaryKey, collectionDef.schema)
|
|
198
|
+
};
|
|
199
|
+
return enhancedCollections;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
//#endregion
|
|
203
|
+
export { MaterializedState, createStateSchema, isChangeEvent, isControlEvent };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@durable-streams/state",
|
|
3
3
|
"description": "State change event protocol for Durable Streams",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"author": "Durable Stream contributors",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -33,6 +33,16 @@
|
|
|
33
33
|
"default": "./dist/index.cjs"
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
|
+
"./db": {
|
|
37
|
+
"import": {
|
|
38
|
+
"types": "./dist/db.d.ts",
|
|
39
|
+
"default": "./dist/db.js"
|
|
40
|
+
},
|
|
41
|
+
"require": {
|
|
42
|
+
"types": "./dist/db.d.cts",
|
|
43
|
+
"default": "./dist/db.cjs"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
36
46
|
"./package.json": "./package.json"
|
|
37
47
|
},
|
|
38
48
|
"sideEffects": false,
|
|
@@ -50,13 +60,16 @@
|
|
|
50
60
|
],
|
|
51
61
|
"dependencies": {
|
|
52
62
|
"@standard-schema/spec": "^1.0.0",
|
|
53
|
-
"@tanstack/db": "^0.6.0",
|
|
54
63
|
"@durable-streams/client": "0.2.6"
|
|
55
64
|
},
|
|
65
|
+
"peerDependencies": {
|
|
66
|
+
"@tanstack/db": ">=0.6.0 <1.0.0"
|
|
67
|
+
},
|
|
56
68
|
"devDependencies": {
|
|
69
|
+
"@tanstack/db": "0.6.0",
|
|
57
70
|
"@tanstack/intent": "latest",
|
|
58
71
|
"tsdown": "^0.9.0",
|
|
59
|
-
"@durable-streams/server": "0.3.
|
|
72
|
+
"@durable-streams/server": "0.3.6"
|
|
60
73
|
},
|
|
61
74
|
"engines": {
|
|
62
75
|
"node": ">=18.0.0"
|
|
@@ -28,7 +28,7 @@ optimistic actions, and transaction confirmation.
|
|
|
28
28
|
## Setup
|
|
29
29
|
|
|
30
30
|
```typescript
|
|
31
|
-
import { createStreamDB, createStateSchema } from "@durable-streams/state"
|
|
31
|
+
import { createStreamDB, createStateSchema } from "@durable-streams/state/db"
|
|
32
32
|
import { DurableStream } from "@durable-streams/client"
|
|
33
33
|
import { z } from "zod"
|
|
34
34
|
|
|
@@ -83,7 +83,7 @@ StreamDB collections are TanStack DB collections. Use framework adapters for rea
|
|
|
83
83
|
|
|
84
84
|
```typescript
|
|
85
85
|
import { useLiveQuery } from "@tanstack/react-db"
|
|
86
|
-
import { eq } from "@durable-streams/state"
|
|
86
|
+
import { eq } from "@durable-streams/state/db"
|
|
87
87
|
|
|
88
88
|
// List query — destructure { data } with a default empty array
|
|
89
89
|
function UserList() {
|
|
@@ -111,7 +111,7 @@ function UserProfile({ userId }: { userId: string }) {
|
|
|
111
111
|
### Optimistic actions with server confirmation
|
|
112
112
|
|
|
113
113
|
```typescript
|
|
114
|
-
import { createStreamDB, createStateSchema } from "@durable-streams/state"
|
|
114
|
+
import { createStreamDB, createStateSchema } from "@durable-streams/state/db"
|
|
115
115
|
import { z } from "zod"
|
|
116
116
|
|
|
117
117
|
const schema = createStateSchema({
|
package/src/db.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// `@durable-streams/state/db`: the full surface, including the reactive,
|
|
2
|
+
// TanStack DB-backed StreamDB layer. Importing this entry pulls in @tanstack/db,
|
|
3
|
+
// which is a peer dependency — consumers using this subpath must install it.
|
|
4
|
+
//
|
|
5
|
+
// It is a strict superset of the db-free main entry (`@durable-streams/state`),
|
|
6
|
+
// so code that uses both `createStateSchema` and `createStreamDB` can import
|
|
7
|
+
// everything from here.
|
|
8
|
+
|
|
9
|
+
export * from "./index"
|
|
10
|
+
|
|
11
|
+
// Reactive StreamDB layer
|
|
12
|
+
export { createStreamDB, getStreamDBCollectionId } from "./stream-db"
|
|
13
|
+
export type {
|
|
14
|
+
CreateStreamDBOptions,
|
|
15
|
+
StreamDB,
|
|
16
|
+
StreamDBMethods,
|
|
17
|
+
StreamDBUtils,
|
|
18
|
+
StreamDBWithActions,
|
|
19
|
+
ActionFactory,
|
|
20
|
+
ActionMap,
|
|
21
|
+
ActionDefinition,
|
|
22
|
+
} from "./stream-db"
|
|
23
|
+
|
|
24
|
+
// Re-export key types and utilities from @tanstack/db for convenience.
|
|
25
|
+
// This ensures consumers can use the same module resolution for type compatibility.
|
|
26
|
+
export type { Collection, SyncConfig } from "@tanstack/db"
|
|
27
|
+
export {
|
|
28
|
+
createCollection,
|
|
29
|
+
createLiveQueryCollection,
|
|
30
|
+
createOptimisticAction,
|
|
31
|
+
createTransaction,
|
|
32
|
+
deepEquals,
|
|
33
|
+
localOnlyCollectionOptions,
|
|
34
|
+
queryOnce,
|
|
35
|
+
// Comparison operators
|
|
36
|
+
eq,
|
|
37
|
+
gt,
|
|
38
|
+
gte,
|
|
39
|
+
lt,
|
|
40
|
+
lte,
|
|
41
|
+
like,
|
|
42
|
+
ilike,
|
|
43
|
+
inArray,
|
|
44
|
+
// Logical operators
|
|
45
|
+
and,
|
|
46
|
+
or,
|
|
47
|
+
not,
|
|
48
|
+
// Null checking
|
|
49
|
+
isNull,
|
|
50
|
+
isUndefined,
|
|
51
|
+
// Aggregate functions
|
|
52
|
+
count,
|
|
53
|
+
sum,
|
|
54
|
+
avg,
|
|
55
|
+
min,
|
|
56
|
+
max,
|
|
57
|
+
// Includes/projection functions
|
|
58
|
+
concat,
|
|
59
|
+
coalesce,
|
|
60
|
+
toArray,
|
|
61
|
+
} from "@tanstack/db"
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
// Main entry: the db-free surface of the state protocol.
|
|
2
|
+
//
|
|
3
|
+
// Everything exported here works without @tanstack/db installed — defining
|
|
4
|
+
// schemas, constructing/validating change events, and materializing state
|
|
5
|
+
// in-memory. The reactive, TanStack DB-backed StreamDB layer (createStreamDB,
|
|
6
|
+
// live queries, optimistic actions) lives under the `@durable-streams/state/db`
|
|
7
|
+
// subpath, which carries the `@tanstack/db` peer dependency.
|
|
8
|
+
|
|
1
9
|
// Types
|
|
2
10
|
export type {
|
|
3
11
|
Operation,
|
|
@@ -11,66 +19,15 @@ export type {
|
|
|
11
19
|
|
|
12
20
|
export { isChangeEvent, isControlEvent } from "./types"
|
|
13
21
|
|
|
14
|
-
//
|
|
22
|
+
// In-memory materialization
|
|
15
23
|
export { MaterializedState } from "./materialized-state"
|
|
16
24
|
|
|
17
|
-
//
|
|
18
|
-
export {
|
|
19
|
-
createStreamDB,
|
|
20
|
-
createStateSchema,
|
|
21
|
-
getStreamDBCollectionId,
|
|
22
|
-
} from "./stream-db"
|
|
25
|
+
// Schema definition + event construction (producer side)
|
|
26
|
+
export { createStateSchema } from "./schema"
|
|
23
27
|
export type {
|
|
24
28
|
CollectionDefinition,
|
|
25
29
|
CollectionEventHelpers,
|
|
26
30
|
CollectionWithHelpers,
|
|
27
31
|
StreamStateDefinition,
|
|
28
32
|
StateSchema,
|
|
29
|
-
|
|
30
|
-
StreamDB,
|
|
31
|
-
StreamDBMethods,
|
|
32
|
-
StreamDBUtils,
|
|
33
|
-
StreamDBWithActions,
|
|
34
|
-
ActionFactory,
|
|
35
|
-
ActionMap,
|
|
36
|
-
ActionDefinition,
|
|
37
|
-
} from "./stream-db"
|
|
38
|
-
|
|
39
|
-
// Re-export key types and utilities from @tanstack/db for convenience
|
|
40
|
-
// This ensures consumers can use the same module resolution for type compatibility
|
|
41
|
-
export type { Collection, SyncConfig } from "@tanstack/db"
|
|
42
|
-
export {
|
|
43
|
-
createCollection,
|
|
44
|
-
createLiveQueryCollection,
|
|
45
|
-
createOptimisticAction,
|
|
46
|
-
createTransaction,
|
|
47
|
-
deepEquals,
|
|
48
|
-
localOnlyCollectionOptions,
|
|
49
|
-
queryOnce,
|
|
50
|
-
// Comparison operators
|
|
51
|
-
eq,
|
|
52
|
-
gt,
|
|
53
|
-
gte,
|
|
54
|
-
lt,
|
|
55
|
-
lte,
|
|
56
|
-
like,
|
|
57
|
-
ilike,
|
|
58
|
-
inArray,
|
|
59
|
-
// Logical operators
|
|
60
|
-
and,
|
|
61
|
-
or,
|
|
62
|
-
not,
|
|
63
|
-
// Null checking
|
|
64
|
-
isNull,
|
|
65
|
-
isUndefined,
|
|
66
|
-
// Aggregate functions
|
|
67
|
-
count,
|
|
68
|
-
sum,
|
|
69
|
-
avg,
|
|
70
|
-
min,
|
|
71
|
-
max,
|
|
72
|
-
// Includes/projection functions
|
|
73
|
-
concat,
|
|
74
|
-
coalesce,
|
|
75
|
-
toArray,
|
|
76
|
-
} from "@tanstack/db"
|
|
33
|
+
} from "./schema"
|