@sequenceholdings/managed-functions 0.3.4 → 0.4.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.
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FunctionProcessReadError = exports.functionProcessReadRequestSchema = exports.functionProcessRunsPageSchema = exports.functionProcessRunsPageQuerySchema = exports.functionProcessRunQuerySchema = exports.functionProcessRunSummarySchema = exports.functionProcessRunStatusSchema = exports.functionProcessReadScopesSchema = exports.FUNCTION_PROCESS_READ_PATH = exports.FUNCTION_PROCESS_TOKEN_HEADER = void 0;
4
+ exports.readFunctionProcessScopes = readFunctionProcessScopes;
5
+ const zod_1 = require("zod");
6
+ exports.FUNCTION_PROCESS_TOKEN_HEADER = 'x-sequence-process-service-token';
7
+ exports.FUNCTION_PROCESS_READ_PATH = '/api/managed-functions/processes/read';
8
+ const processId = zod_1.z
9
+ .string()
10
+ .min(1)
11
+ .max(255)
12
+ .regex(/^[A-Za-z0-9._-]+$/);
13
+ const entityId = zod_1.z.string().min(1).max(255);
14
+ exports.functionProcessReadScopesSchema = zod_1.z.strictObject({
15
+ read: zod_1.z
16
+ .array(processId)
17
+ .min(1)
18
+ .max(64)
19
+ .refine((ids) => new Set(ids).size === ids.length, 'Process ids must be distinct')
20
+ .refine((ids) => ids.reduce((bytes, id) => bytes + id.length, 0) <= 8192, 'Process ids must fit within 8192 bytes'),
21
+ });
22
+ function readFunctionProcessScopes(manifest) {
23
+ if (typeof manifest !== 'object' || manifest === null)
24
+ return [];
25
+ const capabilities = Reflect.get(manifest, 'capabilities');
26
+ if (typeof capabilities !== 'object' || capabilities === null)
27
+ return [];
28
+ const processes = Reflect.get(capabilities, 'processes');
29
+ if (processes === undefined)
30
+ return [];
31
+ return exports.functionProcessReadScopesSchema.parse(processes).read;
32
+ }
33
+ exports.functionProcessRunStatusSchema = zod_1.z.enum([
34
+ 'pending',
35
+ 'running',
36
+ 'completed',
37
+ 'failed',
38
+ 'cancelled',
39
+ 'timed_out',
40
+ ]);
41
+ exports.functionProcessRunSummarySchema = zod_1.z.object({
42
+ id: zod_1.z.string().min(1).max(255),
43
+ processId,
44
+ status: exports.functionProcessRunStatusSchema,
45
+ entityType: zod_1.z.string().max(255).nullable(),
46
+ entityId: zod_1.z.string().max(255).nullable(),
47
+ startedAt: zod_1.z.iso.datetime({ offset: true }),
48
+ completedAt: zod_1.z.iso.datetime({ offset: true }).nullable(),
49
+ });
50
+ exports.functionProcessRunQuerySchema = zod_1.z.strictObject({
51
+ process: processId,
52
+ runId: zod_1.z.string().min(1).max(255),
53
+ });
54
+ exports.functionProcessRunsPageQuerySchema = zod_1.z.strictObject({
55
+ process: processId,
56
+ statuses: zod_1.z.array(exports.functionProcessRunStatusSchema).min(1).max(6).optional(),
57
+ entity: zod_1.z
58
+ .strictObject({
59
+ type: entityId,
60
+ id: entityId.optional(),
61
+ ids: zod_1.z.array(entityId).min(1).max(500).optional(),
62
+ })
63
+ .refine((value) => value.id === undefined || value.ids === undefined, 'Name one entity id or a set, not both')
64
+ .optional(),
65
+ limit: zod_1.z.number().int().min(1).max(500).optional(),
66
+ cursor: zod_1.z.string().min(1).max(8192).optional(),
67
+ archived: zod_1.z.boolean().optional(),
68
+ });
69
+ exports.functionProcessRunsPageSchema = zod_1.z.object({
70
+ runs: zod_1.z.array(exports.functionProcessRunSummarySchema).max(500),
71
+ nextCursor: zod_1.z.string().nullable(),
72
+ });
73
+ exports.functionProcessReadRequestSchema = zod_1.z.discriminatedUnion('operation', [
74
+ exports.functionProcessRunQuerySchema.extend({ operation: zod_1.z.literal('getRun') }),
75
+ exports.functionProcessRunsPageQuerySchema.extend({
76
+ operation: zod_1.z.literal('listRunsPage'),
77
+ }),
78
+ ]);
79
+ class FunctionProcessReadError extends Error {
80
+ status;
81
+ code;
82
+ name = 'FunctionProcessReadError';
83
+ constructor(status, code, message) {
84
+ super(message);
85
+ this.status = status;
86
+ this.code = code;
87
+ }
88
+ }
89
+ exports.FunctionProcessReadError = FunctionProcessReadError;
@@ -0,0 +1,14 @@
1
+ import { type FunctionProcessRunQuery, type FunctionProcessRunSummary, type FunctionProcessRunsPage, type FunctionProcessRunsPageQuery } from './processes-contract';
2
+ export * from './processes-contract';
3
+ export interface FunctionServiceProcessesOptions {
4
+ request: {
5
+ headers: Pick<Headers, 'get'> | Readonly<Record<string, string | readonly string[] | undefined>>;
6
+ };
7
+ baseUrl: string;
8
+ fetchImpl?: typeof globalThis.fetch;
9
+ }
10
+ export interface FunctionServiceProcessesClient {
11
+ getRun(query: FunctionProcessRunQuery): Promise<FunctionProcessRunSummary>;
12
+ listRunsPage(query: FunctionProcessRunsPageQuery): Promise<FunctionProcessRunsPage>;
13
+ }
14
+ export declare function createFunctionServiceProcessesClient({ request, baseUrl, fetchImpl, }: FunctionServiceProcessesOptions): FunctionServiceProcessesClient;
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.createFunctionServiceProcessesClient = createFunctionServiceProcessesClient;
18
+ const zod_1 = require("zod");
19
+ const processes_contract_1 = require("./processes-contract");
20
+ __exportStar(require("./processes-contract"), exports);
21
+ const invalidContext = () => new processes_contract_1.FunctionProcessReadError(401, 'invalid_context', 'This invocation has no valid process-read credential');
22
+ function readCredential({ request, }) {
23
+ const { headers } = request;
24
+ const matches = typeof headers.get === 'function'
25
+ ? [headers.get(processes_contract_1.FUNCTION_PROCESS_TOKEN_HEADER)]
26
+ : Object.entries(headers)
27
+ .filter(([name]) => name.toLowerCase() === processes_contract_1.FUNCTION_PROCESS_TOKEN_HEADER)
28
+ .map(([, value]) => value);
29
+ const token = matches[0];
30
+ if (matches.length !== 1 ||
31
+ typeof token !== 'string' ||
32
+ token.length > 16_384)
33
+ throw invalidContext();
34
+ const envelope = /^([A-Za-z0-9_-]+)\.([A-Za-z0-9_-]{43})$/.exec(token);
35
+ if (!envelope?.[1])
36
+ throw invalidContext();
37
+ let value;
38
+ try {
39
+ value = JSON.parse(Buffer.from(envelope[1], 'base64url').toString('utf8'));
40
+ }
41
+ catch {
42
+ throw invalidContext();
43
+ }
44
+ const parsed = zod_1.z
45
+ .object({
46
+ audience: zod_1.z.literal('atlas-managed-function-process-read'),
47
+ sub: zod_1.z.string().startsWith('service-account|'),
48
+ functionId: zod_1.z.string().min(1),
49
+ invocationId: zod_1.z.string().min(1),
50
+ exp: zod_1.z.number().int(),
51
+ })
52
+ .safeParse(value);
53
+ if (!parsed.success)
54
+ throw invalidContext();
55
+ return { token, expiresAt: parsed.data.exp };
56
+ }
57
+ function origin(baseUrl) {
58
+ let url;
59
+ try {
60
+ url = new URL(baseUrl);
61
+ }
62
+ catch {
63
+ throw new processes_contract_1.FunctionProcessReadError(400, 'invalid_configuration', 'Configure a trusted Atlas origin');
64
+ }
65
+ const loopback = url.protocol === 'http:' &&
66
+ ['localhost', '127.0.0.1', '[::1]'].includes(url.hostname);
67
+ if ((!loopback && url.protocol !== 'https:') ||
68
+ url.username ||
69
+ url.password ||
70
+ url.pathname !== '/' ||
71
+ url.search ||
72
+ url.hash) {
73
+ throw new processes_contract_1.FunctionProcessReadError(400, 'invalid_configuration', 'Configure an HTTPS Atlas origin without credentials, path, query or fragment');
74
+ }
75
+ return url.origin;
76
+ }
77
+ function createFunctionServiceProcessesClient({ request, baseUrl, fetchImpl = globalThis.fetch, }) {
78
+ const url = `${origin(baseUrl)}${processes_contract_1.FUNCTION_PROCESS_READ_PATH}`;
79
+ const { token, expiresAt } = readCredential({ request });
80
+ async function read({ body, schema, }) {
81
+ if (expiresAt <= Date.now() / 1000)
82
+ throw new processes_contract_1.FunctionProcessReadError(401, 'expired_context', 'The process-read credential expired; start a new invocation');
83
+ const parsed = processes_contract_1.functionProcessReadRequestSchema.parse(body);
84
+ const response = await fetchImpl(url, {
85
+ method: 'POST',
86
+ headers: {
87
+ 'content-type': 'application/json',
88
+ [processes_contract_1.FUNCTION_PROCESS_TOKEN_HEADER]: token,
89
+ },
90
+ body: JSON.stringify(parsed),
91
+ redirect: 'error',
92
+ signal: AbortSignal.timeout(30_000),
93
+ });
94
+ const result = await response.json().catch(() => null);
95
+ if (!response.ok) {
96
+ const error = zod_1.z
97
+ .object({
98
+ code: zod_1.z.string().max(128).optional(),
99
+ detail: zod_1.z.string().max(2000).optional(),
100
+ })
101
+ .safeParse(result);
102
+ throw new processes_contract_1.FunctionProcessReadError(response.status, error.success
103
+ ? (error.data.code ?? 'process_read_failed')
104
+ : 'process_read_failed', error.success
105
+ ? (error.data.detail ?? 'Process read failed')
106
+ : 'Process read failed');
107
+ }
108
+ const validated = schema.safeParse(result);
109
+ if (!validated.success)
110
+ throw new processes_contract_1.FunctionProcessReadError(502, 'malformed_response', 'Atlas returned an invalid process-read response');
111
+ return validated.data;
112
+ }
113
+ return {
114
+ async getRun(query) {
115
+ const run = await read({
116
+ body: { ...query, operation: 'getRun' },
117
+ schema: processes_contract_1.functionProcessRunSummarySchema,
118
+ });
119
+ if (run.id !== query.runId || run.processId !== query.process)
120
+ throw new processes_contract_1.FunctionProcessReadError(502, 'malformed_response', 'Atlas returned a different process run');
121
+ return run;
122
+ },
123
+ async listRunsPage(query) {
124
+ const page = await read({
125
+ body: { ...query, operation: 'listRunsPage' },
126
+ schema: processes_contract_1.functionProcessRunsPageSchema,
127
+ });
128
+ if (page.runs.some((run) => run.processId !== query.process))
129
+ throw new processes_contract_1.FunctionProcessReadError(502, 'malformed_response', 'Atlas returned runs from a different process');
130
+ return page;
131
+ },
132
+ };
133
+ }