@sequenceholdings/artifact-studio 0.2.5 → 0.3.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 +35 -0
- package/VERSION-PIN.md +14 -3
- package/dist/api.d.ts +11 -1
- package/dist/api.js +26 -9
- package/dist/atlas-ui-css-plugin.d.ts +11 -7
- package/dist/atlas-ui-css-plugin.js +38 -10
- package/dist/build.d.ts +2 -2
- package/dist/build.js +14 -6
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +108 -37
- package/dist/dev-link.d.ts +1 -1
- package/dist/dev-link.js +2 -1
- package/dist/gateway/admission.d.ts +55 -0
- package/dist/gateway/admission.js +132 -0
- package/dist/gateway/control.d.ts +91 -0
- package/dist/gateway/control.js +113 -0
- package/dist/gateway/hosted-proxy.d.ts +36 -0
- package/dist/gateway/hosted-proxy.js +347 -0
- package/dist/gateway/release-admission.d.ts +23 -0
- package/dist/gateway/release-admission.js +259 -0
- package/dist/gateway/server.d.ts +29 -0
- package/dist/gateway/server.js +298 -0
- package/dist/gateway/session.d.ts +71 -0
- package/dist/gateway/session.js +143 -0
- package/dist/gateway/usage.d.ts +37 -0
- package/dist/gateway/usage.js +46 -0
- package/dist/gateway-main.d.ts +1 -0
- package/dist/gateway-main.js +84 -0
- package/dist/git-clone.d.ts +23 -1
- package/dist/git-clone.js +32 -15
- package/dist/manifest-next.d.ts +217 -0
- package/dist/manifest-next.js +120 -0
- package/dist/manifest-static.d.ts +76 -0
- package/dist/manifest-static.js +124 -0
- package/dist/manifest.d.ts +124 -19
- package/dist/manifest.js +12 -118
- package/dist/next/bridge.d.ts +47 -0
- package/dist/next/bridge.js +229 -0
- package/dist/next/browser-logs.d.ts +7 -0
- package/dist/next/browser-logs.js +51 -0
- package/dist/next/browser.d.ts +6 -0
- package/dist/next/browser.js +57 -0
- package/dist/next/protocol.d.ts +73 -0
- package/dist/next/protocol.js +56 -0
- package/dist/next/release.d.ts +15 -0
- package/dist/next/release.js +46 -0
- package/dist/next/requests.d.ts +15 -0
- package/dist/next/requests.js +59 -0
- package/dist/next/routes.d.ts +4 -0
- package/dist/next/routes.js +21 -0
- package/dist/next/streams.d.ts +16 -0
- package/dist/next/streams.js +132 -0
- package/dist/next/theme.d.ts +10 -0
- package/dist/next/theme.js +45 -0
- package/dist/next/transport.d.ts +15 -0
- package/dist/next/transport.js +13 -0
- package/dist/next-build.d.ts +29 -0
- package/dist/next-build.js +129 -0
- package/dist/next-cache/adapter-runtime.d.ts +51 -0
- package/dist/next-cache/adapter-runtime.js +73 -0
- package/dist/next-cache/component-handler.d.ts +3 -0
- package/dist/next-cache/component-handler.js +10 -0
- package/dist/next-cache/components.d.ts +20 -0
- package/dist/next-cache/components.js +113 -0
- package/dist/next-cache/config.d.ts +9 -0
- package/dist/next-cache/config.js +78 -0
- package/dist/next-cache/incremental-handler.d.ts +18 -0
- package/dist/next-cache/incremental-handler.js +17 -0
- package/dist/next-cache/incremental.d.ts +27 -0
- package/dist/next-cache/incremental.js +99 -0
- package/dist/next-cache/index.d.ts +18 -0
- package/dist/next-cache/index.js +16 -0
- package/dist/next-cache/memory-store.d.ts +27 -0
- package/dist/next-cache/memory-store.js +118 -0
- package/dist/next-cache/redis-scripts.d.ts +3 -0
- package/dist/next-cache/redis-scripts.js +85 -0
- package/dist/next-cache/redis-store.d.ts +18 -0
- package/dist/next-cache/redis-store.js +56 -0
- package/dist/next-cache/runtime.d.ts +29 -0
- package/dist/next-cache/runtime.js +63 -0
- package/dist/next-cache/serialization.d.ts +39 -0
- package/dist/next-cache/serialization.js +333 -0
- package/dist/next-cache/store.d.ts +54 -0
- package/dist/next-cache/store.js +45 -0
- package/dist/next-cache/valkey-transport.d.ts +52 -0
- package/dist/next-cache/valkey-transport.js +189 -0
- package/dist/next-dev-control.d.ts +48 -0
- package/dist/next-dev-control.js +63 -0
- package/dist/next-dev-process.d.ts +17 -0
- package/dist/next-dev-process.js +110 -0
- package/dist/next-dev.d.ts +12 -0
- package/dist/next-dev.js +267 -0
- package/dist/next-image-config.d.ts +9 -0
- package/dist/next-image-config.js +101 -0
- package/dist/next-project.d.ts +22 -0
- package/dist/next-project.js +64 -0
- package/dist/next-public-fonts-local.d.ts +7 -0
- package/dist/next-public-fonts-local.js +64 -0
- package/dist/next-public-fonts.d.ts +13 -0
- package/dist/next-public-fonts.js +49 -0
- package/dist/next-source-hash.d.ts +14 -0
- package/dist/next-source-hash.js +18 -0
- package/dist/next-source.d.ts +35 -0
- package/dist/next-source.js +155 -0
- package/dist/next.d.ts +9 -0
- package/dist/next.js +5 -0
- package/dist/project.d.ts +3 -0
- package/dist/project.js +9 -1
- package/dist/runtime-contracts.d.ts +229 -0
- package/dist/runtime-contracts.js +224 -0
- package/dist/sdk.d.ts +420 -1
- package/dist/sdk.js +35 -0
- package/dist/server/broker-contract.d.ts +68 -0
- package/dist/server/broker-contract.js +33 -0
- package/dist/server/client.d.ts +67 -0
- package/dist/server/client.js +159 -0
- package/dist/server/logger.d.ts +26 -0
- package/dist/server/logger.js +24 -0
- package/dist/server/release-forms.d.ts +13 -0
- package/dist/server/release-forms.js +124 -0
- package/dist/server/request-context.d.ts +74 -0
- package/dist/server/request-context.js +103 -0
- package/dist/server/runtime-config.d.ts +53 -0
- package/dist/server/runtime-config.js +64 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.js +4 -0
- package/dist/templates/nextjs/.artifactignore +14 -0
- package/dist/templates/nextjs/README.md +151 -0
- package/dist/templates/nextjs/app/about/page.tsx +26 -0
- package/dist/templates/nextjs/app/actions.ts +18 -0
- package/dist/templates/nextjs/app/api/health/route.ts +6 -0
- package/dist/templates/nextjs/app/components/counter.tsx +19 -0
- package/dist/templates/nextjs/app/components/greeting-form.tsx +38 -0
- package/dist/templates/nextjs/app/globals.css +4 -0
- package/dist/templates/nextjs/app/layout.tsx +27 -0
- package/dist/templates/nextjs/app/page.tsx +51 -0
- package/dist/templates/nextjs/app/providers.tsx +107 -0
- package/dist/templates/nextjs/artifact.bundle.yml +55 -0
- package/dist/templates/nextjs/next.config.mjs +27 -0
- package/dist/templates/nextjs/package.json +32 -0
- package/dist/templates/nextjs/pnpm-workspace.yaml +8 -0
- package/dist/templates/nextjs/postcss.config.mjs +5 -0
- package/dist/templates/nextjs/tsconfig.json +20 -0
- package/dist/templates/react-vite/artifact.bundle.yml +0 -1
- package/dist/templates/react-vite/package.json +1 -1
- package/dist/tunnel/broker.d.ts +75 -0
- package/dist/tunnel/broker.js +451 -0
- package/dist/tunnel/client.d.ts +22 -0
- package/dist/tunnel/client.js +177 -0
- package/dist/tunnel/index.d.ts +6 -0
- package/dist/tunnel/index.js +4 -0
- package/dist/tunnel/io.d.ts +33 -0
- package/dist/tunnel/io.js +108 -0
- package/dist/tunnel/peer.d.ts +25 -0
- package/dist/tunnel/peer.js +147 -0
- package/dist/tunnel/protocol.d.ts +69 -0
- package/dist/tunnel/protocol.js +196 -0
- package/dist/tunnel/redis-ownership.d.ts +38 -0
- package/dist/tunnel/redis-ownership.js +100 -0
- package/dist/tunnel/redis-socket.d.ts +13 -0
- package/dist/tunnel/redis-socket.js +48 -0
- package/dist/tunnel/redis-types.d.ts +70 -0
- package/dist/tunnel/redis-types.js +9 -0
- package/dist/tunnel/redis.d.ts +53 -0
- package/dist/tunnel/redis.js +391 -0
- package/dist/tunnel/transport.d.ts +17 -0
- package/dist/tunnel/transport.js +1 -0
- package/package.json +68 -4
- package/templates/nextjs/.artifactignore +14 -0
- package/templates/nextjs/README.md +151 -0
- package/templates/nextjs/app/about/page.tsx +26 -0
- package/templates/nextjs/app/actions.ts +18 -0
- package/templates/nextjs/app/api/health/route.ts +6 -0
- package/templates/nextjs/app/components/counter.tsx +19 -0
- package/templates/nextjs/app/components/greeting-form.tsx +38 -0
- package/templates/nextjs/app/globals.css +4 -0
- package/templates/nextjs/app/layout.tsx +27 -0
- package/templates/nextjs/app/page.tsx +51 -0
- package/templates/nextjs/app/providers.tsx +107 -0
- package/templates/nextjs/artifact.bundle.yml +55 -0
- package/templates/nextjs/next.config.mjs +27 -0
- package/templates/nextjs/package.json +32 -0
- package/templates/nextjs/pnpm-workspace.yaml +8 -0
- package/templates/nextjs/postcss.config.mjs +5 -0
- package/templates/nextjs/tsconfig.json +20 -0
- package/templates/react-vite/artifact.bundle.yml +0 -1
- package/templates/react-vite/package.json +1 -1
package/dist/sdk.js
CHANGED
|
@@ -1,4 +1,39 @@
|
|
|
1
|
+
import { storageClient } from '@sequenceholdings/storage';
|
|
1
2
|
export const seq = typeof window !== 'undefined'
|
|
2
3
|
? window.seq
|
|
3
4
|
: undefined;
|
|
4
5
|
export default seq;
|
|
6
|
+
/** Typed metadata API plus direct browser-host transfers for sandboxed Artifacts. */
|
|
7
|
+
export function createArtifactStorageClient(sdk = seq) {
|
|
8
|
+
const client = storageClient({ async request(method, path, body) {
|
|
9
|
+
const response = await sdk.api.fetch(path, { method, body });
|
|
10
|
+
if (!response.ok)
|
|
11
|
+
throw new Error(`Storage request failed (${response.status})`);
|
|
12
|
+
return response.body;
|
|
13
|
+
} });
|
|
14
|
+
return { ...client, workspace(id) {
|
|
15
|
+
const workspace = client.workspace(id);
|
|
16
|
+
return { ...workspace, uploads: { ...workspace.uploads,
|
|
17
|
+
put: (input, file, options) => sdk.storage.upload(id, input, file, options),
|
|
18
|
+
resume: (uploadId, file, options) => sdk.storage.resume(id, uploadId, file, options),
|
|
19
|
+
} };
|
|
20
|
+
} };
|
|
21
|
+
}
|
|
22
|
+
/** Explicit scope keeps concurrent browser actions isolated. The key is a safe
|
|
23
|
+
* author label, never a payload or an authentication claim. */
|
|
24
|
+
export function withTraceAction({ key, sdk = seq }, run) {
|
|
25
|
+
if (!/^[a-zA-Z0-9_.:/-]{1,120}$/.test(key))
|
|
26
|
+
throw new Error('Use a bounded operation key for tracing');
|
|
27
|
+
const traceAction = { id: crypto.randomUUID(), key };
|
|
28
|
+
const fetch = (path, options) => sdk.api.fetch(path, { ...options, traceAction });
|
|
29
|
+
const request = async (method, path, body) => {
|
|
30
|
+
const response = await fetch(path, { method, body });
|
|
31
|
+
if (!response.ok)
|
|
32
|
+
throw new Error(`API request failed (${response.status})`);
|
|
33
|
+
return response.body;
|
|
34
|
+
};
|
|
35
|
+
return run({ api: { ...sdk.api, fetch,
|
|
36
|
+
get: (path) => request('GET', path), post: (path, body) => request('POST', path, body),
|
|
37
|
+
put: (path, body) => request('PUT', path, body), patch: (path, body) => request('PATCH', path, body), delete: (path, body) => request('DELETE', path, body),
|
|
38
|
+
}, functions: { invoke: (id, params) => sdk.functions.invoke(id, params, traceAction) } });
|
|
39
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const artifactServerMethodSchema: z.ZodEnum<{
|
|
3
|
+
GET: "GET";
|
|
4
|
+
POST: "POST";
|
|
5
|
+
DELETE: "DELETE";
|
|
6
|
+
HEAD: "HEAD";
|
|
7
|
+
PUT: "PUT";
|
|
8
|
+
PATCH: "PATCH";
|
|
9
|
+
}>;
|
|
10
|
+
export declare const artifactServerModeSchema: z.ZodDefault<z.ZodEnum<{
|
|
11
|
+
user: "user";
|
|
12
|
+
service: "service";
|
|
13
|
+
}>>;
|
|
14
|
+
/** Reject paths that intermediaries can normalize into a different authorization target. */
|
|
15
|
+
export declare function isArtifactApiPath(path: string): boolean;
|
|
16
|
+
/** Atlas validates these operations against current manifest, bindings and principal grants. */
|
|
17
|
+
export declare const artifactServerRequestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
18
|
+
operation: z.ZodLiteral<"api">;
|
|
19
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
20
|
+
user: "user";
|
|
21
|
+
service: "service";
|
|
22
|
+
}>>;
|
|
23
|
+
method: z.ZodEnum<{
|
|
24
|
+
GET: "GET";
|
|
25
|
+
POST: "POST";
|
|
26
|
+
DELETE: "DELETE";
|
|
27
|
+
HEAD: "HEAD";
|
|
28
|
+
PUT: "PUT";
|
|
29
|
+
PATCH: "PATCH";
|
|
30
|
+
}>;
|
|
31
|
+
path: z.ZodString;
|
|
32
|
+
body: z.ZodOptional<z.ZodUnknown>;
|
|
33
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
34
|
+
operation: z.ZodLiteral<"orm">;
|
|
35
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
36
|
+
user: "user";
|
|
37
|
+
service: "service";
|
|
38
|
+
}>>;
|
|
39
|
+
namespace: z.ZodString;
|
|
40
|
+
op: z.ZodString;
|
|
41
|
+
variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
42
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
43
|
+
operation: z.ZodLiteral<"storage">;
|
|
44
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
45
|
+
user: "user";
|
|
46
|
+
service: "service";
|
|
47
|
+
}>>;
|
|
48
|
+
method: z.ZodEnum<{
|
|
49
|
+
GET: "GET";
|
|
50
|
+
POST: "POST";
|
|
51
|
+
DELETE: "DELETE";
|
|
52
|
+
HEAD: "HEAD";
|
|
53
|
+
PUT: "PUT";
|
|
54
|
+
PATCH: "PATCH";
|
|
55
|
+
}>;
|
|
56
|
+
path: z.ZodString;
|
|
57
|
+
body: z.ZodOptional<z.ZodUnknown>;
|
|
58
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
59
|
+
operation: z.ZodLiteral<"function">;
|
|
60
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
61
|
+
user: "user";
|
|
62
|
+
service: "service";
|
|
63
|
+
}>>;
|
|
64
|
+
id: z.ZodString;
|
|
65
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
66
|
+
}, z.core.$strict>], "operation">;
|
|
67
|
+
export type ArtifactServerRequest = z.infer<typeof artifactServerRequestSchema>;
|
|
68
|
+
export type ArtifactServerMode = z.infer<typeof artifactServerModeSchema>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const artifactServerMethodSchema = z.enum(['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']);
|
|
3
|
+
export const artifactServerModeSchema = z.enum(['user', 'service']).default('user');
|
|
4
|
+
/** Reject paths that intermediaries can normalize into a different authorization target. */
|
|
5
|
+
export function isArtifactApiPath(path) {
|
|
6
|
+
if (!path.startsWith('/api/') || path.startsWith('//') || /[\\\r\n\u0000#]/.test(path))
|
|
7
|
+
return false;
|
|
8
|
+
const pathname = path.split('?')[0];
|
|
9
|
+
if (/%(?:2e|2f|5c|25)/i.test(pathname) || pathname.includes('//'))
|
|
10
|
+
return false;
|
|
11
|
+
if (pathname.split('/').some(part => part === '.' || part === '..'))
|
|
12
|
+
return false;
|
|
13
|
+
try {
|
|
14
|
+
return new URL(path, 'https://artifact.invalid').pathname === pathname;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const path = z.string().min(6).max(8192).refine(isArtifactApiPath, 'Expected a canonical Atlas API path');
|
|
21
|
+
const method = artifactServerMethodSchema;
|
|
22
|
+
const mode = artifactServerModeSchema;
|
|
23
|
+
const id = z.string().min(1).max(255).regex(/^[a-zA-Z0-9_-]+$/);
|
|
24
|
+
/** Atlas validates these operations against current manifest, bindings and principal grants. */
|
|
25
|
+
export const artifactServerRequestSchema = z.discriminatedUnion('operation', [
|
|
26
|
+
z.object({ operation: z.literal('api'), mode, method, path, body: z.unknown().optional() }).strict(),
|
|
27
|
+
z.object({ operation: z.literal('orm'), mode, namespace: id, op: z.string().regex(/^sha256:[a-f0-9]{64}$/), variables: z.record(z.string(), z.unknown()).optional() }).strict(),
|
|
28
|
+
z.object({ operation: z.literal('storage'), mode, method, path: path.refine(value => {
|
|
29
|
+
const pathname = value.split('?')[0];
|
|
30
|
+
return pathname === '/api/storage' || pathname.startsWith('/api/storage/');
|
|
31
|
+
}, 'Expected a Storage API path'), body: z.unknown().optional() }).strict(),
|
|
32
|
+
z.object({ operation: z.literal('function'), mode, id, params: z.record(z.string(), z.unknown()).optional() }).strict(),
|
|
33
|
+
]);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { storageClient } from '@sequenceholdings/storage';
|
|
2
|
+
import { type OperationsManifest, type OperationExecutor } from '@sequenceholdings/orm/client';
|
|
3
|
+
import type { SequenceApiFetchResponse } from '../sdk.js';
|
|
4
|
+
import { type ArtifactServerMode, type ArtifactServerRequest } from './broker-contract.js';
|
|
5
|
+
import { createArtifactLogger, type ArtifactLogRecord } from './logger.js';
|
|
6
|
+
import { type ArtifactRequestContext } from './request-context.js';
|
|
7
|
+
import { type ArtifactRuntimeConfig, type ArtifactRequestHeaders } from './runtime-config.js';
|
|
8
|
+
export declare class ArtifactServerError extends Error {
|
|
9
|
+
readonly status: number;
|
|
10
|
+
readonly code: string;
|
|
11
|
+
constructor(status: number, code: string, message: string);
|
|
12
|
+
}
|
|
13
|
+
export interface ArtifactServerFetchOptions {
|
|
14
|
+
method?: 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
15
|
+
body?: unknown;
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
}
|
|
18
|
+
export interface ArtifactServerClient {
|
|
19
|
+
context: ArtifactRequestContext;
|
|
20
|
+
mode: ArtifactServerMode;
|
|
21
|
+
log: ReturnType<typeof createArtifactLogger>;
|
|
22
|
+
api: ReturnType<typeof createApiClient>;
|
|
23
|
+
storage: ReturnType<typeof storageClient>;
|
|
24
|
+
functions: {
|
|
25
|
+
invoke<TOutput = unknown>(id: string, params?: Record<string, unknown>): Promise<TOutput>;
|
|
26
|
+
};
|
|
27
|
+
orm(input: {
|
|
28
|
+
namespace: string;
|
|
29
|
+
manifest: OperationsManifest;
|
|
30
|
+
}): OperationExecutor;
|
|
31
|
+
asService(): ArtifactServerClient;
|
|
32
|
+
}
|
|
33
|
+
/** One instance per incoming request. Never cache it at module scope. */
|
|
34
|
+
export declare function createArtifactServerClient({ headers, runtime, fetch: fetcher, method, path, logSink }: {
|
|
35
|
+
headers: ArtifactRequestHeaders;
|
|
36
|
+
/** Optional explicit platform config; otherwise reads SEQUENCE_ARTIFACT_RUNTIME_FILE. */
|
|
37
|
+
runtime?: ArtifactRuntimeConfig;
|
|
38
|
+
fetch?: typeof globalThis.fetch;
|
|
39
|
+
method?: string;
|
|
40
|
+
path?: string;
|
|
41
|
+
logSink?: (record: ArtifactLogRecord) => void;
|
|
42
|
+
}): Promise<ArtifactServerClient>;
|
|
43
|
+
type ClientArguments = {
|
|
44
|
+
context: ArtifactRequestContext;
|
|
45
|
+
token: string;
|
|
46
|
+
getRuntime: () => Promise<ArtifactRuntimeConfig>;
|
|
47
|
+
fetcher: typeof globalThis.fetch;
|
|
48
|
+
mode: ArtifactServerMode;
|
|
49
|
+
logSink?: (record: ArtifactLogRecord) => void;
|
|
50
|
+
};
|
|
51
|
+
type BrokerTransport = ReturnType<typeof createBrokerTransport>;
|
|
52
|
+
declare function createBrokerTransport({ context, token, getRuntime, fetcher }: ClientArguments): (input: ArtifactServerRequest, signal?: AbortSignal) => Promise<Response>;
|
|
53
|
+
declare function createApiClient({ mode, request, json }: {
|
|
54
|
+
mode: ArtifactServerMode;
|
|
55
|
+
request: BrokerTransport;
|
|
56
|
+
json: (operation: ArtifactServerRequest, signal?: AbortSignal) => Promise<unknown>;
|
|
57
|
+
}): {
|
|
58
|
+
request: (path: string, options?: ArtifactServerFetchOptions) => Promise<Response>;
|
|
59
|
+
fetch<TBody = unknown>(path: string, options?: ArtifactServerFetchOptions): Promise<SequenceApiFetchResponse<TBody>>;
|
|
60
|
+
stream(path: string, options?: ArtifactServerFetchOptions): Promise<ReadableStream<Uint8Array<ArrayBuffer>>>;
|
|
61
|
+
get: <T = unknown>(path: string) => Promise<T>;
|
|
62
|
+
post: <T = unknown>(path: string, body?: unknown) => Promise<T>;
|
|
63
|
+
put: <T = unknown>(path: string, body?: unknown) => Promise<T>;
|
|
64
|
+
patch: <T = unknown>(path: string, body?: unknown) => Promise<T>;
|
|
65
|
+
delete: <T = unknown>(path: string, body?: unknown) => Promise<T>;
|
|
66
|
+
};
|
|
67
|
+
export {};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { StorageApiError, storageClient, storageErrorSchema } from '@sequenceholdings/storage';
|
|
2
|
+
import { OrmOperationError } from '@sequenceholdings/orm/client';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { artifactServerRequestSchema } from './broker-contract.js';
|
|
5
|
+
import { createArtifactLogger } from './logger.js';
|
|
6
|
+
import { ARTIFACT_REQUEST_CONTEXT_HEADER, ARTIFACT_RUNTIME_TOKEN_HEADER, ARTIFACT_SERVER_REQUEST_PATH, verifyArtifactRequestContext } from './request-context.js';
|
|
7
|
+
import { artifactRuntimeConfigSchema, readArtifactRuntimeConfig } from './runtime-config.js';
|
|
8
|
+
export class ArtifactServerError extends Error {
|
|
9
|
+
status;
|
|
10
|
+
code;
|
|
11
|
+
constructor(status, code, message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.status = status;
|
|
14
|
+
this.code = code;
|
|
15
|
+
this.name = 'ArtifactServerError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const graphqlResponseSchema = z.object({
|
|
19
|
+
data: z.unknown().optional(),
|
|
20
|
+
errors: z.array(z.object({ message: z.string(), extensions: z.object({ code: z.string().optional() }).passthrough().optional() })).optional(),
|
|
21
|
+
});
|
|
22
|
+
const responseHeaders = ['content-type', 'content-disposition', 'content-length', 'retry-after'];
|
|
23
|
+
const maxBodyBytes = 1024 * 1024;
|
|
24
|
+
/** One instance per incoming request. Never cache it at module scope. */
|
|
25
|
+
export async function createArtifactServerClient({ headers, runtime, fetch: fetcher = globalThis.fetch, method, path, logSink }) {
|
|
26
|
+
const config = runtime ? artifactRuntimeConfigSchema.parse(runtime) : await readArtifactRuntimeConfig();
|
|
27
|
+
if (!config)
|
|
28
|
+
throw new ArtifactServerError(401, 'runtime_not_connected', 'Run seq-studio artifact dev to connect platform services, or use a hosted preview.');
|
|
29
|
+
const token = headers.get(ARTIFACT_REQUEST_CONTEXT_HEADER);
|
|
30
|
+
const context = verifyArtifactRequestContext({ token, publicKeys: config.publicKeys, projectId: config.projectId, environment: config.environment, method, path });
|
|
31
|
+
if (!token || (config.devSessionId && context.devSessionId !== config.devSessionId) ||
|
|
32
|
+
(config.deploymentId && context.deploymentId !== config.deploymentId))
|
|
33
|
+
throw new ArtifactServerError(401, 'invalid_runtime_session', 'Artifact request does not belong to this runtime');
|
|
34
|
+
const getRuntime = async () => {
|
|
35
|
+
const current = runtime ? config : await readArtifactRuntimeConfig();
|
|
36
|
+
if (!current || current.projectId !== config.projectId || current.environment !== config.environment || current.atlasUrl !== config.atlasUrl || current.devSessionId !== config.devSessionId || current.deploymentId !== config.deploymentId)
|
|
37
|
+
throw new ArtifactServerError(401, 'runtime_changed', 'Artifact runtime disconnected or changed; reload the preview');
|
|
38
|
+
return current;
|
|
39
|
+
};
|
|
40
|
+
return buildClient({ context, token, getRuntime, fetcher, mode: 'user', logSink });
|
|
41
|
+
}
|
|
42
|
+
function buildClient(args) {
|
|
43
|
+
const { context, mode, logSink } = args;
|
|
44
|
+
const request = createBrokerTransport(args);
|
|
45
|
+
const json = async (operation, signal) => {
|
|
46
|
+
const response = await request(operation, signal);
|
|
47
|
+
if (!response.ok)
|
|
48
|
+
throw new ArtifactServerError(response.status, 'platform_request_failed', `Atlas rejected the ${operation.operation} request (${response.status})`);
|
|
49
|
+
return response.status === 204 ? undefined : response.json();
|
|
50
|
+
};
|
|
51
|
+
// storageClient retries replay-safe calls on 503 permissions_pending / storage_busy, so Storage
|
|
52
|
+
// failures keep the envelope's status and code rather than collapsing into platform_request_failed.
|
|
53
|
+
const storageJson = async (operation) => {
|
|
54
|
+
const response = await request(operation);
|
|
55
|
+
if (!response.ok)
|
|
56
|
+
throw await storageApiError(response);
|
|
57
|
+
return response.status === 204 ? undefined : response.json();
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
context,
|
|
61
|
+
mode,
|
|
62
|
+
log: createArtifactLogger({ context, sink: logSink }),
|
|
63
|
+
api: createApiClient({ mode, request, json }),
|
|
64
|
+
storage: storageClient({ request: (method, path, body) => storageJson(artifactServerRequestSchema.parse({ operation: 'storage', mode, method, path, body })) }),
|
|
65
|
+
functions: {
|
|
66
|
+
invoke: async (id, params) => {
|
|
67
|
+
// The caller supplies its application's function output type; Atlas owns runtime validation.
|
|
68
|
+
return await json(artifactServerRequestSchema.parse({ operation: 'function', mode, id, params }));
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
orm: ({ namespace, manifest }) => ({
|
|
72
|
+
async execute(op, variables = {}) {
|
|
73
|
+
const hash = op.startsWith('sha256:') ? op : manifest.operations[op]?.hash;
|
|
74
|
+
if (!hash)
|
|
75
|
+
throw new ArtifactServerError(400, 'unknown_orm_operation', 'ORM operation is not in the generated manifest; run seq-studio orm generate');
|
|
76
|
+
const response = await request(artifactServerRequestSchema.parse({ operation: 'orm', mode, namespace, op: hash, variables }));
|
|
77
|
+
const parsed = graphqlResponseSchema.safeParse(await response.json().catch(() => null));
|
|
78
|
+
if (!parsed.success)
|
|
79
|
+
throw new OrmOperationError('Malformed Data API response', response.status, 'invalid_response');
|
|
80
|
+
const first = parsed.data.errors?.[0];
|
|
81
|
+
if (!response.ok || first)
|
|
82
|
+
throw new OrmOperationError(first?.message ?? `Data API request failed (${response.status})`, response.status, first?.extensions?.code ?? null, parsed.data.errors);
|
|
83
|
+
if (parsed.data.data === undefined || parsed.data.data === null)
|
|
84
|
+
throw new OrmOperationError('Malformed Data API response: missing data', response.status, 'invalid_response');
|
|
85
|
+
// Mirrors the generated ORM executor contract: consumers provide their generated result type.
|
|
86
|
+
return parsed.data.data;
|
|
87
|
+
},
|
|
88
|
+
}),
|
|
89
|
+
/** Deliberate escalation, still clamped to a separately attached service identity. */
|
|
90
|
+
asService() {
|
|
91
|
+
if (!context.serviceAccountId)
|
|
92
|
+
throw new ArtifactServerError(403, 'service_identity_unavailable', 'No service identity is attached to this runtime');
|
|
93
|
+
return buildClient({ ...args, mode: 'service' });
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/** Mirrors createStorageClient: Storage's `{ error: { code, message } }` envelope survives the broker. */
|
|
98
|
+
async function storageApiError(response) {
|
|
99
|
+
const envelope = storageErrorSchema.safeParse(await response.json().catch(() => undefined));
|
|
100
|
+
return new StorageApiError(response.status, envelope.success ? envelope.data.error.code : 'request_failed', envelope.success ? envelope.data.error.message : `Storage request failed (${response.status})`);
|
|
101
|
+
}
|
|
102
|
+
function createBrokerTransport({ context, token, getRuntime, fetcher }) {
|
|
103
|
+
return async (input, signal) => {
|
|
104
|
+
if (context.exp <= Math.floor(Date.now() / 1000))
|
|
105
|
+
throw new ArtifactServerError(401, 'request_context_expired', 'Artifact request context expired; begin a new request');
|
|
106
|
+
const operation = artifactServerRequestSchema.parse(input);
|
|
107
|
+
if (operation.mode === 'service' && !context.serviceAccountId)
|
|
108
|
+
throw new ArtifactServerError(403, 'service_identity_unavailable', 'No service identity is attached to this runtime');
|
|
109
|
+
const body = JSON.stringify(operation);
|
|
110
|
+
if (Buffer.byteLength(body, 'utf8') > maxBodyBytes)
|
|
111
|
+
throw new ArtifactServerError(413, 'request_too_large', 'Server SDK requests are limited to 1 MiB; use Storage uploads for files');
|
|
112
|
+
const runtime = await getRuntime();
|
|
113
|
+
return fetcher(`${runtime.atlasUrl}${ARTIFACT_SERVER_REQUEST_PATH}`, {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers: {
|
|
116
|
+
'Content-Type': 'application/json',
|
|
117
|
+
[ARTIFACT_RUNTIME_TOKEN_HEADER]: runtime.sessionToken,
|
|
118
|
+
[ARTIFACT_REQUEST_CONTEXT_HEADER]: token,
|
|
119
|
+
},
|
|
120
|
+
body,
|
|
121
|
+
cache: 'no-store',
|
|
122
|
+
redirect: 'error',
|
|
123
|
+
credentials: 'omit',
|
|
124
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(60_000)]) : AbortSignal.timeout(60_000),
|
|
125
|
+
});
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function createApiClient({ mode, request, json }) {
|
|
129
|
+
const operation = (path, options = {}) => artifactServerRequestSchema.parse({ operation: 'api', mode, method: options.method ?? 'GET', path, body: options.body });
|
|
130
|
+
const fetchResponse = (path, options = {}) => request(operation(path, options), options.signal);
|
|
131
|
+
const typedJson = async (path, options = {}) => {
|
|
132
|
+
// Explicit caller result types match the existing browser SDK; the server validates endpoint schemas.
|
|
133
|
+
return await json(operation(path, options), options.signal);
|
|
134
|
+
};
|
|
135
|
+
return {
|
|
136
|
+
request: fetchResponse,
|
|
137
|
+
async fetch(path, options = {}) {
|
|
138
|
+
const response = await fetchResponse(path, options);
|
|
139
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
140
|
+
const body = response.status === 204 ? undefined : contentType.includes('json') ? await response.json() : await response.arrayBuffer();
|
|
141
|
+
// The caller declares the endpoint's response type, consistent with SequenceApiFetchResponse.
|
|
142
|
+
return { ok: response.ok, status: response.status, body: body, headers: Object.fromEntries(responseHeaders.flatMap(key => {
|
|
143
|
+
const value = response.headers.get(key);
|
|
144
|
+
return value === null ? [] : [[key, value]];
|
|
145
|
+
})) };
|
|
146
|
+
},
|
|
147
|
+
async stream(path, options = {}) {
|
|
148
|
+
const response = await fetchResponse(path, options);
|
|
149
|
+
if (!response.ok || !response.body)
|
|
150
|
+
throw new ArtifactServerError(response.status, 'stream_unavailable', 'Atlas did not return a readable response');
|
|
151
|
+
return response.body;
|
|
152
|
+
},
|
|
153
|
+
get: (path) => typedJson(path),
|
|
154
|
+
post: (path, body) => typedJson(path, { method: 'POST', body }),
|
|
155
|
+
put: (path, body) => typedJson(path, { method: 'PUT', body }),
|
|
156
|
+
patch: (path, body) => typedJson(path, { method: 'PATCH', body }),
|
|
157
|
+
delete: (path, body) => typedJson(path, { method: 'DELETE', body }),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ArtifactRequestContext } from './request-context.js';
|
|
2
|
+
export interface ArtifactLogMetadata {
|
|
3
|
+
resourceId?: string;
|
|
4
|
+
operationId?: string;
|
|
5
|
+
durationMs?: number;
|
|
6
|
+
count?: number;
|
|
7
|
+
status?: number;
|
|
8
|
+
errorCode?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ArtifactLogRecord extends ArtifactLogMetadata {
|
|
11
|
+
event: string;
|
|
12
|
+
level: 'info' | 'warn' | 'error';
|
|
13
|
+
projectId: string;
|
|
14
|
+
requestId: string;
|
|
15
|
+
deploymentId: string | null;
|
|
16
|
+
devSessionId: string | null;
|
|
17
|
+
}
|
|
18
|
+
/** Structured events deliberately exclude request bodies, URLs, arbitrary messages and error objects. */
|
|
19
|
+
export declare function createArtifactLogger({ context, sink }: {
|
|
20
|
+
context: ArtifactRequestContext;
|
|
21
|
+
sink?: (record: ArtifactLogRecord) => void;
|
|
22
|
+
}): {
|
|
23
|
+
info: (event: string, metadata?: ArtifactLogMetadata) => void;
|
|
24
|
+
warn: (event: string, metadata?: ArtifactLogMetadata) => void;
|
|
25
|
+
error: (event: string, metadata?: ArtifactLogMetadata) => void;
|
|
26
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Structured events deliberately exclude request bodies, URLs, arbitrary messages and error objects. */
|
|
2
|
+
export function createArtifactLogger({ context, sink = record => console[record.level](record) }) {
|
|
3
|
+
const write = (level, event, metadata = {}) => {
|
|
4
|
+
if (!/^[a-z][a-z0-9_.-]{0,95}$/.test(event))
|
|
5
|
+
throw new Error('Log event must be a static dot-separated identifier');
|
|
6
|
+
const safe = {};
|
|
7
|
+
for (const key of ['durationMs', 'count', 'status']) {
|
|
8
|
+
const value = metadata[key];
|
|
9
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
10
|
+
safe[key] = value;
|
|
11
|
+
}
|
|
12
|
+
for (const key of ['resourceId', 'operationId', 'errorCode']) {
|
|
13
|
+
const value = metadata[key];
|
|
14
|
+
if (typeof value === 'string' && /^[a-zA-Z0-9_.:-]{1,128}$/.test(value))
|
|
15
|
+
safe[key] = value;
|
|
16
|
+
}
|
|
17
|
+
sink({ ...safe, event, level, projectId: context.projectId, requestId: context.requestId, deploymentId: context.deploymentId, devSessionId: context.devSessionId });
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
info: (event, metadata) => write('info', event, metadata),
|
|
21
|
+
warn: (event, metadata) => write('warn', event, metadata),
|
|
22
|
+
error: (event, metadata) => write('error', event, metadata),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { TransformStream } from 'node:stream/web';
|
|
2
|
+
/**
|
|
3
|
+
* Stamp forms with the document's release identity, including GET forms whose
|
|
4
|
+
* submitter can override the method to POST. The gateway removes the reserved
|
|
5
|
+
* field from GET query strings before forwarding them to application code.
|
|
6
|
+
* Only use on decompressed UTF-8 HTML; remove Content-Length and ETag upstream.
|
|
7
|
+
* Named hidden inputs are skipped by React 19 hydration, preserving RSC markup.
|
|
8
|
+
* The gateway must reject missing/conflicting markers before admitting mutations.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createReleaseFormTransform({ deploymentId, documentUrl }: {
|
|
11
|
+
deploymentId: string;
|
|
12
|
+
documentUrl: string;
|
|
13
|
+
}): TransformStream<Uint8Array, Uint8Array>;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Tokenizer, TokenizerMode } from 'parse5';
|
|
2
|
+
import { TransformStream } from 'node:stream/web';
|
|
3
|
+
const MAX_TOKEN_CHARACTERS = 64 * 1024;
|
|
4
|
+
const PARSE_CHUNK_CHARACTERS = 8 * 1024;
|
|
5
|
+
const rawTextElements = new Set(['style', 'xmp', 'iframe', 'noembed', 'noframes']);
|
|
6
|
+
/**
|
|
7
|
+
* Use the HTML tokenizer rather than a DOM: streaming RSC documents can contain
|
|
8
|
+
* arbitrarily long scripts, and serializing a DOM would change application HTML.
|
|
9
|
+
* The exact parse5 version is pinned because its tokenizer is an internal API.
|
|
10
|
+
*/
|
|
11
|
+
class BoundedHtmlTokenizer extends Tokenizer {
|
|
12
|
+
completedToken = null;
|
|
13
|
+
prepareToken(token) {
|
|
14
|
+
super.prepareToken(token);
|
|
15
|
+
this.completedToken = token;
|
|
16
|
+
}
|
|
17
|
+
_callState(codePoint) {
|
|
18
|
+
super._callState(codePoint);
|
|
19
|
+
const pending = this.currentToken;
|
|
20
|
+
if (pending !== this.completedToken && pending?.location &&
|
|
21
|
+
this.preprocessor.offset - pending.location.startOffset > MAX_TOKEN_CHARACTERS) {
|
|
22
|
+
throw new Error('Artifact HTML contains a token exceeding the 64 KiB limit');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
releaseConsumedText() {
|
|
26
|
+
// There is no DOM consumer. Do not retain a script/text token between writes.
|
|
27
|
+
this._emitCurrentCharacterToken(null);
|
|
28
|
+
this.preprocessor.dropParsedChunk();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function resolvesToApplication({ action, baseUrl, documentUrl }) {
|
|
32
|
+
try {
|
|
33
|
+
// Missing/empty form actions resolve to the document, not its <base> URL.
|
|
34
|
+
const target = !action ? documentUrl : new URL(action, baseUrl);
|
|
35
|
+
return target.origin === documentUrl.origin && !target.username && !target.password;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Stamp forms with the document's release identity, including GET forms whose
|
|
43
|
+
* submitter can override the method to POST. The gateway removes the reserved
|
|
44
|
+
* field from GET query strings before forwarding them to application code.
|
|
45
|
+
* Only use on decompressed UTF-8 HTML; remove Content-Length and ETag upstream.
|
|
46
|
+
* Named hidden inputs are skipped by React 19 hydration, preserving RSC markup.
|
|
47
|
+
* The gateway must reject missing/conflicting markers before admitting mutations.
|
|
48
|
+
*/
|
|
49
|
+
export function createReleaseFormTransform({ deploymentId, documentUrl }) {
|
|
50
|
+
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(deploymentId))
|
|
51
|
+
throw new Error('Invalid Artifact release identifier');
|
|
52
|
+
const document = new URL(documentUrl);
|
|
53
|
+
if (!['https:', 'http:'].includes(document.protocol))
|
|
54
|
+
throw new Error('Invalid Artifact document URL');
|
|
55
|
+
const marker = `<input type="hidden" name="__sequence_release" value="${deploymentId}">`;
|
|
56
|
+
const decoder = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
|
|
57
|
+
const encoder = new TextEncoder();
|
|
58
|
+
let baseUrl = document;
|
|
59
|
+
let hasBase = false;
|
|
60
|
+
let sourceOffset = 0;
|
|
61
|
+
let insertionOffsets = [];
|
|
62
|
+
const tokenizer = new BoundedHtmlTokenizer({ sourceCodeLocationInfo: true }, {
|
|
63
|
+
onStartTag(token) {
|
|
64
|
+
const attributes = new Map(token.attrs.map(attribute => [attribute.name, attribute.value]));
|
|
65
|
+
if (token.tagName === 'base' && !hasBase && attributes.has('href')) {
|
|
66
|
+
hasBase = true;
|
|
67
|
+
try {
|
|
68
|
+
baseUrl = new URL(attributes.get('href') ?? '', document);
|
|
69
|
+
}
|
|
70
|
+
catch { /* Invalid base falls back to document. */ }
|
|
71
|
+
}
|
|
72
|
+
if (token.tagName === 'form' &&
|
|
73
|
+
resolvesToApplication({ action: attributes.get('action'), baseUrl, documentUrl: document }) && token.location) {
|
|
74
|
+
insertionOffsets.push(token.location.endOffset);
|
|
75
|
+
}
|
|
76
|
+
if (token.tagName === 'script')
|
|
77
|
+
tokenizer.state = TokenizerMode.SCRIPT_DATA;
|
|
78
|
+
else if (rawTextElements.has(token.tagName))
|
|
79
|
+
tokenizer.state = TokenizerMode.RAWTEXT;
|
|
80
|
+
else if (token.tagName === 'textarea' || token.tagName === 'title')
|
|
81
|
+
tokenizer.state = TokenizerMode.RCDATA;
|
|
82
|
+
else if (token.tagName === 'plaintext')
|
|
83
|
+
tokenizer.state = TokenizerMode.PLAINTEXT;
|
|
84
|
+
},
|
|
85
|
+
onEndTag() { },
|
|
86
|
+
onComment() { },
|
|
87
|
+
onDoctype() { },
|
|
88
|
+
onEof() { },
|
|
89
|
+
onCharacter() { },
|
|
90
|
+
onNullCharacter() { },
|
|
91
|
+
onWhitespaceCharacter() { },
|
|
92
|
+
});
|
|
93
|
+
function transformText({ text, controller, final = false }) {
|
|
94
|
+
// Bound tokenizer work and buffers even if upstream sends a very large chunk.
|
|
95
|
+
for (let offset = 0; offset < text.length;) {
|
|
96
|
+
let end = Math.min(offset + PARSE_CHUNK_CHARACTERS, text.length);
|
|
97
|
+
// Do not re-encode one half of a decoded astral Unicode character.
|
|
98
|
+
const last = text.charCodeAt(end - 1);
|
|
99
|
+
if (end < text.length && last >= 0xd800 && last <= 0xdbff)
|
|
100
|
+
end--;
|
|
101
|
+
const part = text.slice(offset, end);
|
|
102
|
+
offset = end;
|
|
103
|
+
insertionOffsets = [];
|
|
104
|
+
tokenizer.write(part, false);
|
|
105
|
+
tokenizer.releaseConsumedText();
|
|
106
|
+
let partOffset = 0;
|
|
107
|
+
let output = '';
|
|
108
|
+
for (const insertionOffset of insertionOffsets) {
|
|
109
|
+
const relative = insertionOffset - sourceOffset;
|
|
110
|
+
output += part.slice(partOffset, relative) + marker;
|
|
111
|
+
partOffset = relative;
|
|
112
|
+
}
|
|
113
|
+
output += part.slice(partOffset);
|
|
114
|
+
sourceOffset += part.length;
|
|
115
|
+
controller.enqueue(encoder.encode(output));
|
|
116
|
+
}
|
|
117
|
+
if (final)
|
|
118
|
+
tokenizer.write('', true);
|
|
119
|
+
}
|
|
120
|
+
return new TransformStream({
|
|
121
|
+
transform(chunk, controller) { transformText({ text: decoder.decode(chunk, { stream: true }), controller }); },
|
|
122
|
+
flush(controller) { transformText({ text: decoder.decode(), controller, final: true }); },
|
|
123
|
+
});
|
|
124
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { NextArtifactBridgeBootstrap, NextArtifactInitialContext } from '../next/protocol.js';
|
|
3
|
+
export declare const ARTIFACT_REQUEST_CONTEXT_HEADER = "x-sequence-artifact-context";
|
|
4
|
+
export declare const ARTIFACT_RUNTIME_TOKEN_HEADER = "x-sequence-artifact-runtime";
|
|
5
|
+
export declare const ARTIFACT_SERVER_REQUEST_PATH = "/api/artifact-studio/server/request";
|
|
6
|
+
export declare const ARTIFACT_CONTEXT_MAX_AGE_SECONDS = 60;
|
|
7
|
+
/** Public request context. No bearer credentials or secret values belong here. */
|
|
8
|
+
export declare const artifactRequestContextSchema: z.ZodObject<{
|
|
9
|
+
v: z.ZodLiteral<1>;
|
|
10
|
+
iss: z.ZodLiteral<"atlas-artifact-gateway">;
|
|
11
|
+
aud: z.ZodString;
|
|
12
|
+
sub: z.ZodString;
|
|
13
|
+
projectId: z.ZodString;
|
|
14
|
+
environment: z.ZodString;
|
|
15
|
+
mode: z.ZodEnum<{
|
|
16
|
+
preview: "preview";
|
|
17
|
+
published: "published";
|
|
18
|
+
development: "development";
|
|
19
|
+
}>;
|
|
20
|
+
deploymentId: z.ZodNullable<z.ZodString>;
|
|
21
|
+
devSessionId: z.ZodNullable<z.ZodString>;
|
|
22
|
+
requestId: z.ZodString;
|
|
23
|
+
method: z.ZodEnum<{
|
|
24
|
+
GET: "GET";
|
|
25
|
+
POST: "POST";
|
|
26
|
+
DELETE: "DELETE";
|
|
27
|
+
HEAD: "HEAD";
|
|
28
|
+
PUT: "PUT";
|
|
29
|
+
PATCH: "PATCH";
|
|
30
|
+
OPTIONS: "OPTIONS";
|
|
31
|
+
}>;
|
|
32
|
+
path: z.ZodString;
|
|
33
|
+
iat: z.ZodNumber;
|
|
34
|
+
exp: z.ZodNumber;
|
|
35
|
+
capabilities: z.ZodArray<z.ZodString>;
|
|
36
|
+
serviceAccountId: z.ZodNullable<z.ZodString>;
|
|
37
|
+
bridge: z.ZodObject<{
|
|
38
|
+
parentOrigin: z.ZodString;
|
|
39
|
+
channelId: z.ZodString;
|
|
40
|
+
nonce: z.ZodString;
|
|
41
|
+
}, z.core.$strict>;
|
|
42
|
+
theme: z.ZodEnum<{
|
|
43
|
+
dark: "dark";
|
|
44
|
+
light: "light";
|
|
45
|
+
system: "system";
|
|
46
|
+
}>;
|
|
47
|
+
lattice: z.ZodObject<{
|
|
48
|
+
runId: z.ZodNullable<z.ZodString>;
|
|
49
|
+
nodeId: z.ZodNullable<z.ZodString>;
|
|
50
|
+
stepId: z.ZodNullable<z.ZodString>;
|
|
51
|
+
processId: z.ZodNullable<z.ZodString>;
|
|
52
|
+
}, z.core.$strict>;
|
|
53
|
+
taskRoutes: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
54
|
+
}, z.core.$strict>;
|
|
55
|
+
export type ArtifactRequestContext = z.infer<typeof artifactRequestContextSchema>;
|
|
56
|
+
export type ArtifactGatewayPublicKeys = Readonly<Record<string, string>>;
|
|
57
|
+
export declare class ArtifactContextError extends Error {
|
|
58
|
+
constructor();
|
|
59
|
+
}
|
|
60
|
+
/** Only the gateway owns private keys. Applications pin platform-provided public keys. */
|
|
61
|
+
export declare function verifyArtifactRequestContext({ token, publicKeys, projectId, environment, method, path, now }: {
|
|
62
|
+
token: string | null;
|
|
63
|
+
publicKeys: ArtifactGatewayPublicKeys;
|
|
64
|
+
projectId: string;
|
|
65
|
+
environment: string;
|
|
66
|
+
/** Route handlers supply these; RSC callers use the signed gateway request metadata. */
|
|
67
|
+
method?: string;
|
|
68
|
+
path?: string;
|
|
69
|
+
now?: number;
|
|
70
|
+
}): ArtifactRequestContext;
|
|
71
|
+
/** Safe props for the client bridge; intentionally excludes actor and runtime credentials. */
|
|
72
|
+
export declare function getArtifactBrowserBootstrap(context: ArtifactRequestContext): NextArtifactBridgeBootstrap & {
|
|
73
|
+
initialContext: NextArtifactInitialContext;
|
|
74
|
+
};
|