ocel 0.0.1-alpha.1 → 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,287 @@
|
|
|
1
|
+
import { encodeEvent, isParams, readRuntime, } from "./realtime.js";
|
|
2
|
+
import { mintToken } from "./token.js";
|
|
3
|
+
import { resolveTransport } from "./transport.js";
|
|
4
|
+
import { encodeWireChannel } from "./wire.js";
|
|
5
|
+
/** The most bytes a realtime handler reads of a request body before refusing it with 413. */
|
|
6
|
+
export const maxRequestBytes = 1024 * 1024;
|
|
7
|
+
const maxOperations = 50;
|
|
8
|
+
const operationKeys = new Set(["op", "pattern", "params", "body"]);
|
|
9
|
+
function respond(status, body, headers = {}) {
|
|
10
|
+
return new Response(body === undefined ? null : JSON.stringify(body), {
|
|
11
|
+
status,
|
|
12
|
+
headers: {
|
|
13
|
+
"cache-control": "no-store",
|
|
14
|
+
...(body === undefined ? {} : { "content-type": "application/json" }),
|
|
15
|
+
...headers,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function isPlainObject(value) {
|
|
20
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
21
|
+
}
|
|
22
|
+
function readFirstHeaderValue(request, name) {
|
|
23
|
+
return request.headers.get(name)?.split(",")[0]?.trim() || undefined;
|
|
24
|
+
}
|
|
25
|
+
function readOwnOrigin(request) {
|
|
26
|
+
const url = new URL(request.url);
|
|
27
|
+
return {
|
|
28
|
+
scheme: readFirstHeaderValue(request, "x-forwarded-proto")?.toLowerCase() ??
|
|
29
|
+
url.protocol.slice(0, -1),
|
|
30
|
+
host: readFirstHeaderValue(request, "x-forwarded-host") ??
|
|
31
|
+
(request.headers.get("host") || url.host),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function resolveSocketURL(url, request) {
|
|
35
|
+
if (!url.startsWith("/"))
|
|
36
|
+
return url;
|
|
37
|
+
const own = readOwnOrigin(request);
|
|
38
|
+
return `${own.scheme === "https" ? "wss" : "ws"}://${own.host}${url}`;
|
|
39
|
+
}
|
|
40
|
+
function judgeOrigin(request, allowedOrigins) {
|
|
41
|
+
const origin = request.headers.get("origin");
|
|
42
|
+
if (origin === null)
|
|
43
|
+
return { allowed: true, cors: {} };
|
|
44
|
+
if (allowedOrigins.includes(origin)) {
|
|
45
|
+
return {
|
|
46
|
+
allowed: true,
|
|
47
|
+
cors: { "access-control-allow-origin": origin, vary: "Origin" },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
let url;
|
|
51
|
+
try {
|
|
52
|
+
url = new URL(origin);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return { allowed: false };
|
|
56
|
+
}
|
|
57
|
+
const own = readOwnOrigin(request);
|
|
58
|
+
return url.host !== "" && url.protocol === `${own.scheme}:` && url.host === own.host
|
|
59
|
+
? { allowed: true, cors: {} }
|
|
60
|
+
: { allowed: false };
|
|
61
|
+
}
|
|
62
|
+
function readSubject(auth) {
|
|
63
|
+
const id = auth?.id;
|
|
64
|
+
return typeof id === "string" || typeof id === "number" ? String(id) : "anonymous";
|
|
65
|
+
}
|
|
66
|
+
async function readBoundedText(request) {
|
|
67
|
+
const reader = request.body?.getReader();
|
|
68
|
+
if (!reader)
|
|
69
|
+
return "";
|
|
70
|
+
const chunks = [];
|
|
71
|
+
let bytes = 0;
|
|
72
|
+
for (;;) {
|
|
73
|
+
const { done, value } = await reader.read();
|
|
74
|
+
if (done)
|
|
75
|
+
break;
|
|
76
|
+
bytes += value.byteLength;
|
|
77
|
+
if (bytes > maxRequestBytes) {
|
|
78
|
+
await reader.cancel();
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
chunks.push(value);
|
|
82
|
+
}
|
|
83
|
+
const joined = new Uint8Array(bytes);
|
|
84
|
+
let offset = 0;
|
|
85
|
+
for (const chunk of chunks) {
|
|
86
|
+
joined.set(chunk, offset);
|
|
87
|
+
offset += chunk.byteLength;
|
|
88
|
+
}
|
|
89
|
+
return new TextDecoder().decode(joined);
|
|
90
|
+
}
|
|
91
|
+
function copyRequest(request, text) {
|
|
92
|
+
return new Request(request, { body: text });
|
|
93
|
+
}
|
|
94
|
+
function parseBatch(text) {
|
|
95
|
+
let value;
|
|
96
|
+
try {
|
|
97
|
+
value = JSON.parse(text);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
if (!isPlainObject(value))
|
|
103
|
+
return undefined;
|
|
104
|
+
if (Object.keys(value).some((key) => key !== "connect" && key !== "ops"))
|
|
105
|
+
return undefined;
|
|
106
|
+
const { connect, ops: operations } = value;
|
|
107
|
+
if (!Array.isArray(operations) || (connect !== undefined && typeof connect !== "boolean")) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
return { connect: connect ?? false, operations };
|
|
111
|
+
}
|
|
112
|
+
async function runRule(rule) {
|
|
113
|
+
try {
|
|
114
|
+
return (await rule()) ? "allowed" : "forbidden";
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return "rule-error";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function evaluateOperation(batch, value, i) {
|
|
121
|
+
const deny = (code) => ({ denied: { i, code } });
|
|
122
|
+
if (!isPlainObject(value) ||
|
|
123
|
+
Object.keys(value).some((key) => !operationKeys.has(key)) ||
|
|
124
|
+
(value.op === "subscribe" && Object.hasOwn(value, "body"))) {
|
|
125
|
+
return deny("invalid-op");
|
|
126
|
+
}
|
|
127
|
+
const operation = value.op;
|
|
128
|
+
if (operation !== "subscribe" && operation !== "publish")
|
|
129
|
+
return deny("unknown-op");
|
|
130
|
+
const channel = typeof value.pattern === "string" ? batch.runtime.channels.get(value.pattern) : undefined;
|
|
131
|
+
if (!channel)
|
|
132
|
+
return deny("unknown-pattern");
|
|
133
|
+
if (operation === "publish" && !channel.publish)
|
|
134
|
+
return deny("no-publish-rule");
|
|
135
|
+
const params = value.params ?? {};
|
|
136
|
+
if (!isParams(params))
|
|
137
|
+
return deny("invalid-params");
|
|
138
|
+
const wire = encodeWireChannel(batch.runtime.name, channel.pattern, params, operation === "subscribe" && channel.wildcard);
|
|
139
|
+
if ("refused" in wire)
|
|
140
|
+
return deny(wire.refused);
|
|
141
|
+
return operation === "subscribe"
|
|
142
|
+
? evaluateSubscribe(batch, channel, params, wire.channel, i)
|
|
143
|
+
: evaluatePublish(batch, channel, params, wire.channel, value, i);
|
|
144
|
+
}
|
|
145
|
+
async function evaluateSubscribe(batch, channel, params, wire, i) {
|
|
146
|
+
const rule = channel.subscribe;
|
|
147
|
+
if (rule !== "public") {
|
|
148
|
+
if (batch.auth === null)
|
|
149
|
+
return { denied: { i, code: "unauthenticated" } };
|
|
150
|
+
const request = copyRequest(batch.request, batch.text);
|
|
151
|
+
const verdict = await runRule(() => rule({ auth: batch.auth, params, request }));
|
|
152
|
+
if (verdict !== "allowed")
|
|
153
|
+
return { denied: { i, code: verdict } };
|
|
154
|
+
}
|
|
155
|
+
const { token } = mintToken(batch.properties, batch.runtime, batch.subject, "subscribe", wire);
|
|
156
|
+
return { grant: { i, wire, token } };
|
|
157
|
+
}
|
|
158
|
+
async function evaluatePublish(batch, channel, params, wire, operation, i) {
|
|
159
|
+
const rule = channel.publish;
|
|
160
|
+
if (!rule)
|
|
161
|
+
return { denied: { i, code: "no-publish-rule" } };
|
|
162
|
+
if (!Object.hasOwn(operation, "body"))
|
|
163
|
+
return { denied: { i, code: "invalid-body" } };
|
|
164
|
+
const event = await encodeEvent(channel, wire, operation.body);
|
|
165
|
+
if ("refused" in event)
|
|
166
|
+
return { denied: { i, code: event.refused } };
|
|
167
|
+
if (batch.auth === null)
|
|
168
|
+
return { denied: { i, code: "unauthenticated" } };
|
|
169
|
+
const request = copyRequest(batch.request, batch.text);
|
|
170
|
+
const verdict = await runRule(() => rule({ auth: batch.auth, params, body: event.body, request }));
|
|
171
|
+
if (verdict !== "allowed")
|
|
172
|
+
return { denied: { i, code: verdict } };
|
|
173
|
+
try {
|
|
174
|
+
await batch.runtime.publish("handler", event.channel, event.envelope);
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return { denied: { i, code: "publish-failed" } };
|
|
178
|
+
}
|
|
179
|
+
return { grant: { i, wire } };
|
|
180
|
+
}
|
|
181
|
+
function isOperationNamed(value, name) {
|
|
182
|
+
return isPlainObject(value) && value.op === name;
|
|
183
|
+
}
|
|
184
|
+
function evaluateOperations(batch, operations) {
|
|
185
|
+
let publishing = Promise.resolve();
|
|
186
|
+
return Promise.all(operations.map((value, i) => {
|
|
187
|
+
if (!isOperationNamed(value, "publish"))
|
|
188
|
+
return evaluateOperation(batch, value, i);
|
|
189
|
+
const outcome = publishing.then(() => evaluateOperation(batch, value, i));
|
|
190
|
+
publishing = outcome.catch(() => undefined);
|
|
191
|
+
return outcome;
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
async function serveBatch(runtime, request, cors) {
|
|
195
|
+
const mediaType = request.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase();
|
|
196
|
+
if (mediaType !== "application/json") {
|
|
197
|
+
return respond(415, { error: "the realtime handler takes application/json" }, cors);
|
|
198
|
+
}
|
|
199
|
+
const tooLarge = () => respond(413, { error: `a request is at most ${maxRequestBytes} bytes` }, cors);
|
|
200
|
+
if (Number(request.headers.get("content-length") ?? 0) > maxRequestBytes)
|
|
201
|
+
return tooLarge();
|
|
202
|
+
const text = await readBoundedText(request);
|
|
203
|
+
if (text === undefined)
|
|
204
|
+
return tooLarge();
|
|
205
|
+
const parsed = parseBatch(text);
|
|
206
|
+
if (!parsed) {
|
|
207
|
+
return respond(400, { error: "the body is { connect?: boolean, ops: [{ op, pattern, params?, body? }] }" }, cors);
|
|
208
|
+
}
|
|
209
|
+
if (parsed.operations.length > maxOperations) {
|
|
210
|
+
return respond(400, { error: `a request holds at most ${maxOperations} ops` }, cors);
|
|
211
|
+
}
|
|
212
|
+
const properties = runtime.properties("handler");
|
|
213
|
+
const transport = resolveTransport(properties, runtime.name);
|
|
214
|
+
let auth = null;
|
|
215
|
+
if (runtime.authorize) {
|
|
216
|
+
try {
|
|
217
|
+
auth = (await runtime.authorize(copyRequest(request, text))) ?? null;
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return respond(500, { error: "authorize failed" }, cors);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const batch = {
|
|
224
|
+
runtime,
|
|
225
|
+
text,
|
|
226
|
+
request,
|
|
227
|
+
auth,
|
|
228
|
+
subject: readSubject(auth),
|
|
229
|
+
properties,
|
|
230
|
+
transport,
|
|
231
|
+
};
|
|
232
|
+
const connect = parsed.connect
|
|
233
|
+
? mintToken(properties, runtime, batch.subject, "connect", `/${runtime.name}`)
|
|
234
|
+
: undefined;
|
|
235
|
+
const outcomes = await evaluateOperations(batch, parsed.operations);
|
|
236
|
+
const answer = {
|
|
237
|
+
transport: transport.name,
|
|
238
|
+
url: resolveSocketURL(properties.url, request),
|
|
239
|
+
...(transport.host === undefined ? {} : { host: transport.host }),
|
|
240
|
+
...(connect === undefined ? {} : { connect }),
|
|
241
|
+
grants: outcomes.flatMap((outcome) => ("grant" in outcome ? [outcome.grant] : [])),
|
|
242
|
+
denied: outcomes.flatMap((outcome) => ("denied" in outcome ? [outcome.denied] : [])),
|
|
243
|
+
};
|
|
244
|
+
return respond(200, answer, cors);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Serves `rt` to browsers as Web `Request` to `Response` handlers, for a framework that
|
|
248
|
+
* speaks the Fetch API. It takes `POST` with `application/json` alone, serves its own
|
|
249
|
+
* origin and those in `allowedOrigins`, answers `Cache-Control: no-store`, and never sets
|
|
250
|
+
* a cookie.
|
|
251
|
+
*/
|
|
252
|
+
export function createWebRealtimeHandler(rt, options = {}) {
|
|
253
|
+
const runtime = readRuntime(rt);
|
|
254
|
+
const allowedOrigins = options.allowedOrigins ?? [];
|
|
255
|
+
const respondMethodNotAllowed = () => respond(405, { error: "the realtime handler takes POST" }, { allow: "POST" });
|
|
256
|
+
return {
|
|
257
|
+
async POST(request) {
|
|
258
|
+
let cors = {};
|
|
259
|
+
try {
|
|
260
|
+
const verdict = judgeOrigin(request, allowedOrigins);
|
|
261
|
+
if (!verdict.allowed) {
|
|
262
|
+
return respond(403, { error: "this origin may not call the realtime handler" });
|
|
263
|
+
}
|
|
264
|
+
cors = verdict.cors;
|
|
265
|
+
return await serveBatch(runtime, request, cors);
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
return respond(500, { error: "the realtime handler failed" }, cors);
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
async GET() {
|
|
272
|
+
return respondMethodNotAllowed();
|
|
273
|
+
},
|
|
274
|
+
async OPTIONS(request) {
|
|
275
|
+
const origin = request.headers.get("origin");
|
|
276
|
+
if (origin === null || !allowedOrigins.includes(origin))
|
|
277
|
+
return respondMethodNotAllowed();
|
|
278
|
+
return respond(204, undefined, {
|
|
279
|
+
"access-control-allow-origin": origin,
|
|
280
|
+
"access-control-allow-methods": "POST",
|
|
281
|
+
"access-control-allow-headers": "authorization, content-type",
|
|
282
|
+
"access-control-max-age": "600",
|
|
283
|
+
vary: "Origin",
|
|
284
|
+
});
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
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
|
+
/** A Hono handler serving a realtime resource. */
|
|
7
|
+
export type HonoRealtimeHandler = (c: Context) => Promise<Response>;
|
|
8
|
+
/**
|
|
9
|
+
* Serves `rt` to browsers from a Hono route, mounted at any path:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* app.all("/api/realtime", createRealtimeHandler(rt));
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* It takes `POST` with `application/json` alone, serves its own origin and those in
|
|
16
|
+
* `allowedOrigins`, answers `Cache-Control: no-store`, and never sets a cookie.
|
|
17
|
+
* `authorize` receives the request as a Web `Request`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createRealtimeHandler(rt: Realtime<any, any>, options?: RealtimeHandlerOptions): HonoRealtimeHandler;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createWebRealtimeHandler } from "./handler.js";
|
|
2
|
+
export * from "./index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Serves `rt` to browsers from a Hono route, mounted at any path:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* app.all("/api/realtime", createRealtimeHandler(rt));
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* It takes `POST` with `application/json` alone, serves its own origin and those in
|
|
11
|
+
* `allowedOrigins`, answers `Cache-Control: no-store`, and never sets a cookie.
|
|
12
|
+
* `authorize` receives the request as a Web `Request`.
|
|
13
|
+
*/
|
|
14
|
+
export function createRealtimeHandler(rt, options) {
|
|
15
|
+
const handler = createWebRealtimeHandler(rt, options);
|
|
16
|
+
return (c) => {
|
|
17
|
+
const request = c.req.raw;
|
|
18
|
+
if (request.method === "POST")
|
|
19
|
+
return handler.POST(request);
|
|
20
|
+
if (request.method === "OPTIONS")
|
|
21
|
+
return handler.OPTIONS(request);
|
|
22
|
+
return handler.GET(request);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { UnprovisionedResourceError } from "../binding/unprovisioned.js";
|
|
2
|
+
export { type ChannelParams, type ChannelRules, type ChannelShape, type PublishContext, type PublishMessage, type PublishRefusal, Realtime, type RealtimeOptions, RealtimePublishError, realtime, type SubscribeContext, } from "./realtime.js";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { NextRequest } from "next/server";
|
|
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
|
+
/** The route handlers a Next.js `route.ts` exports to serve a realtime resource. */
|
|
7
|
+
export interface NextRealtimeHandlers {
|
|
8
|
+
/** Serves a batch of `connect`, `subscribe` and `publish` requests. */
|
|
9
|
+
POST: (request: NextRequest | Request) => Promise<Response>;
|
|
10
|
+
/** Refuses with 405: the handler takes POST alone. */
|
|
11
|
+
GET: (request: NextRequest | Request) => Promise<Response>;
|
|
12
|
+
/** Answers the CORS preflight of an origin in `allowedOrigins`, and refuses any other. */
|
|
13
|
+
OPTIONS: (request: NextRequest | Request) => Promise<Response>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Serves `rt` to browsers from a Next.js route handler, mounted at any path:
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* // app/api/realtime/route.ts
|
|
20
|
+
* export const { GET, POST, OPTIONS } = createRealtimeHandler(rt);
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* It takes `POST` with `application/json` alone, serves its own origin and those in
|
|
24
|
+
* `allowedOrigins`, answers `Cache-Control: no-store`, and never sets a cookie.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createRealtimeHandler(rt: Realtime<any, any>, options?: RealtimeHandlerOptions): NextRealtimeHandlers;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createWebRealtimeHandler } from "./handler.js";
|
|
2
|
+
export * from "./index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Serves `rt` to browsers from a Next.js route handler, mounted at any path:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* // app/api/realtime/route.ts
|
|
8
|
+
* export const { GET, POST, OPTIONS } = createRealtimeHandler(rt);
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* It takes `POST` with `application/json` alone, serves its own origin and those in
|
|
12
|
+
* `allowedOrigins`, answers `Cache-Control: no-store`, and never sets a cookie.
|
|
13
|
+
*/
|
|
14
|
+
export function createRealtimeHandler(rt, options) {
|
|
15
|
+
return createWebRealtimeHandler(rt, options);
|
|
16
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
import { type Duration } from "../delivery/duration.js";
|
|
3
|
+
import type { RealtimeProperties } from "../gen/proto/common/bindings/v1/bindings_pb.js";
|
|
4
|
+
import type { PatternParameters } from "../kv/pattern.js";
|
|
5
|
+
import type { TokenIssuer } from "./token.js";
|
|
6
|
+
import { type ChannelPattern, type WireRefusal } from "./wire.js";
|
|
7
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
8
|
+
/** One value for each `:parameter` of a pattern such as `"orders/:orderId"`. */
|
|
9
|
+
export type ChannelParams<TPattern extends string> = {
|
|
10
|
+
[Name in PatternParameters<TPattern>]: string;
|
|
11
|
+
};
|
|
12
|
+
/** What a `subscribe` rule decides from: the caller's auth, the channel's params and the request. */
|
|
13
|
+
export interface SubscribeContext<TAuth, TParams> {
|
|
14
|
+
/** What `authorize` returned for this request. */
|
|
15
|
+
auth: TAuth;
|
|
16
|
+
/** The params the caller asked for; on a `wildcard` pattern, trailing ones may be missing. */
|
|
17
|
+
params: TParams;
|
|
18
|
+
/** The request the realtime handler received. */
|
|
19
|
+
request: Request;
|
|
20
|
+
}
|
|
21
|
+
/** What a `publish` rule decides from: a {@link SubscribeContext} and the event's body. */
|
|
22
|
+
export interface PublishContext<TAuth, TParams, TBody> extends SubscribeContext<TAuth, TParams> {
|
|
23
|
+
/** The event the caller asked to publish, as its schema parsed it. */
|
|
24
|
+
body: TBody;
|
|
25
|
+
}
|
|
26
|
+
/** The parts of a channel's declaration its rules are typed from. */
|
|
27
|
+
export interface ChannelShape {
|
|
28
|
+
/** A Standard Schema every event on the channel is validated against. */
|
|
29
|
+
schema: StandardSchemaV1;
|
|
30
|
+
/** Lets a subscriber leave off trailing params, receiving every channel under the rest. */
|
|
31
|
+
wildcard?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* `"public"`, which anyone may subscribe to, or a rule answering whether a caller may;
|
|
34
|
+
* a rule needs `authorize` on the resource.
|
|
35
|
+
*/
|
|
36
|
+
subscribe: unknown;
|
|
37
|
+
/**
|
|
38
|
+
* A rule answering whether a caller may publish from a browser; it needs `authorize` on
|
|
39
|
+
* the resource. Without it, only the server publishes.
|
|
40
|
+
*/
|
|
41
|
+
publish?: unknown;
|
|
42
|
+
}
|
|
43
|
+
type SubscribeParams<TPattern extends string, TChannel> = TChannel extends {
|
|
44
|
+
wildcard: true;
|
|
45
|
+
} ? Partial<ChannelParams<TPattern>> : ChannelParams<TPattern>;
|
|
46
|
+
type BodyOf<TChannel> = TChannel extends {
|
|
47
|
+
schema: infer TSchema extends StandardSchemaV1;
|
|
48
|
+
} ? StandardSchemaV1.InferOutput<TSchema> : never;
|
|
49
|
+
type BodyInputOf<TChannel> = TChannel extends {
|
|
50
|
+
schema: infer TSchema extends StandardSchemaV1;
|
|
51
|
+
} ? StandardSchemaV1.InferInput<TSchema> : never;
|
|
52
|
+
/** The access rules of one channel pattern. */
|
|
53
|
+
export interface ChannelRules<TAuth, TPattern extends string, TChannel> {
|
|
54
|
+
/** `"public"`, which anyone may subscribe to, or a rule answering whether this caller may. */
|
|
55
|
+
subscribe: "public" | ((context: SubscribeContext<TAuth, SubscribeParams<TPattern, TChannel>>) => MaybePromise<boolean>);
|
|
56
|
+
/** A rule answering whether this caller may publish from a browser; without it, only the server publishes. */
|
|
57
|
+
publish?: (context: PublishContext<TAuth, ChannelParams<TPattern>, BodyOf<TChannel>>) => MaybePromise<boolean>;
|
|
58
|
+
}
|
|
59
|
+
/** How a realtime resource is declared. */
|
|
60
|
+
export interface RealtimeOptions<TAuth, TChannels extends Record<string, ChannelShape>> {
|
|
61
|
+
/**
|
|
62
|
+
* Identifies the caller of the realtime handler from its request, with whatever auth the
|
|
63
|
+
* app already uses, and answers `null` for nobody. Its result is `auth` in every rule;
|
|
64
|
+
* its `id`, when a string or number, is the `sub` of every token minted for the caller.
|
|
65
|
+
*/
|
|
66
|
+
authorize?: (request: Request) => MaybePromise<TAuth | null | undefined>;
|
|
67
|
+
/** How long each minted token lives, from 10 to 300 seconds; without it, 60 seconds. */
|
|
68
|
+
tokenTtl?: Duration;
|
|
69
|
+
/**
|
|
70
|
+
* The channel patterns, each at most 4 `/`-separated segments that are each a literal
|
|
71
|
+
* or a `:param`, mapped to the event schema and access rules of every channel it names.
|
|
72
|
+
*/
|
|
73
|
+
channels: {
|
|
74
|
+
[TPattern in keyof TChannels]: {
|
|
75
|
+
[Key in keyof TChannels[TPattern]]: TChannels[TPattern][Key];
|
|
76
|
+
} & ChannelRules<TAuth, TPattern & string, TChannels[TPattern]>;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** The `{ params, body }` a publish on `TPattern` takes. */
|
|
80
|
+
export type PublishMessage<TPattern extends string, TChannel> = [
|
|
81
|
+
PatternParameters<TPattern>
|
|
82
|
+
] extends [never] ? {
|
|
83
|
+
params?: Record<string, never>;
|
|
84
|
+
body: BodyInputOf<TChannel>;
|
|
85
|
+
} : {
|
|
86
|
+
params: ChannelParams<TPattern>;
|
|
87
|
+
body: BodyInputOf<TChannel>;
|
|
88
|
+
};
|
|
89
|
+
/** A channel pattern as the resource declared it, its rules untyped. */
|
|
90
|
+
export interface DeclaredChannel {
|
|
91
|
+
pattern: ChannelPattern;
|
|
92
|
+
schema: StandardSchemaV1;
|
|
93
|
+
wildcard: boolean;
|
|
94
|
+
subscribe: "public" | ((context: SubscribeContext<unknown, unknown>) => MaybePromise<boolean>);
|
|
95
|
+
publish?: (context: PublishContext<unknown, unknown, unknown>) => MaybePromise<boolean>;
|
|
96
|
+
}
|
|
97
|
+
/** What the realtime handler and publish read of a declared resource. */
|
|
98
|
+
export interface RealtimeRuntime extends TokenIssuer {
|
|
99
|
+
channels: Map<string, DeclaredChannel>;
|
|
100
|
+
authorize?: (request: Request) => MaybePromise<unknown>;
|
|
101
|
+
properties(access: string): RealtimeProperties;
|
|
102
|
+
/** Hands the ocel runtime `envelope` to publish on the wire `channel`. */
|
|
103
|
+
publish(access: string, channel: string, envelope: string): Promise<void>;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Why `publish` refused an event: the pattern was never declared, the params are not all
|
|
107
|
+
* strings, a param is missing, unknown, empty or over 30 bytes, the body fails the
|
|
108
|
+
* pattern's schema, or the event is over 240 KiB.
|
|
109
|
+
*/
|
|
110
|
+
export type PublishRefusal = "unknown-pattern" | "invalid-params" | "invalid-body" | "body-too-large" | WireRefusal;
|
|
111
|
+
/** Thrown by `publish` when the pattern, params or body cannot be published, naming why. */
|
|
112
|
+
export declare class RealtimePublishError extends Error {
|
|
113
|
+
name: string;
|
|
114
|
+
/**
|
|
115
|
+
* The reason: `unknown-pattern`, `invalid-params`, `missing-param`, `unknown-param`,
|
|
116
|
+
* `empty-value`, `value-too-long`, `invalid-body` or `body-too-large`.
|
|
117
|
+
*/
|
|
118
|
+
readonly code: PublishRefusal;
|
|
119
|
+
constructor(code: PublishRefusal, message: string);
|
|
120
|
+
}
|
|
121
|
+
/** Reads what the realtime handler serves of `rt`, throwing for one `realtime()` did not declare. */
|
|
122
|
+
export declare function readRuntime(rt: Realtime): RealtimeRuntime;
|
|
123
|
+
/** A declared realtime resource, and the handle its channels are published through. */
|
|
124
|
+
export declare class Realtime<TAuth = unknown, TChannels extends Record<string, ChannelShape> = Record<string, ChannelShape>> {
|
|
125
|
+
/** The name this resource was declared under, which begins every channel. */
|
|
126
|
+
readonly name: string;
|
|
127
|
+
/** Type-only: the auth `authorize` answers and the declared channels, for a client's types. */
|
|
128
|
+
readonly types?: {
|
|
129
|
+
auth: TAuth;
|
|
130
|
+
channels: TChannels;
|
|
131
|
+
};
|
|
132
|
+
constructor(name: string, options: RealtimeOptions<TAuth, TChannels>);
|
|
133
|
+
/**
|
|
134
|
+
* Publishes `body` on the channel `params` fill in `pattern`, to every subscriber, after
|
|
135
|
+
* validating it against the pattern's schema. Every param is required. Throws a
|
|
136
|
+
* {@link RealtimePublishError} for a pattern, params or body that cannot be published.
|
|
137
|
+
*/
|
|
138
|
+
publish<TPattern extends keyof TChannels & string>(pattern: TPattern, message: PublishMessage<TPattern, TChannels[TPattern]>): Promise<void>;
|
|
139
|
+
}
|
|
140
|
+
/** An event ready for the runtime to publish, or why it cannot be published. */
|
|
141
|
+
export type PreparedEvent = {
|
|
142
|
+
channel: string;
|
|
143
|
+
body: unknown;
|
|
144
|
+
envelope: string;
|
|
145
|
+
} | {
|
|
146
|
+
refused: PublishRefusal;
|
|
147
|
+
reason: string;
|
|
148
|
+
};
|
|
149
|
+
/** Whether `params` is an object mapping each name to a string. */
|
|
150
|
+
export declare function isParams(params: unknown): params is Record<string, string>;
|
|
151
|
+
/**
|
|
152
|
+
* Validates `body` against `channel`'s schema and encodes the envelope it is published in
|
|
153
|
+
* on `wire`, refusing a body the schema rejects or an event over 240 KiB.
|
|
154
|
+
*/
|
|
155
|
+
export declare function encodeEvent(channel: DeclaredChannel, wire: string, body: unknown): Promise<PreparedEvent>;
|
|
156
|
+
/** Prepares the event the server publishes on `pattern`, or says why it cannot. */
|
|
157
|
+
export declare function prepareEvent(runtime: RealtimeRuntime, pattern: string, params: unknown, body: unknown): Promise<PreparedEvent>;
|
|
158
|
+
/**
|
|
159
|
+
* Declares a realtime resource named `name`: typed channels that browsers subscribe to
|
|
160
|
+
* and the server publishes on, each pattern with the rules that decide who may.
|
|
161
|
+
*
|
|
162
|
+
* ```ts
|
|
163
|
+
* export const rt = realtime("app", {
|
|
164
|
+
* authorize: async (req) => (await getSession(req)) ?? null,
|
|
165
|
+
* channels: {
|
|
166
|
+
* "orders/:orderId": {
|
|
167
|
+
* schema: OrderEvent,
|
|
168
|
+
* subscribe: async ({ auth, params }) => ownsOrder(auth, params.orderId),
|
|
169
|
+
* },
|
|
170
|
+
* status: { schema: Status, subscribe: "public" },
|
|
171
|
+
* },
|
|
172
|
+
* });
|
|
173
|
+
*
|
|
174
|
+
* await rt.publish("orders/:orderId", { params: { orderId }, body: { status: "shipped" } });
|
|
175
|
+
* ```
|
|
176
|
+
*
|
|
177
|
+
* Serve it to browsers with `createRealtimeHandler` from `ocel/realtime/next`,
|
|
178
|
+
* `ocel/realtime/express` or `ocel/realtime/hono`.
|
|
179
|
+
*/
|
|
180
|
+
export declare function realtime<TAuth, TChannels extends Record<string, ChannelShape>>(name: string, options: RealtimeOptions<TAuth, TChannels>): Realtime<TAuth, TChannels>;
|
|
181
|
+
export {};
|