@portalshq/channel-registry 0.1.0
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/README.md +3 -0
- package/dist/index.d.ts +58 -0
- package/dist/index.js +51 -0
- package/package.json +15 -0
package/README.md
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
declare const channel: z.ZodObject<{
|
|
3
|
+
controlEndpoint: z.ZodString;
|
|
4
|
+
queueTokenEnv: z.ZodString;
|
|
5
|
+
requiredEntities: z.ZodArray<z.ZodString>;
|
|
6
|
+
youtube: z.ZodOptional<z.ZodObject<{
|
|
7
|
+
liveChatId: z.ZodString;
|
|
8
|
+
clientIdEnv: z.ZodString;
|
|
9
|
+
clientSecretEnv: z.ZodString;
|
|
10
|
+
refreshTokenEnv: z.ZodString;
|
|
11
|
+
}, z.core.$strip>>;
|
|
12
|
+
twitch: z.ZodOptional<z.ZodObject<{
|
|
13
|
+
broadcasterUserId: z.ZodString;
|
|
14
|
+
userId: z.ZodString;
|
|
15
|
+
clientIdEnv: z.ZodString;
|
|
16
|
+
clientSecretEnv: z.ZodString;
|
|
17
|
+
refreshTokenEnv: z.ZodString;
|
|
18
|
+
}, z.core.$strip>>;
|
|
19
|
+
}, z.core.$strip>;
|
|
20
|
+
declare const registrySchema: z.ZodObject<{
|
|
21
|
+
channels: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
22
|
+
controlEndpoint: z.ZodString;
|
|
23
|
+
queueTokenEnv: z.ZodString;
|
|
24
|
+
requiredEntities: z.ZodArray<z.ZodString>;
|
|
25
|
+
youtube: z.ZodOptional<z.ZodObject<{
|
|
26
|
+
liveChatId: z.ZodString;
|
|
27
|
+
clientIdEnv: z.ZodString;
|
|
28
|
+
clientSecretEnv: z.ZodString;
|
|
29
|
+
refreshTokenEnv: z.ZodString;
|
|
30
|
+
}, z.core.$strip>>;
|
|
31
|
+
twitch: z.ZodOptional<z.ZodObject<{
|
|
32
|
+
broadcasterUserId: z.ZodString;
|
|
33
|
+
userId: z.ZodString;
|
|
34
|
+
clientIdEnv: z.ZodString;
|
|
35
|
+
clientSecretEnv: z.ZodString;
|
|
36
|
+
refreshTokenEnv: z.ZodString;
|
|
37
|
+
}, z.core.$strip>>;
|
|
38
|
+
}, z.core.$strip>>;
|
|
39
|
+
entities: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
40
|
+
id: z.ZodString;
|
|
41
|
+
}, z.core.$loose>>>;
|
|
42
|
+
entitiesFetchedAt: z.ZodOptional<z.ZodString>;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
export type Channel = z.infer<typeof channel>;
|
|
45
|
+
export type EntityManifest = {
|
|
46
|
+
id: string;
|
|
47
|
+
} & Record<string, unknown>;
|
|
48
|
+
export type RegistryData = z.infer<typeof registrySchema>;
|
|
49
|
+
export type ChannelRegistry = {
|
|
50
|
+
get(channelId: string): Channel;
|
|
51
|
+
manifests(channelId: string): readonly EntityManifest[];
|
|
52
|
+
refresh(): Promise<void>;
|
|
53
|
+
};
|
|
54
|
+
export declare function openChannelRegistry(options: {
|
|
55
|
+
filePath: string;
|
|
56
|
+
resolveManifests(uris: readonly string[]): Promise<readonly EntityManifest[]>;
|
|
57
|
+
}): ChannelRegistry;
|
|
58
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
const secretRef = z.string().regex(/^[A-Z][A-Z0-9_]*$/);
|
|
5
|
+
const entityUri = z.string().regex(/^px:\/\/[\w.-]+\/[\w.-]+\/[\w.-]+$/);
|
|
6
|
+
const channel = z.object({
|
|
7
|
+
controlEndpoint: z.string().url(),
|
|
8
|
+
queueTokenEnv: secretRef,
|
|
9
|
+
requiredEntities: z.array(entityUri).min(1),
|
|
10
|
+
youtube: z.object({ liveChatId: z.string().min(1), clientIdEnv: secretRef, clientSecretEnv: secretRef, refreshTokenEnv: secretRef }).optional(),
|
|
11
|
+
twitch: z.object({ broadcasterUserId: z.string().min(1), userId: z.string().min(1), clientIdEnv: secretRef, clientSecretEnv: secretRef, refreshTokenEnv: secretRef }).optional(),
|
|
12
|
+
});
|
|
13
|
+
const registrySchema = z.object({
|
|
14
|
+
channels: z.record(z.string().regex(/^[\w.-]+$/), channel),
|
|
15
|
+
entities: z.record(entityUri, z.object({ id: entityUri }).passthrough()).default({}),
|
|
16
|
+
entitiesFetchedAt: z.string().datetime().optional(),
|
|
17
|
+
});
|
|
18
|
+
export function openChannelRegistry(options) {
|
|
19
|
+
let current;
|
|
20
|
+
function requireCurrent() {
|
|
21
|
+
if (!current)
|
|
22
|
+
throw new Error("Channel registry has not been refreshed.");
|
|
23
|
+
return current;
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
get(channelId) {
|
|
27
|
+
const value = requireCurrent().channels[channelId];
|
|
28
|
+
if (!value)
|
|
29
|
+
throw new Error(`Unknown channel: ${channelId}`);
|
|
30
|
+
return value;
|
|
31
|
+
},
|
|
32
|
+
manifests(channelId) {
|
|
33
|
+
const registry = requireCurrent();
|
|
34
|
+
return this.get(channelId).requiredEntities.map((uri) => registry.entities[uri]).filter((entity) => Boolean(entity));
|
|
35
|
+
},
|
|
36
|
+
async refresh() {
|
|
37
|
+
const filePath = path.resolve(options.filePath);
|
|
38
|
+
const parsed = registrySchema.parse(JSON.parse(await readFile(filePath, "utf8")));
|
|
39
|
+
const uris = [...new Set(Object.values(parsed.channels).flatMap((value) => value.requiredEntities))];
|
|
40
|
+
const manifests = await options.resolveManifests(uris);
|
|
41
|
+
if (manifests.length !== uris.length)
|
|
42
|
+
throw new Error("Every required PX entity must resolve before the registry can refresh.");
|
|
43
|
+
const entities = Object.fromEntries(manifests.map((manifest) => [manifest.id, manifest]));
|
|
44
|
+
const refreshed = { ...parsed, entities, entitiesFetchedAt: new Date().toISOString() };
|
|
45
|
+
const temporary = `${filePath}.${process.pid}.tmp`;
|
|
46
|
+
await writeFile(temporary, `${JSON.stringify(refreshed, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
47
|
+
await rename(temporary, filePath);
|
|
48
|
+
current = refreshed;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@portalshq/channel-registry",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"repository": { "type": "git", "url": "git+https://github.com/portalshq/portals-cloud.git", "directory": "packages/channel-registry" },
|
|
5
|
+
"description": "Validated, PX-refreshed deployment-local channel registries.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } },
|
|
10
|
+
"files": ["dist", "README.md"],
|
|
11
|
+
"scripts": { "build": "rm -rf dist && tsc -p tsconfig.json", "typecheck": "tsc -p tsconfig.json --noEmit", "test": "vitest run", "prepack": "npm run build" },
|
|
12
|
+
"dependencies": { "zod": "^4.3.6" },
|
|
13
|
+
"devDependencies": { "@types/node": "^20.0.0", "typescript": "^5.6.3", "vitest": "^3.2.7" },
|
|
14
|
+
"publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" }
|
|
15
|
+
}
|