@bunworks/inngest-realtime 0.1.0 → 0.1.5
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/CHANGELOG.md +6 -6
- package/README.md +1 -1
- package/_virtual/_rolldown/runtime.cjs +29 -0
- package/api.cjs +45 -0
- package/api.cjs.map +1 -0
- package/api.mjs +44 -0
- package/api.mjs.map +1 -0
- package/channel.cjs +67 -0
- package/channel.cjs.map +1 -0
- package/channel.d.cts +19 -0
- package/channel.d.cts.map +1 -0
- package/channel.d.mts +19 -0
- package/channel.d.mts.map +1 -0
- package/channel.mjs +66 -0
- package/channel.mjs.map +1 -0
- package/env.cjs +72 -0
- package/env.cjs.map +1 -0
- package/env.mjs +71 -0
- package/env.mjs.map +1 -0
- package/hooks.cjs +147 -0
- package/hooks.cjs.map +1 -0
- package/hooks.d.cts +70 -0
- package/hooks.d.cts.map +1 -0
- package/hooks.d.mts +70 -0
- package/hooks.d.mts.map +1 -0
- package/hooks.mjs +144 -0
- package/hooks.mjs.map +1 -0
- package/index.cjs +19 -0
- package/index.d.cts +5 -0
- package/index.d.mts +6 -0
- package/index.mjs +7 -0
- package/middleware.cjs +41 -0
- package/middleware.cjs.map +1 -0
- package/middleware.d.cts +29 -0
- package/middleware.d.cts.map +1 -0
- package/middleware.d.mts +29 -0
- package/middleware.d.mts.map +1 -0
- package/middleware.mjs +34 -0
- package/middleware.mjs.map +1 -0
- package/package.json +6 -5
- package/subscribe/StreamFanout.cjs +47 -0
- package/subscribe/StreamFanout.cjs.map +1 -0
- package/subscribe/StreamFanout.mjs +46 -0
- package/subscribe/StreamFanout.mjs.map +1 -0
- package/subscribe/TokenSubscription.cjs +438 -0
- package/subscribe/TokenSubscription.cjs.map +1 -0
- package/subscribe/TokenSubscription.mjs +436 -0
- package/subscribe/TokenSubscription.mjs.map +1 -0
- package/subscribe/helpers.cjs +41 -0
- package/subscribe/helpers.cjs.map +1 -0
- package/subscribe/helpers.d.cts +64 -0
- package/subscribe/helpers.d.cts.map +1 -0
- package/subscribe/helpers.d.mts +64 -0
- package/subscribe/helpers.d.mts.map +1 -0
- package/subscribe/helpers.mjs +40 -0
- package/subscribe/helpers.mjs.map +1 -0
- package/subscribe/index.cjs +1 -0
- package/subscribe/index.d.mts +1 -0
- package/subscribe/index.mjs +3 -0
- package/topic.cjs +30 -0
- package/topic.cjs.map +1 -0
- package/topic.d.cts +19 -0
- package/topic.d.cts.map +1 -0
- package/topic.d.mts +19 -0
- package/topic.d.mts.map +1 -0
- package/topic.mjs +28 -0
- package/topic.mjs.map +1 -0
- package/types.cjs +45 -0
- package/types.cjs.map +1 -0
- package/types.d.cts +241 -0
- package/types.d.cts.map +1 -0
- package/types.d.mts +241 -0
- package/types.d.mts.map +1 -0
- package/types.mjs +39 -0
- package/types.mjs.map +1 -0
- package/util.cjs +72 -0
- package/util.cjs.map +1 -0
- package/util.mjs +69 -0
- package/util.mjs.map +1 -0
- package/CLAUDE.md +0 -69
- package/bun.lock +0 -527
- package/eslint.config.mjs +0 -19
- package/src/api.ts +0 -83
- package/src/channel.ts +0 -122
- package/src/env.d.ts +0 -15
- package/src/env.ts +0 -152
- package/src/hooks.ts +0 -256
- package/src/index.test.ts +0 -840
- package/src/index.ts +0 -4
- package/src/middleware.ts +0 -68
- package/src/subscribe/StreamFanout.ts +0 -82
- package/src/subscribe/TokenSubscription.ts +0 -524
- package/src/subscribe/helpers.ts +0 -153
- package/src/subscribe/index.ts +0 -1
- package/src/topic.ts +0 -51
- package/src/types.ts +0 -499
- package/src/util.ts +0 -104
- package/tsconfig.build.json +0 -7
- package/tsconfig.json +0 -114
- package/tsdown.config.ts +0 -18
- package/vitest.config.ts +0 -22
package/eslint.config.mjs
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import pluginJs from "@eslint/js";
|
|
2
|
-
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
|
|
3
|
-
import globals from "globals";
|
|
4
|
-
import tseslint from "typescript-eslint";
|
|
5
|
-
|
|
6
|
-
/** @type {import('eslint').Linter.Config[]} */
|
|
7
|
-
export default [
|
|
8
|
-
{ files: ["**/*.{js,mjs,cjs,ts}"] },
|
|
9
|
-
{ languageOptions: { globals: globals.browser } },
|
|
10
|
-
pluginJs.configs.recommended,
|
|
11
|
-
...tseslint.configs.recommended,
|
|
12
|
-
eslintPluginPrettierRecommended,
|
|
13
|
-
{
|
|
14
|
-
rules: {
|
|
15
|
-
"@typescript-eslint/no-namespace": "off",
|
|
16
|
-
"@typescript-eslint/ban-types": "off",
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
];
|
package/src/api.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { getEnvVar } from "./env";
|
|
3
|
-
import { fetchWithAuthFallback, parseAsBoolean } from "./util";
|
|
4
|
-
|
|
5
|
-
const tokenSchema = z.object({ jwt: z.string() });
|
|
6
|
-
|
|
7
|
-
export const api = {
|
|
8
|
-
async getSubscriptionToken({
|
|
9
|
-
channel,
|
|
10
|
-
topics,
|
|
11
|
-
signingKey,
|
|
12
|
-
signingKeyFallback,
|
|
13
|
-
apiBaseUrl,
|
|
14
|
-
}: {
|
|
15
|
-
channel: string;
|
|
16
|
-
topics: string[];
|
|
17
|
-
signingKey: string | undefined;
|
|
18
|
-
signingKeyFallback: string | undefined;
|
|
19
|
-
apiBaseUrl: string | undefined;
|
|
20
|
-
}): Promise<string> {
|
|
21
|
-
let url: URL;
|
|
22
|
-
const path = "/v1/realtime/token";
|
|
23
|
-
const inputBaseUrl =
|
|
24
|
-
apiBaseUrl ||
|
|
25
|
-
getEnvVar("BUNWORKS_BASE_URL") ||
|
|
26
|
-
getEnvVar("BUNWORKS_API_BASE_URL");
|
|
27
|
-
|
|
28
|
-
const devEnvVar = getEnvVar("BUNWORKS_DEV");
|
|
29
|
-
|
|
30
|
-
if (inputBaseUrl) {
|
|
31
|
-
url = new URL(path, inputBaseUrl);
|
|
32
|
-
} else if (devEnvVar) {
|
|
33
|
-
try {
|
|
34
|
-
const devUrl = new URL(devEnvVar);
|
|
35
|
-
url = new URL(path, devUrl);
|
|
36
|
-
} catch {
|
|
37
|
-
if (parseAsBoolean(devEnvVar)) {
|
|
38
|
-
url = new URL(path, "http://localhost:8288/");
|
|
39
|
-
} else {
|
|
40
|
-
url = new URL(path, "https://api.inngest.com/");
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
} else {
|
|
44
|
-
url = new URL(
|
|
45
|
-
path,
|
|
46
|
-
getEnvVar("NODE_ENV") === "production"
|
|
47
|
-
? "https://api.inngest.com/"
|
|
48
|
-
: "http://localhost:8288/",
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const body = topics.map((topic) => ({
|
|
53
|
-
channel,
|
|
54
|
-
name: topic,
|
|
55
|
-
kind: "run",
|
|
56
|
-
}));
|
|
57
|
-
|
|
58
|
-
const res = await fetchWithAuthFallback({
|
|
59
|
-
authToken: signingKey,
|
|
60
|
-
authTokenFallback: signingKeyFallback,
|
|
61
|
-
fetch,
|
|
62
|
-
url,
|
|
63
|
-
options: {
|
|
64
|
-
method: "POST",
|
|
65
|
-
body: JSON.stringify(body),
|
|
66
|
-
headers: {
|
|
67
|
-
"Content-Type": "application/json",
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
if (!res.ok) {
|
|
73
|
-
throw new Error(
|
|
74
|
-
`Не удалось получить токен подписки: ${res.status} ${
|
|
75
|
-
res.statusText
|
|
76
|
-
} - ${await res.text()}`,
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const data = await res.json();
|
|
81
|
-
return tokenSchema.parse(data).jwt;
|
|
82
|
-
},
|
|
83
|
-
};
|
package/src/channel.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { topic } from "./topic";
|
|
2
|
-
import { type Realtime } from "./types";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* TODO
|
|
6
|
-
*/
|
|
7
|
-
export const channel: Realtime.Channel.Builder = (
|
|
8
|
-
/**
|
|
9
|
-
* TODO
|
|
10
|
-
*/
|
|
11
|
-
id,
|
|
12
|
-
) => {
|
|
13
|
-
// eslint-disable-next-line prefer-const, @typescript-eslint/no-explicit-any
|
|
14
|
-
let channelDefinition: any;
|
|
15
|
-
const topics: Record<string, Realtime.Topic.Definition> = {};
|
|
16
|
-
|
|
17
|
-
const builder = (...args: unknown[]) => {
|
|
18
|
-
const finalId: string = typeof id === "string" ? id : id(...args);
|
|
19
|
-
|
|
20
|
-
const topicsFns = Object.entries(topics).reduce<
|
|
21
|
-
Record<string, (data: unknown) => Promise<Realtime.Message.Input>>
|
|
22
|
-
>((acc, [name, topic]) => {
|
|
23
|
-
acc[name] = createTopicFn(finalId, topic);
|
|
24
|
-
|
|
25
|
-
return acc;
|
|
26
|
-
}, {});
|
|
27
|
-
|
|
28
|
-
const channel: Realtime.Channel = {
|
|
29
|
-
name: finalId,
|
|
30
|
-
topics,
|
|
31
|
-
...topicsFns,
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
return channel;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const extras: Record<string, unknown> = {
|
|
38
|
-
topics,
|
|
39
|
-
addTopic: (topic: Realtime.Topic.Definition) => {
|
|
40
|
-
topics[topic.name] = topic;
|
|
41
|
-
|
|
42
|
-
return channelDefinition;
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
channelDefinition = Object.assign(builder, extras);
|
|
47
|
-
|
|
48
|
-
return channelDefinition;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* TODO
|
|
53
|
-
*/
|
|
54
|
-
export const typeOnlyChannel = <
|
|
55
|
-
TChannelDef extends Realtime.Channel.Definition,
|
|
56
|
-
TId extends string = Realtime.Channel.Definition.InferId<TChannelDef>,
|
|
57
|
-
TTopics extends Record<
|
|
58
|
-
string,
|
|
59
|
-
Realtime.Topic.Definition
|
|
60
|
-
> = Realtime.Channel.Definition.InferTopics<TChannelDef>,
|
|
61
|
-
TOutput extends Realtime.Channel = Realtime.Channel<TId, TTopics>,
|
|
62
|
-
>(
|
|
63
|
-
/**
|
|
64
|
-
* TODO
|
|
65
|
-
*/
|
|
66
|
-
id: TId,
|
|
67
|
-
) => {
|
|
68
|
-
const blankChannel = {
|
|
69
|
-
...channel(id),
|
|
70
|
-
topics: new Proxy(
|
|
71
|
-
{},
|
|
72
|
-
{
|
|
73
|
-
get: (target, prop) => {
|
|
74
|
-
if (prop in target) {
|
|
75
|
-
return target[prop as keyof typeof target];
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (typeof prop === "string") {
|
|
79
|
-
return topic(prop);
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
),
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const ch = new Proxy(blankChannel, {
|
|
87
|
-
get: (target, prop) => {
|
|
88
|
-
if (prop in target) {
|
|
89
|
-
return target[prop as keyof typeof target];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (typeof prop === "string") {
|
|
93
|
-
return createTopicFn(id, topic(prop));
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
return ch as unknown as TOutput;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const createTopicFn = (channelId: string, topic: Realtime.Topic.Definition) => {
|
|
102
|
-
return async (data: unknown) => {
|
|
103
|
-
const schema = topic.getSchema();
|
|
104
|
-
if (schema) {
|
|
105
|
-
try {
|
|
106
|
-
await schema["~standard"].validate(data);
|
|
107
|
-
} catch (err) {
|
|
108
|
-
console.error(
|
|
109
|
-
`Failed schema validation for channel "${channelId}" topic "${topic.name}":`,
|
|
110
|
-
err,
|
|
111
|
-
);
|
|
112
|
-
throw new Error("Failed schema validation");
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
channel: channelId,
|
|
118
|
-
topic: topic.name,
|
|
119
|
-
data,
|
|
120
|
-
};
|
|
121
|
-
};
|
|
122
|
-
};
|
package/src/env.d.ts
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
// For Vite
|
|
2
|
-
interface ImportMeta {
|
|
3
|
-
env: {
|
|
4
|
-
INNGEST_DEV?: string;
|
|
5
|
-
VITE_INNGEST_DEV?: string;
|
|
6
|
-
MODE: "development" | "production";
|
|
7
|
-
VITE_MODE: "development" | "production";
|
|
8
|
-
INNGEST_BASE_URL?: string;
|
|
9
|
-
VITE_INNGEST_BASE_URL?: string;
|
|
10
|
-
INNGEST_API_BASE_URL?: string;
|
|
11
|
-
VITE_INNGEST_API_BASE_URL?: string;
|
|
12
|
-
INNGEST_SIGNING_KEY?: string;
|
|
13
|
-
INNGEST_SIGNING_KEY_FALLBACK?: string;
|
|
14
|
-
};
|
|
15
|
-
}
|
package/src/env.ts
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
export type EnvValue = string | undefined;
|
|
2
|
-
export type Env = Record<string, EnvValue>;
|
|
3
|
-
|
|
4
|
-
export type ExpectedEnv = {
|
|
5
|
-
BUNWORKS_DEV: string | undefined;
|
|
6
|
-
NODE_ENV: string | undefined;
|
|
7
|
-
BUNWORKS_BASE_URL: string | undefined;
|
|
8
|
-
BUNWORKS_API_BASE_URL: string | undefined;
|
|
9
|
-
BUNWORKS_SIGNING_KEY: string | undefined;
|
|
10
|
-
BUNWORKS_SIGNING_KEY_FALLBACK: string | undefined;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* The environment variables that we wish to access in the environment.
|
|
15
|
-
*
|
|
16
|
-
* Due to the way that some environment variables are exposed across different
|
|
17
|
-
* runtimes and bundling tools, we need to be careful about how we access them.
|
|
18
|
-
*
|
|
19
|
-
* The most basic annoyance is that environment variables are exposed in
|
|
20
|
-
* different locations (e.g. `process.env`, `Deno.env`, `Netlify.env`,
|
|
21
|
-
* `import.meta.env`).
|
|
22
|
-
*
|
|
23
|
-
* Bundling can be more disruptive though, where some will literally
|
|
24
|
-
* find/replace `process.env.MY_VAR` with the value of `MY_VAR` at build time,
|
|
25
|
-
* which requires us to ensure that the full env var is used in code instead of
|
|
26
|
-
* dynamically building it.
|
|
27
|
-
*/
|
|
28
|
-
const env: ExpectedEnv | undefined = (() => {
|
|
29
|
-
// Pure vite
|
|
30
|
-
try {
|
|
31
|
-
// @ts-expect-error - import.meta only available in some environments
|
|
32
|
-
const viteEnv = import.meta.env;
|
|
33
|
-
|
|
34
|
-
if (viteEnv) {
|
|
35
|
-
return {
|
|
36
|
-
BUNWORKS_DEV: viteEnv.BUNWORKS_DEV ?? viteEnv.VITE_BUNWORKS_DEV,
|
|
37
|
-
NODE_ENV: viteEnv.NODE_ENV,
|
|
38
|
-
BUNWORKS_BASE_URL:
|
|
39
|
-
viteEnv.BUNWORKS_BASE_URL ?? viteEnv.VITE_BUNWORKS_BASE_URL,
|
|
40
|
-
BUNWORKS_API_BASE_URL:
|
|
41
|
-
viteEnv.BUNWORKS_API_BASE_URL ?? viteEnv.VITE_BUNWORKS_API_BASE_URL,
|
|
42
|
-
BUNWORKS_SIGNING_KEY: viteEnv.BUNWORKS_SIGNING_KEY,
|
|
43
|
-
BUNWORKS_SIGNING_KEY_FALLBACK: viteEnv.BUNWORKS_SIGNING_KEY_FALLBACK,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
} catch {
|
|
47
|
-
// noop
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
// Node-like environments (sometimes polyfilled Vite)
|
|
52
|
-
if (process.env) {
|
|
53
|
-
return {
|
|
54
|
-
BUNWORKS_DEV:
|
|
55
|
-
process.env.BUNWORKS_DEV ??
|
|
56
|
-
process.env.NEXT_PUBLIC_BUNWORKS_DEV ??
|
|
57
|
-
process.env.REACT_APP_BUNWORKS_DEV ??
|
|
58
|
-
process.env.NUXT_PUBLIC_BUNWORKS_DEV ??
|
|
59
|
-
process.env.VUE_APP_BUNWORKS_DEV ??
|
|
60
|
-
process.env.VITE_BUNWORKS_DEV,
|
|
61
|
-
|
|
62
|
-
NODE_ENV:
|
|
63
|
-
process.env.NODE_ENV ??
|
|
64
|
-
process.env.NEXT_PUBLIC_NODE_ENV ??
|
|
65
|
-
process.env.REACT_APP_NODE_ENV ??
|
|
66
|
-
process.env.NUXT_PUBLIC_NODE_ENV ??
|
|
67
|
-
process.env.VUE_APP_NODE_ENV ??
|
|
68
|
-
process.env.VITE_NODE_ENV ??
|
|
69
|
-
process.env.VITE_MODE,
|
|
70
|
-
|
|
71
|
-
BUNWORKS_BASE_URL:
|
|
72
|
-
process.env.BUNWORKS_BASE_URL ??
|
|
73
|
-
process.env.NEXT_PUBLIC_BUNWORKS_BASE_URL ??
|
|
74
|
-
process.env.REACT_APP_BUNWORKS_BASE_URL ??
|
|
75
|
-
process.env.NUXT_PUBLIC_BUNWORKS_BASE_URL ??
|
|
76
|
-
process.env.VUE_APP_BUNWORKS_BASE_URL ??
|
|
77
|
-
process.env.VITE_BUNWORKS_BASE_URL,
|
|
78
|
-
|
|
79
|
-
BUNWORKS_API_BASE_URL:
|
|
80
|
-
process.env.BUNWORKS_API_BASE_URL ??
|
|
81
|
-
process.env.NEXT_PUBLIC_BUNWORKS_API_BASE_URL ??
|
|
82
|
-
process.env.REACT_APP_BUNWORKS_API_BASE_URL ??
|
|
83
|
-
process.env.NUXT_PUBLIC_BUNWORKS_API_BASE_URL ??
|
|
84
|
-
process.env.VUE_APP_BUNWORKS_API_BASE_URL ??
|
|
85
|
-
process.env.VITE_BUNWORKS_API_BASE_URL,
|
|
86
|
-
|
|
87
|
-
BUNWORKS_SIGNING_KEY: process.env.BUNWORKS_SIGNING_KEY,
|
|
88
|
-
|
|
89
|
-
BUNWORKS_SIGNING_KEY_FALLBACK: process.env.BUNWORKS_SIGNING_KEY_FALLBACK,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
} catch {
|
|
93
|
-
// noop
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Deno
|
|
97
|
-
try {
|
|
98
|
-
const denoEnv = Deno.env.toObject();
|
|
99
|
-
|
|
100
|
-
if (denoEnv) {
|
|
101
|
-
return {
|
|
102
|
-
BUNWORKS_DEV: denoEnv.BUNWORKS_DEV,
|
|
103
|
-
NODE_ENV: denoEnv.NODE_ENV,
|
|
104
|
-
BUNWORKS_BASE_URL: denoEnv.BUNWORKS_BASE_URL,
|
|
105
|
-
BUNWORKS_API_BASE_URL: denoEnv.BUNWORKS_API_BASE_URL,
|
|
106
|
-
BUNWORKS_SIGNING_KEY: denoEnv.BUNWORKS_SIGNING_KEY,
|
|
107
|
-
BUNWORKS_SIGNING_KEY_FALLBACK: denoEnv.BUNWORKS_SIGNING_KEY_FALLBACK,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
} catch {
|
|
111
|
-
// noop
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Netlify
|
|
115
|
-
try {
|
|
116
|
-
const netlifyEnv = Netlify.env.toObject();
|
|
117
|
-
|
|
118
|
-
if (netlifyEnv) {
|
|
119
|
-
return {
|
|
120
|
-
BUNWORKS_DEV: netlifyEnv.BUNWORKS_DEV,
|
|
121
|
-
NODE_ENV: netlifyEnv.NODE_ENV,
|
|
122
|
-
BUNWORKS_BASE_URL: netlifyEnv.BUNWORKS_BASE_URL,
|
|
123
|
-
BUNWORKS_API_BASE_URL: netlifyEnv.BUNWORKS_API_BASE_URL,
|
|
124
|
-
BUNWORKS_SIGNING_KEY: netlifyEnv.BUNWORKS_SIGNING_KEY,
|
|
125
|
-
BUNWORKS_SIGNING_KEY_FALLBACK: netlifyEnv.BUNWORKS_SIGNING_KEY_FALLBACK,
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
} catch {
|
|
129
|
-
// noop
|
|
130
|
-
}
|
|
131
|
-
})();
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* The Deno environment, which is not always available.
|
|
135
|
-
*/
|
|
136
|
-
declare const Deno: {
|
|
137
|
-
env: { toObject: () => Env };
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* The Netlify environment, which is not always available.
|
|
142
|
-
*/
|
|
143
|
-
declare const Netlify: {
|
|
144
|
-
env: { toObject: () => Env };
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Given a `key`, get the environment variable under that key.
|
|
149
|
-
*/
|
|
150
|
-
export const getEnvVar = (key: keyof ExpectedEnv): string | undefined => {
|
|
151
|
-
return env?.[key];
|
|
152
|
-
};
|
package/src/hooks.ts
DELETED
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from "react";
|
|
2
|
-
import { subscribe } from "./subscribe";
|
|
3
|
-
import { type Realtime } from "./types";
|
|
4
|
-
|
|
5
|
-
export enum InngestSubscriptionState {
|
|
6
|
-
Closed = "closed",
|
|
7
|
-
Error = "error",
|
|
8
|
-
RefreshingToken = "refresh_token",
|
|
9
|
-
Connecting = "connecting",
|
|
10
|
-
Active = "active",
|
|
11
|
-
Closing = "closing",
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* TODO
|
|
16
|
-
*/
|
|
17
|
-
export interface InngestSubscription<TToken extends Realtime.Subscribe.Token> {
|
|
18
|
-
/**
|
|
19
|
-
* TODO
|
|
20
|
-
*/
|
|
21
|
-
data: Realtime.Subscribe.Token.InferMessage<TToken>[];
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* TODO
|
|
25
|
-
*/
|
|
26
|
-
latestData: Realtime.Subscribe.Token.InferMessage<TToken> | null;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* TODO
|
|
30
|
-
*/
|
|
31
|
-
freshData: Realtime.Subscribe.Token.InferMessage<TToken>[];
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* TODO
|
|
35
|
-
*/
|
|
36
|
-
error: Error | null;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* TODO
|
|
40
|
-
*/
|
|
41
|
-
state: InngestSubscriptionState;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* TODO
|
|
46
|
-
*/
|
|
47
|
-
export function useInngestSubscription<
|
|
48
|
-
const TToken extends Realtime.Subscribe.Token | null | undefined,
|
|
49
|
-
>({
|
|
50
|
-
token: tokenInput,
|
|
51
|
-
refreshToken,
|
|
52
|
-
key,
|
|
53
|
-
enabled = true,
|
|
54
|
-
bufferInterval = 0,
|
|
55
|
-
}: {
|
|
56
|
-
/**
|
|
57
|
-
* TODO
|
|
58
|
-
*/
|
|
59
|
-
token?: TToken;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* TODO
|
|
63
|
-
*/
|
|
64
|
-
refreshToken?: () => Promise<TToken>;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* TODO
|
|
68
|
-
*/
|
|
69
|
-
key?: string;
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* TODO
|
|
73
|
-
*/
|
|
74
|
-
enabled?: boolean;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* TODO
|
|
78
|
-
*/
|
|
79
|
-
bufferInterval?: number;
|
|
80
|
-
}): InngestSubscription<NonNullable<TToken>> {
|
|
81
|
-
const [token, setToken] = useState<TToken | null | undefined>(tokenInput);
|
|
82
|
-
const [data, setData] = useState<Realtime.Message[]>([]);
|
|
83
|
-
const [freshData, setFreshData] = useState<Realtime.Message[]>([]);
|
|
84
|
-
const [error, setError] = useState<Error | null>(null);
|
|
85
|
-
const [state, setState] = useState<InngestSubscriptionState>(
|
|
86
|
-
InngestSubscriptionState.Closed,
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
const subscriptionRef = useRef<Realtime.Subscribe.StreamSubscription | null>(
|
|
90
|
-
null,
|
|
91
|
-
);
|
|
92
|
-
const readerRef =
|
|
93
|
-
useRef<ReadableStreamDefaultReader<Realtime.Message> | null>(null);
|
|
94
|
-
const messageBuffer = useRef<Realtime.Message[]>([]);
|
|
95
|
-
const bufferIntervalRef = useRef<number>(bufferInterval);
|
|
96
|
-
|
|
97
|
-
// Sync token if tokenInput prop changes
|
|
98
|
-
useEffect(() => {
|
|
99
|
-
if (tokenInput) setToken(tokenInput);
|
|
100
|
-
}, [tokenInput]);
|
|
101
|
-
|
|
102
|
-
// Token fetch fallback on mount
|
|
103
|
-
useEffect(() => {
|
|
104
|
-
if (!token && enabled) {
|
|
105
|
-
if (refreshToken) {
|
|
106
|
-
setState(InngestSubscriptionState.RefreshingToken);
|
|
107
|
-
refreshToken()
|
|
108
|
-
.then((newToken) => setToken(newToken))
|
|
109
|
-
.catch((err) => {
|
|
110
|
-
setError(err);
|
|
111
|
-
setState(InngestSubscriptionState.Error);
|
|
112
|
-
});
|
|
113
|
-
} else {
|
|
114
|
-
setError(new Error("No token provided and no refreshToken handler."));
|
|
115
|
-
setState(InngestSubscriptionState.Error);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}, []);
|
|
119
|
-
|
|
120
|
-
// Subscription management
|
|
121
|
-
useEffect(() => {
|
|
122
|
-
setError(null);
|
|
123
|
-
if (!enabled || !token) return;
|
|
124
|
-
let cancelled = false;
|
|
125
|
-
|
|
126
|
-
const start = async () => {
|
|
127
|
-
try {
|
|
128
|
-
setState(InngestSubscriptionState.Connecting);
|
|
129
|
-
const stream = await subscribe({ ...token });
|
|
130
|
-
if (cancelled) return;
|
|
131
|
-
|
|
132
|
-
subscriptionRef.current = stream;
|
|
133
|
-
setState(InngestSubscriptionState.Active);
|
|
134
|
-
|
|
135
|
-
// Explicitly get and manage the reader so that we can manually release
|
|
136
|
-
// the lock if anything goes wrong or we're done with it.
|
|
137
|
-
//
|
|
138
|
-
// Especially when this is unmounted.
|
|
139
|
-
const reader = stream.getReader();
|
|
140
|
-
readerRef.current = reader;
|
|
141
|
-
try {
|
|
142
|
-
while (!cancelled) {
|
|
143
|
-
const { done, value } = await reader.read();
|
|
144
|
-
if (done || cancelled) break;
|
|
145
|
-
|
|
146
|
-
if (bufferIntervalRef.current === 0) {
|
|
147
|
-
setFreshData([value]);
|
|
148
|
-
setData((prev) => [...prev, value]);
|
|
149
|
-
} else {
|
|
150
|
-
messageBuffer.current.push(value);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
} finally {
|
|
154
|
-
try {
|
|
155
|
-
reader.releaseLock();
|
|
156
|
-
} catch {
|
|
157
|
-
// Reader might already be released
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
readerRef.current = null;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Stream has closed cleanly
|
|
164
|
-
if (!cancelled) {
|
|
165
|
-
setState(InngestSubscriptionState.Closed);
|
|
166
|
-
if (enabled) start();
|
|
167
|
-
}
|
|
168
|
-
} catch (err) {
|
|
169
|
-
if (cancelled) return;
|
|
170
|
-
if (refreshToken) {
|
|
171
|
-
setState(InngestSubscriptionState.RefreshingToken);
|
|
172
|
-
refreshToken()
|
|
173
|
-
.then((newToken) => setToken(newToken))
|
|
174
|
-
.catch((e) => {
|
|
175
|
-
setError(e);
|
|
176
|
-
setState(InngestSubscriptionState.Error);
|
|
177
|
-
});
|
|
178
|
-
} else {
|
|
179
|
-
setError(err as Error);
|
|
180
|
-
setState(InngestSubscriptionState.Error);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
start();
|
|
186
|
-
|
|
187
|
-
return () => {
|
|
188
|
-
cancelled = true;
|
|
189
|
-
|
|
190
|
-
const cleanup = async () => {
|
|
191
|
-
const readerToRemove = readerRef.current;
|
|
192
|
-
const subToRemove = subscriptionRef.current;
|
|
193
|
-
|
|
194
|
-
readerRef.current = null;
|
|
195
|
-
subscriptionRef.current = null;
|
|
196
|
-
|
|
197
|
-
try {
|
|
198
|
-
await readerToRemove?.cancel();
|
|
199
|
-
} catch {
|
|
200
|
-
// Reader might already be cancelled
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
readerToRemove?.releaseLock();
|
|
205
|
-
} catch {
|
|
206
|
-
// Reader might already be released
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
try {
|
|
210
|
-
await subToRemove?.cancel();
|
|
211
|
-
} catch {
|
|
212
|
-
// Subscription might already be cancelled
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
cleanup()
|
|
217
|
-
.catch((err) => {
|
|
218
|
-
console.error("Error cleaning up Inngest subscription", err);
|
|
219
|
-
})
|
|
220
|
-
.finally(() => {
|
|
221
|
-
// Ensure state is always updated even if cleanup fails
|
|
222
|
-
setState(InngestSubscriptionState.Closed);
|
|
223
|
-
});
|
|
224
|
-
};
|
|
225
|
-
}, [token, enabled, key]);
|
|
226
|
-
|
|
227
|
-
// Buffer flushing
|
|
228
|
-
useEffect(() => {
|
|
229
|
-
bufferIntervalRef.current = bufferInterval;
|
|
230
|
-
let interval: NodeJS.Timeout | null = null;
|
|
231
|
-
|
|
232
|
-
if (bufferInterval > 0) {
|
|
233
|
-
interval = setInterval(() => {
|
|
234
|
-
if (messageBuffer.current.length > 0) {
|
|
235
|
-
const buffered = [...messageBuffer.current];
|
|
236
|
-
messageBuffer.current = [];
|
|
237
|
-
|
|
238
|
-
setFreshData(buffered);
|
|
239
|
-
setData((prev) => [...prev, ...buffered]);
|
|
240
|
-
}
|
|
241
|
-
}, bufferInterval);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return () => {
|
|
245
|
-
if (interval) clearInterval(interval);
|
|
246
|
-
};
|
|
247
|
-
}, [bufferInterval]);
|
|
248
|
-
|
|
249
|
-
return {
|
|
250
|
-
data,
|
|
251
|
-
latestData: data[data.length - 1] ?? null,
|
|
252
|
-
freshData,
|
|
253
|
-
error,
|
|
254
|
-
state,
|
|
255
|
-
} as unknown as InngestSubscription<NonNullable<TToken>>;
|
|
256
|
-
}
|