@durable-streams/server 0.3.2 → 0.3.4

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.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;
@@ -695,6 +709,8 @@ declare class DurableStreamTestServer {
695
709
  private isShuttingDown;
696
710
  /** Injected faults for testing retry/resilience */
697
711
  private injectedFaults;
712
+ private subscriptionManager;
713
+ private subscriptionRoutes;
698
714
  constructor(options?: TestServerOptions);
699
715
  /**
700
716
  * Start the server.
@@ -764,6 +780,7 @@ declare class DurableStreamTestServer {
764
780
  * Handle POST - append data
765
781
  */
766
782
  private handleAppend;
783
+ private notifyStreamAppend;
767
784
  /**
768
785
  * Handle DELETE - delete stream
769
786
  */
@@ -886,4 +903,221 @@ declare function generateResponseCursor(clientCursor: string | undefined, option
886
903
  declare function handleCursorCollision(currentCursor: string, previousCursor: string | undefined, options?: CursorOptions): string;
887
904
 
888
905
  //#endregion
889
- export { CursorOptions, DEFAULT_CURSOR_EPOCH, DEFAULT_CURSOR_INTERVAL_SECONDS, DurableStreamTestServer, FileBackedStreamStore, PendingLongPoll, Stream, StreamLifecycleEvent, StreamLifecycleHook, StreamMessage, StreamStore, TestServerOptions, calculateCursor, createRegistryHooks, decodeStreamPath, encodeStreamPath, generateResponseCursor, handleCursorCollision };
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;
@@ -695,6 +709,8 @@ declare class DurableStreamTestServer {
695
709
  private isShuttingDown;
696
710
  /** Injected faults for testing retry/resilience */
697
711
  private injectedFaults;
712
+ private subscriptionManager;
713
+ private subscriptionRoutes;
698
714
  constructor(options?: TestServerOptions);
699
715
  /**
700
716
  * Start the server.
@@ -764,6 +780,7 @@ declare class DurableStreamTestServer {
764
780
  * Handle POST - append data
765
781
  */
766
782
  private handleAppend;
783
+ private notifyStreamAppend;
767
784
  /**
768
785
  * Handle DELETE - delete stream
769
786
  */
@@ -886,4 +903,221 @@ declare function generateResponseCursor(clientCursor: string | undefined, option
886
903
  declare function handleCursorCollision(currentCursor: string, previousCursor: string | undefined, options?: CursorOptions): string;
887
904
 
888
905
  //#endregion
889
- export { CursorOptions, DEFAULT_CURSOR_EPOCH, DEFAULT_CURSOR_INTERVAL_SECONDS, DurableStreamTestServer, FileBackedStreamStore, PendingLongPoll, Stream, StreamLifecycleEvent, StreamLifecycleHook, StreamMessage, StreamStore, TestServerOptions, calculateCursor, createRegistryHooks, decodeStreamPath, encodeStreamPath, generateResponseCursor, handleCursorCollision };
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 };