@feltdb/core 0.2.0 → 0.4.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/LICENSE +19 -0
- package/dist/analytics-backend.d.ts +39 -0
- package/dist/analytics-backend.d.ts.map +1 -0
- package/dist/analytics-backend.js +152 -0
- package/dist/application-contract.d.ts +49 -0
- package/dist/application-contract.d.ts.map +1 -0
- package/dist/application-contract.js +13 -0
- package/dist/application-manifest.d.ts +305 -0
- package/dist/application-manifest.d.ts.map +1 -0
- package/dist/application-manifest.js +26 -0
- package/dist/artifact.d.ts +73 -0
- package/dist/artifact.d.ts.map +1 -0
- package/dist/artifact.js +21 -0
- package/dist/authorization.d.ts +99 -0
- package/dist/authorization.d.ts.map +1 -0
- package/dist/authorization.js +15 -0
- package/dist/bundle.d.ts +55 -0
- package/dist/bundle.d.ts.map +1 -0
- package/dist/bundle.js +13 -0
- package/dist/collection.d.ts +28 -0
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +83 -0
- package/dist/db.d.ts +18 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +20 -8
- package/dist/distributed-indexing.d.ts +170 -0
- package/dist/distributed-indexing.d.ts.map +1 -0
- package/dist/distributed-indexing.js +260 -0
- package/dist/flowspec.d.ts +4 -0
- package/dist/flowspec.d.ts.map +1 -1
- package/dist/flowspec.js +8 -0
- package/dist/http-client.d.ts +35 -0
- package/dist/http-client.d.ts.map +1 -0
- package/dist/http-client.js +70 -0
- package/dist/identity.d.ts +40 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +10 -0
- package/dist/index-analytics.d.ts +121 -0
- package/dist/index-analytics.d.ts.map +1 -0
- package/dist/index-analytics.js +279 -0
- package/dist/index-backend.d.ts +26 -0
- package/dist/index-backend.d.ts.map +1 -0
- package/dist/index-backend.js +115 -0
- package/dist/index-dashboard.d.ts +114 -0
- package/dist/index-dashboard.d.ts.map +1 -0
- package/dist/index-dashboard.js +256 -0
- package/dist/index-manager.d.ts +18 -0
- package/dist/index-manager.d.ts.map +1 -0
- package/dist/index-manager.js +333 -0
- package/dist/index-monitoring.d.ts +133 -0
- package/dist/index-monitoring.d.ts.map +1 -0
- package/dist/index-monitoring.js +218 -0
- package/dist/index-recovery.d.ts +57 -0
- package/dist/index-recovery.d.ts.map +1 -0
- package/dist/index-recovery.js +117 -0
- package/dist/index-store.d.ts +62 -0
- package/dist/index-store.d.ts.map +1 -0
- package/dist/index-store.js +141 -0
- package/dist/index-types.d.ts +70 -0
- package/dist/index-types.d.ts.map +1 -0
- package/dist/index-types.js +6 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -0
- package/dist/observe.d.ts +51 -0
- package/dist/observe.d.ts.map +1 -0
- package/dist/observe.js +14 -0
- package/dist/protocol.d.ts +25 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +18 -0
- package/dist/provider.d.ts +61 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +17 -0
- package/dist/query-planner.d.ts +69 -0
- package/dist/query-planner.d.ts.map +1 -0
- package/dist/query-planner.js +184 -0
- package/dist/release.d.ts +60 -0
- package/dist/release.d.ts.map +1 -0
- package/dist/release.js +18 -0
- package/dist/sharding.d.ts +159 -0
- package/dist/sharding.d.ts.map +1 -0
- package/dist/sharding.js +317 -0
- package/dist/state-contract.d.ts +173 -0
- package/dist/state-contract.d.ts.map +1 -0
- package/dist/state-contract.js +24 -0
- package/dist/sync-contract.d.ts +101 -0
- package/dist/sync-contract.d.ts.map +1 -0
- package/dist/sync-contract.js +90 -0
- package/dist/worker.d.ts +90 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +26 -0
- package/dist/workload.d.ts +102 -0
- package/dist/workload.d.ts.map +1 -0
- package/dist/workload.js +23 -0
- package/package.json +17 -5
package/dist/sharding.js
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sharding - Horizontal data partitioning for scaling
|
|
3
|
+
*
|
|
4
|
+
* Distributes data across shards for linear scalability:
|
|
5
|
+
* - Consistent hashing for automatic shard assignment
|
|
6
|
+
* - Hotspot detection based on metrics
|
|
7
|
+
* - Automatic rebalancing recommendations
|
|
8
|
+
* - Multi-shard query support
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Shard Manager - TypeScript implementation
|
|
12
|
+
*/
|
|
13
|
+
export class ShardManager {
|
|
14
|
+
constructor(shardingKey, strategy = 'consistent-hash') {
|
|
15
|
+
this.shardRanges = [];
|
|
16
|
+
this.shardMetrics = new Map();
|
|
17
|
+
this.rebalanceHistory = [];
|
|
18
|
+
this.rebalanceThreshold = 0.5;
|
|
19
|
+
this.shardingKey = shardingKey;
|
|
20
|
+
this.strategy = strategy;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Hash a value using simple algorithm
|
|
24
|
+
*/
|
|
25
|
+
hashValue(value) {
|
|
26
|
+
let hash = 5381;
|
|
27
|
+
for (let i = 0; i < value.length; i++) {
|
|
28
|
+
hash = ((hash << 5) + hash) ^ value.charCodeAt(i);
|
|
29
|
+
}
|
|
30
|
+
// Convert to positive number in 0 to 2^53 range
|
|
31
|
+
return Math.abs(hash) % (2 ** 53 - 1);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Initialize shards
|
|
35
|
+
*/
|
|
36
|
+
initializeShards(shardIds, replicaNodes) {
|
|
37
|
+
if (shardIds.length === 0)
|
|
38
|
+
return;
|
|
39
|
+
const hashSpace = 2 ** 53 - 1;
|
|
40
|
+
const shardSize = Math.floor(hashSpace / shardIds.length);
|
|
41
|
+
for (let i = 0; i < shardIds.length; i++) {
|
|
42
|
+
const shardId = shardIds[i];
|
|
43
|
+
const startHash = i * shardSize;
|
|
44
|
+
const endHash = i === shardIds.length - 1 ? hashSpace : (i + 1) * shardSize - 1;
|
|
45
|
+
this.shardRanges.push({
|
|
46
|
+
shardId,
|
|
47
|
+
startHash,
|
|
48
|
+
endHash,
|
|
49
|
+
replicaNodes: replicaNodes?.[shardId] ?? [],
|
|
50
|
+
});
|
|
51
|
+
this.shardMetrics.set(shardId, {
|
|
52
|
+
shardId,
|
|
53
|
+
entryCount: 0,
|
|
54
|
+
sizeBytes: 0,
|
|
55
|
+
reads: 0,
|
|
56
|
+
writes: 0,
|
|
57
|
+
avgQueryTimeMs: 0,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// Sort ranges for binary search
|
|
61
|
+
this.shardRanges.sort((a, b) => a.startHash - b.startHash);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get shard for a key
|
|
65
|
+
*/
|
|
66
|
+
getShardForKey(key) {
|
|
67
|
+
const hash = this.hashValue(key);
|
|
68
|
+
return this.shardRanges.find((r) => hash >= r.startHash && hash <= r.endHash) ?? null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get replica nodes for a key
|
|
72
|
+
*/
|
|
73
|
+
getReplicasForKey(key) {
|
|
74
|
+
const shard = this.getShardForKey(key);
|
|
75
|
+
return shard?.replicaNodes ?? [];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Record a shard operation
|
|
79
|
+
*/
|
|
80
|
+
recordShardOperation(shardId, operation, timeMs, recordsAffected, sizeBytes) {
|
|
81
|
+
const metrics = this.shardMetrics.get(shardId);
|
|
82
|
+
if (!metrics)
|
|
83
|
+
return;
|
|
84
|
+
switch (operation) {
|
|
85
|
+
case 'read':
|
|
86
|
+
metrics.reads++;
|
|
87
|
+
const oldAvg = metrics.avgQueryTimeMs;
|
|
88
|
+
metrics.avgQueryTimeMs = (oldAvg * (metrics.reads - 1) + timeMs) / metrics.reads;
|
|
89
|
+
break;
|
|
90
|
+
case 'write':
|
|
91
|
+
metrics.writes++;
|
|
92
|
+
metrics.entryCount += recordsAffected;
|
|
93
|
+
metrics.sizeBytes += sizeBytes;
|
|
94
|
+
break;
|
|
95
|
+
case 'delete':
|
|
96
|
+
metrics.entryCount = Math.max(0, metrics.entryCount - recordsAffected);
|
|
97
|
+
metrics.sizeBytes = Math.max(0, metrics.sizeBytes - sizeBytes);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Get all shard metrics
|
|
103
|
+
*/
|
|
104
|
+
getAllMetrics() {
|
|
105
|
+
return Array.from(this.shardMetrics.values());
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Detect hotspots
|
|
109
|
+
*/
|
|
110
|
+
detectHotspots() {
|
|
111
|
+
const alerts = [];
|
|
112
|
+
const allMetrics = this.getAllMetrics();
|
|
113
|
+
if (allMetrics.length === 0)
|
|
114
|
+
return alerts;
|
|
115
|
+
const totalReads = allMetrics.reduce((sum, m) => sum + m.reads, 0);
|
|
116
|
+
const totalWrites = allMetrics.reduce((sum, m) => sum + m.writes, 0);
|
|
117
|
+
const totalEntries = allMetrics.reduce((sum, m) => sum + m.entryCount, 0);
|
|
118
|
+
const numShards = allMetrics.length;
|
|
119
|
+
const avgReads = totalReads / numShards;
|
|
120
|
+
const avgWrites = totalWrites / numShards;
|
|
121
|
+
const avgEntries = totalEntries / numShards;
|
|
122
|
+
// Calculate standard deviations
|
|
123
|
+
const readStdDev = this.calculateStdDev(allMetrics.map((m) => m.reads), avgReads);
|
|
124
|
+
const writeStdDev = this.calculateStdDev(allMetrics.map((m) => m.writes), avgWrites);
|
|
125
|
+
const entryStdDev = this.calculateStdDev(allMetrics.map((m) => m.entryCount), avgEntries);
|
|
126
|
+
// Detect hotspots
|
|
127
|
+
for (const metrics of allMetrics) {
|
|
128
|
+
// High read load
|
|
129
|
+
if (readStdDev > 0 && Math.abs(metrics.reads - avgReads) > readStdDev * 2) {
|
|
130
|
+
alerts.push({
|
|
131
|
+
shardId: metrics.shardId,
|
|
132
|
+
reason: 'High read load',
|
|
133
|
+
metricValue: metrics.reads,
|
|
134
|
+
threshold: avgReads + readStdDev * 2,
|
|
135
|
+
severity: metrics.reads > avgReads * 2 ? 'high' : 'medium',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// High write load
|
|
139
|
+
if (writeStdDev > 0 && Math.abs(metrics.writes - avgWrites) > writeStdDev * 2) {
|
|
140
|
+
alerts.push({
|
|
141
|
+
shardId: metrics.shardId,
|
|
142
|
+
reason: 'High write load',
|
|
143
|
+
metricValue: metrics.writes,
|
|
144
|
+
threshold: avgWrites + writeStdDev * 2,
|
|
145
|
+
severity: metrics.writes > avgWrites * 2 ? 'high' : 'medium',
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
// Data imbalance
|
|
149
|
+
if (entryStdDev > 0 && Math.abs(metrics.entryCount - avgEntries) > entryStdDev * 3) {
|
|
150
|
+
alerts.push({
|
|
151
|
+
shardId: metrics.shardId,
|
|
152
|
+
reason: 'Data imbalance',
|
|
153
|
+
metricValue: metrics.entryCount,
|
|
154
|
+
threshold: avgEntries + entryStdDev * 3,
|
|
155
|
+
severity: metrics.entryCount > avgEntries * 3 ? 'high' : 'low',
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return alerts;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Generate rebalance operations
|
|
163
|
+
*/
|
|
164
|
+
generateRebalanceOps() {
|
|
165
|
+
const hotspots = this.detectHotspots();
|
|
166
|
+
const operations = [];
|
|
167
|
+
for (const hotspot of hotspots) {
|
|
168
|
+
if (hotspot.severity === 'high') {
|
|
169
|
+
const shardRange = this.shardRanges.find((r) => r.shardId === hotspot.shardId);
|
|
170
|
+
if (shardRange) {
|
|
171
|
+
const midHash = Math.floor((shardRange.startHash + shardRange.endHash) / 2);
|
|
172
|
+
const newShardId = `${hotspot.shardId}-split-${Date.now()}`;
|
|
173
|
+
operations.push({
|
|
174
|
+
sourceShard: hotspot.shardId,
|
|
175
|
+
targetShard: newShardId,
|
|
176
|
+
keyRangeStart: midHash,
|
|
177
|
+
keyRangeEnd: shardRange.endHash,
|
|
178
|
+
estimatedRecords: Math.floor(hotspot.metricValue / 2),
|
|
179
|
+
priority: 200,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return operations;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Get distribution summary
|
|
188
|
+
*/
|
|
189
|
+
getDistributionSummary() {
|
|
190
|
+
const allMetrics = this.getAllMetrics();
|
|
191
|
+
const totalEntries = allMetrics.reduce((sum, m) => sum + m.entryCount, 0);
|
|
192
|
+
const totalSize = allMetrics.reduce((sum, m) => sum + m.sizeBytes, 0);
|
|
193
|
+
const totalReads = allMetrics.reduce((sum, m) => sum + m.reads, 0);
|
|
194
|
+
const totalWrites = allMetrics.reduce((sum, m) => sum + m.writes, 0);
|
|
195
|
+
const maxEntries = Math.max(...allMetrics.map((m) => m.entryCount), 0);
|
|
196
|
+
const minEntries = Math.min(...allMetrics.map((m) => m.entryCount), 0);
|
|
197
|
+
return {
|
|
198
|
+
numShards: allMetrics.length,
|
|
199
|
+
totalEntries,
|
|
200
|
+
totalSizeBytes: totalSize,
|
|
201
|
+
totalReads,
|
|
202
|
+
totalWrites,
|
|
203
|
+
maxEntriesPerShard: maxEntries,
|
|
204
|
+
minEntriesPerShard: minEntries,
|
|
205
|
+
imbalanceRatio: minEntries > 0 ? maxEntries / minEntries : 1,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Record a rebalance operation
|
|
210
|
+
*/
|
|
211
|
+
recordRebalance(operation) {
|
|
212
|
+
this.rebalanceHistory.push(operation);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get rebalance history
|
|
216
|
+
*/
|
|
217
|
+
getRebalanceHistory() {
|
|
218
|
+
return this.rebalanceHistory;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Calculate standard deviation
|
|
222
|
+
*/
|
|
223
|
+
calculateStdDev(values, mean) {
|
|
224
|
+
if (values.length === 0)
|
|
225
|
+
return 0;
|
|
226
|
+
const variance = values.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / values.length;
|
|
227
|
+
return Math.sqrt(variance);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Shard Router - Routes queries to appropriate shards
|
|
232
|
+
*/
|
|
233
|
+
export class ShardRouter {
|
|
234
|
+
constructor(shardManager) {
|
|
235
|
+
this.shardConnections = new Map(); // Map of shardId to connection
|
|
236
|
+
this.shardManager = shardManager;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Route a query to appropriate shards
|
|
240
|
+
*/
|
|
241
|
+
routeQuery(key) {
|
|
242
|
+
const shard = this.shardManager.getShardForKey(key);
|
|
243
|
+
if (!shard)
|
|
244
|
+
return [];
|
|
245
|
+
// Return primary shard + replicas
|
|
246
|
+
return [shard.shardId, ...shard.replicaNodes];
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Route a range query to multiple shards
|
|
250
|
+
*/
|
|
251
|
+
routeRangeQuery(keyPattern) {
|
|
252
|
+
// For range queries, we might need multiple shards
|
|
253
|
+
// This is a simplified implementation
|
|
254
|
+
const shards = new Set();
|
|
255
|
+
const allMetrics = this.shardManager.getAllMetrics();
|
|
256
|
+
for (const metrics of allMetrics) {
|
|
257
|
+
shards.add(metrics.shardId);
|
|
258
|
+
}
|
|
259
|
+
return Array.from(shards);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Register shard connection
|
|
263
|
+
*/
|
|
264
|
+
registerShardConnection(shardId, connection) {
|
|
265
|
+
this.shardConnections.set(shardId, connection);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Get shard connection
|
|
269
|
+
*/
|
|
270
|
+
getShardConnection(shardId) {
|
|
271
|
+
return this.shardConnections.get(shardId);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Rebalancer - Coordinates data migration between shards
|
|
276
|
+
*/
|
|
277
|
+
export class Rebalancer {
|
|
278
|
+
constructor(shardManager, shardRouter) {
|
|
279
|
+
this.rebalanceInProgress = false;
|
|
280
|
+
this.shardManager = shardManager;
|
|
281
|
+
this.shardRouter = shardRouter;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Start automatic rebalancing
|
|
285
|
+
*/
|
|
286
|
+
async startAutoRebalance(intervalMs = 60000) {
|
|
287
|
+
setInterval(async () => {
|
|
288
|
+
if (!this.rebalanceInProgress) {
|
|
289
|
+
await this.performRebalance();
|
|
290
|
+
}
|
|
291
|
+
}, intervalMs);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Perform rebalancing
|
|
295
|
+
*/
|
|
296
|
+
async performRebalance() {
|
|
297
|
+
this.rebalanceInProgress = true;
|
|
298
|
+
try {
|
|
299
|
+
const operations = this.shardManager.generateRebalanceOps();
|
|
300
|
+
for (const operation of operations) {
|
|
301
|
+
console.debug(`Rebalancing: ${operation.sourceShard} → ${operation.targetShard}`);
|
|
302
|
+
// In real implementation, this would coordinate data migration
|
|
303
|
+
this.shardManager.recordRebalance(operation);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
finally {
|
|
307
|
+
this.rebalanceInProgress = false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Check if rebalancing is needed
|
|
312
|
+
*/
|
|
313
|
+
isRebalanceNeeded() {
|
|
314
|
+
const hotspots = this.shardManager.detectHotspots();
|
|
315
|
+
return hotspots.some((h) => h.severity === 'high');
|
|
316
|
+
}
|
|
317
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/** Transport types for the Rust-owned FeltDB state contract. */
|
|
2
|
+
export type PrimitiveType = 'string' | 'integer' | 'number' | 'boolean' | 'timestamp' | 'json';
|
|
3
|
+
export type FieldType = {
|
|
4
|
+
kind: 'primitive';
|
|
5
|
+
primitive: PrimitiveType;
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'enum';
|
|
8
|
+
values: string[];
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'reference';
|
|
11
|
+
collection: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'array';
|
|
14
|
+
items: FieldType;
|
|
15
|
+
};
|
|
16
|
+
export interface StateField {
|
|
17
|
+
name: string;
|
|
18
|
+
field_type: FieldType;
|
|
19
|
+
nullable?: boolean;
|
|
20
|
+
required?: boolean;
|
|
21
|
+
default?: unknown;
|
|
22
|
+
constraints?: {
|
|
23
|
+
minimum?: number;
|
|
24
|
+
maximum?: number;
|
|
25
|
+
min_length?: number;
|
|
26
|
+
max_length?: number;
|
|
27
|
+
pattern?: string;
|
|
28
|
+
};
|
|
29
|
+
computed?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface StateSchema {
|
|
32
|
+
contract_version: number;
|
|
33
|
+
schema_version: number;
|
|
34
|
+
application_id: string;
|
|
35
|
+
revision_id: string;
|
|
36
|
+
collections: {
|
|
37
|
+
name: string;
|
|
38
|
+
version: number;
|
|
39
|
+
fields: StateField[];
|
|
40
|
+
indexes: {
|
|
41
|
+
name: string;
|
|
42
|
+
fields: string[];
|
|
43
|
+
unique?: boolean;
|
|
44
|
+
sparse?: boolean;
|
|
45
|
+
version: number;
|
|
46
|
+
}[];
|
|
47
|
+
}[];
|
|
48
|
+
}
|
|
49
|
+
export type QueryFilter = {
|
|
50
|
+
operator: 'eq' | 'neq' | 'lt' | 'lte' | 'gt' | 'gte';
|
|
51
|
+
field: string;
|
|
52
|
+
value: unknown;
|
|
53
|
+
} | {
|
|
54
|
+
operator: 'in';
|
|
55
|
+
field: string;
|
|
56
|
+
values: unknown[];
|
|
57
|
+
} | {
|
|
58
|
+
operator: 'contains';
|
|
59
|
+
field: string;
|
|
60
|
+
value: unknown;
|
|
61
|
+
} | {
|
|
62
|
+
operator: 'startsWith' | 'endsWith';
|
|
63
|
+
field: string;
|
|
64
|
+
value: string;
|
|
65
|
+
} | {
|
|
66
|
+
operator: 'exists';
|
|
67
|
+
field: string;
|
|
68
|
+
exists: boolean;
|
|
69
|
+
} | {
|
|
70
|
+
operator: 'and' | 'or';
|
|
71
|
+
filters: QueryFilter[];
|
|
72
|
+
} | {
|
|
73
|
+
operator: 'not';
|
|
74
|
+
filter: QueryFilter;
|
|
75
|
+
};
|
|
76
|
+
export interface CanonicalQuery {
|
|
77
|
+
collection: string;
|
|
78
|
+
filter?: QueryFilter;
|
|
79
|
+
order_by?: {
|
|
80
|
+
field: string;
|
|
81
|
+
direction: 'asc' | 'desc';
|
|
82
|
+
}[];
|
|
83
|
+
limit?: number;
|
|
84
|
+
offset?: number;
|
|
85
|
+
cursor?: string;
|
|
86
|
+
projection?: string[];
|
|
87
|
+
group_by?: string[];
|
|
88
|
+
aggregates?: {
|
|
89
|
+
name: string;
|
|
90
|
+
function: 'count' | 'sum' | 'avg' | 'min' | 'max';
|
|
91
|
+
field?: string;
|
|
92
|
+
}[];
|
|
93
|
+
references?: {
|
|
94
|
+
field: string;
|
|
95
|
+
collection: string;
|
|
96
|
+
projection?: string[];
|
|
97
|
+
as_name: string;
|
|
98
|
+
}[];
|
|
99
|
+
}
|
|
100
|
+
export interface QueryResult<T = Record<string, unknown>> {
|
|
101
|
+
query_id: string;
|
|
102
|
+
query_hash: string;
|
|
103
|
+
application_revision: string;
|
|
104
|
+
schema_version: number;
|
|
105
|
+
state_version: number;
|
|
106
|
+
causal_cursor: Record<string, number>;
|
|
107
|
+
records: T[];
|
|
108
|
+
aggregates: Record<string, unknown>[];
|
|
109
|
+
next_cursor?: string;
|
|
110
|
+
plan: {
|
|
111
|
+
implementation: string;
|
|
112
|
+
collection: string;
|
|
113
|
+
index?: string;
|
|
114
|
+
scan_fallback: boolean;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export interface TransactionOperation {
|
|
118
|
+
kind: 'insert' | 'update' | 'delete';
|
|
119
|
+
collection: string;
|
|
120
|
+
id: string;
|
|
121
|
+
value?: unknown;
|
|
122
|
+
if_version?: number;
|
|
123
|
+
}
|
|
124
|
+
export interface TransactionResult {
|
|
125
|
+
transaction_id: string;
|
|
126
|
+
state_before: number;
|
|
127
|
+
state_after: number;
|
|
128
|
+
causal_cursor: Record<string, number>;
|
|
129
|
+
duplicate: boolean;
|
|
130
|
+
audit: unknown;
|
|
131
|
+
}
|
|
132
|
+
export interface StateFailure {
|
|
133
|
+
code: 'VALIDATION_FAILED' | 'PRECONDITION_FAILED' | 'AUTHORIZATION_DENIED' | 'SCHEMA_MISMATCH' | 'CONFLICT' | 'STORAGE_FAILURE' | 'INVALID_QUERY' | 'UNKNOWN_FIELD' | 'UNKNOWN_COLLECTION' | 'UNAUTHORIZED_PROJECTION' | 'INVALID_CURSOR' | 'STATE_UNAVAILABLE';
|
|
134
|
+
message: string;
|
|
135
|
+
expected?: unknown;
|
|
136
|
+
actual?: unknown;
|
|
137
|
+
resource?: string;
|
|
138
|
+
transaction?: string;
|
|
139
|
+
causal_position?: number;
|
|
140
|
+
}
|
|
141
|
+
declare class TransactionBuilder {
|
|
142
|
+
readonly operations: TransactionOperation[];
|
|
143
|
+
collection(name: string): {
|
|
144
|
+
insert: (id: string, value: unknown) => string;
|
|
145
|
+
update: (id: string, value: unknown, options?: {
|
|
146
|
+
ifVersion?: number;
|
|
147
|
+
}) => void;
|
|
148
|
+
delete: (id: string, options?: {
|
|
149
|
+
ifVersion?: number;
|
|
150
|
+
}) => void;
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
export declare class StateContractClient {
|
|
154
|
+
private readonly target;
|
|
155
|
+
private readonly baseUrl;
|
|
156
|
+
constructor(target: {
|
|
157
|
+
applicationId: string;
|
|
158
|
+
revisionId: string;
|
|
159
|
+
environment?: string;
|
|
160
|
+
}, baseUrl?: string);
|
|
161
|
+
private request;
|
|
162
|
+
schema(): Promise<{
|
|
163
|
+
schema: StateSchema;
|
|
164
|
+
}>;
|
|
165
|
+
stateVersion(): Promise<unknown>;
|
|
166
|
+
query<T = Record<string, unknown>>(query: CanonicalQuery): Promise<QueryResult<T>>;
|
|
167
|
+
transaction(work: (tx: TransactionBuilder) => void | Promise<void>, options?: {
|
|
168
|
+
transactionId?: string;
|
|
169
|
+
causalParent?: number;
|
|
170
|
+
}): Promise<TransactionResult>;
|
|
171
|
+
}
|
|
172
|
+
export {};
|
|
173
|
+
//# sourceMappingURL=state-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-contract.d.ts","sourceRoot":"","sources":["../src/state-contract.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class TransactionBuilder {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.operations = [];
|
|
4
|
+
}
|
|
5
|
+
collection(name) { return { insert: (id, value) => { this.operations.push({ kind: 'insert', collection: name, id, value }); return id; }, update: (id, value, options) => { this.operations.push({ kind: 'update', collection: name, id, value, if_version: options?.ifVersion }); }, delete: (id, options) => { this.operations.push({ kind: 'delete', collection: name, id, if_version: options?.ifVersion }); } }; }
|
|
6
|
+
}
|
|
7
|
+
export class StateContractClient {
|
|
8
|
+
constructor(target, baseUrl = '') {
|
|
9
|
+
this.target = target;
|
|
10
|
+
this.baseUrl = baseUrl;
|
|
11
|
+
}
|
|
12
|
+
async request(path, options) { const response = await fetch(`${this.baseUrl}${path}`, { ...options, headers: { ...(options?.body ? { 'Content-Type': 'application/json' } : {}), ...options?.headers } }); const body = await response.json(); if (!response.ok) {
|
|
13
|
+
let failure = body;
|
|
14
|
+
try {
|
|
15
|
+
failure = JSON.parse(body.error);
|
|
16
|
+
}
|
|
17
|
+
catch { }
|
|
18
|
+
throw Object.assign(new Error(failure?.message || body.error || 'FeltDB state request failed'), { status: response.status, failure });
|
|
19
|
+
} return body; }
|
|
20
|
+
schema() { const q = new URLSearchParams({ application_id: this.target.applicationId, revision_id: this.target.revisionId, environment: this.target.environment || 'production' }); return this.request(`/v1/schema?${q}`); }
|
|
21
|
+
stateVersion() { const q = new URLSearchParams({ application_id: this.target.applicationId, revision_id: this.target.revisionId, environment: this.target.environment || 'production' }); return this.request(`/v1/state/version?${q}`); }
|
|
22
|
+
query(query) { return this.request('/v1/query', { method: 'POST', body: JSON.stringify({ application_id: this.target.applicationId, revision_id: this.target.revisionId, environment: this.target.environment || 'production', query }) }); }
|
|
23
|
+
async transaction(work, options) { const tx = new TransactionBuilder(); await work(tx); return this.request('/v1/transactions', { method: 'POST', body: JSON.stringify({ application_id: this.target.applicationId, revision_id: this.target.revisionId, environment: this.target.environment || 'production', transaction: { transaction_id: options?.transactionId, tenant_id: 'transport', application_id: this.target.applicationId, revision_id: this.target.revisionId, schema_version: 0, causal_parent: options?.causalParent, authorization: { subject: 'transport', tenant_id: 'transport', application_id: this.target.applicationId, revision_id: this.target.revisionId, capabilities: [] }, operations: tx.operations } }) }); }
|
|
24
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { SignedGrant, AuthorizationContext, AuthorizationResource, AuthorizationSubject } from './authorization.js';
|
|
2
|
+
export type SyncStatus = 'offline' | 'connecting' | 'syncing' | 'synced' | 'degraded';
|
|
3
|
+
export interface SyncScope {
|
|
4
|
+
resources?: string[];
|
|
5
|
+
collections?: string[];
|
|
6
|
+
fields?: Record<string, string[]>;
|
|
7
|
+
}
|
|
8
|
+
export interface SyncCursor {
|
|
9
|
+
contract_version: 1;
|
|
10
|
+
tenant_id: string;
|
|
11
|
+
application_id: string;
|
|
12
|
+
subject: AuthorizationSubject;
|
|
13
|
+
scope_hash: string;
|
|
14
|
+
position: number;
|
|
15
|
+
}
|
|
16
|
+
export interface SyncOperation {
|
|
17
|
+
contract_version: 1;
|
|
18
|
+
operation_id: string;
|
|
19
|
+
tenant_id: string;
|
|
20
|
+
application_id: string;
|
|
21
|
+
subject: AuthorizationSubject;
|
|
22
|
+
device_id: string;
|
|
23
|
+
resource: AuthorizationResource;
|
|
24
|
+
operation: {
|
|
25
|
+
kind: 'insert' | 'update' | 'delete';
|
|
26
|
+
collection: string;
|
|
27
|
+
id: string;
|
|
28
|
+
value?: unknown;
|
|
29
|
+
if_version?: number;
|
|
30
|
+
};
|
|
31
|
+
causal_context: Record<string, number>;
|
|
32
|
+
schema_version: number;
|
|
33
|
+
authorization_context: AuthorizationContext;
|
|
34
|
+
offline_grant: SignedGrant;
|
|
35
|
+
application_revision: string;
|
|
36
|
+
created_at: number;
|
|
37
|
+
}
|
|
38
|
+
export interface SyncResult {
|
|
39
|
+
acknowledgements: Array<{
|
|
40
|
+
operation_id: string;
|
|
41
|
+
outcome: string;
|
|
42
|
+
server_position?: number;
|
|
43
|
+
}>;
|
|
44
|
+
rejections: Array<{
|
|
45
|
+
operation_id: string;
|
|
46
|
+
code: string;
|
|
47
|
+
message: string;
|
|
48
|
+
retryable: boolean;
|
|
49
|
+
}>;
|
|
50
|
+
conflicts: Array<{
|
|
51
|
+
operation_id: string;
|
|
52
|
+
reason: string;
|
|
53
|
+
}>;
|
|
54
|
+
cursor: SyncCursor;
|
|
55
|
+
}
|
|
56
|
+
export interface SyncConfig {
|
|
57
|
+
endpoint?: string;
|
|
58
|
+
scope: SyncScope;
|
|
59
|
+
device: {
|
|
60
|
+
device_id: string;
|
|
61
|
+
tenant_id: string;
|
|
62
|
+
application_id: string;
|
|
63
|
+
subject: AuthorizationSubject;
|
|
64
|
+
};
|
|
65
|
+
offlineGrant: SignedGrant;
|
|
66
|
+
fetcher?: typeof fetch;
|
|
67
|
+
}
|
|
68
|
+
declare class StatusSignal {
|
|
69
|
+
private value;
|
|
70
|
+
private listeners;
|
|
71
|
+
constructor(value?: SyncStatus);
|
|
72
|
+
subscribe(listener: (status: SyncStatus) => void): () => boolean;
|
|
73
|
+
set(value: SyncStatus): void;
|
|
74
|
+
get(): SyncStatus;
|
|
75
|
+
}
|
|
76
|
+
export declare class SyncController {
|
|
77
|
+
private config;
|
|
78
|
+
readonly status: StatusSignal;
|
|
79
|
+
private readonly fetcher;
|
|
80
|
+
private sessionId?;
|
|
81
|
+
private cursor?;
|
|
82
|
+
private error?;
|
|
83
|
+
private timer?;
|
|
84
|
+
constructor(config: SyncConfig);
|
|
85
|
+
private key;
|
|
86
|
+
private restore;
|
|
87
|
+
private persist;
|
|
88
|
+
private readOutbox;
|
|
89
|
+
private writeOutbox;
|
|
90
|
+
private request;
|
|
91
|
+
start(): Promise<this>;
|
|
92
|
+
stop(): Promise<void>;
|
|
93
|
+
enqueue(operation: SyncOperation): string;
|
|
94
|
+
flush(): Promise<void>;
|
|
95
|
+
pending(): SyncOperation[];
|
|
96
|
+
conflicts(): SyncOperation[];
|
|
97
|
+
lastCursor(): SyncCursor | undefined;
|
|
98
|
+
lastError(): string | undefined;
|
|
99
|
+
}
|
|
100
|
+
export {};
|
|
101
|
+
//# sourceMappingURL=sync-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-contract.d.ts","sourceRoot":"","sources":["../src/sync-contract.ts"],"names":[],"mappings":"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"}
|