@durable-streams/server 0.3.1 → 0.3.3
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/dist/index.cjs +1344 -266
- package/dist/index.d.cts +258 -2
- package/dist/index.d.ts +258 -2
- package/dist/index.js +1391 -318
- package/package.json +4 -4
- package/src/crypto.ts +217 -0
- package/src/file-store.ts +239 -144
- package/src/glob.ts +70 -0
- package/src/index.ts +14 -0
- package/src/log.ts +56 -0
- package/src/server.ts +96 -40
- package/src/store.ts +66 -10
- package/src/subscription-manager.ts +882 -0
- package/src/subscription-routes.ts +504 -0
- package/src/subscription-types.ts +80 -0
- package/src/types.ts +8 -0
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
|
|
1
3
|
//#region src/types.d.ts
|
|
2
4
|
/**
|
|
3
5
|
* Types for the in-memory durable streams test server.
|
|
@@ -5,6 +7,12 @@
|
|
|
5
7
|
/**
|
|
6
8
|
* A single message in a stream.
|
|
7
9
|
*/
|
|
10
|
+
/**
|
|
11
|
+
* Types for the in-memory durable streams test server.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* A single message in a stream.
|
|
15
|
+
*/
|
|
8
16
|
interface StreamMessage {
|
|
9
17
|
/**
|
|
10
18
|
* The raw bytes of the message.
|
|
@@ -172,6 +180,13 @@ interface TestServerOptions {
|
|
|
172
180
|
* Default: October 9, 2024 00:00:00 UTC.
|
|
173
181
|
*/
|
|
174
182
|
cursorEpoch?: Date;
|
|
183
|
+
/**
|
|
184
|
+
* Enable webhook subscriptions.
|
|
185
|
+
* Pull-wake subscription routes are always mounted, but type=webhook creates
|
|
186
|
+
* are rejected unless this is true.
|
|
187
|
+
* Default: false.
|
|
188
|
+
*/
|
|
189
|
+
webhooks?: boolean;
|
|
175
190
|
}
|
|
176
191
|
/**
|
|
177
192
|
* Producer state for idempotent writes.
|
|
@@ -466,7 +481,6 @@ interface FileBackedStreamStoreOptions {
|
|
|
466
481
|
*/
|
|
467
482
|
declare class FileBackedStreamStore {
|
|
468
483
|
private db;
|
|
469
|
-
private fileManager;
|
|
470
484
|
private fileHandlePool;
|
|
471
485
|
private pendingLongPolls;
|
|
472
486
|
private dataDir;
|
|
@@ -475,6 +489,13 @@ declare class FileBackedStreamStore {
|
|
|
475
489
|
* Key: "{streamPath}:{producerId}"
|
|
476
490
|
*/
|
|
477
491
|
private producerLocks;
|
|
492
|
+
/**
|
|
493
|
+
* Per-stream append locks. Serializes the read-modify-write of currentOffset
|
|
494
|
+
* across all concurrent appenders on the same stream so the LMDB-tracked
|
|
495
|
+
* offset cannot drift behind the file's actual byte position.
|
|
496
|
+
* Key: streamPath
|
|
497
|
+
*/
|
|
498
|
+
private streamAppendLocks;
|
|
478
499
|
constructor(options: FileBackedStreamStoreOptions);
|
|
479
500
|
/**
|
|
480
501
|
* Recover streams from disk on startup.
|
|
@@ -505,6 +526,15 @@ declare class FileBackedStreamStore {
|
|
|
505
526
|
*/
|
|
506
527
|
private acquireProducerLock;
|
|
507
528
|
/**
|
|
529
|
+
* Acquire a per-stream append lock that serializes the read-modify-write
|
|
530
|
+
* of currentOffset across all concurrent appenders on the same stream.
|
|
531
|
+
* Without this, two concurrent appends can read the same starting
|
|
532
|
+
* currentOffset, both compute their newOffset, both write a frame to the
|
|
533
|
+
* file, but only one of their LMDB updates wins — leaving currentOffset
|
|
534
|
+
* lagging the file's actual byte position. Returns a release function.
|
|
535
|
+
*/
|
|
536
|
+
private acquireStreamAppendLock;
|
|
537
|
+
/**
|
|
508
538
|
* Get the current epoch for a producer on a stream.
|
|
509
539
|
* Returns undefined if the producer doesn't exist or stream not found.
|
|
510
540
|
*/
|
|
@@ -550,9 +580,15 @@ declare class FileBackedStreamStore {
|
|
|
550
580
|
* whose refcount drops to zero.
|
|
551
581
|
*/
|
|
552
582
|
private deleteWithCascade;
|
|
583
|
+
/**
|
|
584
|
+
* Public append entry point. Serializes concurrent appends to the same
|
|
585
|
+
* stream so the read-modify-write of currentOffset cannot interleave —
|
|
586
|
+
* see acquireStreamAppendLock for the underlying race.
|
|
587
|
+
*/
|
|
553
588
|
append(streamPath: string, data: Uint8Array, options?: AppendOptions & {
|
|
554
589
|
isInitialCreate?: boolean;
|
|
555
590
|
}): Promise<StreamMessage | AppendResult | null>;
|
|
591
|
+
private appendInner;
|
|
556
592
|
/**
|
|
557
593
|
* Append with producer serialization for concurrent request handling.
|
|
558
594
|
* This ensures that validation+append is atomic per producer.
|
|
@@ -673,6 +709,8 @@ declare class DurableStreamTestServer {
|
|
|
673
709
|
private isShuttingDown;
|
|
674
710
|
/** Injected faults for testing retry/resilience */
|
|
675
711
|
private injectedFaults;
|
|
712
|
+
private subscriptionManager;
|
|
713
|
+
private subscriptionRoutes;
|
|
676
714
|
constructor(options?: TestServerOptions);
|
|
677
715
|
/**
|
|
678
716
|
* Start the server.
|
|
@@ -742,6 +780,7 @@ declare class DurableStreamTestServer {
|
|
|
742
780
|
* Handle POST - append data
|
|
743
781
|
*/
|
|
744
782
|
private handleAppend;
|
|
783
|
+
private notifyStreamAppend;
|
|
745
784
|
/**
|
|
746
785
|
* Handle DELETE - delete stream
|
|
747
786
|
*/
|
|
@@ -864,4 +903,221 @@ declare function generateResponseCursor(clientCursor: string | undefined, option
|
|
|
864
903
|
declare function handleCursorCollision(currentCursor: string, previousCursor: string | undefined, options?: CursorOptions): string;
|
|
865
904
|
|
|
866
905
|
//#endregion
|
|
867
|
-
|
|
906
|
+
//#region src/crypto.d.ts
|
|
907
|
+
interface WebhookPublicJwk {
|
|
908
|
+
kty: `OKP`;
|
|
909
|
+
crv: `Ed25519`;
|
|
910
|
+
x: string;
|
|
911
|
+
kid: string;
|
|
912
|
+
use: `sig`;
|
|
913
|
+
alg: `EdDSA`;
|
|
914
|
+
}
|
|
915
|
+
interface WebhookJwks {
|
|
916
|
+
keys: Array<WebhookPublicJwk>;
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Generate a unique wake ID.
|
|
920
|
+
*/
|
|
921
|
+
|
|
922
|
+
declare function getWebhookJwks(): WebhookJwks;
|
|
923
|
+
|
|
924
|
+
//#endregion
|
|
925
|
+
//#region src/subscription-types.d.ts
|
|
926
|
+
/**
|
|
927
|
+
* Sign a webhook payload for the Webhook-Signature header.
|
|
928
|
+
* Format: t=<timestamp>,kid=<key_id>,ed25519=<base64url_signature>
|
|
929
|
+
*/
|
|
930
|
+
type SubscriptionType = `webhook` | `pull-wake`;
|
|
931
|
+
type SubscriptionStatus = `active` | `failed`;
|
|
932
|
+
type SubscriptionLinkType = `glob` | `explicit`;
|
|
933
|
+
interface SubscriptionStreamLink {
|
|
934
|
+
path: string;
|
|
935
|
+
link_types: Set<SubscriptionLinkType>;
|
|
936
|
+
acked_offset: string;
|
|
937
|
+
}
|
|
938
|
+
interface SubscriptionWebhookConfig {
|
|
939
|
+
url: string;
|
|
940
|
+
}
|
|
941
|
+
interface SubscriptionRecord {
|
|
942
|
+
id: string;
|
|
943
|
+
type: SubscriptionType;
|
|
944
|
+
pattern?: string;
|
|
945
|
+
webhook?: SubscriptionWebhookConfig;
|
|
946
|
+
wake_stream?: string;
|
|
947
|
+
lease_ttl_ms: number;
|
|
948
|
+
description?: string;
|
|
949
|
+
created_at: string;
|
|
950
|
+
status: SubscriptionStatus;
|
|
951
|
+
config_hash: string;
|
|
952
|
+
streams: Map<string, SubscriptionStreamLink>;
|
|
953
|
+
generation: number;
|
|
954
|
+
wake_id: string | null;
|
|
955
|
+
wake_snapshot: Map<string, string>;
|
|
956
|
+
token: string | null;
|
|
957
|
+
holder: string | null;
|
|
958
|
+
lease_timer: ReturnType<typeof setTimeout> | null;
|
|
959
|
+
retry_count: number;
|
|
960
|
+
retry_timer: ReturnType<typeof setTimeout> | null;
|
|
961
|
+
next_attempt_at: number | null;
|
|
962
|
+
}
|
|
963
|
+
interface SubscriptionStreamInfo {
|
|
964
|
+
path: string;
|
|
965
|
+
link_type: SubscriptionLinkType;
|
|
966
|
+
acked_offset: string;
|
|
967
|
+
tail_offset: string;
|
|
968
|
+
has_pending: boolean;
|
|
969
|
+
}
|
|
970
|
+
interface SubscriptionCreateInput {
|
|
971
|
+
type: SubscriptionType;
|
|
972
|
+
pattern?: string;
|
|
973
|
+
streams: Array<string>;
|
|
974
|
+
webhook?: {
|
|
975
|
+
url: string;
|
|
976
|
+
};
|
|
977
|
+
wake_stream?: string;
|
|
978
|
+
lease_ttl_ms: number;
|
|
979
|
+
description?: string;
|
|
980
|
+
}
|
|
981
|
+
interface SubscriptionCallbackRequest {
|
|
982
|
+
wake_id?: string;
|
|
983
|
+
generation?: number;
|
|
984
|
+
acks?: Array<{
|
|
985
|
+
stream?: string;
|
|
986
|
+
path?: string;
|
|
987
|
+
offset: string;
|
|
988
|
+
}>;
|
|
989
|
+
done?: boolean;
|
|
990
|
+
}
|
|
991
|
+
type SubscriptionErrorCode = `INVALID_REQUEST` | `SUBSCRIPTION_NOT_FOUND` | `SUBSCRIPTION_ALREADY_EXISTS` | `WEBHOOK_URL_REJECTED` | `TOKEN_INVALID` | `TOKEN_EXPIRED` | `FENCED` | `ALREADY_CLAIMED` | `NO_PENDING_WORK` | `INVALID_OFFSET`;
|
|
992
|
+
interface SubscriptionError {
|
|
993
|
+
code: SubscriptionErrorCode;
|
|
994
|
+
message: string;
|
|
995
|
+
current_holder?: string;
|
|
996
|
+
generation?: number;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
//#endregion
|
|
1000
|
+
//#region src/subscription-manager.d.ts
|
|
1001
|
+
interface StreamLike {
|
|
1002
|
+
currentOffset: string;
|
|
1003
|
+
softDeleted?: boolean;
|
|
1004
|
+
}
|
|
1005
|
+
interface SubscriptionStreamStore {
|
|
1006
|
+
has: (path: string) => boolean;
|
|
1007
|
+
get: (path: string) => StreamLike | undefined;
|
|
1008
|
+
list: () => Array<string>;
|
|
1009
|
+
append: (path: string, data: Uint8Array) => unknown;
|
|
1010
|
+
}
|
|
1011
|
+
declare function validateWebhookUrl(rawUrl: string): {
|
|
1012
|
+
ok: true;
|
|
1013
|
+
} | {
|
|
1014
|
+
ok: false;
|
|
1015
|
+
message: string;
|
|
1016
|
+
};
|
|
1017
|
+
declare class SubscriptionManager {
|
|
1018
|
+
private readonly subscriptions;
|
|
1019
|
+
private readonly streamStore;
|
|
1020
|
+
private readonly callbackBaseUrl;
|
|
1021
|
+
private readonly webhooksEnabled;
|
|
1022
|
+
private isShuttingDown;
|
|
1023
|
+
constructor(opts: {
|
|
1024
|
+
callbackBaseUrl: string;
|
|
1025
|
+
streamStore: SubscriptionStreamStore;
|
|
1026
|
+
webhooksEnabled?: boolean;
|
|
1027
|
+
});
|
|
1028
|
+
createOrConfirm(id: string, input: SubscriptionCreateInput): {
|
|
1029
|
+
subscription: SubscriptionRecord;
|
|
1030
|
+
created: boolean;
|
|
1031
|
+
} | {
|
|
1032
|
+
error: SubscriptionError;
|
|
1033
|
+
};
|
|
1034
|
+
get(id: string): SubscriptionRecord | undefined;
|
|
1035
|
+
delete(id: string): boolean;
|
|
1036
|
+
addExplicitStreams(id: string, streams: Array<string>): boolean;
|
|
1037
|
+
removeExplicitStream(id: string, streamPath: string): boolean;
|
|
1038
|
+
onStreamAppend(absolutePath: string): Promise<void>;
|
|
1039
|
+
onStreamDeleted(absolutePath: string): void;
|
|
1040
|
+
handleWebhookCallback(id: string, token: string, request: SubscriptionCallbackRequest): Promise<{
|
|
1041
|
+
status: number;
|
|
1042
|
+
body: Record<string, unknown>;
|
|
1043
|
+
}>;
|
|
1044
|
+
claim(id: string, worker: string): Promise<{
|
|
1045
|
+
status: number;
|
|
1046
|
+
body: Record<string, unknown>;
|
|
1047
|
+
}>;
|
|
1048
|
+
ack(id: string, token: string, request: SubscriptionCallbackRequest): Promise<{
|
|
1049
|
+
status: number;
|
|
1050
|
+
body: Record<string, unknown>;
|
|
1051
|
+
}>;
|
|
1052
|
+
release(id: string, token: string, request: SubscriptionCallbackRequest): Promise<{
|
|
1053
|
+
status: number;
|
|
1054
|
+
body?: Record<string, unknown>;
|
|
1055
|
+
}>;
|
|
1056
|
+
serialize(subscription: SubscriptionRecord): Record<string, unknown>;
|
|
1057
|
+
getWebhookJwks(): ReturnType<typeof getWebhookJwks>;
|
|
1058
|
+
shutdown(): void;
|
|
1059
|
+
private maybeWake;
|
|
1060
|
+
private createWake;
|
|
1061
|
+
private deliverWebhook;
|
|
1062
|
+
private scheduleWebhookRetry;
|
|
1063
|
+
private writePullWakeEvent;
|
|
1064
|
+
private autoAckWakeSnapshot;
|
|
1065
|
+
private applyAcks;
|
|
1066
|
+
private validateWakeToken;
|
|
1067
|
+
private triggerNextWakeIfPending;
|
|
1068
|
+
private hasPendingWork;
|
|
1069
|
+
private firstPendingStream;
|
|
1070
|
+
private streamInfos;
|
|
1071
|
+
private linkStream;
|
|
1072
|
+
private listStreams;
|
|
1073
|
+
private getTailOffset;
|
|
1074
|
+
private subscriptionActionUrl;
|
|
1075
|
+
private webhookJwksUrl;
|
|
1076
|
+
private webhookSigningMetadata;
|
|
1077
|
+
private extendLease;
|
|
1078
|
+
private clearLease;
|
|
1079
|
+
private tokenSubject;
|
|
1080
|
+
private errorResponse;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
//#endregion
|
|
1084
|
+
//#region src/subscription-routes.d.ts
|
|
1085
|
+
declare class SubscriptionRoutes {
|
|
1086
|
+
private readonly manager;
|
|
1087
|
+
constructor(manager: SubscriptionManager);
|
|
1088
|
+
handleRequest(method: string, path: string, req: IncomingMessage, res: ServerResponse): Promise<boolean>;
|
|
1089
|
+
private handleBase;
|
|
1090
|
+
private handleJwks;
|
|
1091
|
+
private handleStreams;
|
|
1092
|
+
private handleStream;
|
|
1093
|
+
private handleCallback;
|
|
1094
|
+
private handleClaim;
|
|
1095
|
+
private handleAck;
|
|
1096
|
+
private handleRelease;
|
|
1097
|
+
private parseCreateInput;
|
|
1098
|
+
private parseRoute;
|
|
1099
|
+
private readBearerToken;
|
|
1100
|
+
private readJson;
|
|
1101
|
+
private writeManagerResult;
|
|
1102
|
+
private writeJson;
|
|
1103
|
+
private writeError;
|
|
1104
|
+
private methodNotAllowed;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
//#endregion
|
|
1108
|
+
//#region src/glob.d.ts
|
|
1109
|
+
/**
|
|
1110
|
+
* Glob pattern matching for webhook subscription patterns.
|
|
1111
|
+
*
|
|
1112
|
+
* Supports:
|
|
1113
|
+
* - `*` matches exactly one path segment
|
|
1114
|
+
* - `**` matches zero or more path segments (recursive)
|
|
1115
|
+
* - Literal segments match exactly
|
|
1116
|
+
*/
|
|
1117
|
+
/**
|
|
1118
|
+
* Match a stream path against a glob pattern.
|
|
1119
|
+
*/
|
|
1120
|
+
declare function globMatch(pattern: string, path: string): boolean;
|
|
1121
|
+
|
|
1122
|
+
//#endregion
|
|
1123
|
+
export { CursorOptions, DEFAULT_CURSOR_EPOCH, DEFAULT_CURSOR_INTERVAL_SECONDS, DurableStreamTestServer, FileBackedStreamStore, PendingLongPoll, Stream, StreamLifecycleEvent, StreamLifecycleHook, StreamMessage, StreamStore, SubscriptionCallbackRequest, SubscriptionCreateInput, SubscriptionError, SubscriptionErrorCode, SubscriptionManager, SubscriptionRecord, SubscriptionRoutes, SubscriptionStatus, SubscriptionStreamInfo, SubscriptionStreamLink, SubscriptionType, TestServerOptions, calculateCursor, createRegistryHooks, decodeStreamPath, encodeStreamPath, generateResponseCursor, globMatch, handleCursorCollision, validateWebhookUrl };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
|
|
1
3
|
//#region src/types.d.ts
|
|
2
4
|
/**
|
|
3
5
|
* Types for the in-memory durable streams test server.
|
|
@@ -5,6 +7,12 @@
|
|
|
5
7
|
/**
|
|
6
8
|
* A single message in a stream.
|
|
7
9
|
*/
|
|
10
|
+
/**
|
|
11
|
+
* Types for the in-memory durable streams test server.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* A single message in a stream.
|
|
15
|
+
*/
|
|
8
16
|
interface StreamMessage {
|
|
9
17
|
/**
|
|
10
18
|
* The raw bytes of the message.
|
|
@@ -172,6 +180,13 @@ interface TestServerOptions {
|
|
|
172
180
|
* Default: October 9, 2024 00:00:00 UTC.
|
|
173
181
|
*/
|
|
174
182
|
cursorEpoch?: Date;
|
|
183
|
+
/**
|
|
184
|
+
* Enable webhook subscriptions.
|
|
185
|
+
* Pull-wake subscription routes are always mounted, but type=webhook creates
|
|
186
|
+
* are rejected unless this is true.
|
|
187
|
+
* Default: false.
|
|
188
|
+
*/
|
|
189
|
+
webhooks?: boolean;
|
|
175
190
|
}
|
|
176
191
|
/**
|
|
177
192
|
* Producer state for idempotent writes.
|
|
@@ -466,7 +481,6 @@ interface FileBackedStreamStoreOptions {
|
|
|
466
481
|
*/
|
|
467
482
|
declare class FileBackedStreamStore {
|
|
468
483
|
private db;
|
|
469
|
-
private fileManager;
|
|
470
484
|
private fileHandlePool;
|
|
471
485
|
private pendingLongPolls;
|
|
472
486
|
private dataDir;
|
|
@@ -475,6 +489,13 @@ declare class FileBackedStreamStore {
|
|
|
475
489
|
* Key: "{streamPath}:{producerId}"
|
|
476
490
|
*/
|
|
477
491
|
private producerLocks;
|
|
492
|
+
/**
|
|
493
|
+
* Per-stream append locks. Serializes the read-modify-write of currentOffset
|
|
494
|
+
* across all concurrent appenders on the same stream so the LMDB-tracked
|
|
495
|
+
* offset cannot drift behind the file's actual byte position.
|
|
496
|
+
* Key: streamPath
|
|
497
|
+
*/
|
|
498
|
+
private streamAppendLocks;
|
|
478
499
|
constructor(options: FileBackedStreamStoreOptions);
|
|
479
500
|
/**
|
|
480
501
|
* Recover streams from disk on startup.
|
|
@@ -505,6 +526,15 @@ declare class FileBackedStreamStore {
|
|
|
505
526
|
*/
|
|
506
527
|
private acquireProducerLock;
|
|
507
528
|
/**
|
|
529
|
+
* Acquire a per-stream append lock that serializes the read-modify-write
|
|
530
|
+
* of currentOffset across all concurrent appenders on the same stream.
|
|
531
|
+
* Without this, two concurrent appends can read the same starting
|
|
532
|
+
* currentOffset, both compute their newOffset, both write a frame to the
|
|
533
|
+
* file, but only one of their LMDB updates wins — leaving currentOffset
|
|
534
|
+
* lagging the file's actual byte position. Returns a release function.
|
|
535
|
+
*/
|
|
536
|
+
private acquireStreamAppendLock;
|
|
537
|
+
/**
|
|
508
538
|
* Get the current epoch for a producer on a stream.
|
|
509
539
|
* Returns undefined if the producer doesn't exist or stream not found.
|
|
510
540
|
*/
|
|
@@ -550,9 +580,15 @@ declare class FileBackedStreamStore {
|
|
|
550
580
|
* whose refcount drops to zero.
|
|
551
581
|
*/
|
|
552
582
|
private deleteWithCascade;
|
|
583
|
+
/**
|
|
584
|
+
* Public append entry point. Serializes concurrent appends to the same
|
|
585
|
+
* stream so the read-modify-write of currentOffset cannot interleave —
|
|
586
|
+
* see acquireStreamAppendLock for the underlying race.
|
|
587
|
+
*/
|
|
553
588
|
append(streamPath: string, data: Uint8Array, options?: AppendOptions & {
|
|
554
589
|
isInitialCreate?: boolean;
|
|
555
590
|
}): Promise<StreamMessage | AppendResult | null>;
|
|
591
|
+
private appendInner;
|
|
556
592
|
/**
|
|
557
593
|
* Append with producer serialization for concurrent request handling.
|
|
558
594
|
* This ensures that validation+append is atomic per producer.
|
|
@@ -673,6 +709,8 @@ declare class DurableStreamTestServer {
|
|
|
673
709
|
private isShuttingDown;
|
|
674
710
|
/** Injected faults for testing retry/resilience */
|
|
675
711
|
private injectedFaults;
|
|
712
|
+
private subscriptionManager;
|
|
713
|
+
private subscriptionRoutes;
|
|
676
714
|
constructor(options?: TestServerOptions);
|
|
677
715
|
/**
|
|
678
716
|
* Start the server.
|
|
@@ -742,6 +780,7 @@ declare class DurableStreamTestServer {
|
|
|
742
780
|
* Handle POST - append data
|
|
743
781
|
*/
|
|
744
782
|
private handleAppend;
|
|
783
|
+
private notifyStreamAppend;
|
|
745
784
|
/**
|
|
746
785
|
* Handle DELETE - delete stream
|
|
747
786
|
*/
|
|
@@ -864,4 +903,221 @@ declare function generateResponseCursor(clientCursor: string | undefined, option
|
|
|
864
903
|
declare function handleCursorCollision(currentCursor: string, previousCursor: string | undefined, options?: CursorOptions): string;
|
|
865
904
|
|
|
866
905
|
//#endregion
|
|
867
|
-
|
|
906
|
+
//#region src/crypto.d.ts
|
|
907
|
+
interface WebhookPublicJwk {
|
|
908
|
+
kty: `OKP`;
|
|
909
|
+
crv: `Ed25519`;
|
|
910
|
+
x: string;
|
|
911
|
+
kid: string;
|
|
912
|
+
use: `sig`;
|
|
913
|
+
alg: `EdDSA`;
|
|
914
|
+
}
|
|
915
|
+
interface WebhookJwks {
|
|
916
|
+
keys: Array<WebhookPublicJwk>;
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Generate a unique wake ID.
|
|
920
|
+
*/
|
|
921
|
+
|
|
922
|
+
declare function getWebhookJwks(): WebhookJwks;
|
|
923
|
+
|
|
924
|
+
//#endregion
|
|
925
|
+
//#region src/subscription-types.d.ts
|
|
926
|
+
/**
|
|
927
|
+
* Sign a webhook payload for the Webhook-Signature header.
|
|
928
|
+
* Format: t=<timestamp>,kid=<key_id>,ed25519=<base64url_signature>
|
|
929
|
+
*/
|
|
930
|
+
type SubscriptionType = `webhook` | `pull-wake`;
|
|
931
|
+
type SubscriptionStatus = `active` | `failed`;
|
|
932
|
+
type SubscriptionLinkType = `glob` | `explicit`;
|
|
933
|
+
interface SubscriptionStreamLink {
|
|
934
|
+
path: string;
|
|
935
|
+
link_types: Set<SubscriptionLinkType>;
|
|
936
|
+
acked_offset: string;
|
|
937
|
+
}
|
|
938
|
+
interface SubscriptionWebhookConfig {
|
|
939
|
+
url: string;
|
|
940
|
+
}
|
|
941
|
+
interface SubscriptionRecord {
|
|
942
|
+
id: string;
|
|
943
|
+
type: SubscriptionType;
|
|
944
|
+
pattern?: string;
|
|
945
|
+
webhook?: SubscriptionWebhookConfig;
|
|
946
|
+
wake_stream?: string;
|
|
947
|
+
lease_ttl_ms: number;
|
|
948
|
+
description?: string;
|
|
949
|
+
created_at: string;
|
|
950
|
+
status: SubscriptionStatus;
|
|
951
|
+
config_hash: string;
|
|
952
|
+
streams: Map<string, SubscriptionStreamLink>;
|
|
953
|
+
generation: number;
|
|
954
|
+
wake_id: string | null;
|
|
955
|
+
wake_snapshot: Map<string, string>;
|
|
956
|
+
token: string | null;
|
|
957
|
+
holder: string | null;
|
|
958
|
+
lease_timer: ReturnType<typeof setTimeout> | null;
|
|
959
|
+
retry_count: number;
|
|
960
|
+
retry_timer: ReturnType<typeof setTimeout> | null;
|
|
961
|
+
next_attempt_at: number | null;
|
|
962
|
+
}
|
|
963
|
+
interface SubscriptionStreamInfo {
|
|
964
|
+
path: string;
|
|
965
|
+
link_type: SubscriptionLinkType;
|
|
966
|
+
acked_offset: string;
|
|
967
|
+
tail_offset: string;
|
|
968
|
+
has_pending: boolean;
|
|
969
|
+
}
|
|
970
|
+
interface SubscriptionCreateInput {
|
|
971
|
+
type: SubscriptionType;
|
|
972
|
+
pattern?: string;
|
|
973
|
+
streams: Array<string>;
|
|
974
|
+
webhook?: {
|
|
975
|
+
url: string;
|
|
976
|
+
};
|
|
977
|
+
wake_stream?: string;
|
|
978
|
+
lease_ttl_ms: number;
|
|
979
|
+
description?: string;
|
|
980
|
+
}
|
|
981
|
+
interface SubscriptionCallbackRequest {
|
|
982
|
+
wake_id?: string;
|
|
983
|
+
generation?: number;
|
|
984
|
+
acks?: Array<{
|
|
985
|
+
stream?: string;
|
|
986
|
+
path?: string;
|
|
987
|
+
offset: string;
|
|
988
|
+
}>;
|
|
989
|
+
done?: boolean;
|
|
990
|
+
}
|
|
991
|
+
type SubscriptionErrorCode = `INVALID_REQUEST` | `SUBSCRIPTION_NOT_FOUND` | `SUBSCRIPTION_ALREADY_EXISTS` | `WEBHOOK_URL_REJECTED` | `TOKEN_INVALID` | `TOKEN_EXPIRED` | `FENCED` | `ALREADY_CLAIMED` | `NO_PENDING_WORK` | `INVALID_OFFSET`;
|
|
992
|
+
interface SubscriptionError {
|
|
993
|
+
code: SubscriptionErrorCode;
|
|
994
|
+
message: string;
|
|
995
|
+
current_holder?: string;
|
|
996
|
+
generation?: number;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
//#endregion
|
|
1000
|
+
//#region src/subscription-manager.d.ts
|
|
1001
|
+
interface StreamLike {
|
|
1002
|
+
currentOffset: string;
|
|
1003
|
+
softDeleted?: boolean;
|
|
1004
|
+
}
|
|
1005
|
+
interface SubscriptionStreamStore {
|
|
1006
|
+
has: (path: string) => boolean;
|
|
1007
|
+
get: (path: string) => StreamLike | undefined;
|
|
1008
|
+
list: () => Array<string>;
|
|
1009
|
+
append: (path: string, data: Uint8Array) => unknown;
|
|
1010
|
+
}
|
|
1011
|
+
declare function validateWebhookUrl(rawUrl: string): {
|
|
1012
|
+
ok: true;
|
|
1013
|
+
} | {
|
|
1014
|
+
ok: false;
|
|
1015
|
+
message: string;
|
|
1016
|
+
};
|
|
1017
|
+
declare class SubscriptionManager {
|
|
1018
|
+
private readonly subscriptions;
|
|
1019
|
+
private readonly streamStore;
|
|
1020
|
+
private readonly callbackBaseUrl;
|
|
1021
|
+
private readonly webhooksEnabled;
|
|
1022
|
+
private isShuttingDown;
|
|
1023
|
+
constructor(opts: {
|
|
1024
|
+
callbackBaseUrl: string;
|
|
1025
|
+
streamStore: SubscriptionStreamStore;
|
|
1026
|
+
webhooksEnabled?: boolean;
|
|
1027
|
+
});
|
|
1028
|
+
createOrConfirm(id: string, input: SubscriptionCreateInput): {
|
|
1029
|
+
subscription: SubscriptionRecord;
|
|
1030
|
+
created: boolean;
|
|
1031
|
+
} | {
|
|
1032
|
+
error: SubscriptionError;
|
|
1033
|
+
};
|
|
1034
|
+
get(id: string): SubscriptionRecord | undefined;
|
|
1035
|
+
delete(id: string): boolean;
|
|
1036
|
+
addExplicitStreams(id: string, streams: Array<string>): boolean;
|
|
1037
|
+
removeExplicitStream(id: string, streamPath: string): boolean;
|
|
1038
|
+
onStreamAppend(absolutePath: string): Promise<void>;
|
|
1039
|
+
onStreamDeleted(absolutePath: string): void;
|
|
1040
|
+
handleWebhookCallback(id: string, token: string, request: SubscriptionCallbackRequest): Promise<{
|
|
1041
|
+
status: number;
|
|
1042
|
+
body: Record<string, unknown>;
|
|
1043
|
+
}>;
|
|
1044
|
+
claim(id: string, worker: string): Promise<{
|
|
1045
|
+
status: number;
|
|
1046
|
+
body: Record<string, unknown>;
|
|
1047
|
+
}>;
|
|
1048
|
+
ack(id: string, token: string, request: SubscriptionCallbackRequest): Promise<{
|
|
1049
|
+
status: number;
|
|
1050
|
+
body: Record<string, unknown>;
|
|
1051
|
+
}>;
|
|
1052
|
+
release(id: string, token: string, request: SubscriptionCallbackRequest): Promise<{
|
|
1053
|
+
status: number;
|
|
1054
|
+
body?: Record<string, unknown>;
|
|
1055
|
+
}>;
|
|
1056
|
+
serialize(subscription: SubscriptionRecord): Record<string, unknown>;
|
|
1057
|
+
getWebhookJwks(): ReturnType<typeof getWebhookJwks>;
|
|
1058
|
+
shutdown(): void;
|
|
1059
|
+
private maybeWake;
|
|
1060
|
+
private createWake;
|
|
1061
|
+
private deliverWebhook;
|
|
1062
|
+
private scheduleWebhookRetry;
|
|
1063
|
+
private writePullWakeEvent;
|
|
1064
|
+
private autoAckWakeSnapshot;
|
|
1065
|
+
private applyAcks;
|
|
1066
|
+
private validateWakeToken;
|
|
1067
|
+
private triggerNextWakeIfPending;
|
|
1068
|
+
private hasPendingWork;
|
|
1069
|
+
private firstPendingStream;
|
|
1070
|
+
private streamInfos;
|
|
1071
|
+
private linkStream;
|
|
1072
|
+
private listStreams;
|
|
1073
|
+
private getTailOffset;
|
|
1074
|
+
private subscriptionActionUrl;
|
|
1075
|
+
private webhookJwksUrl;
|
|
1076
|
+
private webhookSigningMetadata;
|
|
1077
|
+
private extendLease;
|
|
1078
|
+
private clearLease;
|
|
1079
|
+
private tokenSubject;
|
|
1080
|
+
private errorResponse;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
//#endregion
|
|
1084
|
+
//#region src/subscription-routes.d.ts
|
|
1085
|
+
declare class SubscriptionRoutes {
|
|
1086
|
+
private readonly manager;
|
|
1087
|
+
constructor(manager: SubscriptionManager);
|
|
1088
|
+
handleRequest(method: string, path: string, req: IncomingMessage, res: ServerResponse): Promise<boolean>;
|
|
1089
|
+
private handleBase;
|
|
1090
|
+
private handleJwks;
|
|
1091
|
+
private handleStreams;
|
|
1092
|
+
private handleStream;
|
|
1093
|
+
private handleCallback;
|
|
1094
|
+
private handleClaim;
|
|
1095
|
+
private handleAck;
|
|
1096
|
+
private handleRelease;
|
|
1097
|
+
private parseCreateInput;
|
|
1098
|
+
private parseRoute;
|
|
1099
|
+
private readBearerToken;
|
|
1100
|
+
private readJson;
|
|
1101
|
+
private writeManagerResult;
|
|
1102
|
+
private writeJson;
|
|
1103
|
+
private writeError;
|
|
1104
|
+
private methodNotAllowed;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
//#endregion
|
|
1108
|
+
//#region src/glob.d.ts
|
|
1109
|
+
/**
|
|
1110
|
+
* Glob pattern matching for webhook subscription patterns.
|
|
1111
|
+
*
|
|
1112
|
+
* Supports:
|
|
1113
|
+
* - `*` matches exactly one path segment
|
|
1114
|
+
* - `**` matches zero or more path segments (recursive)
|
|
1115
|
+
* - Literal segments match exactly
|
|
1116
|
+
*/
|
|
1117
|
+
/**
|
|
1118
|
+
* Match a stream path against a glob pattern.
|
|
1119
|
+
*/
|
|
1120
|
+
declare function globMatch(pattern: string, path: string): boolean;
|
|
1121
|
+
|
|
1122
|
+
//#endregion
|
|
1123
|
+
export { CursorOptions, DEFAULT_CURSOR_EPOCH, DEFAULT_CURSOR_INTERVAL_SECONDS, DurableStreamTestServer, FileBackedStreamStore, PendingLongPoll, Stream, StreamLifecycleEvent, StreamLifecycleHook, StreamMessage, StreamStore, SubscriptionCallbackRequest, SubscriptionCreateInput, SubscriptionError, SubscriptionErrorCode, SubscriptionManager, SubscriptionRecord, SubscriptionRoutes, SubscriptionStatus, SubscriptionStreamInfo, SubscriptionStreamLink, SubscriptionType, TestServerOptions, calculateCursor, createRegistryHooks, decodeStreamPath, encodeStreamPath, generateResponseCursor, globMatch, handleCursorCollision, validateWebhookUrl };
|