ocel 0.0.1-alpha.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +98 -0
- package/dist/binding/binding.d.ts +22 -0
- package/dist/binding/binding.js +60 -0
- package/dist/binding/unprovisioned.d.ts +17 -0
- package/dist/binding/unprovisioned.js +25 -0
- package/dist/bucket/bucket-client.d.ts +4 -0
- package/dist/bucket/bucket-client.js +5 -0
- package/dist/bucket/bucket-context.d.ts +8 -0
- package/dist/bucket/bucket-context.js +10 -0
- package/dist/bucket/bucket.d.ts +60 -0
- package/dist/bucket/bucket.js +91 -0
- package/dist/bucket/client.d.ts +49 -0
- package/dist/bucket/client.js +95 -0
- package/dist/bucket/errors.d.ts +12 -0
- package/dist/bucket/errors.js +20 -0
- package/dist/bucket/express.d.ts +10 -0
- package/dist/bucket/express.js +24 -0
- package/dist/bucket/hono.d.ts +18 -0
- package/dist/bucket/hono.js +13 -0
- package/dist/bucket/index.d.ts +8 -0
- package/dist/bucket/index.js +6 -0
- package/dist/bucket/keys.d.ts +5 -0
- package/dist/bucket/keys.js +24 -0
- package/dist/bucket/metadata.d.ts +6 -0
- package/dist/bucket/metadata.js +6 -0
- package/dist/bucket/next.d.ts +15 -0
- package/dist/bucket/next.js +11 -0
- package/dist/bucket/objects.d.ts +130 -0
- package/dist/bucket/objects.js +350 -0
- package/dist/bucket/route.d.ts +13 -0
- package/dist/bucket/route.js +247 -0
- package/dist/bucket/server-only.js +5 -0
- package/dist/bucket/types.d.ts +63 -0
- package/dist/bucket/uploader.d.ts +3 -0
- package/dist/bucket/uploader.js +6 -0
- package/dist/build-env.d.ts +17 -0
- package/dist/build-env.js +41 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.js +9 -0
- package/dist/declaration/callsite.d.ts +15 -0
- package/dist/declaration/callsite.js +43 -0
- package/dist/declaration/component.d.ts +4 -0
- package/dist/declaration/component.js +1 -0
- package/dist/declaration/defer.d.ts +5 -0
- package/dist/declaration/defer.js +9 -0
- package/dist/delivery/duration.d.ts +13 -0
- package/dist/delivery/duration.js +36 -0
- package/dist/delivery/lane.d.ts +4 -0
- package/dist/delivery/lane.js +15 -0
- package/dist/delivery/payload.d.ts +6 -0
- package/dist/delivery/payload.js +14 -0
- package/dist/delivery/retry.d.ts +16 -0
- package/dist/delivery/retry.js +10 -0
- package/dist/delivery/schema.d.ts +10 -0
- package/dist/delivery/schema.js +20 -0
- package/dist/dns.d.ts +11 -0
- package/dist/dns.js +4 -0
- package/dist/edge.d.ts +4 -0
- package/dist/edge.js +4 -0
- package/dist/env/access.d.ts +22 -0
- package/dist/env/access.js +49 -0
- package/dist/env/client.d.ts +5 -0
- package/dist/env/client.js +10 -0
- package/dist/env/declare.d.ts +2 -0
- package/dist/env/declare.js +83 -0
- package/dist/env/definition.d.ts +46 -0
- package/dist/env/definition.js +164 -0
- package/dist/env/deployment.d.ts +23 -0
- package/dist/env/deployment.js +13 -0
- package/dist/env/edge.d.ts +10 -0
- package/dist/env/edge.js +30 -0
- package/dist/env/errors.d.ts +9 -0
- package/dist/env/errors.js +9 -0
- package/dist/env/file.d.ts +1 -0
- package/dist/env/file.js +27 -0
- package/dist/env/index.d.ts +10 -0
- package/dist/env/index.js +63 -0
- package/dist/env/live.d.ts +3 -0
- package/dist/env/live.js +12 -0
- package/dist/env/schema.d.ts +27 -0
- package/dist/env/schema.js +43 -0
- package/dist/env/scope.d.ts +7 -0
- package/dist/env/scope.js +45 -0
- package/dist/env/standard.d.ts +10 -0
- package/dist/env/standard.js +21 -0
- package/dist/env/value.d.ts +5 -0
- package/dist/env/value.js +26 -0
- package/dist/env-source.d.ts +17 -0
- package/dist/env-source.js +16 -0
- package/dist/gen/proto/app/bucket/v1/bucket_pb.d.ts +911 -0
- package/dist/gen/proto/app/bucket/v1/bucket_pb.js +253 -0
- package/dist/gen/proto/app/realtime/v1/realtime_pb.d.ts +50 -0
- package/dist/gen/proto/app/realtime/v1/realtime_pb.js +23 -0
- package/dist/gen/proto/app/resources/v1/resources_pb.d.ts +554 -0
- package/dist/gen/proto/app/resources/v1/resources_pb.js +214 -0
- package/dist/gen/proto/app/resources/v1/variables_pb.d.ts +231 -0
- package/dist/gen/proto/app/resources/v1/variables_pb.js +101 -0
- package/dist/gen/proto/app/task/v1/task_pb.d.ts +498 -0
- package/dist/gen/proto/app/task/v1/task_pb.js +151 -0
- package/dist/gen/proto/app/topic/v1/topic_pb.d.ts +456 -0
- package/dist/gen/proto/app/topic/v1/topic_pb.js +125 -0
- package/dist/gen/proto/buf/validate/validate_pb.d.ts +4833 -0
- package/dist/gen/proto/buf/validate/validate_pb.js +356 -0
- package/dist/gen/proto/common/bindings/v1/bindings_pb.d.ts +378 -0
- package/dist/gen/proto/common/bindings/v1/bindings_pb.js +140 -0
- package/dist/generated/config.d.ts +428 -0
- package/dist/generated/config.js +2 -0
- package/dist/kv/entries.d.ts +193 -0
- package/dist/kv/entries.js +325 -0
- package/dist/kv/errors.d.ts +12 -0
- package/dist/kv/errors.js +14 -0
- package/dist/kv/index.d.ts +6 -0
- package/dist/kv/index.js +3 -0
- package/dist/kv/ioredis.d.ts +2 -0
- package/dist/kv/ioredis.js +20 -0
- package/dist/kv/kv.d.ts +84 -0
- package/dist/kv/kv.js +177 -0
- package/dist/kv/pattern.d.ts +27 -0
- package/dist/kv/pattern.js +73 -0
- package/dist/kv/ttl.d.ts +22 -0
- package/dist/kv/ttl.js +21 -0
- package/dist/postgres/index.d.ts +21 -2
- package/dist/postgres/index.js +73 -2
- package/dist/postgres/pg.d.ts +5 -4
- package/dist/postgres/pg.js +16 -12
- package/dist/providers/aws/dns.d.ts +11 -0
- package/dist/providers/aws/dns.js +4 -0
- package/dist/providers/aws/edge.d.ts +6 -0
- package/dist/providers/aws/edge.js +8 -0
- package/dist/providers/aws/index.d.ts +4 -0
- package/dist/providers/aws/index.js +4 -0
- package/dist/providers/gcp/edge.d.ts +14 -0
- package/dist/providers/gcp/edge.js +14 -0
- package/dist/providers/gcp/index.d.ts +9 -0
- package/dist/providers/gcp/index.js +9 -0
- package/dist/providers/vps/index.d.ts +4 -0
- package/dist/providers/vps/index.js +4 -0
- package/dist/realtime/client-error.d.ts +30 -0
- package/dist/realtime/client-error.js +17 -0
- package/dist/realtime/client-event-socket.d.ts +8 -0
- package/dist/realtime/client-event-socket.js +169 -0
- package/dist/realtime/client-transport.d.ts +29 -0
- package/dist/realtime/client-transport.js +13 -0
- package/dist/realtime/client.d.ts +150 -0
- package/dist/realtime/client.js +345 -0
- package/dist/realtime/express.d.ts +18 -0
- package/dist/realtime/express.js +83 -0
- package/dist/realtime/handler.d.ts +56 -0
- package/dist/realtime/handler.js +287 -0
- package/dist/realtime/hono.d.ts +19 -0
- package/dist/realtime/hono.js +24 -0
- package/dist/realtime/index.d.ts +2 -0
- package/dist/realtime/index.js +2 -0
- package/dist/realtime/next.d.ts +26 -0
- package/dist/realtime/next.js +16 -0
- package/dist/realtime/realtime.d.ts +181 -0
- package/dist/realtime/realtime.js +205 -0
- package/dist/realtime/token.d.ts +20 -0
- package/dist/realtime/token.js +45 -0
- package/dist/realtime/transport.d.ts +11 -0
- package/dist/realtime/transport.js +15 -0
- package/dist/realtime/wire.d.ts +30 -0
- package/dist/realtime/wire.js +104 -0
- package/dist/runtime/dev-server.d.ts +8 -0
- package/dist/runtime/dev-server.js +8 -0
- package/dist/runtime/rpc.d.ts +23 -0
- package/dist/runtime/rpc.js +24 -0
- package/dist/runtime/transport.d.ts +11 -0
- package/dist/runtime/transport.js +35 -0
- package/dist/task/errors.d.ts +7 -0
- package/dist/task/errors.js +7 -0
- package/dist/task/hooks.d.ts +57 -0
- package/dist/task/hooks.js +1 -0
- package/dist/task/index.d.ts +9 -0
- package/dist/task/index.js +4 -0
- package/dist/task/runs.d.ts +74 -0
- package/dist/task/runs.js +82 -0
- package/dist/task/task.d.ts +117 -0
- package/dist/task/task.js +109 -0
- package/dist/topic/dead-letters.d.ts +53 -0
- package/dist/topic/dead-letters.js +62 -0
- package/dist/topic/index.d.ts +8 -0
- package/dist/topic/index.js +4 -0
- package/dist/topic/topic.d.ts +85 -0
- package/dist/topic/topic.js +117 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/dist/worker/context.d.ts +36 -0
- package/dist/worker/context.js +1 -0
- package/dist/worker/deliver.d.ts +13 -0
- package/dist/worker/deliver.js +258 -0
- package/dist/worker/index.d.ts +4 -0
- package/dist/worker/index.js +2 -0
- package/dist/worker/registry.d.ts +28 -0
- package/dist/worker/registry.js +22 -0
- package/dist/worker/semaphore.d.ts +7 -0
- package/dist/worker/semaphore.js +36 -0
- package/dist/worker/worker.d.ts +21 -0
- package/dist/worker/worker.js +32 -0
- package/package.json +158 -57
- package/bin/run.js +0 -39
- package/dist/blob/bucket.d.ts +0 -55
- package/dist/blob/bucket.js +0 -65
- package/dist/blob/index.d.ts +0 -8
- package/dist/blob/index.js +0 -13
- package/dist/blob/integrations/client.d.ts +0 -25
- package/dist/blob/integrations/client.js +0 -71
- package/dist/blob/integrations/hono.d.ts +0 -31
- package/dist/blob/integrations/hono.js +0 -11
- package/dist/blob/mime-types.d.ts +0 -1
- package/dist/blob/parse.d.ts +0 -16
- package/dist/blob/parse.js +0 -69
- package/dist/blob/s3-listener.d.ts +0 -7
- package/dist/blob/s3-listener.js +0 -35
- package/dist/blob/types.d.ts +0 -128
- package/dist/blob/types.js +0 -1
- package/dist/blob/upload-error.d.ts +0 -35
- package/dist/blob/upload-error.js +0 -54
- package/dist/blob/uploader.d.ts +0 -67
- package/dist/blob/uploader.js +0 -323
- package/dist/internal/db/index.d.ts +0 -68
- package/dist/internal/db/index.js +0 -73
- package/dist/lambda/index.d.ts +0 -2
- package/dist/lambda/index.js +0 -4
- package/dist/lambda/integrations/express.d.ts +0 -3
- package/dist/lambda/integrations/express.js +0 -13
- package/dist/lambda/integrations/hono.d.ts +0 -3
- package/dist/lambda/integrations/hono.js +0 -13
- package/dist/lambda/lambda.d.ts +0 -43
- package/dist/lambda/lambda.js +0 -30
- package/dist/utils/component.d.ts +0 -3
- package/dist/utils/constants.d.ts +0 -2
- package/dist/utils/constants.js +0 -2
- package/dist/utils/nanoid.d.ts +0 -1
- package/dist/utils/nanoid.js +0 -6
- package/dist/utils/rpc.d.ts +0 -11
- package/dist/utils/rpc.js +0 -17
- package/dist/utils/stack.d.ts +0 -4
- package/dist/utils/stack.js +0 -23
- package/dist/workflow/index.d.ts +0 -3
- package/dist/workflow/index.js +0 -4
- package/dist/workflow/workflow.d.ts +0 -34
- package/dist/workflow/workflow.js +0 -146
- package/src/blob/bucket.ts +0 -92
- package/src/blob/index.ts +0 -34
- package/src/blob/integrations/client.ts +0 -127
- package/src/blob/integrations/hono.ts +0 -16
- package/src/blob/mime-types.ts +0 -68
- package/src/blob/parse.ts +0 -93
- package/src/blob/s3-listener.ts +0 -48
- package/src/blob/types.ts +0 -142
- package/src/blob/upload-error.ts +0 -67
- package/src/blob/uploader.ts +0 -426
- package/src/internal/db/index.ts +0 -78
- package/src/lambda/index.ts +0 -8
- package/src/lambda/integrations/express.ts +0 -25
- package/src/lambda/integrations/hono.ts +0 -25
- package/src/lambda/lambda.ts +0 -94
- package/src/postgres/index.ts +0 -5
- package/src/postgres/pg.ts +0 -33
- package/src/utils/component.ts +0 -3
- package/src/utils/constants.ts +0 -2
- package/src/utils/nanoid.ts +0 -9
- package/src/utils/rpc.ts +0 -28
- package/src/utils/stack.ts +0 -26
- package/src/workflow/index.ts +0 -9
- package/src/workflow/workflow.ts +0 -235
- /package/dist/{blob/mime-types.js → bucket/server-only.d.ts} +0 -0
- /package/dist/{utils/component.js → bucket/types.js} +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { RealtimeError } from "./client-error.js";
|
|
2
|
+
const transports = {
|
|
3
|
+
"ocel-gateway": async () => (await import("./client-event-socket.js")).gatewayTransport,
|
|
4
|
+
"appsync-events": async () => (await import("./client-event-socket.js")).appSyncTransport,
|
|
5
|
+
};
|
|
6
|
+
/** Loads the transport the realtime handler named, so a bundle carries only the one it uses. */
|
|
7
|
+
export async function loadTransport(name) {
|
|
8
|
+
const load = transports[name];
|
|
9
|
+
if (!load) {
|
|
10
|
+
throw new RealtimeError("unsupported-transport", false, `the realtime handler named transport "${name}", which this client does not speak`);
|
|
11
|
+
}
|
|
12
|
+
return load();
|
|
13
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
import { RealtimeError } from "./client-error.js";
|
|
3
|
+
import type { ChannelShape } from "./realtime.js";
|
|
4
|
+
export { RealtimeError, type RealtimeErrorCode } from "./client-error.js";
|
|
5
|
+
/**
|
|
6
|
+
* The client-side types of one channel pattern: the event its subscribers receive, the body
|
|
7
|
+
* a browser publishes when a `publish` rule exists, and whether subscribers may leave off
|
|
8
|
+
* trailing params. `ocel generate` writes these for a realtime resource declared in Go,
|
|
9
|
+
* Python or Rust.
|
|
10
|
+
*/
|
|
11
|
+
export interface ChannelTypes {
|
|
12
|
+
/** The event every subscriber receives. */
|
|
13
|
+
event: unknown;
|
|
14
|
+
/** The body a browser publishes; absent where only the server publishes. */
|
|
15
|
+
publish?: unknown;
|
|
16
|
+
/** Present when subscribers may leave off trailing params. */
|
|
17
|
+
wildcard?: true;
|
|
18
|
+
}
|
|
19
|
+
/** The client-side types of a realtime resource's channels, keyed by pattern. */
|
|
20
|
+
export type RealtimeTypes = Record<string, ChannelTypes>;
|
|
21
|
+
type ChannelTypesOf<TChannel> = TChannel extends {
|
|
22
|
+
schema: infer TSchema extends StandardSchemaV1;
|
|
23
|
+
} ? {
|
|
24
|
+
event: StandardSchemaV1.InferOutput<TSchema>;
|
|
25
|
+
} & (TChannel extends {
|
|
26
|
+
wildcard: true;
|
|
27
|
+
} ? {
|
|
28
|
+
wildcard: true;
|
|
29
|
+
} : unknown) & (TChannel extends {
|
|
30
|
+
publish: unknown;
|
|
31
|
+
} ? {
|
|
32
|
+
publish: StandardSchemaV1.InferInput<TSchema>;
|
|
33
|
+
} : unknown) : never;
|
|
34
|
+
/**
|
|
35
|
+
* The channel types a client is typed from: those of a resource `realtime()` declared, when
|
|
36
|
+
* given its type, or the {@link RealtimeTypes} `ocel generate` wrote.
|
|
37
|
+
*/
|
|
38
|
+
export type ClientChannels<T> = T extends {
|
|
39
|
+
readonly name: string;
|
|
40
|
+
readonly types?: {
|
|
41
|
+
channels: infer TChannels extends Record<string, ChannelShape>;
|
|
42
|
+
};
|
|
43
|
+
} ? {
|
|
44
|
+
[TPattern in keyof TChannels & string]: ChannelTypesOf<TChannels[TPattern]>;
|
|
45
|
+
} : T extends RealtimeTypes ? T : never;
|
|
46
|
+
type ParamList<TPattern extends string> = TPattern extends `${infer Head}/${infer Rest}` ? [...ParamList<Head>, ...ParamList<Rest>] : TPattern extends `:${infer Name}` ? [Name] : [];
|
|
47
|
+
type Filled<TNames extends string> = {
|
|
48
|
+
[Name in TNames]: string;
|
|
49
|
+
};
|
|
50
|
+
type Left<TNames extends string> = {
|
|
51
|
+
[Name in TNames]?: undefined;
|
|
52
|
+
};
|
|
53
|
+
type TrailingParams<TNames extends string[], TLeft extends string = never> = TNames extends [
|
|
54
|
+
...infer Init extends string[],
|
|
55
|
+
infer Last extends string
|
|
56
|
+
] ? (Filled<TNames[number]> & Left<TLeft>) | TrailingParams<Init, TLeft | Last> : Left<TLeft>;
|
|
57
|
+
type ParamsOption<TParams> = [keyof TParams] extends [never] ? {
|
|
58
|
+
params?: Record<string, never>;
|
|
59
|
+
} : Record<never, never> extends TParams ? {
|
|
60
|
+
params?: TParams;
|
|
61
|
+
} : {
|
|
62
|
+
params: TParams;
|
|
63
|
+
};
|
|
64
|
+
/** The params a subscribe on `TPattern` takes: every one, or on a `wildcard` pattern a leading run. */
|
|
65
|
+
export type SubscribeParams<TPattern extends string, TChannel> = TChannel extends {
|
|
66
|
+
wildcard: true;
|
|
67
|
+
} ? TrailingParams<ParamList<TPattern>> : Filled<ParamList<TPattern>[number]>;
|
|
68
|
+
/** How one subscription is made. */
|
|
69
|
+
export type SubscribeOptions<TPattern extends string, TChannel> = ParamsOption<SubscribeParams<TPattern, TChannel>> & {
|
|
70
|
+
/**
|
|
71
|
+
* Hears why the subscription failed. When `error.retriable` is false the subscription has
|
|
72
|
+
* ended; when true the client keeps trying on its own.
|
|
73
|
+
*/
|
|
74
|
+
onError?: (error: RealtimeError) => void;
|
|
75
|
+
};
|
|
76
|
+
/** The `{ params, body }` a browser publish on `TPattern` takes; every param is required. */
|
|
77
|
+
export type ClientPublishMessage<TPattern extends string, TChannel> = ParamsOption<Filled<ParamList<TPattern>[number]>> & {
|
|
78
|
+
body: TChannel extends {
|
|
79
|
+
publish: infer TBody;
|
|
80
|
+
} ? TBody : never;
|
|
81
|
+
};
|
|
82
|
+
/** What each event carries besides its body, from its envelope. */
|
|
83
|
+
export interface EventMeta {
|
|
84
|
+
/** The event's id, 32 lowercase hex characters. */
|
|
85
|
+
id: string;
|
|
86
|
+
/** The wire channel it was published on, such as `/app/orders/o-1`. */
|
|
87
|
+
channel: string;
|
|
88
|
+
/** When the server published it, in epoch milliseconds. */
|
|
89
|
+
publishedAt: number;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The client's socket: `idle` while no subscription needs one, `connecting` until it opens,
|
|
93
|
+
* `connected` while it is open, `reconnecting` after it dropped with subscriptions left to
|
|
94
|
+
* restore, until it opens again, and `closed` once `close` is called. Whether each
|
|
95
|
+
* subscription is restored is reported through its own `onError`, not here.
|
|
96
|
+
*/
|
|
97
|
+
export type RealtimeClientState = "idle" | "connecting" | "connected" | "reconnecting" | "closed";
|
|
98
|
+
/** Where a realtime client finds the app's realtime handler. */
|
|
99
|
+
export interface RealtimeClientOptions {
|
|
100
|
+
/** The URL the app mounted its realtime handler at, such as `"/api/realtime"`. */
|
|
101
|
+
url: string;
|
|
102
|
+
/**
|
|
103
|
+
* Headers sent with every request to the handler, such as a bearer token for a handler on
|
|
104
|
+
* another origin; a function is called before each request, so a token can be refreshed.
|
|
105
|
+
*/
|
|
106
|
+
headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
|
|
107
|
+
}
|
|
108
|
+
type Publishable<TChannels> = {
|
|
109
|
+
[TPattern in keyof TChannels]: TChannels[TPattern] extends {
|
|
110
|
+
publish: unknown;
|
|
111
|
+
} ? TPattern : never;
|
|
112
|
+
}[keyof TChannels] & string;
|
|
113
|
+
/** A browser's connection to one realtime resource. */
|
|
114
|
+
export interface RealtimeClient<TChannels> {
|
|
115
|
+
/** The connection's current state. */
|
|
116
|
+
readonly state: RealtimeClientState;
|
|
117
|
+
/** Calls `listener` with each new state; returns the function that stops it. */
|
|
118
|
+
onStateChange(listener: (state: RealtimeClientState) => void): () => void;
|
|
119
|
+
/**
|
|
120
|
+
* Subscribes to the channel `params` fill in `pattern`, calling `handler` with each event.
|
|
121
|
+
* Subscriptions made together are authorized in one request, and every live one is
|
|
122
|
+
* authorized again after a reconnect. Returns the function that unsubscribes.
|
|
123
|
+
*/
|
|
124
|
+
subscribe<TPattern extends keyof TChannels & string>(pattern: TPattern, options: SubscribeOptions<TPattern, TChannels[TPattern]>, handler: (event: EventOf<TChannels[TPattern]>, meta: EventMeta) => void): () => void;
|
|
125
|
+
/**
|
|
126
|
+
* Publishes `body` on the channel `params` fill in `pattern`, through the app's realtime
|
|
127
|
+
* handler, which runs the pattern's `publish` rule. Rejects with a {@link RealtimeError}.
|
|
128
|
+
*/
|
|
129
|
+
publish<TPattern extends Publishable<TChannels>>(pattern: TPattern, message: ClientPublishMessage<TPattern, TChannels[TPattern]>): Promise<void>;
|
|
130
|
+
/** Ends every subscription and closes the socket. */
|
|
131
|
+
close(): void;
|
|
132
|
+
}
|
|
133
|
+
type EventOf<TChannel> = TChannel extends {
|
|
134
|
+
event: infer TEvent;
|
|
135
|
+
} ? TEvent : never;
|
|
136
|
+
/**
|
|
137
|
+
* Connects a browser to the realtime resource whose handler the app mounted at `url`.
|
|
138
|
+
* `T` is the resource's type, `typeof rt` for one declared with `realtime()`, which the
|
|
139
|
+
* client imports as a type alone so no server code reaches the bundle, or the
|
|
140
|
+
* {@link RealtimeTypes} `ocel generate` wrote for a resource declared in Go, Python or Rust.
|
|
141
|
+
*
|
|
142
|
+
* ```ts
|
|
143
|
+
* import type { rt } from "../server/realtime";
|
|
144
|
+
*
|
|
145
|
+
* const live = createRealtimeClient<typeof rt>({ url: "/api/realtime" });
|
|
146
|
+
* const stop = live.subscribe("orders/:orderId", { params: { orderId } }, (event) => …);
|
|
147
|
+
* await live.publish("rooms/:roomId", { params: { roomId }, body: { text: "hi" } });
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export declare function createRealtimeClient<T>(options: RealtimeClientOptions): RealtimeClient<ClientChannels<T>>;
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { RealtimeError } from "./client-error.js";
|
|
2
|
+
import { loadTransport } from "./client-transport.js";
|
|
3
|
+
export { RealtimeError } from "./client-error.js";
|
|
4
|
+
const maxOperationsPerRequest = 50;
|
|
5
|
+
const initialBackoffMilliseconds = 500;
|
|
6
|
+
const maxBackoffMilliseconds = 30_000;
|
|
7
|
+
const requestDeadlineMilliseconds = 10_000;
|
|
8
|
+
const retriableDenials = new Set(["rule-error", "publish-failed"]);
|
|
9
|
+
function dropUndefined(params) {
|
|
10
|
+
const out = {};
|
|
11
|
+
for (const [name, value] of Object.entries(params ?? {})) {
|
|
12
|
+
if (value !== undefined)
|
|
13
|
+
out[name] = value;
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
function isRetriableStatus(status) {
|
|
18
|
+
return status >= 500 || status === 408 || status === 429;
|
|
19
|
+
}
|
|
20
|
+
function readPattern(operation) {
|
|
21
|
+
return operation.kind === "subscribe" ? operation.subscription.pattern : operation.pattern;
|
|
22
|
+
}
|
|
23
|
+
function describeOperation(operation) {
|
|
24
|
+
return operation.kind === "subscribe"
|
|
25
|
+
? {
|
|
26
|
+
op: "subscribe",
|
|
27
|
+
pattern: operation.subscription.pattern,
|
|
28
|
+
params: operation.subscription.params,
|
|
29
|
+
}
|
|
30
|
+
: { op: "publish", pattern: operation.pattern, params: operation.params, body: operation.body };
|
|
31
|
+
}
|
|
32
|
+
function deliver(subscription, text) {
|
|
33
|
+
let envelope;
|
|
34
|
+
try {
|
|
35
|
+
envelope = JSON.parse(text);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
subscription.handler(envelope.data, {
|
|
41
|
+
id: envelope.id,
|
|
42
|
+
channel: envelope.ch,
|
|
43
|
+
publishedAt: envelope.ts,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Connects a browser to the realtime resource whose handler the app mounted at `url`.
|
|
48
|
+
* `T` is the resource's type, `typeof rt` for one declared with `realtime()`, which the
|
|
49
|
+
* client imports as a type alone so no server code reaches the bundle, or the
|
|
50
|
+
* {@link RealtimeTypes} `ocel generate` wrote for a resource declared in Go, Python or Rust.
|
|
51
|
+
*
|
|
52
|
+
* ```ts
|
|
53
|
+
* import type { rt } from "../server/realtime";
|
|
54
|
+
*
|
|
55
|
+
* const live = createRealtimeClient<typeof rt>({ url: "/api/realtime" });
|
|
56
|
+
* const stop = live.subscribe("orders/:orderId", { params: { orderId } }, (event) => …);
|
|
57
|
+
* await live.publish("rooms/:roomId", { params: { roomId }, body: { text: "hi" } });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function createRealtimeClient(options) {
|
|
61
|
+
let state = "idle";
|
|
62
|
+
const stateListeners = new Set();
|
|
63
|
+
const subscriptions = new Map();
|
|
64
|
+
let queue = [];
|
|
65
|
+
let sending = [];
|
|
66
|
+
let isPumping = false;
|
|
67
|
+
let isClosed = false;
|
|
68
|
+
let hasDropped = false;
|
|
69
|
+
let connection;
|
|
70
|
+
let failedAttempts = 0;
|
|
71
|
+
let retryTimer;
|
|
72
|
+
let nextId = 0;
|
|
73
|
+
const settleState = () => {
|
|
74
|
+
let next;
|
|
75
|
+
if (isClosed)
|
|
76
|
+
next = "closed";
|
|
77
|
+
else if (connection)
|
|
78
|
+
next = "connected";
|
|
79
|
+
else if (subscriptions.size === 0) {
|
|
80
|
+
hasDropped = false;
|
|
81
|
+
next = "idle";
|
|
82
|
+
}
|
|
83
|
+
else
|
|
84
|
+
next = hasDropped ? "reconnecting" : "connecting";
|
|
85
|
+
if (state === next)
|
|
86
|
+
return;
|
|
87
|
+
state = next;
|
|
88
|
+
for (const listener of stateListeners)
|
|
89
|
+
listener(next);
|
|
90
|
+
};
|
|
91
|
+
const isLive = (subscription) => subscriptions.get(subscription.id) === subscription;
|
|
92
|
+
const isAwaiting = (subscription) => [...queue, ...sending].some((operation) => operation.kind === "subscribe" && operation.subscription === subscription);
|
|
93
|
+
const scheduleRetry = () => {
|
|
94
|
+
if (isClosed || retryTimer !== undefined)
|
|
95
|
+
return;
|
|
96
|
+
const ceiling = Math.min(maxBackoffMilliseconds, initialBackoffMilliseconds * 2 ** failedAttempts);
|
|
97
|
+
failedAttempts += 1;
|
|
98
|
+
retryTimer = setTimeout(() => {
|
|
99
|
+
retryTimer = undefined;
|
|
100
|
+
for (const subscription of subscriptions.values()) {
|
|
101
|
+
if (!subscription.isSubscribed && !isAwaiting(subscription)) {
|
|
102
|
+
queue.push({ kind: "subscribe", subscription });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
void pump();
|
|
106
|
+
}, Math.random() * ceiling);
|
|
107
|
+
};
|
|
108
|
+
const failSubscription = (subscription, error) => {
|
|
109
|
+
if (!isLive(subscription))
|
|
110
|
+
return;
|
|
111
|
+
if (error.retriable) {
|
|
112
|
+
scheduleRetry();
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
subscriptions.delete(subscription.id);
|
|
116
|
+
settleState();
|
|
117
|
+
}
|
|
118
|
+
subscription.onError?.(error);
|
|
119
|
+
};
|
|
120
|
+
const failOperation = (operation, error) => {
|
|
121
|
+
if (operation.kind === "publish")
|
|
122
|
+
operation.settle(error);
|
|
123
|
+
else
|
|
124
|
+
failSubscription(operation.subscription, error);
|
|
125
|
+
};
|
|
126
|
+
const onDrop = (error) => {
|
|
127
|
+
connection = undefined;
|
|
128
|
+
hasDropped = true;
|
|
129
|
+
for (const subscription of subscriptions.values())
|
|
130
|
+
subscription.isSubscribed = false;
|
|
131
|
+
settleState();
|
|
132
|
+
for (const subscription of [...subscriptions.values()])
|
|
133
|
+
subscription.onError?.(error);
|
|
134
|
+
if (subscriptions.size > 0)
|
|
135
|
+
scheduleRetry();
|
|
136
|
+
};
|
|
137
|
+
const readHeaders = async () => typeof options.headers === "function" ? await options.headers() : (options.headers ?? {});
|
|
138
|
+
const request = async (body) => {
|
|
139
|
+
let headers;
|
|
140
|
+
try {
|
|
141
|
+
headers = await readHeaders();
|
|
142
|
+
}
|
|
143
|
+
catch (cause) {
|
|
144
|
+
return {
|
|
145
|
+
error: new RealtimeError("headers-failed", true, `the headers for the realtime handler could not be read: ${cause.message}`),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const controller = new AbortController();
|
|
149
|
+
const deadline = setTimeout(() => controller.abort(), requestDeadlineMilliseconds);
|
|
150
|
+
const unreachable = (cause, failure) => new RealtimeError("handler-unreachable", true, controller.signal.aborted
|
|
151
|
+
? `the realtime handler at ${options.url} did not answer within ${requestDeadlineMilliseconds}ms`
|
|
152
|
+
: `the realtime handler at ${options.url} ${failure}: ${cause.message}`);
|
|
153
|
+
try {
|
|
154
|
+
let response;
|
|
155
|
+
try {
|
|
156
|
+
response = await fetch(options.url, {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers: { ...headers, "content-type": "application/json" },
|
|
159
|
+
body: JSON.stringify(body),
|
|
160
|
+
signal: controller.signal,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch (cause) {
|
|
164
|
+
return { error: unreachable(cause, "could not be reached") };
|
|
165
|
+
}
|
|
166
|
+
if (!response.ok) {
|
|
167
|
+
return {
|
|
168
|
+
error: new RealtimeError("handler-refused", isRetriableStatus(response.status), `the realtime handler at ${options.url} answered ${response.status}`),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
return { answer: (await response.json()) };
|
|
173
|
+
}
|
|
174
|
+
catch (cause) {
|
|
175
|
+
return { error: unreachable(cause, "sent no JSON answer") };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
clearTimeout(deadline);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
const settleDenials = (live, denied) => {
|
|
183
|
+
for (const { i, code } of denied) {
|
|
184
|
+
const operation = live[i];
|
|
185
|
+
if (!operation)
|
|
186
|
+
continue;
|
|
187
|
+
failOperation(operation, new RealtimeError(code, retriableDenials.has(code), `the realtime handler denied the ${operation.kind} on "${readPattern(operation)}": ${code}`));
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
const settleGrants = (live, grants) => {
|
|
191
|
+
const granted = [];
|
|
192
|
+
for (const { i, wire, token } of grants) {
|
|
193
|
+
const operation = live[i];
|
|
194
|
+
if (operation?.kind === "publish") {
|
|
195
|
+
operation.settle();
|
|
196
|
+
}
|
|
197
|
+
else if (operation?.kind === "subscribe" && token) {
|
|
198
|
+
granted.push({ subscription: operation.subscription, wire, token });
|
|
199
|
+
}
|
|
200
|
+
else if (operation?.kind === "subscribe") {
|
|
201
|
+
failSubscription(operation.subscription, new RealtimeError("invalid-grant", false, `the realtime handler granted the subscribe on "${operation.subscription.pattern}" without a token`));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return granted;
|
|
205
|
+
};
|
|
206
|
+
const connect = async (answer, granted) => {
|
|
207
|
+
if (!answer.connect) {
|
|
208
|
+
scheduleRetry();
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
let opened;
|
|
212
|
+
try {
|
|
213
|
+
const transport = await loadTransport(answer.transport);
|
|
214
|
+
opened = await transport.connect({ url: answer.url, host: answer.host, token: answer.connect.token }, onDrop);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
for (const { subscription } of granted) {
|
|
218
|
+
failSubscription(subscription, error);
|
|
219
|
+
}
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
if (isClosed) {
|
|
223
|
+
opened.close();
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
connection = opened;
|
|
227
|
+
failedAttempts = 0;
|
|
228
|
+
hasDropped = false;
|
|
229
|
+
settleState();
|
|
230
|
+
return opened;
|
|
231
|
+
};
|
|
232
|
+
const subscribeGranted = async (opened, { subscription, wire, token }) => {
|
|
233
|
+
if (!isLive(subscription))
|
|
234
|
+
return;
|
|
235
|
+
try {
|
|
236
|
+
await opened.subscribe(subscription.id, wire, token, (text) => deliver(subscription, text));
|
|
237
|
+
subscription.isSubscribed = isLive(subscription);
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
if (connection === opened)
|
|
241
|
+
failSubscription(subscription, error);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
const send = async (batch) => {
|
|
245
|
+
const live = batch.filter((operation) => operation.kind === "publish" || isLive(operation.subscription));
|
|
246
|
+
if (live.length === 0)
|
|
247
|
+
return;
|
|
248
|
+
const needsConnection = !connection && live.some((operation) => operation.kind === "subscribe");
|
|
249
|
+
const result = await request({
|
|
250
|
+
...(needsConnection ? { connect: true } : {}),
|
|
251
|
+
ops: live.map(describeOperation),
|
|
252
|
+
});
|
|
253
|
+
if ("error" in result) {
|
|
254
|
+
for (const operation of live)
|
|
255
|
+
failOperation(operation, result.error);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
settleDenials(live, result.answer.denied);
|
|
259
|
+
const granted = settleGrants(live, result.answer.grants);
|
|
260
|
+
if (granted.length === 0)
|
|
261
|
+
return;
|
|
262
|
+
const opened = connection ?? (await connect(result.answer, granted));
|
|
263
|
+
if (!opened)
|
|
264
|
+
return;
|
|
265
|
+
await Promise.all(granted.map((grant) => subscribeGranted(opened, grant)));
|
|
266
|
+
};
|
|
267
|
+
const pump = async () => {
|
|
268
|
+
if (isPumping)
|
|
269
|
+
return;
|
|
270
|
+
isPumping = true;
|
|
271
|
+
await Promise.resolve();
|
|
272
|
+
try {
|
|
273
|
+
while (queue.length > 0 && !isClosed) {
|
|
274
|
+
sending = queue.splice(0, maxOperationsPerRequest);
|
|
275
|
+
await send(sending);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
finally {
|
|
279
|
+
sending = [];
|
|
280
|
+
isPumping = false;
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
const refuseClosed = () => new RealtimeError("closed", false, "the realtime client was closed");
|
|
284
|
+
return {
|
|
285
|
+
get state() {
|
|
286
|
+
return state;
|
|
287
|
+
},
|
|
288
|
+
onStateChange(listener) {
|
|
289
|
+
stateListeners.add(listener);
|
|
290
|
+
return () => stateListeners.delete(listener);
|
|
291
|
+
},
|
|
292
|
+
subscribe(pattern, subscribeOptions, handler) {
|
|
293
|
+
if (isClosed)
|
|
294
|
+
throw refuseClosed();
|
|
295
|
+
const subscription = {
|
|
296
|
+
id: `s${nextId++}`,
|
|
297
|
+
pattern,
|
|
298
|
+
params: dropUndefined(subscribeOptions.params),
|
|
299
|
+
handler: handler,
|
|
300
|
+
onError: subscribeOptions.onError,
|
|
301
|
+
isSubscribed: false,
|
|
302
|
+
};
|
|
303
|
+
subscriptions.set(subscription.id, subscription);
|
|
304
|
+
queue.push({ kind: "subscribe", subscription });
|
|
305
|
+
settleState();
|
|
306
|
+
void pump();
|
|
307
|
+
return () => {
|
|
308
|
+
if (!isLive(subscription))
|
|
309
|
+
return;
|
|
310
|
+
subscriptions.delete(subscription.id);
|
|
311
|
+
queue = queue.filter((operation) => operation.kind !== "subscribe" || operation.subscription !== subscription);
|
|
312
|
+
connection?.unsubscribe(subscription.id);
|
|
313
|
+
subscription.isSubscribed = false;
|
|
314
|
+
settleState();
|
|
315
|
+
};
|
|
316
|
+
},
|
|
317
|
+
publish(pattern, message) {
|
|
318
|
+
if (isClosed)
|
|
319
|
+
return Promise.reject(refuseClosed());
|
|
320
|
+
return new Promise((resolve, reject) => {
|
|
321
|
+
queue.push({
|
|
322
|
+
kind: "publish",
|
|
323
|
+
pattern,
|
|
324
|
+
params: dropUndefined(message.params),
|
|
325
|
+
body: message.body,
|
|
326
|
+
settle: (error) => (error ? reject(error) : resolve()),
|
|
327
|
+
});
|
|
328
|
+
void pump();
|
|
329
|
+
});
|
|
330
|
+
},
|
|
331
|
+
close() {
|
|
332
|
+
isClosed = true;
|
|
333
|
+
clearTimeout(retryTimer);
|
|
334
|
+
subscriptions.clear();
|
|
335
|
+
for (const operation of queue) {
|
|
336
|
+
if (operation.kind === "publish")
|
|
337
|
+
operation.settle(refuseClosed());
|
|
338
|
+
}
|
|
339
|
+
queue = [];
|
|
340
|
+
connection?.close();
|
|
341
|
+
connection = undefined;
|
|
342
|
+
settleState();
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { RequestHandler } from "express";
|
|
2
|
+
import { type RealtimeHandlerOptions } from "./handler.js";
|
|
3
|
+
import type { Realtime } from "./realtime.js";
|
|
4
|
+
export type { RealtimeHandlerOptions } from "./handler.js";
|
|
5
|
+
export * from "./index.js";
|
|
6
|
+
/**
|
|
7
|
+
* Serves `rt` to browsers from an Express route, mounted at any path:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* app.all("/api/realtime", createRealtimeHandler(rt));
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* It takes `POST` with `application/json` alone, serves its own origin and those in
|
|
14
|
+
* `allowedOrigins`, answers `Cache-Control: no-store`, and never sets a cookie.
|
|
15
|
+
* `authorize` receives the request as a Web `Request`, read whether or not a body
|
|
16
|
+
* parser ran first.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createRealtimeHandler(rt: Realtime<any, any>, options?: RealtimeHandlerOptions): RequestHandler;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { createWebRealtimeHandler, maxRequestBytes, } from "./handler.js";
|
|
2
|
+
export * from "./index.js";
|
|
3
|
+
function readBoundedStream(req) {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
const chunks = [];
|
|
6
|
+
let bytes = 0;
|
|
7
|
+
const finish = (error) => {
|
|
8
|
+
req.off("data", onData);
|
|
9
|
+
req.off("end", onEnd);
|
|
10
|
+
req.off("error", onError);
|
|
11
|
+
if (error)
|
|
12
|
+
reject(error);
|
|
13
|
+
else
|
|
14
|
+
resolve(Buffer.concat(chunks));
|
|
15
|
+
};
|
|
16
|
+
const onData = (chunk) => {
|
|
17
|
+
chunks.push(chunk);
|
|
18
|
+
bytes += chunk.byteLength;
|
|
19
|
+
if (bytes > maxRequestBytes)
|
|
20
|
+
finish();
|
|
21
|
+
};
|
|
22
|
+
const onEnd = () => finish();
|
|
23
|
+
const onError = (error) => finish(error);
|
|
24
|
+
req.on("data", onData);
|
|
25
|
+
req.on("end", onEnd);
|
|
26
|
+
req.on("error", onError);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async function readExpressBody(req) {
|
|
30
|
+
if (req.method === "GET" || req.method === "HEAD")
|
|
31
|
+
return undefined;
|
|
32
|
+
const parsed = req.body;
|
|
33
|
+
if (typeof parsed === "string" || Buffer.isBuffer(parsed))
|
|
34
|
+
return parsed;
|
|
35
|
+
if (req.readableEnded)
|
|
36
|
+
return parsed === undefined ? undefined : JSON.stringify(parsed);
|
|
37
|
+
return readBoundedStream(req);
|
|
38
|
+
}
|
|
39
|
+
async function convertToWebRequest(req) {
|
|
40
|
+
const headers = new Headers();
|
|
41
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
42
|
+
if (value === undefined)
|
|
43
|
+
continue;
|
|
44
|
+
for (const one of Array.isArray(value) ? value : [value])
|
|
45
|
+
headers.append(name, one);
|
|
46
|
+
}
|
|
47
|
+
const url = `${req.protocol}://${req.get("host") ?? "localhost"}${req.originalUrl}`;
|
|
48
|
+
return new Request(url, { method: req.method, headers, body: await readExpressBody(req) });
|
|
49
|
+
}
|
|
50
|
+
async function send(res, response) {
|
|
51
|
+
res.status(response.status);
|
|
52
|
+
response.headers.forEach((value, name) => {
|
|
53
|
+
res.setHeader(name, value);
|
|
54
|
+
});
|
|
55
|
+
res.end(Buffer.from(await response.arrayBuffer()));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Serves `rt` to browsers from an Express route, mounted at any path:
|
|
59
|
+
*
|
|
60
|
+
* ```ts
|
|
61
|
+
* app.all("/api/realtime", createRealtimeHandler(rt));
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* It takes `POST` with `application/json` alone, serves its own origin and those in
|
|
65
|
+
* `allowedOrigins`, answers `Cache-Control: no-store`, and never sets a cookie.
|
|
66
|
+
* `authorize` receives the request as a Web `Request`, read whether or not a body
|
|
67
|
+
* parser ran first.
|
|
68
|
+
*/
|
|
69
|
+
export function createRealtimeHandler(rt, options) {
|
|
70
|
+
const handler = createWebRealtimeHandler(rt, options);
|
|
71
|
+
return (req, res, next) => {
|
|
72
|
+
convertToWebRequest(req)
|
|
73
|
+
.then((request) => {
|
|
74
|
+
if (request.method === "POST")
|
|
75
|
+
return handler.POST(request);
|
|
76
|
+
if (request.method === "OPTIONS")
|
|
77
|
+
return handler.OPTIONS(request);
|
|
78
|
+
return handler.GET(request);
|
|
79
|
+
})
|
|
80
|
+
.then((response) => send(res, response))
|
|
81
|
+
.catch(next);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type Realtime } from "./realtime.js";
|
|
2
|
+
import { type Token } from "./token.js";
|
|
3
|
+
import { type Transport } from "./transport.js";
|
|
4
|
+
import { type WireRefusal } from "./wire.js";
|
|
5
|
+
/** How a realtime handler serves browsers. */
|
|
6
|
+
export interface RealtimeHandlerOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Origins besides the handler's own that may call it, such as `"https://app.example"`.
|
|
9
|
+
* Each is answered with CORS headers; without any, only the handler's own origin is
|
|
10
|
+
* served and no CORS header is ever sent.
|
|
11
|
+
*/
|
|
12
|
+
allowedOrigins?: string[];
|
|
13
|
+
}
|
|
14
|
+
/** The Web `Request` to `Response` handlers serving a realtime resource, one per method. */
|
|
15
|
+
export interface RealtimeHandler {
|
|
16
|
+
/** Serves a batch of `connect`, `subscribe` and `publish` requests. */
|
|
17
|
+
POST(request: Request): Promise<Response>;
|
|
18
|
+
/** Refuses with 405: the handler takes POST alone. */
|
|
19
|
+
GET(request: Request): Promise<Response>;
|
|
20
|
+
/** Answers the CORS preflight of an origin in `allowedOrigins`, and refuses any other. */
|
|
21
|
+
OPTIONS(request: Request): Promise<Response>;
|
|
22
|
+
}
|
|
23
|
+
/** The most bytes a realtime handler reads of a request body before refusing it with 413. */
|
|
24
|
+
export declare const maxRequestBytes: number;
|
|
25
|
+
/** Why the realtime handler denied one operation of a batch. */
|
|
26
|
+
export type RealtimeDenial = "invalid-op" | "unknown-op" | "unknown-pattern" | "no-publish-rule" | "invalid-params" | WireRefusal | "invalid-body" | "body-too-large" | "unauthenticated" | "forbidden" | "rule-error" | "publish-failed";
|
|
27
|
+
type Grant = {
|
|
28
|
+
i: number;
|
|
29
|
+
wire: string;
|
|
30
|
+
token?: string;
|
|
31
|
+
};
|
|
32
|
+
type Denial = {
|
|
33
|
+
i: number;
|
|
34
|
+
code: RealtimeDenial;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* What the realtime handler answers a batch with: the transport and socket URL, a connect
|
|
38
|
+
* token when the batch asked to connect, and for each operation, by its index `i`, either a
|
|
39
|
+
* grant carrying its wire channel (and a subscribe token for a subscribe) or a denial.
|
|
40
|
+
*/
|
|
41
|
+
export interface RealtimeBatchAnswer {
|
|
42
|
+
transport: Transport["name"];
|
|
43
|
+
url: string;
|
|
44
|
+
host?: string;
|
|
45
|
+
connect?: Token;
|
|
46
|
+
grants: Grant[];
|
|
47
|
+
denied: Denial[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Serves `rt` to browsers as Web `Request` to `Response` handlers, for a framework that
|
|
51
|
+
* speaks the Fetch API. It takes `POST` with `application/json` alone, serves its own
|
|
52
|
+
* origin and those in `allowedOrigins`, answers `Cache-Control: no-store`, and never sets
|
|
53
|
+
* a cookie.
|
|
54
|
+
*/
|
|
55
|
+
export declare function createWebRealtimeHandler(rt: Realtime, options?: RealtimeHandlerOptions): RealtimeHandler;
|
|
56
|
+
export {};
|