@lifesi/genesis 0.1.0 → 0.1.2
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/dist/beacons.d.ts +46 -0
- package/dist/beacons.js +20 -0
- package/dist/client.d.ts +7179 -1234
- package/dist/client.js +139 -5
- package/dist/dials.d.ts +254 -6
- package/dist/dials.js +82 -1
- package/dist/fleets.d.ts +53 -1
- package/dist/fleets.js +35 -1
- package/dist/flocks.d.ts +73 -0
- package/dist/flocks.js +22 -0
- package/dist/generated/operations.d.ts +7178 -1233
- package/dist/generated/operations.js +8991 -1579
- package/dist/generated/schema.d.ts +15611 -5129
- package/dist/genesis.d.ts +990 -29
- package/dist/genesis.js +46 -4
- package/dist/genesis.openapi.json +22450 -6816
- package/dist/hives-cells.d.ts +127 -0
- package/dist/hives-cells.js +151 -0
- package/dist/hives-environment.d.ts +28 -0
- package/dist/hives-environment.js +58 -0
- package/dist/hives-functions.d.ts +15 -0
- package/dist/hives-functions.js +94 -0
- package/dist/hives-node.d.ts +9 -0
- package/dist/hives-node.js +152 -0
- package/dist/hives-operations.d.ts +304 -0
- package/dist/hives-operations.js +39 -0
- package/dist/hives-queues.d.ts +121 -0
- package/dist/hives-queues.js +106 -0
- package/dist/hives-resources-transport.d.ts +18 -0
- package/dist/hives-resources-transport.js +165 -0
- package/dist/hives-response.d.ts +2 -0
- package/dist/hives-response.js +32 -0
- package/dist/hives-runtimes.d.ts +34 -0
- package/dist/hives-runtimes.js +32 -0
- package/dist/hives-upload.d.ts +9 -0
- package/dist/hives-upload.js +23 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +7 -1
- package/dist/runner-grant.d.ts +10 -0
- package/dist/runner-grant.js +133 -0
- package/dist/runner.d.ts +16 -0
- package/dist/runner.js +4 -0
- package/dist/types.d.ts +11 -1
- package/package.json +14 -2
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { GenesisError } from './errors.js';
|
|
2
|
+
export const HIVES_OBJECT_BYTES = 10 * 1024 * 1024;
|
|
3
|
+
export const HIVES_QUEUE_BYTES = 256 * 1024;
|
|
4
|
+
export const HIVES_RESOURCE_JSON_BYTES = 2 * 1024 * 1024;
|
|
5
|
+
export function hivesResourceId(value) {
|
|
6
|
+
if (typeof value !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(value))
|
|
7
|
+
throw resourceError(0);
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
export function resourceError(status, id) {
|
|
11
|
+
return new GenesisError({ status, type: status ? `http_${status}` : 'invalid_request', operationId: id,
|
|
12
|
+
message: status === 404 ? 'Hives resource not found in the authenticated organization and project.'
|
|
13
|
+
: status === 409 ? 'Hives resource state conflicted. Read persisted state before another mutation.'
|
|
14
|
+
: status === 503 ? 'Hives outcome is unavailable. Read persisted state or reconcile before another mutation.'
|
|
15
|
+
: 'Hives resource request failed. Check the request and persisted state; no automatic retry was made.' });
|
|
16
|
+
}
|
|
17
|
+
export const isHivesResourceOperation = (id) => /^(?:hives_.*_native_queue|hives_(?:list_buckets|create_bucket|get_bucket|delete_bucket|list_objects|upload_object(?:_by_key)?|delete_object(?:_by_key)?|download_object_by_key|get_cors_config|set_cors_config|generate_presigned_url|reconcile|list_runtimes|get_runtime|select_runtime|list_project_runtimes))$/.test(id);
|
|
18
|
+
export const isHivesObjectUpload = (id) => id === 'hives_upload_object_by_key' || id === 'hives_upload_object';
|
|
19
|
+
/** Shared-client hook proposal: snapshot only the two real raw Cells upload operations. */
|
|
20
|
+
export async function hivesObjectBody(value) {
|
|
21
|
+
let bytes;
|
|
22
|
+
if (value instanceof Blob) {
|
|
23
|
+
if (value.size > HIVES_OBJECT_BYTES)
|
|
24
|
+
throw resourceError(0);
|
|
25
|
+
bytes = new Uint8Array(await value.arrayBuffer());
|
|
26
|
+
}
|
|
27
|
+
else if (value instanceof ArrayBuffer)
|
|
28
|
+
bytes = new Uint8Array(value);
|
|
29
|
+
else if (ArrayBuffer.isView(value))
|
|
30
|
+
bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
31
|
+
else
|
|
32
|
+
throw resourceError(0);
|
|
33
|
+
if (bytes.byteLength > HIVES_OBJECT_BYTES)
|
|
34
|
+
throw resourceError(0);
|
|
35
|
+
return Uint8Array.from(bytes).buffer;
|
|
36
|
+
}
|
|
37
|
+
export function hivesContentType(value = 'application/octet-stream') {
|
|
38
|
+
if (typeof value !== 'string' || !value.length || new TextEncoder().encode(value).length > 512 || /[\u0000-\u001f\u007f]/.test(value))
|
|
39
|
+
throw resourceError(0);
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
export async function hivesResourceBytes(response, limit, signal) {
|
|
43
|
+
const chunks = [];
|
|
44
|
+
let length = 0;
|
|
45
|
+
const reader = response.body?.getReader();
|
|
46
|
+
if (!reader)
|
|
47
|
+
return new Uint8Array();
|
|
48
|
+
const cancel = () => { void reader.cancel().catch(() => { }); };
|
|
49
|
+
signal?.addEventListener('abort', cancel, { once: true });
|
|
50
|
+
if (signal?.aborted)
|
|
51
|
+
cancel();
|
|
52
|
+
try {
|
|
53
|
+
for (;;) {
|
|
54
|
+
const next = await reader.read();
|
|
55
|
+
if (signal?.aborted)
|
|
56
|
+
throw resourceError(0);
|
|
57
|
+
if (next.done)
|
|
58
|
+
break;
|
|
59
|
+
length += next.value.byteLength;
|
|
60
|
+
if (length > limit) {
|
|
61
|
+
await reader.cancel();
|
|
62
|
+
throw resourceError(0);
|
|
63
|
+
}
|
|
64
|
+
chunks.push(next.value);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
signal?.removeEventListener('abort', cancel);
|
|
69
|
+
reader.releaseLock();
|
|
70
|
+
}
|
|
71
|
+
const bytes = new Uint8Array(length);
|
|
72
|
+
let offset = 0;
|
|
73
|
+
for (const part of chunks) {
|
|
74
|
+
bytes.set(part, offset);
|
|
75
|
+
offset += part.byteLength;
|
|
76
|
+
}
|
|
77
|
+
return bytes;
|
|
78
|
+
}
|
|
79
|
+
/** Whole operation budget includes credential/fetch and incremental body read. No retries or redirects. */
|
|
80
|
+
export async function hivesResourceCall(client, id, input, options = {}, mode = 'json', limit = HIVES_RESOURCE_JSON_BYTES, budget = 20_000) {
|
|
81
|
+
let snapshot;
|
|
82
|
+
try {
|
|
83
|
+
if (input.body instanceof ArrayBuffer)
|
|
84
|
+
snapshot = { ...input, body: input.body.slice(0) };
|
|
85
|
+
else {
|
|
86
|
+
const value = JSON.stringify(input);
|
|
87
|
+
if (new TextEncoder().encode(value).length > 512 * 1024)
|
|
88
|
+
throw resourceError(0);
|
|
89
|
+
snapshot = JSON.parse(value);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
throw resourceError(0, id);
|
|
94
|
+
}
|
|
95
|
+
const controller = new AbortController();
|
|
96
|
+
const abort = () => controller.abort();
|
|
97
|
+
let rejectAbort = () => { };
|
|
98
|
+
const failure = new Promise((_, reject) => { rejectAbort = reject; });
|
|
99
|
+
void failure.catch(() => { });
|
|
100
|
+
const aborted = () => rejectAbort(resourceError(0, id));
|
|
101
|
+
controller.signal.addEventListener('abort', aborted, { once: true });
|
|
102
|
+
options.signal?.addEventListener('abort', abort, { once: true });
|
|
103
|
+
const timer = setTimeout(abort, budget);
|
|
104
|
+
if (typeof timer === 'object' && 'unref' in timer)
|
|
105
|
+
timer.unref();
|
|
106
|
+
let response;
|
|
107
|
+
let completed = false;
|
|
108
|
+
const operation = (async () => {
|
|
109
|
+
if (options.signal?.aborted)
|
|
110
|
+
abort();
|
|
111
|
+
if (controller.signal.aborted)
|
|
112
|
+
throw resourceError(0, id);
|
|
113
|
+
response = await client.raw(id, snapshot, { signal: controller.signal });
|
|
114
|
+
if (controller.signal.aborted) {
|
|
115
|
+
await response.body?.cancel();
|
|
116
|
+
throw resourceError(0, id);
|
|
117
|
+
}
|
|
118
|
+
if (response.status < 200 || response.status >= 300) {
|
|
119
|
+
await response.body?.cancel();
|
|
120
|
+
throw resourceError(response.status, id);
|
|
121
|
+
}
|
|
122
|
+
if (mode === 'empty' && ![200, 204].includes(response.status))
|
|
123
|
+
throw resourceError(0, id);
|
|
124
|
+
const bytes = await hivesResourceBytes(response, mode === 'empty' ? 0 : limit, controller.signal);
|
|
125
|
+
if (controller.signal.aborted)
|
|
126
|
+
throw resourceError(0, id);
|
|
127
|
+
if (mode === 'empty')
|
|
128
|
+
return undefined;
|
|
129
|
+
if (mode === 'binary') {
|
|
130
|
+
if (response.status !== 200)
|
|
131
|
+
throw resourceError(0, id);
|
|
132
|
+
const headers = new Headers({ 'content-type': hivesContentType(response.headers.get('content-type') ?? undefined), 'cache-control': 'no-store', 'content-disposition': 'attachment' });
|
|
133
|
+
return new Response(Uint8Array.from(bytes).buffer, { status: 200, headers });
|
|
134
|
+
}
|
|
135
|
+
if (!response.headers.get('content-type')?.toLowerCase().includes('application/json'))
|
|
136
|
+
throw resourceError(0, id);
|
|
137
|
+
try {
|
|
138
|
+
return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
throw resourceError(0, id);
|
|
142
|
+
}
|
|
143
|
+
})();
|
|
144
|
+
void operation.then(() => { if (controller.signal.aborted)
|
|
145
|
+
void response?.body?.cancel().catch(() => { }); }, () => { });
|
|
146
|
+
try {
|
|
147
|
+
const result = await Promise.race([operation, failure]);
|
|
148
|
+
completed = true;
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
if (error instanceof GenesisError)
|
|
153
|
+
throw resourceError(error.status, id);
|
|
154
|
+
throw resourceError(0, id);
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
options.signal?.removeEventListener('abort', abort);
|
|
159
|
+
controller.signal.removeEventListener('abort', aborted);
|
|
160
|
+
if (!completed) {
|
|
161
|
+
controller.abort();
|
|
162
|
+
void response?.body?.cancel().catch(() => { });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { MAX_FUNCTION_BODY_BYTES } from './hives-functions.js';
|
|
2
|
+
/** Bound completed response bytes without losing binary data or treating HTTP errors as JSON wrappers. */
|
|
3
|
+
export async function hivesResponseBytes(response) {
|
|
4
|
+
if (!response.body)
|
|
5
|
+
return new Uint8Array();
|
|
6
|
+
const reader = response.body.getReader();
|
|
7
|
+
const chunks = [];
|
|
8
|
+
let size = 0;
|
|
9
|
+
try {
|
|
10
|
+
for (;;) {
|
|
11
|
+
const { value, done } = await reader.read();
|
|
12
|
+
if (done)
|
|
13
|
+
break;
|
|
14
|
+
size += value.byteLength;
|
|
15
|
+
if (size > MAX_FUNCTION_BODY_BYTES) {
|
|
16
|
+
await reader.cancel();
|
|
17
|
+
throw new Error('Hives guest response exceeds 10 MiB.');
|
|
18
|
+
}
|
|
19
|
+
chunks.push(value);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
reader.releaseLock();
|
|
24
|
+
}
|
|
25
|
+
const bytes = new Uint8Array(size);
|
|
26
|
+
let offset = 0;
|
|
27
|
+
for (const chunk of chunks) {
|
|
28
|
+
bytes.set(chunk, offset);
|
|
29
|
+
offset += chunk.length;
|
|
30
|
+
}
|
|
31
|
+
return bytes;
|
|
32
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { GenesisClient } from './client.js';
|
|
2
|
+
import { type HivesResourceOptions } from './hives-resources-transport.js';
|
|
3
|
+
export interface HivesRuntime {
|
|
4
|
+
name: string;
|
|
5
|
+
version: string;
|
|
6
|
+
wasiVersions: string[];
|
|
7
|
+
runners: string[];
|
|
8
|
+
artifactKinds: string[];
|
|
9
|
+
compiledFeatures: string[];
|
|
10
|
+
capabilityScope: 'compiled_serving_implementation';
|
|
11
|
+
features: string[];
|
|
12
|
+
useCases: string[];
|
|
13
|
+
}
|
|
14
|
+
export interface HivesRuntimeSelection {
|
|
15
|
+
selected: string;
|
|
16
|
+
reason: string;
|
|
17
|
+
wasi: string;
|
|
18
|
+
runner: string;
|
|
19
|
+
artifact: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function hivesRuntimes(client: GenesisClient): {
|
|
22
|
+
list: (options?: HivesResourceOptions) => Promise<{
|
|
23
|
+
runtimes: HivesRuntime[];
|
|
24
|
+
}>;
|
|
25
|
+
project: (project_id: string, options?: HivesResourceOptions) => Promise<{
|
|
26
|
+
runtimes: HivesRuntime[];
|
|
27
|
+
}>;
|
|
28
|
+
get: (name: string, options?: HivesResourceOptions) => Promise<HivesRuntime>;
|
|
29
|
+
select: (input: {
|
|
30
|
+
wasi: string;
|
|
31
|
+
runner: string;
|
|
32
|
+
artifact: string;
|
|
33
|
+
}, options?: HivesResourceOptions) => Promise<HivesRuntimeSelection>;
|
|
34
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { hivesResourceCall, hivesResourceId, resourceError } from './hives-resources-transport.js';
|
|
2
|
+
function runtime(value) {
|
|
3
|
+
if (!value || typeof value !== 'object')
|
|
4
|
+
throw resourceError(0);
|
|
5
|
+
const row = value;
|
|
6
|
+
if (typeof row.name !== 'string' || typeof row.version !== 'string' || row.capabilityScope !== 'compiled_serving_implementation'
|
|
7
|
+
|| ['wasiVersions', 'runners', 'artifactKinds', 'compiledFeatures', 'features', 'useCases'].some(k => !Array.isArray(row[k]) || !row[k].every(v => typeof v === 'string'))
|
|
8
|
+
|| !row.wasiVersions.length || !row.runners.length || !row.artifactKinds.length)
|
|
9
|
+
throw resourceError(0);
|
|
10
|
+
return { name: row.name, version: row.version, wasiVersions: [...row.wasiVersions], runners: [...row.runners], artifactKinds: [...row.artifactKinds], compiledFeatures: [...row.compiledFeatures], capabilityScope: row.capabilityScope, features: [...row.features], useCases: [...row.useCases] };
|
|
11
|
+
}
|
|
12
|
+
export function hivesRuntimes(client) {
|
|
13
|
+
const list = async (id, input, options) => {
|
|
14
|
+
const result = await hivesResourceCall(client, id, input, options);
|
|
15
|
+
if (!result || !Array.isArray(result.runtimes))
|
|
16
|
+
throw resourceError(0);
|
|
17
|
+
return { runtimes: result.runtimes.map(runtime) };
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
list: (options) => list('hives_list_runtimes', {}, options),
|
|
21
|
+
project: (project_id, options) => list('hives_list_project_runtimes', { project_id: hivesResourceId(project_id) }, options),
|
|
22
|
+
get: async (name, options) => runtime(await hivesResourceCall(client, 'hives_get_runtime', { name: hivesResourceId(name) }, options)),
|
|
23
|
+
select: async (input, options) => {
|
|
24
|
+
if (Object.keys(input).some(k => !['wasi', 'runner', 'artifact'].includes(k)) || Object.values(input).some(v => typeof v !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(v)))
|
|
25
|
+
throw resourceError(0);
|
|
26
|
+
const result = await hivesResourceCall(client, 'hives_select_runtime', { ...input }, options);
|
|
27
|
+
if (!result || typeof result.selected !== 'string' || typeof result.reason !== 'string' || result.wasi !== input.wasi || result.runner !== input.runner || result.artifact !== input.artifact)
|
|
28
|
+
throw resourceError(0);
|
|
29
|
+
return result;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const MAX_WASM_BYTES: number;
|
|
2
|
+
export interface HivesUploadOptions {
|
|
3
|
+
filename?: string;
|
|
4
|
+
git_sha?: string;
|
|
5
|
+
git_branch?: string;
|
|
6
|
+
git_message?: string;
|
|
7
|
+
}
|
|
8
|
+
/** Real multipart binary; the org and digest are resolved by the authenticated backend. */
|
|
9
|
+
export declare function hivesUploadBody(file: Blob, options?: HivesUploadOptions): Promise<FormData>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const MAX_WASM_BYTES = 10 * 1024 * 1024;
|
|
2
|
+
/** Real multipart binary; the org and digest are resolved by the authenticated backend. */
|
|
3
|
+
export async function hivesUploadBody(file, options = {}) {
|
|
4
|
+
if (!(file instanceof Blob) || file.size < 4 || file.size > MAX_WASM_BYTES)
|
|
5
|
+
throw new Error('Choose a WebAssembly binary of 4 bytes to 10 MiB.');
|
|
6
|
+
const magic = new Uint8Array(await file.slice(0, 4).arrayBuffer());
|
|
7
|
+
if (magic[0] !== 0 || magic[1] !== 97 || magic[2] !== 115 || magic[3] !== 109)
|
|
8
|
+
throw new Error('The file must be a WebAssembly binary.');
|
|
9
|
+
const limits = { git_sha: 40, git_branch: 255, git_message: 1000 };
|
|
10
|
+
if (options.filename !== undefined && (typeof options.filename !== 'string' || !options.filename.length || options.filename.length > 255 || /[\r\n]/.test(options.filename)))
|
|
11
|
+
throw new Error('WASM filename must contain 1–255 characters without newlines.');
|
|
12
|
+
const form = new FormData();
|
|
13
|
+
form.append('file', file, options.filename ?? 'module.wasm');
|
|
14
|
+
for (const key of Object.keys(limits)) {
|
|
15
|
+
const value = options[key];
|
|
16
|
+
if (value === undefined)
|
|
17
|
+
continue;
|
|
18
|
+
if (typeof value !== 'string' || value.length > limits[key] || (key === 'git_sha' && value.length < 7))
|
|
19
|
+
throw new Error('Invalid deployment Git metadata.');
|
|
20
|
+
form.append(key, value);
|
|
21
|
+
}
|
|
22
|
+
return form;
|
|
23
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,14 @@ export { GenesisError } from './errors.js';
|
|
|
4
4
|
export { operationTable, services, type OperationId } from './generated/operations.js';
|
|
5
5
|
export type { OperationInput, OperationOutput, OperationMeta, ServiceMeta } from './types.js';
|
|
6
6
|
export type { paths, operations, components } from './generated/schema.js';
|
|
7
|
+
export { hivesUploadBody, MAX_WASM_BYTES, type HivesUploadOptions } from './hives-upload.js';
|
|
8
|
+
export { validateHivesInvocation, hivesFunctionResponseHeaders, MAX_FUNCTION_BODY_BYTES, MAX_FUNCTION_JSON_BYTES, type HivesInvokeRequest } from './hives-functions.js';
|
|
9
|
+
export { hivesResponseBytes } from './hives-response.js';
|
|
7
10
|
export { runnerModule, type RunnerStartInput, type RunnerSnapshot, type RunnerCatalog, type RunnerEvent, type RunnerProviderResult, type RunnerRequestOptions } from './runner.js';
|
|
11
|
+
export type { RunnerSelfExecutionGrantInput, RunnerSelfExecutionGrant } from './runner-grant.js';
|
|
8
12
|
export { beaconsModule } from './beacons.js';
|
|
9
|
-
export { dialsModule, type DialsScope, type DialsOutboundCall, type DialsCallControl, type DialsEmergencyAddress } from './dials.js';
|
|
13
|
+
export { dialsModule, dialsEmergencyAddressFeedback, type DialsEmergencyAddressFeedback, type DialsScope, type DialsOutboundCall, type DialsCallControl, type DialsEmergencyAddress, type DialsCallListItem, type DialsCallDispatchState, type DialsMessage, type DialsMessageTemplate, type DialsMessagingBrand, type DialsMessagingCampaign, type DialsWorkflowDocument, type DialsRoutingAction, type DialsRoutingRule, type DialsRoutingRuleReceipt, type DialsOutboundCallReceipt, type DialsRecoveredOutboundCallReceipt } from './dials.js';
|
|
14
|
+
export { hivesObjectKey, type HivesBucket, type HivesObject, type HivesObjectAcknowledgement, type HivesObjectList, type HivesCorsRule, type HivesCreateBucket, type HivesPresign, type HivesPresignedUrl } from './hives-cells.js';
|
|
15
|
+
export { hivesQueueBody, type HivesQueue, type HivesQueueConfig, type HivesQueueCreate, type HivesQueueSend, type HivesQueueDelivery, type HivesDeadLetter, type HivesDeadLetterStats } from './hives-queues.js';
|
|
16
|
+
export type { HivesRuntime, HivesRuntimeSelection } from './hives-runtimes.js';
|
|
17
|
+
export { HIVES_OBJECT_BYTES, HIVES_QUEUE_BYTES, type HivesResourceOptions } from './hives-resources-transport.js';
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,12 @@ export { Genesis } from './genesis.js';
|
|
|
2
2
|
export { GenesisClient, DEFAULT_BASE_URL } from './client.js';
|
|
3
3
|
export { GenesisError } from './errors.js';
|
|
4
4
|
export { operationTable, services } from './generated/operations.js';
|
|
5
|
+
export { hivesUploadBody, MAX_WASM_BYTES } from './hives-upload.js';
|
|
6
|
+
export { validateHivesInvocation, hivesFunctionResponseHeaders, MAX_FUNCTION_BODY_BYTES, MAX_FUNCTION_JSON_BYTES } from './hives-functions.js';
|
|
7
|
+
export { hivesResponseBytes } from './hives-response.js';
|
|
5
8
|
export { runnerModule } from './runner.js';
|
|
6
9
|
export { beaconsModule } from './beacons.js';
|
|
7
|
-
export { dialsModule } from './dials.js';
|
|
10
|
+
export { dialsModule, dialsEmergencyAddressFeedback } from './dials.js';
|
|
11
|
+
export { hivesObjectKey } from './hives-cells.js';
|
|
12
|
+
export { hivesQueueBody } from './hives-queues.js';
|
|
13
|
+
export { HIVES_OBJECT_BYTES, HIVES_QUEUE_BYTES } from './hives-resources-transport.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { GenesisError } from './errors.js';
|
|
2
|
+
import type { components } from './generated/schema.js';
|
|
3
|
+
export type RunnerSelfExecutionGrantInput = components['schemas']['Iam.RunnerSelfExecutionGrantRequest'];
|
|
4
|
+
export type RunnerSelfExecutionGrant = components['schemas']['Iam.RunnerSelfExecutionGrant'];
|
|
5
|
+
export declare const RUNNER_SELF_GRANT_OPERATION: "iam_runnerSelfExecutionGrant";
|
|
6
|
+
/** Copy only primitive data descriptors before any asynchronous credential call. */
|
|
7
|
+
export declare function snapshotRunnerSelfGrant(args: Record<string, unknown>, routing: string): RunnerSelfExecutionGrantInput;
|
|
8
|
+
export declare function runnerGrantTransportError(): GenesisError;
|
|
9
|
+
/** A bounded closed response; no arbitrary upstream body reaches CLI or MCP. */
|
|
10
|
+
export declare function runnerGrantResponse(res: Response, input: RunnerSelfExecutionGrantInput): Promise<Response>;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { GenesisError } from './errors.js';
|
|
2
|
+
export const RUNNER_SELF_GRANT_OPERATION = 'iam_runnerSelfExecutionGrant';
|
|
3
|
+
const requestKeys = ['schemaVersion', 'requestId', 'operationId', 'organizationId', 'projectId', 'platformId', 'workspaceId', 'fleetId', 'resourceEnvironment', 'expiresAtMs'];
|
|
4
|
+
const receiptKeys = ['id', 'principal_id', 'organization_id', 'project_id', 'platform_id', 'workspace_id', 'environmentId', 'role_ids', 'permissions', 'expires_at', 'created_at'];
|
|
5
|
+
const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
6
|
+
const match = (p, v) => typeof v === 'string' && p.exec(v)?.[0] === v;
|
|
7
|
+
const native = (v, prefix) => match(new RegExp('^' + prefix + '[A-Za-z0-9_]{1,32}$'), v);
|
|
8
|
+
const instant = (v) => typeof v === 'number' && Number.isSafeInteger(v) && v > 0;
|
|
9
|
+
function exact(v, keys) {
|
|
10
|
+
return !!v && typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length === keys.length && keys.every(k => Object.hasOwn(v, k));
|
|
11
|
+
}
|
|
12
|
+
const invalid = () => new TypeError('Runner self-execution permission requires the exact ordinary-human Sandbox selection, operation, request correlation and finite expiry');
|
|
13
|
+
/** Copy only primitive data descriptors before any asynchronous credential call. */
|
|
14
|
+
export function snapshotRunnerSelfGrant(args, routing) {
|
|
15
|
+
if (routing !== 'gateway' || !exact(args, ['body']) || Reflect.ownKeys(args).length !== 1)
|
|
16
|
+
throw invalid();
|
|
17
|
+
const descriptor = Object.getOwnPropertyDescriptor(args, 'body');
|
|
18
|
+
if (!descriptor || !('value' in descriptor))
|
|
19
|
+
throw invalid();
|
|
20
|
+
const body = descriptor.value;
|
|
21
|
+
if (!exact(body, requestKeys) || ![Object.prototype, null].includes(Object.getPrototypeOf(body)) || Reflect.ownKeys(body).length !== requestKeys.length)
|
|
22
|
+
throw invalid();
|
|
23
|
+
const copy = {};
|
|
24
|
+
for (const key of requestKeys) {
|
|
25
|
+
const d = Object.getOwnPropertyDescriptor(body, key);
|
|
26
|
+
if (!d || !('value' in d) || !d.enumerable || (d.value !== null && !['string', 'number'].includes(typeof d.value)))
|
|
27
|
+
throw invalid();
|
|
28
|
+
copy[key] = d.value;
|
|
29
|
+
}
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
if (copy.schemaVersion !== 1 || !match(uuid, copy.requestId) || !match(uuid, copy.operationId)
|
|
32
|
+
|| !native(copy.organizationId, 'o_') || !native(copy.projectId, 'pr_') || !native(copy.workspaceId, 'w_')
|
|
33
|
+
|| copy.platformId !== 'pf_console' || copy.fleetId !== null || copy.resourceEnvironment !== 'sandbox'
|
|
34
|
+
|| !instant(copy.expiresAtMs) || copy.expiresAtMs <= now || copy.expiresAtMs > now + 1_800_000)
|
|
35
|
+
throw invalid();
|
|
36
|
+
return Object.freeze(copy);
|
|
37
|
+
}
|
|
38
|
+
function closedJson(raw) {
|
|
39
|
+
const value = JSON.parse(raw);
|
|
40
|
+
const stack = [];
|
|
41
|
+
for (const token of raw.match(/"(?:\\.|[^"\\])*"|[{}\[\],:]|true|false|null|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g) ?? []) {
|
|
42
|
+
const frame = stack.at(-1);
|
|
43
|
+
if (token === '{' || token === '[')
|
|
44
|
+
stack.push({ object: token === '{', key: token === '{', keys: new Set() });
|
|
45
|
+
else if (token === '}' || token === ']')
|
|
46
|
+
stack.pop();
|
|
47
|
+
else if (token === ',' && frame?.object)
|
|
48
|
+
frame.key = true;
|
|
49
|
+
else if (token === ':' && frame?.object)
|
|
50
|
+
frame.key = false;
|
|
51
|
+
else if (token.startsWith('"') && frame?.object && frame.key) {
|
|
52
|
+
const key = JSON.parse(token);
|
|
53
|
+
if (frame.keys.has(key))
|
|
54
|
+
throw invalid();
|
|
55
|
+
frame.keys.add(key);
|
|
56
|
+
}
|
|
57
|
+
else if (/^-?\d/.test(token) && !/^-?(?:0|[1-9]\d*)$/.test(token))
|
|
58
|
+
throw invalid();
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
const errorRows = [
|
|
63
|
+
[400, 'INVALID_ARGUMENT', 'invalid Runner self-execution grant request', 'invalid_runner_self_execution_grant_request'],
|
|
64
|
+
[400, 'INVALID_ARGUMENT', 'Runner self-execution grant expiry is out of bounds', 'runner_self_execution_grant_expiry_out_of_bounds'],
|
|
65
|
+
[401, 'UNAUTHENTICATED', 'Runner self-execution authority inactive', 'runner_self_execution_grant_inactive'],
|
|
66
|
+
[403, 'PERMISSION_DENIED', 'Runner self-execution authority denied', 'runner_self_execution_grant_denied'],
|
|
67
|
+
[409, 'CONFLICT', 'Runner self-execution grant operation conflict', 'runner_self_execution_grant_conflict'],
|
|
68
|
+
[409, 'CONFLICT', 'Runner self-execution grant replay is stale', 'runner_self_execution_grant_replay_stale'],
|
|
69
|
+
[503, 'PROVIDER_UNAVAILABLE', 'Runner self-execution grant unavailable', 'runner_self_execution_grant_unavailable'],
|
|
70
|
+
];
|
|
71
|
+
function uncertain(status, message) {
|
|
72
|
+
return new GenesisError({ status, type: 'runner_grant_uncertain', operationId: RUNNER_SELF_GRANT_OPERATION, message });
|
|
73
|
+
}
|
|
74
|
+
export function runnerGrantTransportError() {
|
|
75
|
+
return uncertain(0, 'Runner permission response was lost. Retry the same operation, scope and expiry with fresh request correlation.');
|
|
76
|
+
}
|
|
77
|
+
/** A bounded closed response; no arbitrary upstream body reaches CLI or MCP. */
|
|
78
|
+
export async function runnerGrantResponse(res, input) {
|
|
79
|
+
let value;
|
|
80
|
+
try {
|
|
81
|
+
if (res.redirected || res.headers.get('content-type')?.split(';')[0].trim().toLowerCase() !== 'application/json')
|
|
82
|
+
throw invalid();
|
|
83
|
+
const reader = res.body?.getReader();
|
|
84
|
+
const chunks = [];
|
|
85
|
+
let size = 0;
|
|
86
|
+
if (reader) {
|
|
87
|
+
try {
|
|
88
|
+
while (true) {
|
|
89
|
+
const next = await reader.read();
|
|
90
|
+
if (next.done)
|
|
91
|
+
break;
|
|
92
|
+
size += next.value.byteLength;
|
|
93
|
+
if (size > 8192) {
|
|
94
|
+
await reader.cancel();
|
|
95
|
+
throw invalid();
|
|
96
|
+
}
|
|
97
|
+
chunks.push(next.value);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
reader.releaseLock();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const bytes = new Uint8Array(size);
|
|
105
|
+
let offset = 0;
|
|
106
|
+
for (const chunk of chunks) {
|
|
107
|
+
bytes.set(chunk, offset);
|
|
108
|
+
offset += chunk.byteLength;
|
|
109
|
+
}
|
|
110
|
+
value = closedJson(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
throw uncertain(res.status, 'Runner permission could not be confirmed. Retry the same operation, scope and expiry with fresh request correlation.');
|
|
114
|
+
}
|
|
115
|
+
if (res.status !== 200) {
|
|
116
|
+
if (exact(value, ['code', 'message', 'details']) && exact(value.details, ['schemaVersion', 'reason']) && value.details.schemaVersion === 1) {
|
|
117
|
+
const details = value.details;
|
|
118
|
+
const row = errorRows.find(r => r[0] === res.status && r[1] === value.code && r[2] === value.message && r[3] === details.reason);
|
|
119
|
+
if (row)
|
|
120
|
+
throw new GenesisError({ status: res.status, type: 'runner_grant_denied', code: row[1], operationId: RUNNER_SELF_GRANT_OPERATION, message: row[2], body: value });
|
|
121
|
+
}
|
|
122
|
+
throw uncertain(res.status, 'Runner permission is unavailable or unconfirmed. Retry the same operation, scope and expiry with fresh request correlation.');
|
|
123
|
+
}
|
|
124
|
+
if (!exact(value, receiptKeys) || !match(/^g_[0-7][0123456789ABCDEFGHJKMNPQRSTVWXYZ]{25}$/, value.id)
|
|
125
|
+
|| !native(value.principal_id, 'h_') || value.organization_id !== input.organizationId || value.project_id !== input.projectId
|
|
126
|
+
|| value.platform_id !== input.platformId || value.workspace_id !== input.workspaceId || value.environmentId !== input.resourceEnvironment
|
|
127
|
+
|| !Array.isArray(value.role_ids) || value.role_ids.length !== 0 || !Array.isArray(value.permissions)
|
|
128
|
+
|| value.permissions.length !== 1 || value.permissions[0] !== 'runner.tasks.execute'
|
|
129
|
+
|| value.expires_at !== input.expiresAtMs || input.expiresAtMs <= Date.now() || !instant(value.created_at) || value.created_at >= input.expiresAtMs) {
|
|
130
|
+
throw uncertain(502, 'Runner returned an unqualified permission receipt. Retry the same operation, scope and expiry to reconcile it.');
|
|
131
|
+
}
|
|
132
|
+
return Response.json(value, { status: 200, headers: { 'Cache-Control': 'no-store', 'X-Content-Type-Options': 'nosniff' } });
|
|
133
|
+
}
|
package/dist/runner.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { GenesisClient } from './client.js';
|
|
2
2
|
import type { components } from './generated/schema.js';
|
|
3
|
+
import { type RunnerSelfExecutionGrantInput } from './runner-grant.js';
|
|
3
4
|
type Schemas = components['schemas'];
|
|
4
5
|
export type RunnerStartInput = Schemas['Runner.StartInput'];
|
|
5
6
|
export type RunnerSnapshot = Schemas['Runner.Snapshot'];
|
|
@@ -11,6 +12,21 @@ export type RunnerRequestOptions = {
|
|
|
11
12
|
};
|
|
12
13
|
/** Runner routes use the Genesis client's credential; the backend owns org/subject scoping. */
|
|
13
14
|
export declare function runnerModule(client: GenesisClient): Readonly<{
|
|
15
|
+
permission: Readonly<{
|
|
16
|
+
grant: (input: RunnerSelfExecutionGrantInput, opts?: RunnerRequestOptions) => Promise<{
|
|
17
|
+
id: string;
|
|
18
|
+
principal_id: string;
|
|
19
|
+
organization_id: string;
|
|
20
|
+
project_id: string;
|
|
21
|
+
platform_id: "pf_console";
|
|
22
|
+
workspace_id: string;
|
|
23
|
+
environmentId: "sandbox";
|
|
24
|
+
role_ids: unknown[];
|
|
25
|
+
permissions: "runner.tasks.execute"[];
|
|
26
|
+
expires_at: number;
|
|
27
|
+
created_at: number;
|
|
28
|
+
}>;
|
|
29
|
+
}>;
|
|
14
30
|
catalog: (opts?: RunnerRequestOptions) => Promise<{
|
|
15
31
|
workspaces: {
|
|
16
32
|
key: components["schemas"]["Runner.SafeOpaqueRef"];
|
package/dist/runner.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RUNNER_SELF_GRANT_OPERATION } from './runner-grant.js';
|
|
1
2
|
const requestOptions = (options) => ({ signal: options.signal });
|
|
2
3
|
const opaqueId = (v) => typeof v === 'string' && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(v);
|
|
3
4
|
const pathLike = (v) => /^[A-Za-z]:/.test(v) || v.includes('..');
|
|
@@ -95,6 +96,9 @@ function checkedStartInput(input) {
|
|
|
95
96
|
/** Runner routes use the Genesis client's credential; the backend owns org/subject scoping. */
|
|
96
97
|
export function runnerModule(client) {
|
|
97
98
|
return Object.freeze({
|
|
99
|
+
permission: Object.freeze({
|
|
100
|
+
grant: (input, opts = {}) => client.request(RUNNER_SELF_GRANT_OPERATION, { body: input }, requestOptions(opts)),
|
|
101
|
+
}),
|
|
98
102
|
catalog: (opts = {}) => client.request('runner_getCatalog', undefined, requestOptions(opts)),
|
|
99
103
|
jobs: Object.freeze({
|
|
100
104
|
list: (opts = {}) => client.request('runner_listJobs', undefined, requestOptions(opts)),
|
package/dist/types.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface OperationMeta {
|
|
|
24
24
|
pathParams: readonly string[];
|
|
25
25
|
queryParams: readonly string[];
|
|
26
26
|
hasBody: boolean;
|
|
27
|
+
requestContentType?: string;
|
|
28
|
+
rawResponse?: boolean;
|
|
27
29
|
inputSchema: Record<string, unknown>;
|
|
28
30
|
}
|
|
29
31
|
type Ops = operations;
|
|
@@ -43,6 +45,14 @@ type Body<K extends keyof Ops> = Ops[K] extends {
|
|
|
43
45
|
body?: J;
|
|
44
46
|
} : {
|
|
45
47
|
body: J;
|
|
48
|
+
}) : NonNullable<B> extends {
|
|
49
|
+
content: {
|
|
50
|
+
'multipart/form-data': unknown;
|
|
51
|
+
};
|
|
52
|
+
} ? (undefined extends B ? {
|
|
53
|
+
body?: FormData;
|
|
54
|
+
} : {
|
|
55
|
+
body: FormData;
|
|
46
56
|
}) : {} : {};
|
|
47
57
|
/** Flat input for an operation: path params + query params + `body`. */
|
|
48
58
|
export type OperationInput<K extends keyof Ops> = Part<K, 'path'> & Part<K, 'query'> & Body<K>;
|
|
@@ -60,7 +70,7 @@ type Ok<R> = R extends {
|
|
|
60
70
|
};
|
|
61
71
|
} : unknown;
|
|
62
72
|
/** Parsed JSON success body for an operation. */
|
|
63
|
-
export type OperationOutput<K extends keyof Ops> = Ops[K] extends {
|
|
73
|
+
export type OperationOutput<K extends keyof Ops> = K extends 'hives_invoke_function' | 'hives_invoke_function_async' | 'hives_invoke_deployment' ? Response : Ops[K] extends {
|
|
64
74
|
responses: infer R;
|
|
65
75
|
} ? (Ok<R> extends {
|
|
66
76
|
content: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lifesi/genesis",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/l1felabs/genesis-api.git",
|
|
7
|
+
"directory": "packages/sdk"
|
|
8
|
+
},
|
|
4
9
|
"description": "One typed SDK for every Genesis service (Foundry, Hives, Relays, IAM...) via the Genesis API gateway",
|
|
5
10
|
"type": "module",
|
|
6
11
|
"main": "dist/index.js",
|
|
@@ -10,11 +15,18 @@
|
|
|
10
15
|
"types": "./dist/index.d.ts",
|
|
11
16
|
"default": "./dist/index.js"
|
|
12
17
|
},
|
|
13
|
-
"./spec.json": "./dist/genesis.openapi.json"
|
|
18
|
+
"./spec.json": "./dist/genesis.openapi.json",
|
|
19
|
+
"./hives-node": {
|
|
20
|
+
"types": "./dist/hives-node.d.ts",
|
|
21
|
+
"default": "./dist/hives-node.js"
|
|
22
|
+
}
|
|
14
23
|
},
|
|
15
24
|
"files": [
|
|
16
25
|
"dist"
|
|
17
26
|
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"undici": "6.29.0"
|
|
29
|
+
},
|
|
18
30
|
"scripts": {
|
|
19
31
|
"gen": "node scripts/gen.mjs",
|
|
20
32
|
"build": "tsc -p tsconfig.json && cp ../../openapi/genesis.openapi.json dist/"
|