@durable-streams/server 0.2.2 → 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 +23 -57
- package/dist/index.cjs +541 -89
- package/dist/index.d.cts +97 -6
- package/dist/index.d.ts +97 -6
- package/dist/index.js +541 -89
- package/package.json +4 -4
- package/src/file-store.ts +491 -90
- package/src/server.ts +108 -16
- package/src/store.ts +363 -36
- package/src/types.ts +29 -0
package/dist/index.d.cts
CHANGED
|
@@ -57,6 +57,12 @@ interface Stream {
|
|
|
57
57
|
*/
|
|
58
58
|
createdAt: number;
|
|
59
59
|
/**
|
|
60
|
+
* Timestamp of the last read or write (for TTL renewal).
|
|
61
|
+
* Initialized to createdAt. Updated on GET reads and POST appends.
|
|
62
|
+
* HEAD requests do NOT update this field.
|
|
63
|
+
*/
|
|
64
|
+
lastAccessedAt: number;
|
|
65
|
+
/**
|
|
60
66
|
* Producer states for idempotent writes.
|
|
61
67
|
* Maps producer ID to their epoch and sequence state.
|
|
62
68
|
*/
|
|
@@ -75,6 +81,24 @@ interface Stream {
|
|
|
75
81
|
epoch: number;
|
|
76
82
|
seq: number;
|
|
77
83
|
};
|
|
84
|
+
/**
|
|
85
|
+
* Source stream path (set when this stream is a fork).
|
|
86
|
+
*/
|
|
87
|
+
forkedFrom?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Divergence offset from the source stream.
|
|
90
|
+
* Format: "0000000000000000_0000000000000000"
|
|
91
|
+
*/
|
|
92
|
+
forkOffset?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Number of forks referencing this stream.
|
|
95
|
+
* Defaults to 0.
|
|
96
|
+
*/
|
|
97
|
+
refCount: number;
|
|
98
|
+
/**
|
|
99
|
+
* Whether this stream is logically deleted but retained for fork readers.
|
|
100
|
+
*/
|
|
101
|
+
softDeleted?: boolean;
|
|
78
102
|
}
|
|
79
103
|
/**
|
|
80
104
|
* Event data for stream lifecycle hooks.
|
|
@@ -251,13 +275,19 @@ declare class StreamStore {
|
|
|
251
275
|
*/
|
|
252
276
|
private isExpired;
|
|
253
277
|
/**
|
|
254
|
-
* Get a stream,
|
|
255
|
-
* Returns undefined if stream doesn't exist or is expired.
|
|
278
|
+
* Get a stream, handling expiry.
|
|
279
|
+
* Returns undefined if stream doesn't exist or is expired (and has no refs).
|
|
280
|
+
* Expired streams with refCount > 0 are soft-deleted instead of fully deleted.
|
|
256
281
|
*/
|
|
257
282
|
private getIfNotExpired;
|
|
258
283
|
/**
|
|
284
|
+
* Update lastAccessedAt to now. Called on reads and appends (not HEAD).
|
|
285
|
+
*/
|
|
286
|
+
touchAccess(path: string): void;
|
|
287
|
+
/**
|
|
259
288
|
* Create a new stream.
|
|
260
289
|
* @throws Error if stream already exists with different config
|
|
290
|
+
* @throws Error if fork source not found, soft-deleted, or offset invalid
|
|
261
291
|
* @returns existing stream if config matches (idempotent)
|
|
262
292
|
*/
|
|
263
293
|
create(path: string, options?: {
|
|
@@ -266,21 +296,36 @@ declare class StreamStore {
|
|
|
266
296
|
expiresAt?: string;
|
|
267
297
|
initialData?: Uint8Array;
|
|
268
298
|
closed?: boolean;
|
|
299
|
+
forkedFrom?: string;
|
|
300
|
+
forkOffset?: string;
|
|
269
301
|
}): Stream;
|
|
270
302
|
/**
|
|
303
|
+
* Resolve fork expiry per the decision table.
|
|
304
|
+
* Forks have independent lifetimes — no capping at source expiry.
|
|
305
|
+
*/
|
|
306
|
+
private resolveForkExpiry;
|
|
307
|
+
/**
|
|
271
308
|
* Get a stream by path.
|
|
272
309
|
* Returns undefined if stream doesn't exist or is expired.
|
|
310
|
+
* Returns soft-deleted streams (caller should check stream.softDeleted).
|
|
273
311
|
*/
|
|
274
312
|
get(path: string): Stream | undefined;
|
|
275
313
|
/**
|
|
276
|
-
* Check if a stream exists
|
|
314
|
+
* Check if a stream exists, is not expired, and is not soft-deleted.
|
|
277
315
|
*/
|
|
278
316
|
has(path: string): boolean;
|
|
279
317
|
/**
|
|
280
318
|
* Delete a stream.
|
|
319
|
+
* If the stream has forks (refCount > 0), it is soft-deleted instead of fully removed.
|
|
320
|
+
* Returns true if the stream was found and deleted (or soft-deleted).
|
|
281
321
|
*/
|
|
282
322
|
delete(path: string): boolean;
|
|
283
323
|
/**
|
|
324
|
+
* Fully delete a stream and cascade to soft-deleted parents
|
|
325
|
+
* whose refcount drops to zero.
|
|
326
|
+
*/
|
|
327
|
+
private deleteWithCascade;
|
|
328
|
+
/**
|
|
284
329
|
* Validate producer state WITHOUT mutating.
|
|
285
330
|
* Returns proposed state to commit after successful append.
|
|
286
331
|
* Implements Kafka-style idempotent producer validation.
|
|
@@ -346,6 +391,7 @@ declare class StreamStore {
|
|
|
346
391
|
getProducerEpoch(path: string, producerId: string): number | undefined;
|
|
347
392
|
/**
|
|
348
393
|
* Read messages from a stream starting at the given offset.
|
|
394
|
+
* For forked streams, stitches messages from the source chain and the fork's own messages.
|
|
349
395
|
* @throws Error if stream doesn't exist or is expired
|
|
350
396
|
*/
|
|
351
397
|
read(path: string, offset?: string): {
|
|
@@ -353,6 +399,20 @@ declare class StreamStore {
|
|
|
353
399
|
upToDate: boolean;
|
|
354
400
|
};
|
|
355
401
|
/**
|
|
402
|
+
* Read from a forked stream, stitching inherited and own messages.
|
|
403
|
+
*/
|
|
404
|
+
private readFromFork;
|
|
405
|
+
/**
|
|
406
|
+
* Read a stream's own messages starting after the given offset.
|
|
407
|
+
*/
|
|
408
|
+
private readOwnMessages;
|
|
409
|
+
/**
|
|
410
|
+
* Recursively read messages from a fork's source chain.
|
|
411
|
+
* Reads from source (and its sources if also forked), capped at forkOffset.
|
|
412
|
+
* Does NOT check softDeleted — forks must read through soft-deleted sources.
|
|
413
|
+
*/
|
|
414
|
+
private readForkedMessages;
|
|
415
|
+
/**
|
|
356
416
|
* Format messages for response.
|
|
357
417
|
* For JSON mode, wraps concatenated data in array brackets.
|
|
358
418
|
* @throws Error if stream doesn't exist or is expired
|
|
@@ -450,15 +510,25 @@ declare class FileBackedStreamStore {
|
|
|
450
510
|
*/
|
|
451
511
|
getProducerEpoch(streamPath: string, producerId: string): number | undefined;
|
|
452
512
|
/**
|
|
513
|
+
* Update lastAccessedAt to now. Called on reads and appends (not HEAD).
|
|
514
|
+
*/
|
|
515
|
+
touchAccess(streamPath: string): void;
|
|
516
|
+
/**
|
|
453
517
|
* Check if a stream is expired based on TTL or Expires-At.
|
|
454
518
|
*/
|
|
455
519
|
private isExpired;
|
|
456
520
|
/**
|
|
457
521
|
* Get stream metadata, deleting it if expired.
|
|
458
|
-
* Returns undefined if stream doesn't exist or is expired.
|
|
522
|
+
* Returns undefined if stream doesn't exist or is expired (and has no refs).
|
|
523
|
+
* Expired streams with refCount > 0 are soft-deleted instead of fully deleted.
|
|
459
524
|
*/
|
|
460
525
|
private getMetaIfNotExpired;
|
|
461
526
|
/**
|
|
527
|
+
* Resolve fork expiry per the decision table.
|
|
528
|
+
* Forks have independent lifetimes — no capping at source expiry.
|
|
529
|
+
*/
|
|
530
|
+
private resolveForkExpiry;
|
|
531
|
+
/**
|
|
462
532
|
* Close the store, closing all file handles and database.
|
|
463
533
|
* All data is already fsynced on each append, so no final flush needed.
|
|
464
534
|
*/
|
|
@@ -469,10 +539,17 @@ declare class FileBackedStreamStore {
|
|
|
469
539
|
expiresAt?: string;
|
|
470
540
|
initialData?: Uint8Array;
|
|
471
541
|
closed?: boolean;
|
|
542
|
+
forkedFrom?: string;
|
|
543
|
+
forkOffset?: string;
|
|
472
544
|
}): Promise<Stream>;
|
|
473
545
|
get(streamPath: string): Stream | undefined;
|
|
474
546
|
has(streamPath: string): boolean;
|
|
475
547
|
delete(streamPath: string): boolean;
|
|
548
|
+
/**
|
|
549
|
+
* Fully delete a stream and cascade to soft-deleted parents
|
|
550
|
+
* whose refcount drops to zero.
|
|
551
|
+
*/
|
|
552
|
+
private deleteWithCascade;
|
|
476
553
|
append(streamPath: string, data: Uint8Array, options?: AppendOptions & {
|
|
477
554
|
isInitialCreate?: boolean;
|
|
478
555
|
}): Promise<StreamMessage | AppendResult | null>;
|
|
@@ -503,6 +580,21 @@ declare class FileBackedStreamStore {
|
|
|
503
580
|
alreadyClosed: boolean;
|
|
504
581
|
producerResult?: ProducerValidationResult;
|
|
505
582
|
} | null>;
|
|
583
|
+
/**
|
|
584
|
+
* Read messages from a specific segment file.
|
|
585
|
+
* @param segmentPath - Path to the segment file
|
|
586
|
+
* @param startByte - Start byte offset (skip messages at or before this offset)
|
|
587
|
+
* @param baseByteOffset - Base byte offset to add to physical offsets (for fork stitching)
|
|
588
|
+
* @param capByte - Optional cap: stop reading when logical offset exceeds this value
|
|
589
|
+
* @returns Array of messages with properly computed offsets
|
|
590
|
+
*/
|
|
591
|
+
private readMessagesFromSegmentFile;
|
|
592
|
+
/**
|
|
593
|
+
* Recursively read messages from a fork's source chain.
|
|
594
|
+
* Reads from source (and its sources if also forked), capped at capByte.
|
|
595
|
+
* Does NOT check softDeleted -- forks must read through soft-deleted sources.
|
|
596
|
+
*/
|
|
597
|
+
private readForkedMessages;
|
|
506
598
|
read(streamPath: string, offset?: string): {
|
|
507
599
|
messages: Array<StreamMessage>;
|
|
508
600
|
upToDate: boolean;
|
|
@@ -533,8 +625,7 @@ declare class FileBackedStreamStore {
|
|
|
533
625
|
private notifyLongPollsClosed;
|
|
534
626
|
private cancelLongPollsForStream;
|
|
535
627
|
private removePendingLongPoll;
|
|
536
|
-
}
|
|
537
|
-
//#endregion
|
|
628
|
+
} //#endregion
|
|
538
629
|
//#region src/server.d.ts
|
|
539
630
|
/**
|
|
540
631
|
* HTTP server for testing durable streams.
|
package/dist/index.d.ts
CHANGED
|
@@ -57,6 +57,12 @@ interface Stream {
|
|
|
57
57
|
*/
|
|
58
58
|
createdAt: number;
|
|
59
59
|
/**
|
|
60
|
+
* Timestamp of the last read or write (for TTL renewal).
|
|
61
|
+
* Initialized to createdAt. Updated on GET reads and POST appends.
|
|
62
|
+
* HEAD requests do NOT update this field.
|
|
63
|
+
*/
|
|
64
|
+
lastAccessedAt: number;
|
|
65
|
+
/**
|
|
60
66
|
* Producer states for idempotent writes.
|
|
61
67
|
* Maps producer ID to their epoch and sequence state.
|
|
62
68
|
*/
|
|
@@ -75,6 +81,24 @@ interface Stream {
|
|
|
75
81
|
epoch: number;
|
|
76
82
|
seq: number;
|
|
77
83
|
};
|
|
84
|
+
/**
|
|
85
|
+
* Source stream path (set when this stream is a fork).
|
|
86
|
+
*/
|
|
87
|
+
forkedFrom?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Divergence offset from the source stream.
|
|
90
|
+
* Format: "0000000000000000_0000000000000000"
|
|
91
|
+
*/
|
|
92
|
+
forkOffset?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Number of forks referencing this stream.
|
|
95
|
+
* Defaults to 0.
|
|
96
|
+
*/
|
|
97
|
+
refCount: number;
|
|
98
|
+
/**
|
|
99
|
+
* Whether this stream is logically deleted but retained for fork readers.
|
|
100
|
+
*/
|
|
101
|
+
softDeleted?: boolean;
|
|
78
102
|
}
|
|
79
103
|
/**
|
|
80
104
|
* Event data for stream lifecycle hooks.
|
|
@@ -251,13 +275,19 @@ declare class StreamStore {
|
|
|
251
275
|
*/
|
|
252
276
|
private isExpired;
|
|
253
277
|
/**
|
|
254
|
-
* Get a stream,
|
|
255
|
-
* Returns undefined if stream doesn't exist or is expired.
|
|
278
|
+
* Get a stream, handling expiry.
|
|
279
|
+
* Returns undefined if stream doesn't exist or is expired (and has no refs).
|
|
280
|
+
* Expired streams with refCount > 0 are soft-deleted instead of fully deleted.
|
|
256
281
|
*/
|
|
257
282
|
private getIfNotExpired;
|
|
258
283
|
/**
|
|
284
|
+
* Update lastAccessedAt to now. Called on reads and appends (not HEAD).
|
|
285
|
+
*/
|
|
286
|
+
touchAccess(path: string): void;
|
|
287
|
+
/**
|
|
259
288
|
* Create a new stream.
|
|
260
289
|
* @throws Error if stream already exists with different config
|
|
290
|
+
* @throws Error if fork source not found, soft-deleted, or offset invalid
|
|
261
291
|
* @returns existing stream if config matches (idempotent)
|
|
262
292
|
*/
|
|
263
293
|
create(path: string, options?: {
|
|
@@ -266,21 +296,36 @@ declare class StreamStore {
|
|
|
266
296
|
expiresAt?: string;
|
|
267
297
|
initialData?: Uint8Array;
|
|
268
298
|
closed?: boolean;
|
|
299
|
+
forkedFrom?: string;
|
|
300
|
+
forkOffset?: string;
|
|
269
301
|
}): Stream;
|
|
270
302
|
/**
|
|
303
|
+
* Resolve fork expiry per the decision table.
|
|
304
|
+
* Forks have independent lifetimes — no capping at source expiry.
|
|
305
|
+
*/
|
|
306
|
+
private resolveForkExpiry;
|
|
307
|
+
/**
|
|
271
308
|
* Get a stream by path.
|
|
272
309
|
* Returns undefined if stream doesn't exist or is expired.
|
|
310
|
+
* Returns soft-deleted streams (caller should check stream.softDeleted).
|
|
273
311
|
*/
|
|
274
312
|
get(path: string): Stream | undefined;
|
|
275
313
|
/**
|
|
276
|
-
* Check if a stream exists
|
|
314
|
+
* Check if a stream exists, is not expired, and is not soft-deleted.
|
|
277
315
|
*/
|
|
278
316
|
has(path: string): boolean;
|
|
279
317
|
/**
|
|
280
318
|
* Delete a stream.
|
|
319
|
+
* If the stream has forks (refCount > 0), it is soft-deleted instead of fully removed.
|
|
320
|
+
* Returns true if the stream was found and deleted (or soft-deleted).
|
|
281
321
|
*/
|
|
282
322
|
delete(path: string): boolean;
|
|
283
323
|
/**
|
|
324
|
+
* Fully delete a stream and cascade to soft-deleted parents
|
|
325
|
+
* whose refcount drops to zero.
|
|
326
|
+
*/
|
|
327
|
+
private deleteWithCascade;
|
|
328
|
+
/**
|
|
284
329
|
* Validate producer state WITHOUT mutating.
|
|
285
330
|
* Returns proposed state to commit after successful append.
|
|
286
331
|
* Implements Kafka-style idempotent producer validation.
|
|
@@ -346,6 +391,7 @@ declare class StreamStore {
|
|
|
346
391
|
getProducerEpoch(path: string, producerId: string): number | undefined;
|
|
347
392
|
/**
|
|
348
393
|
* Read messages from a stream starting at the given offset.
|
|
394
|
+
* For forked streams, stitches messages from the source chain and the fork's own messages.
|
|
349
395
|
* @throws Error if stream doesn't exist or is expired
|
|
350
396
|
*/
|
|
351
397
|
read(path: string, offset?: string): {
|
|
@@ -353,6 +399,20 @@ declare class StreamStore {
|
|
|
353
399
|
upToDate: boolean;
|
|
354
400
|
};
|
|
355
401
|
/**
|
|
402
|
+
* Read from a forked stream, stitching inherited and own messages.
|
|
403
|
+
*/
|
|
404
|
+
private readFromFork;
|
|
405
|
+
/**
|
|
406
|
+
* Read a stream's own messages starting after the given offset.
|
|
407
|
+
*/
|
|
408
|
+
private readOwnMessages;
|
|
409
|
+
/**
|
|
410
|
+
* Recursively read messages from a fork's source chain.
|
|
411
|
+
* Reads from source (and its sources if also forked), capped at forkOffset.
|
|
412
|
+
* Does NOT check softDeleted — forks must read through soft-deleted sources.
|
|
413
|
+
*/
|
|
414
|
+
private readForkedMessages;
|
|
415
|
+
/**
|
|
356
416
|
* Format messages for response.
|
|
357
417
|
* For JSON mode, wraps concatenated data in array brackets.
|
|
358
418
|
* @throws Error if stream doesn't exist or is expired
|
|
@@ -450,15 +510,25 @@ declare class FileBackedStreamStore {
|
|
|
450
510
|
*/
|
|
451
511
|
getProducerEpoch(streamPath: string, producerId: string): number | undefined;
|
|
452
512
|
/**
|
|
513
|
+
* Update lastAccessedAt to now. Called on reads and appends (not HEAD).
|
|
514
|
+
*/
|
|
515
|
+
touchAccess(streamPath: string): void;
|
|
516
|
+
/**
|
|
453
517
|
* Check if a stream is expired based on TTL or Expires-At.
|
|
454
518
|
*/
|
|
455
519
|
private isExpired;
|
|
456
520
|
/**
|
|
457
521
|
* Get stream metadata, deleting it if expired.
|
|
458
|
-
* Returns undefined if stream doesn't exist or is expired.
|
|
522
|
+
* Returns undefined if stream doesn't exist or is expired (and has no refs).
|
|
523
|
+
* Expired streams with refCount > 0 are soft-deleted instead of fully deleted.
|
|
459
524
|
*/
|
|
460
525
|
private getMetaIfNotExpired;
|
|
461
526
|
/**
|
|
527
|
+
* Resolve fork expiry per the decision table.
|
|
528
|
+
* Forks have independent lifetimes — no capping at source expiry.
|
|
529
|
+
*/
|
|
530
|
+
private resolveForkExpiry;
|
|
531
|
+
/**
|
|
462
532
|
* Close the store, closing all file handles and database.
|
|
463
533
|
* All data is already fsynced on each append, so no final flush needed.
|
|
464
534
|
*/
|
|
@@ -469,10 +539,17 @@ declare class FileBackedStreamStore {
|
|
|
469
539
|
expiresAt?: string;
|
|
470
540
|
initialData?: Uint8Array;
|
|
471
541
|
closed?: boolean;
|
|
542
|
+
forkedFrom?: string;
|
|
543
|
+
forkOffset?: string;
|
|
472
544
|
}): Promise<Stream>;
|
|
473
545
|
get(streamPath: string): Stream | undefined;
|
|
474
546
|
has(streamPath: string): boolean;
|
|
475
547
|
delete(streamPath: string): boolean;
|
|
548
|
+
/**
|
|
549
|
+
* Fully delete a stream and cascade to soft-deleted parents
|
|
550
|
+
* whose refcount drops to zero.
|
|
551
|
+
*/
|
|
552
|
+
private deleteWithCascade;
|
|
476
553
|
append(streamPath: string, data: Uint8Array, options?: AppendOptions & {
|
|
477
554
|
isInitialCreate?: boolean;
|
|
478
555
|
}): Promise<StreamMessage | AppendResult | null>;
|
|
@@ -503,6 +580,21 @@ declare class FileBackedStreamStore {
|
|
|
503
580
|
alreadyClosed: boolean;
|
|
504
581
|
producerResult?: ProducerValidationResult;
|
|
505
582
|
} | null>;
|
|
583
|
+
/**
|
|
584
|
+
* Read messages from a specific segment file.
|
|
585
|
+
* @param segmentPath - Path to the segment file
|
|
586
|
+
* @param startByte - Start byte offset (skip messages at or before this offset)
|
|
587
|
+
* @param baseByteOffset - Base byte offset to add to physical offsets (for fork stitching)
|
|
588
|
+
* @param capByte - Optional cap: stop reading when logical offset exceeds this value
|
|
589
|
+
* @returns Array of messages with properly computed offsets
|
|
590
|
+
*/
|
|
591
|
+
private readMessagesFromSegmentFile;
|
|
592
|
+
/**
|
|
593
|
+
* Recursively read messages from a fork's source chain.
|
|
594
|
+
* Reads from source (and its sources if also forked), capped at capByte.
|
|
595
|
+
* Does NOT check softDeleted -- forks must read through soft-deleted sources.
|
|
596
|
+
*/
|
|
597
|
+
private readForkedMessages;
|
|
506
598
|
read(streamPath: string, offset?: string): {
|
|
507
599
|
messages: Array<StreamMessage>;
|
|
508
600
|
upToDate: boolean;
|
|
@@ -533,8 +625,7 @@ declare class FileBackedStreamStore {
|
|
|
533
625
|
private notifyLongPollsClosed;
|
|
534
626
|
private cancelLongPollsForStream;
|
|
535
627
|
private removePendingLongPoll;
|
|
536
|
-
}
|
|
537
|
-
//#endregion
|
|
628
|
+
} //#endregion
|
|
538
629
|
//#region src/server.d.ts
|
|
539
630
|
/**
|
|
540
631
|
* HTTP server for testing durable streams.
|