@norskvideo/ctl-sdk 0.1.51 → 0.1.53
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/fleet.d.ts +76 -0
- package/fleet.js +70 -0
- package/manifest-schema.d.ts +45 -0
- package/manifest-schema.js +35 -0
- package/package.json +5 -1
package/fleet.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export interface FleetEndpoint {
|
|
2
|
+
transport: string;
|
|
3
|
+
address: string;
|
|
4
|
+
params: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
/** What norsk-discovery holds. */
|
|
7
|
+
export interface FleetResource {
|
|
8
|
+
name: string;
|
|
9
|
+
kind: string;
|
|
10
|
+
labels: Record<string, string>;
|
|
11
|
+
endpoints: FleetEndpoint[];
|
|
12
|
+
}
|
|
13
|
+
/** A runtime as the platform knows it: under norsk-mgr its job id,
|
|
14
|
+
* production, replica and job name; under norsk-ctl alone its instance id
|
|
15
|
+
* and product. */
|
|
16
|
+
export interface FleetRuntime {
|
|
17
|
+
id: string;
|
|
18
|
+
namespace: string;
|
|
19
|
+
[field: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
export interface FleetItem {
|
|
22
|
+
runtime: FleetRuntime;
|
|
23
|
+
/** The platform's own registration of the runtime (`norsk.runtime`), with
|
|
24
|
+
* its `norsk.state`. */
|
|
25
|
+
presence?: FleetResource;
|
|
26
|
+
entries: FleetResource[];
|
|
27
|
+
}
|
|
28
|
+
export interface FleetDirectory {
|
|
29
|
+
items: FleetItem[];
|
|
30
|
+
/** Whether discovery's replay has landed: before it, entries are partial. */
|
|
31
|
+
synced: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface FleetSelection {
|
|
34
|
+
/** The kinds to read, as literals. `norsk.runtime` lists runtimes by their
|
|
35
|
+
* own registration. */
|
|
36
|
+
kind: string | readonly string[];
|
|
37
|
+
/** Every one present on an entry, with the same value. */
|
|
38
|
+
labels?: Readonly<Record<string, string>>;
|
|
39
|
+
}
|
|
40
|
+
export interface FleetWatchHandlers {
|
|
41
|
+
/** The whole current set, after the snapshot and after every change. */
|
|
42
|
+
onChange?: (items: FleetItem[]) => void;
|
|
43
|
+
onSynced?: (synced: boolean) => void;
|
|
44
|
+
/** The stream failed; the browser retries it. */
|
|
45
|
+
onError?: () => void;
|
|
46
|
+
}
|
|
47
|
+
export interface FleetWatch {
|
|
48
|
+
current(): FleetItem[];
|
|
49
|
+
synced(): boolean;
|
|
50
|
+
close(): void;
|
|
51
|
+
}
|
|
52
|
+
interface EventSourceLike {
|
|
53
|
+
addEventListener(name: string, listener: (e: {
|
|
54
|
+
data: string;
|
|
55
|
+
}) => void): void;
|
|
56
|
+
close(): void;
|
|
57
|
+
}
|
|
58
|
+
export interface FleetOptions {
|
|
59
|
+
/** Where the directory is: `/_fleet` on a dashboard host (the default),
|
|
60
|
+
* `/api/v1/fleet` on the platform's own origin. */
|
|
61
|
+
base?: string;
|
|
62
|
+
fetch?: (url: string, init?: {
|
|
63
|
+
credentials?: "same-origin";
|
|
64
|
+
}) => Promise<Response>;
|
|
65
|
+
EventSource?: new (url: string, init?: {
|
|
66
|
+
withCredentials?: boolean;
|
|
67
|
+
}) => EventSourceLike;
|
|
68
|
+
}
|
|
69
|
+
export interface Fleet {
|
|
70
|
+
query(selection: FleetSelection): Promise<FleetDirectory>;
|
|
71
|
+
watch(selection: FleetSelection, handlers?: FleetWatchHandlers): FleetWatch;
|
|
72
|
+
}
|
|
73
|
+
export declare function fleetClient(options?: FleetOptions): Fleet;
|
|
74
|
+
/** The directory on the page's own origin, as a dashboard host serves it. */
|
|
75
|
+
export declare const fleet: Fleet;
|
|
76
|
+
export {};
|
package/fleet.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// The directory, for a dashboard (RFC 0006 §4.5, task 122 K): the runtimes
|
|
2
|
+
// the signed-in user may see, each with what discovery holds under it, read
|
|
3
|
+
// from the page's own origin and filtered by the platform, never by the
|
|
4
|
+
// dashboard.
|
|
5
|
+
//
|
|
6
|
+
// On a dashboard host the directory is `/_fleet/`; in a product's own UI or a
|
|
7
|
+
// dashboard's dev loop it is the platform's API, `/api/v1/fleet/`. The calls
|
|
8
|
+
// are the same either way.
|
|
9
|
+
//
|
|
10
|
+
// Name the kinds a call reads with literals: ctl-dev-kit's dashboard build
|
|
11
|
+
// derives the registration's `uses` from them, and refuses one it cannot read.
|
|
12
|
+
//
|
|
13
|
+
// Browser-only, with no imports, so a dashboard's bundle carries nothing else.
|
|
14
|
+
function queryString(selection) {
|
|
15
|
+
const kinds = typeof selection.kind === "string" ? [selection.kind] : [...selection.kind];
|
|
16
|
+
const params = new URLSearchParams({ kinds: kinds.join(",") });
|
|
17
|
+
const labels = Object.entries(selection.labels ?? {}).map(([k, v]) => `${k}=${v}`);
|
|
18
|
+
if (labels.length > 0)
|
|
19
|
+
params.set("labels", labels.join(","));
|
|
20
|
+
return params.toString();
|
|
21
|
+
}
|
|
22
|
+
export function fleetClient(options = {}) {
|
|
23
|
+
const base = (options.base ?? "/_fleet").replace(/\/+$/, "");
|
|
24
|
+
return {
|
|
25
|
+
async query(selection) {
|
|
26
|
+
const doFetch = options.fetch ?? ((url, init) => globalThis.fetch(url, init));
|
|
27
|
+
const res = await doFetch(`${base}/directory?${queryString(selection)}`, { credentials: "same-origin" });
|
|
28
|
+
if (!res.ok)
|
|
29
|
+
throw new Error(`the directory answered ${res.status}`);
|
|
30
|
+
return (await res.json());
|
|
31
|
+
},
|
|
32
|
+
watch(selection, handlers = {}) {
|
|
33
|
+
const Source = options.EventSource ?? globalThis.EventSource;
|
|
34
|
+
if (Source === undefined)
|
|
35
|
+
throw new Error("this environment has no EventSource");
|
|
36
|
+
const items = new Map();
|
|
37
|
+
let isSynced = false;
|
|
38
|
+
const source = new Source(`${base}/directory/watch?${queryString(selection)}`, { withCredentials: true });
|
|
39
|
+
const changed = () => handlers.onChange?.([...items.values()]);
|
|
40
|
+
const on = (name, apply) => source.addEventListener(name, (e) => apply(JSON.parse(e.data)));
|
|
41
|
+
on("snapshot", (data) => {
|
|
42
|
+
items.clear();
|
|
43
|
+
for (const item of data.items)
|
|
44
|
+
items.set(item.runtime.id, item);
|
|
45
|
+
isSynced = data.synced;
|
|
46
|
+
changed();
|
|
47
|
+
});
|
|
48
|
+
on("upsert", (item) => {
|
|
49
|
+
items.set(item.runtime.id, item);
|
|
50
|
+
changed();
|
|
51
|
+
});
|
|
52
|
+
on("remove", (data) => {
|
|
53
|
+
items.delete(data.id);
|
|
54
|
+
changed();
|
|
55
|
+
});
|
|
56
|
+
on("synced", (data) => {
|
|
57
|
+
isSynced = data.synced;
|
|
58
|
+
handlers.onSynced?.(isSynced);
|
|
59
|
+
});
|
|
60
|
+
source.addEventListener("error", () => handlers.onError?.());
|
|
61
|
+
return {
|
|
62
|
+
current: () => [...items.values()],
|
|
63
|
+
synced: () => isSynced,
|
|
64
|
+
close: () => source.close(),
|
|
65
|
+
};
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/** The directory on the page's own origin, as a dashboard host serves it. */
|
|
70
|
+
export const fleet = fleetClient();
|
package/manifest-schema.d.ts
CHANGED
|
@@ -9,6 +9,29 @@ declare const DefaultProductTemplateSchema: z.ZodObject<{
|
|
|
9
9
|
url: z.ZodString;
|
|
10
10
|
}, z.core.$strip>;
|
|
11
11
|
export type DefaultProductTemplate = z.infer<typeof DefaultProductTemplateSchema>;
|
|
12
|
+
declare const DefaultDashboardSchema: z.ZodObject<{
|
|
13
|
+
name: z.ZodString;
|
|
14
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
15
|
+
url: z.ZodString;
|
|
16
|
+
hosting: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
17
|
+
mode: z.ZodLiteral<"own">;
|
|
18
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
19
|
+
mode: z.ZodLiteral<"shared">;
|
|
20
|
+
group: z.ZodString;
|
|
21
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
22
|
+
mode: z.ZodLiteral<"pattern">;
|
|
23
|
+
pattern: z.ZodString;
|
|
24
|
+
scope: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
25
|
+
kind: z.ZodLiteral<"production">;
|
|
26
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
27
|
+
kind: z.ZodLiteral<"production-template">;
|
|
28
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
29
|
+
kind: z.ZodLiteral<"label">;
|
|
30
|
+
label: z.ZodString;
|
|
31
|
+
}, z.core.$strip>], "kind">;
|
|
32
|
+
}, z.core.$strip>], "mode">>;
|
|
33
|
+
}, z.core.$strip>;
|
|
34
|
+
export type DefaultDashboard = z.infer<typeof DefaultDashboardSchema>;
|
|
12
35
|
/** One product role (RFC 0003 D5a). The name rule is foundation's, so the
|
|
13
36
|
* runner that stamps it and the manifest that declares it cannot disagree. */
|
|
14
37
|
declare const ProductRoleSchema: z.ZodObject<{
|
|
@@ -202,6 +225,28 @@ export declare const ManifestSchema: z.ZodObject<{
|
|
|
202
225
|
name: z.ZodString;
|
|
203
226
|
url: z.ZodString;
|
|
204
227
|
}, z.core.$strip>>>;
|
|
228
|
+
defaultDashboards: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
229
|
+
name: z.ZodString;
|
|
230
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
231
|
+
url: z.ZodString;
|
|
232
|
+
hosting: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
233
|
+
mode: z.ZodLiteral<"own">;
|
|
234
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
235
|
+
mode: z.ZodLiteral<"shared">;
|
|
236
|
+
group: z.ZodString;
|
|
237
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
238
|
+
mode: z.ZodLiteral<"pattern">;
|
|
239
|
+
pattern: z.ZodString;
|
|
240
|
+
scope: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
241
|
+
kind: z.ZodLiteral<"production">;
|
|
242
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
243
|
+
kind: z.ZodLiteral<"production-template">;
|
|
244
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
245
|
+
kind: z.ZodLiteral<"label">;
|
|
246
|
+
label: z.ZodString;
|
|
247
|
+
}, z.core.$strip>], "kind">;
|
|
248
|
+
}, z.core.$strip>], "mode">>;
|
|
249
|
+
}, z.core.$strip>>>;
|
|
205
250
|
export: z.ZodOptional<z.ZodObject<{
|
|
206
251
|
include: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
207
252
|
exclude: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
package/manifest-schema.js
CHANGED
|
@@ -82,6 +82,38 @@ const DefaultProductTemplateSchema = z.object({
|
|
|
82
82
|
description: "Path on the product's HTTP surface that returns the product-template tar. Resolved against the product's base URL — relative paths only (no scheme/host).",
|
|
83
83
|
}),
|
|
84
84
|
});
|
|
85
|
+
/** A fleet dashboard the product brings (norsk-ctl task 123, RFC 0006): a
|
|
86
|
+
* static bundle its control plane serves, registered with norsk-mgr when the
|
|
87
|
+
* product is added and replaced in place on every reload. The hosting is a
|
|
88
|
+
* proposal: the operator's choice, once made, is kept. norsk-ctl serving
|
|
89
|
+
* alone hosts no dashboards and ignores these. */
|
|
90
|
+
const DashboardLabel = z
|
|
91
|
+
.string()
|
|
92
|
+
.regex(/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/, "one DNS label: lowercase letters, digits and hyphens, 1-63 chars");
|
|
93
|
+
const DefaultDashboardSchema = z.object({
|
|
94
|
+
name: DashboardLabel.meta({ description: "The dashboard's name, and its host's label when it has one of its own." }),
|
|
95
|
+
displayName: z.string().min(1).optional(),
|
|
96
|
+
url: z
|
|
97
|
+
.string()
|
|
98
|
+
.regex(/^\/[^/]/, "a path on the product's own HTTP surface")
|
|
99
|
+
.meta({ description: "Path on the product's HTTP surface that returns the dashboard's bundle tar." }),
|
|
100
|
+
hosting: z
|
|
101
|
+
.discriminatedUnion("mode", [
|
|
102
|
+
z.object({ mode: z.literal("own") }),
|
|
103
|
+
z.object({ mode: z.literal("shared"), group: DashboardLabel }),
|
|
104
|
+
z.object({
|
|
105
|
+
mode: z.literal("pattern"),
|
|
106
|
+
pattern: z.string().min(1),
|
|
107
|
+
scope: z.discriminatedUnion("kind", [
|
|
108
|
+
z.object({ kind: z.literal("production") }),
|
|
109
|
+
z.object({ kind: z.literal("production-template") }),
|
|
110
|
+
z.object({ kind: z.literal("label"), label: z.string().min(1) }),
|
|
111
|
+
]),
|
|
112
|
+
}),
|
|
113
|
+
])
|
|
114
|
+
.optional()
|
|
115
|
+
.meta({ description: "The hosting proposed; absent is a host of its own." }),
|
|
116
|
+
});
|
|
85
117
|
/** Save-as-product-template file selection. When the runner exports a running
|
|
86
118
|
* instance's working directory into a new product template, it packs only files
|
|
87
119
|
* matching `include` and not matching `exclude` (glob patterns, relative to the
|
|
@@ -178,5 +210,8 @@ export const ManifestSchema = z.object({
|
|
|
178
210
|
defaultProductTemplates: z.array(DefaultProductTemplateSchema).default([]).meta({
|
|
179
211
|
description: "Product templates the runner auto-fetches at product-registration time. Each entry is GET'd against the product's base URL and stored under its `name` via the runner's product-template store. Lets a product offer 'zero-config' launch paths — operators add the product and immediately have a launchable product template without visiting configure.",
|
|
180
212
|
}),
|
|
213
|
+
defaultDashboards: z.array(DefaultDashboardSchema).default([]).meta({
|
|
214
|
+
description: "Fleet dashboards the product brings: norsk-mgr fetches each bundle from the product at registration and on every reload, registers it as the product's, and removes it with the product. norsk-ctl serving alone ignores them.",
|
|
215
|
+
}),
|
|
181
216
|
export: ExportSchema.optional(),
|
|
182
217
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@norskvideo/ctl-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.53",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
"types": "./components/index.d.ts",
|
|
16
16
|
"default": "./components/index.js"
|
|
17
17
|
},
|
|
18
|
+
"./fleet": {
|
|
19
|
+
"types": "./fleet.d.ts",
|
|
20
|
+
"default": "./fleet.js"
|
|
21
|
+
},
|
|
18
22
|
"./frontend-spec": {
|
|
19
23
|
"types": "./frontend-spec.d.ts",
|
|
20
24
|
"default": "./frontend-spec.js"
|