@norskvideo/ctl-product-template-schema 0.1.3 → 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/index.d.ts +10 -0
- package/index.js +15 -0
- package/package.json +5 -1
- package/read.d.ts +33 -0
- package/read.js +67 -0
package/index.d.ts
CHANGED
|
@@ -38,6 +38,10 @@ export declare const ProductTemplateAllocatedPortSchema: z.ZodObject<{
|
|
|
38
38
|
label: z.ZodOptional<z.ZodString>;
|
|
39
39
|
}, z.core.$strip>;
|
|
40
40
|
export type ProductTemplateAllocatedPort = z.infer<typeof ProductTemplateAllocatedPortSchema>;
|
|
41
|
+
export declare const ProductTemplateSideloadSchema: z.ZodObject<{
|
|
42
|
+
gpuInference: z.ZodOptional<z.ZodBoolean>;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
export type ProductTemplateSideload = z.infer<typeof ProductTemplateSideloadSchema>;
|
|
41
45
|
export declare const ProductTemplateManifestSchema: z.ZodObject<{
|
|
42
46
|
productTemplateSchemaVersion: z.ZodLiteral<1>;
|
|
43
47
|
productName: z.ZodString;
|
|
@@ -64,6 +68,9 @@ export declare const ProductTemplateManifestSchema: z.ZodObject<{
|
|
|
64
68
|
expose: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
65
69
|
}, z.core.$strip>>;
|
|
66
70
|
requiresWorkingDirectory: z.ZodOptional<z.ZodBoolean>;
|
|
71
|
+
sideload: z.ZodOptional<z.ZodObject<{
|
|
72
|
+
gpuInference: z.ZodOptional<z.ZodBoolean>;
|
|
73
|
+
}, z.core.$strip>>;
|
|
67
74
|
advanced: z.ZodOptional<z.ZodObject<{
|
|
68
75
|
networkMode: z.ZodOptional<z.ZodObject<{
|
|
69
76
|
default: z.ZodOptional<z.ZodEnum<{
|
|
@@ -173,6 +180,9 @@ export declare const ProductTemplateMaterialsSchema: z.ZodObject<{
|
|
|
173
180
|
expose: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
174
181
|
}, z.core.$strip>>;
|
|
175
182
|
requiresWorkingDirectory: z.ZodOptional<z.ZodBoolean>;
|
|
183
|
+
sideload: z.ZodOptional<z.ZodObject<{
|
|
184
|
+
gpuInference: z.ZodOptional<z.ZodBoolean>;
|
|
185
|
+
}, z.core.$strip>>;
|
|
176
186
|
advanced: z.ZodOptional<z.ZodObject<{
|
|
177
187
|
networkMode: z.ZodOptional<z.ZodObject<{
|
|
178
188
|
default: z.ZodOptional<z.ZodEnum<{
|
package/index.js
CHANGED
|
@@ -109,6 +109,18 @@ export const ProductTemplateAllocatedPortSchema = z
|
|
|
109
109
|
label: z.string().optional().meta({ description: "Human label, e.g. 'MoQ preview (WebTransport)'" }),
|
|
110
110
|
})
|
|
111
111
|
.meta({ id: "ProductTemplateAllocatedPort", outputId: "ProductTemplateAllocatedPort" });
|
|
112
|
+
// CUDA sideload bundle needs (docker/SIDELOAD.md). Only `gpuInference` is
|
|
113
|
+
// declared: `gpu-video` (NVENC/NVDEC) follows the GPU reservation automatically,
|
|
114
|
+
// so it needs no declaration. The runner mounts the matching bundle only when
|
|
115
|
+
// the image is slimmed AND the GPU is reserved; otherwise this is inert (a stock
|
|
116
|
+
// image needs no bundle; inference without a bundle falls back to CPU).
|
|
117
|
+
export const ProductTemplateSideloadSchema = z
|
|
118
|
+
.object({
|
|
119
|
+
gpuInference: z.boolean().optional().meta({
|
|
120
|
+
description: "Deployment uses GPU-accelerated inference (ONNX NR-IQA/detection/VAD, GPU whisper). When the GPU is reserved on the host the runner mounts the gpu-inference CUDA bundle; otherwise inference falls back to CPU. gpu-video (NVENC/NVDEC) follows the GPU reservation automatically and needs no declaration.",
|
|
121
|
+
}),
|
|
122
|
+
})
|
|
123
|
+
.meta({ id: "ProductTemplateSideload", outputId: "ProductTemplateSideload" });
|
|
112
124
|
export const ProductTemplateManifestSchema = z
|
|
113
125
|
.object({
|
|
114
126
|
productTemplateSchemaVersion: z.literal(1),
|
|
@@ -126,6 +138,9 @@ export const ProductTemplateManifestSchema = z
|
|
|
126
138
|
requiresWorkingDirectory: z.boolean().optional().meta({
|
|
127
139
|
description: "When true, the operator must choose a working directory at launch (a dev product template they own and keep), rather than getting the auto-assigned per-instance default. The runner's launch UI makes the working-directory field mandatory.",
|
|
128
140
|
}),
|
|
141
|
+
sideload: ProductTemplateSideloadSchema.optional().meta({
|
|
142
|
+
description: "CUDA sideload bundle needs. Only gpu-inference is declared (gpu-video follows the GPU reservation automatically). The runner mounts the matching bundle when the image is slimmed and the GPU is reserved.",
|
|
143
|
+
}),
|
|
129
144
|
advanced: ProductTemplateAdvancedSchema.optional().meta({
|
|
130
145
|
description: "Settings-backed runner knobs the operator may override per instance. Key presence opts the knob into the launch form; `default` pre-fills it (else the runner's daemon-wide default applies).",
|
|
131
146
|
}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@norskvideo/ctl-product-template-schema",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
"./params": {
|
|
15
15
|
"types": "./params.d.ts",
|
|
16
16
|
"default": "./params.js"
|
|
17
|
+
},
|
|
18
|
+
"./read": {
|
|
19
|
+
"types": "./read.d.ts",
|
|
20
|
+
"default": "./read.js"
|
|
17
21
|
}
|
|
18
22
|
},
|
|
19
23
|
"main": "./index.js",
|
package/read.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ProductTemplateParameter } from "./index.js";
|
|
2
|
+
import { type ProductTemplateManifest } from "./index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Error taxonomy for product-template lifecycle issues. Codes cover both the
|
|
5
|
+
* "store + extract" path (ctl's ProductTemplateService — extraction, manifest
|
|
6
|
+
* parsing, in-use guarding) and the simpler "store raw bytes" path (mgr —
|
|
7
|
+
* name validation + bytes persistence). Lives with the contract it reports
|
|
8
|
+
* on; @norskvideo/ctl-sdk re-exports it for existing importers.
|
|
9
|
+
*/
|
|
10
|
+
export declare class ProductTemplateError extends Error {
|
|
11
|
+
code: "INVALID_NAME" | "NAME_CONFLICT" | "FILE_NOT_FOUND" | "EXTRACTION_FAILED" | "MANIFEST_MISSING" | "MANIFEST_INVALID" | "COMPOSE_MISSING" | "PARAMETERS_INVALID" | "NOT_FOUND" | "IN_USE" | "PRODUCT_MISMATCH";
|
|
12
|
+
constructor(code: "INVALID_NAME" | "NAME_CONFLICT" | "FILE_NOT_FOUND" | "EXTRACTION_FAILED" | "MANIFEST_MISSING" | "MANIFEST_INVALID" | "COMPOSE_MISSING" | "PARAMETERS_INVALID" | "NOT_FOUND" | "IN_USE" | "PRODUCT_MISMATCH", message: string);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Where the template's root members come from — the seam between the shared
|
|
16
|
+
* contract and each runner's read mechanics. Two real adapters: ctl reads an
|
|
17
|
+
* extracted directory; mgr streams single members with `tar -xOf`.
|
|
18
|
+
* Returns null for an absent member.
|
|
19
|
+
*/
|
|
20
|
+
export interface TarSource {
|
|
21
|
+
readMember(name: string): Promise<string | null> | string | null;
|
|
22
|
+
}
|
|
23
|
+
export interface ProductTemplateContents {
|
|
24
|
+
manifest: ProductTemplateManifest;
|
|
25
|
+
/** `[]` when the template ships no parameters.yaml. */
|
|
26
|
+
parameters: ProductTemplateParameter[];
|
|
27
|
+
/** Whether compose.yml exists at the root. Enforcement is caller policy. */
|
|
28
|
+
hasCompose: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** Validate a product template's members. Throws ProductTemplateError with
|
|
31
|
+
* the canonical taxonomy; forward compatibility (RFC 0001) rides on the
|
|
32
|
+
* schemas' strip-unknown behaviour, pinned in this package's tests. */
|
|
33
|
+
export declare function readProductTemplate(source: TarSource): Promise<ProductTemplateContents>;
|
package/read.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// The reading half of the ctl↔product tar contract: which members a product
|
|
2
|
+
// template carries, in what order they are validated, and what error each
|
|
3
|
+
// failure maps to. Both runners re-implemented this (ctl over an extracted
|
|
4
|
+
// dir, mgr over streamed tar members) with byte-identical error strings —
|
|
5
|
+
// the contract now lives beside the schemas it validates, and the read
|
|
6
|
+
// mechanics stay with the callers as TarSource adapters.
|
|
7
|
+
//
|
|
8
|
+
// Policy stays with the callers too: ctl's import refuses a compose-less tar,
|
|
9
|
+
// mgr (which stores raw bytes for relay) does not — so compose presence is a
|
|
10
|
+
// reported fact here, not a verdict.
|
|
11
|
+
import { ParametersFileSchema, ProductTemplateManifestSchema } from "./index.js";
|
|
12
|
+
/**
|
|
13
|
+
* Error taxonomy for product-template lifecycle issues. Codes cover both the
|
|
14
|
+
* "store + extract" path (ctl's ProductTemplateService — extraction, manifest
|
|
15
|
+
* parsing, in-use guarding) and the simpler "store raw bytes" path (mgr —
|
|
16
|
+
* name validation + bytes persistence). Lives with the contract it reports
|
|
17
|
+
* on; @norskvideo/ctl-sdk re-exports it for existing importers.
|
|
18
|
+
*/
|
|
19
|
+
export class ProductTemplateError extends Error {
|
|
20
|
+
code;
|
|
21
|
+
constructor(code, message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.name = "ProductTemplateError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Validate a product template's members. Throws ProductTemplateError with
|
|
28
|
+
* the canonical taxonomy; forward compatibility (RFC 0001) rides on the
|
|
29
|
+
* schemas' strip-unknown behaviour, pinned in this package's tests. */
|
|
30
|
+
export async function readProductTemplate(source) {
|
|
31
|
+
const manifestText = await source.readMember("manifest.json");
|
|
32
|
+
if (manifestText === null) {
|
|
33
|
+
throw new ProductTemplateError("MANIFEST_MISSING", "product template tar must contain manifest.json at the root");
|
|
34
|
+
}
|
|
35
|
+
let manifestRaw;
|
|
36
|
+
try {
|
|
37
|
+
manifestRaw = JSON.parse(manifestText);
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
throw new ProductTemplateError("MANIFEST_INVALID", `manifest.json is not valid JSON: ${String(e)}`);
|
|
41
|
+
}
|
|
42
|
+
const manifestParsed = ProductTemplateManifestSchema.safeParse(manifestRaw);
|
|
43
|
+
if (!manifestParsed.success) {
|
|
44
|
+
throw new ProductTemplateError("MANIFEST_INVALID", `manifest.json failed schema validation: ${manifestParsed.error.message}`);
|
|
45
|
+
}
|
|
46
|
+
let parameters = [];
|
|
47
|
+
const parametersText = await source.readMember("parameters.yaml");
|
|
48
|
+
if (parametersText !== null) {
|
|
49
|
+
let parametersRaw;
|
|
50
|
+
try {
|
|
51
|
+
parametersRaw = Bun.YAML.parse(parametersText);
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
throw new ProductTemplateError("PARAMETERS_INVALID", `parameters.yaml is not valid YAML: ${String(e)}`);
|
|
55
|
+
}
|
|
56
|
+
const parametersParsed = ParametersFileSchema.safeParse(parametersRaw);
|
|
57
|
+
if (!parametersParsed.success) {
|
|
58
|
+
throw new ProductTemplateError("PARAMETERS_INVALID", `parameters.yaml failed schema validation: ${parametersParsed.error.message}`);
|
|
59
|
+
}
|
|
60
|
+
parameters = parametersParsed.data.parameters;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
manifest: manifestParsed.data,
|
|
64
|
+
parameters,
|
|
65
|
+
hasCompose: (await source.readMember("compose.yml")) !== null,
|
|
66
|
+
};
|
|
67
|
+
}
|