@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.
Files changed (95) hide show
  1. package/LICENSE +19 -0
  2. package/dist/analytics-backend.d.ts +39 -0
  3. package/dist/analytics-backend.d.ts.map +1 -0
  4. package/dist/analytics-backend.js +152 -0
  5. package/dist/application-contract.d.ts +49 -0
  6. package/dist/application-contract.d.ts.map +1 -0
  7. package/dist/application-contract.js +13 -0
  8. package/dist/application-manifest.d.ts +305 -0
  9. package/dist/application-manifest.d.ts.map +1 -0
  10. package/dist/application-manifest.js +26 -0
  11. package/dist/artifact.d.ts +73 -0
  12. package/dist/artifact.d.ts.map +1 -0
  13. package/dist/artifact.js +21 -0
  14. package/dist/authorization.d.ts +99 -0
  15. package/dist/authorization.d.ts.map +1 -0
  16. package/dist/authorization.js +15 -0
  17. package/dist/bundle.d.ts +55 -0
  18. package/dist/bundle.d.ts.map +1 -0
  19. package/dist/bundle.js +13 -0
  20. package/dist/collection.d.ts +28 -0
  21. package/dist/collection.d.ts.map +1 -1
  22. package/dist/collection.js +83 -0
  23. package/dist/db.d.ts +18 -0
  24. package/dist/db.d.ts.map +1 -1
  25. package/dist/db.js +20 -8
  26. package/dist/distributed-indexing.d.ts +170 -0
  27. package/dist/distributed-indexing.d.ts.map +1 -0
  28. package/dist/distributed-indexing.js +260 -0
  29. package/dist/flowspec.d.ts +4 -0
  30. package/dist/flowspec.d.ts.map +1 -1
  31. package/dist/flowspec.js +8 -0
  32. package/dist/http-client.d.ts +35 -0
  33. package/dist/http-client.d.ts.map +1 -0
  34. package/dist/http-client.js +70 -0
  35. package/dist/identity.d.ts +40 -0
  36. package/dist/identity.d.ts.map +1 -0
  37. package/dist/identity.js +10 -0
  38. package/dist/index-analytics.d.ts +121 -0
  39. package/dist/index-analytics.d.ts.map +1 -0
  40. package/dist/index-analytics.js +279 -0
  41. package/dist/index-backend.d.ts +26 -0
  42. package/dist/index-backend.d.ts.map +1 -0
  43. package/dist/index-backend.js +115 -0
  44. package/dist/index-dashboard.d.ts +114 -0
  45. package/dist/index-dashboard.d.ts.map +1 -0
  46. package/dist/index-dashboard.js +256 -0
  47. package/dist/index-manager.d.ts +18 -0
  48. package/dist/index-manager.d.ts.map +1 -0
  49. package/dist/index-manager.js +333 -0
  50. package/dist/index-monitoring.d.ts +133 -0
  51. package/dist/index-monitoring.d.ts.map +1 -0
  52. package/dist/index-monitoring.js +218 -0
  53. package/dist/index-recovery.d.ts +57 -0
  54. package/dist/index-recovery.d.ts.map +1 -0
  55. package/dist/index-recovery.js +117 -0
  56. package/dist/index-store.d.ts +62 -0
  57. package/dist/index-store.d.ts.map +1 -0
  58. package/dist/index-store.js +141 -0
  59. package/dist/index-types.d.ts +70 -0
  60. package/dist/index-types.d.ts.map +1 -0
  61. package/dist/index-types.js +6 -0
  62. package/dist/index.d.ts +26 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +26 -0
  65. package/dist/observe.d.ts +51 -0
  66. package/dist/observe.d.ts.map +1 -0
  67. package/dist/observe.js +14 -0
  68. package/dist/protocol.d.ts +25 -0
  69. package/dist/protocol.d.ts.map +1 -0
  70. package/dist/protocol.js +18 -0
  71. package/dist/provider.d.ts +61 -0
  72. package/dist/provider.d.ts.map +1 -0
  73. package/dist/provider.js +17 -0
  74. package/dist/query-planner.d.ts +69 -0
  75. package/dist/query-planner.d.ts.map +1 -0
  76. package/dist/query-planner.js +184 -0
  77. package/dist/release.d.ts +60 -0
  78. package/dist/release.d.ts.map +1 -0
  79. package/dist/release.js +18 -0
  80. package/dist/sharding.d.ts +159 -0
  81. package/dist/sharding.d.ts.map +1 -0
  82. package/dist/sharding.js +317 -0
  83. package/dist/state-contract.d.ts +173 -0
  84. package/dist/state-contract.d.ts.map +1 -0
  85. package/dist/state-contract.js +24 -0
  86. package/dist/sync-contract.d.ts +101 -0
  87. package/dist/sync-contract.d.ts.map +1 -0
  88. package/dist/sync-contract.js +90 -0
  89. package/dist/worker.d.ts +90 -0
  90. package/dist/worker.d.ts.map +1 -0
  91. package/dist/worker.js +26 -0
  92. package/dist/workload.d.ts +102 -0
  93. package/dist/workload.d.ts.map +1 -0
  94. package/dist/workload.js +23 -0
  95. package/package.json +17 -5
@@ -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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"}