@sequenceholdings/artifact-studio 0.2.6 → 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.
Files changed (181) hide show
  1. package/VERSION-PIN.md +8 -1
  2. package/dist/api.d.ts +11 -1
  3. package/dist/api.js +26 -9
  4. package/dist/build.d.ts +2 -2
  5. package/dist/build.js +4 -0
  6. package/dist/cli.d.ts +1 -0
  7. package/dist/cli.js +105 -10
  8. package/dist/dev-link.d.ts +1 -1
  9. package/dist/dev-link.js +2 -1
  10. package/dist/gateway/admission.d.ts +55 -0
  11. package/dist/gateway/admission.js +132 -0
  12. package/dist/gateway/control.d.ts +91 -0
  13. package/dist/gateway/control.js +113 -0
  14. package/dist/gateway/hosted-proxy.d.ts +36 -0
  15. package/dist/gateway/hosted-proxy.js +347 -0
  16. package/dist/gateway/release-admission.d.ts +23 -0
  17. package/dist/gateway/release-admission.js +259 -0
  18. package/dist/gateway/server.d.ts +29 -0
  19. package/dist/gateway/server.js +298 -0
  20. package/dist/gateway/session.d.ts +71 -0
  21. package/dist/gateway/session.js +143 -0
  22. package/dist/gateway/usage.d.ts +37 -0
  23. package/dist/gateway/usage.js +46 -0
  24. package/dist/gateway-main.d.ts +1 -0
  25. package/dist/gateway-main.js +84 -0
  26. package/dist/git-clone.d.ts +23 -1
  27. package/dist/git-clone.js +32 -15
  28. package/dist/manifest-next.d.ts +217 -0
  29. package/dist/manifest-next.js +120 -0
  30. package/dist/manifest-static.d.ts +76 -0
  31. package/dist/manifest-static.js +124 -0
  32. package/dist/manifest.d.ts +118 -13
  33. package/dist/manifest.js +12 -119
  34. package/dist/next/bridge.d.ts +47 -0
  35. package/dist/next/bridge.js +229 -0
  36. package/dist/next/browser-logs.d.ts +7 -0
  37. package/dist/next/browser-logs.js +51 -0
  38. package/dist/next/browser.d.ts +6 -0
  39. package/dist/next/browser.js +57 -0
  40. package/dist/next/protocol.d.ts +73 -0
  41. package/dist/next/protocol.js +56 -0
  42. package/dist/next/release.d.ts +15 -0
  43. package/dist/next/release.js +46 -0
  44. package/dist/next/requests.d.ts +15 -0
  45. package/dist/next/requests.js +59 -0
  46. package/dist/next/routes.d.ts +4 -0
  47. package/dist/next/routes.js +21 -0
  48. package/dist/next/streams.d.ts +16 -0
  49. package/dist/next/streams.js +132 -0
  50. package/dist/next/theme.d.ts +10 -0
  51. package/dist/next/theme.js +45 -0
  52. package/dist/next/transport.d.ts +15 -0
  53. package/dist/next/transport.js +13 -0
  54. package/dist/next-build.d.ts +29 -0
  55. package/dist/next-build.js +129 -0
  56. package/dist/next-cache/adapter-runtime.d.ts +51 -0
  57. package/dist/next-cache/adapter-runtime.js +73 -0
  58. package/dist/next-cache/component-handler.d.ts +3 -0
  59. package/dist/next-cache/component-handler.js +10 -0
  60. package/dist/next-cache/components.d.ts +20 -0
  61. package/dist/next-cache/components.js +113 -0
  62. package/dist/next-cache/config.d.ts +9 -0
  63. package/dist/next-cache/config.js +78 -0
  64. package/dist/next-cache/incremental-handler.d.ts +18 -0
  65. package/dist/next-cache/incremental-handler.js +17 -0
  66. package/dist/next-cache/incremental.d.ts +27 -0
  67. package/dist/next-cache/incremental.js +99 -0
  68. package/dist/next-cache/index.d.ts +18 -0
  69. package/dist/next-cache/index.js +16 -0
  70. package/dist/next-cache/memory-store.d.ts +27 -0
  71. package/dist/next-cache/memory-store.js +118 -0
  72. package/dist/next-cache/redis-scripts.d.ts +3 -0
  73. package/dist/next-cache/redis-scripts.js +85 -0
  74. package/dist/next-cache/redis-store.d.ts +18 -0
  75. package/dist/next-cache/redis-store.js +56 -0
  76. package/dist/next-cache/runtime.d.ts +29 -0
  77. package/dist/next-cache/runtime.js +63 -0
  78. package/dist/next-cache/serialization.d.ts +39 -0
  79. package/dist/next-cache/serialization.js +333 -0
  80. package/dist/next-cache/store.d.ts +54 -0
  81. package/dist/next-cache/store.js +45 -0
  82. package/dist/next-cache/valkey-transport.d.ts +52 -0
  83. package/dist/next-cache/valkey-transport.js +189 -0
  84. package/dist/next-dev-control.d.ts +48 -0
  85. package/dist/next-dev-control.js +63 -0
  86. package/dist/next-dev-process.d.ts +17 -0
  87. package/dist/next-dev-process.js +110 -0
  88. package/dist/next-dev.d.ts +12 -0
  89. package/dist/next-dev.js +267 -0
  90. package/dist/next-image-config.d.ts +9 -0
  91. package/dist/next-image-config.js +101 -0
  92. package/dist/next-project.d.ts +22 -0
  93. package/dist/next-project.js +64 -0
  94. package/dist/next-public-fonts-local.d.ts +7 -0
  95. package/dist/next-public-fonts-local.js +64 -0
  96. package/dist/next-public-fonts.d.ts +13 -0
  97. package/dist/next-public-fonts.js +49 -0
  98. package/dist/next-source-hash.d.ts +14 -0
  99. package/dist/next-source-hash.js +18 -0
  100. package/dist/next-source.d.ts +35 -0
  101. package/dist/next-source.js +155 -0
  102. package/dist/next.d.ts +9 -0
  103. package/dist/next.js +5 -0
  104. package/dist/project.d.ts +3 -0
  105. package/dist/project.js +9 -1
  106. package/dist/runtime-contracts.d.ts +229 -0
  107. package/dist/runtime-contracts.js +224 -0
  108. package/dist/sdk.d.ts +2 -2
  109. package/dist/server/broker-contract.d.ts +68 -0
  110. package/dist/server/broker-contract.js +33 -0
  111. package/dist/server/client.d.ts +67 -0
  112. package/dist/server/client.js +159 -0
  113. package/dist/server/logger.d.ts +26 -0
  114. package/dist/server/logger.js +24 -0
  115. package/dist/server/release-forms.d.ts +13 -0
  116. package/dist/server/release-forms.js +124 -0
  117. package/dist/server/request-context.d.ts +74 -0
  118. package/dist/server/request-context.js +103 -0
  119. package/dist/server/runtime-config.d.ts +53 -0
  120. package/dist/server/runtime-config.js +64 -0
  121. package/dist/server.d.ts +8 -0
  122. package/dist/server.js +4 -0
  123. package/dist/templates/nextjs/.artifactignore +14 -0
  124. package/dist/templates/nextjs/README.md +151 -0
  125. package/dist/templates/nextjs/app/about/page.tsx +26 -0
  126. package/dist/templates/nextjs/app/actions.ts +18 -0
  127. package/dist/templates/nextjs/app/api/health/route.ts +6 -0
  128. package/dist/templates/nextjs/app/components/counter.tsx +19 -0
  129. package/dist/templates/nextjs/app/components/greeting-form.tsx +38 -0
  130. package/dist/templates/nextjs/app/globals.css +4 -0
  131. package/dist/templates/nextjs/app/layout.tsx +27 -0
  132. package/dist/templates/nextjs/app/page.tsx +51 -0
  133. package/dist/templates/nextjs/app/providers.tsx +107 -0
  134. package/dist/templates/nextjs/artifact.bundle.yml +55 -0
  135. package/dist/templates/nextjs/next.config.mjs +27 -0
  136. package/dist/templates/nextjs/package.json +32 -0
  137. package/dist/templates/nextjs/pnpm-workspace.yaml +8 -0
  138. package/dist/templates/nextjs/postcss.config.mjs +5 -0
  139. package/dist/templates/nextjs/tsconfig.json +20 -0
  140. package/dist/templates/react-vite/package.json +1 -1
  141. package/dist/tunnel/broker.d.ts +75 -0
  142. package/dist/tunnel/broker.js +451 -0
  143. package/dist/tunnel/client.d.ts +22 -0
  144. package/dist/tunnel/client.js +177 -0
  145. package/dist/tunnel/index.d.ts +6 -0
  146. package/dist/tunnel/index.js +4 -0
  147. package/dist/tunnel/io.d.ts +33 -0
  148. package/dist/tunnel/io.js +108 -0
  149. package/dist/tunnel/peer.d.ts +25 -0
  150. package/dist/tunnel/peer.js +147 -0
  151. package/dist/tunnel/protocol.d.ts +69 -0
  152. package/dist/tunnel/protocol.js +196 -0
  153. package/dist/tunnel/redis-ownership.d.ts +38 -0
  154. package/dist/tunnel/redis-ownership.js +100 -0
  155. package/dist/tunnel/redis-socket.d.ts +13 -0
  156. package/dist/tunnel/redis-socket.js +48 -0
  157. package/dist/tunnel/redis-types.d.ts +70 -0
  158. package/dist/tunnel/redis-types.js +9 -0
  159. package/dist/tunnel/redis.d.ts +53 -0
  160. package/dist/tunnel/redis.js +391 -0
  161. package/dist/tunnel/transport.d.ts +17 -0
  162. package/dist/tunnel/transport.js +1 -0
  163. package/package.json +67 -4
  164. package/templates/nextjs/.artifactignore +14 -0
  165. package/templates/nextjs/README.md +151 -0
  166. package/templates/nextjs/app/about/page.tsx +26 -0
  167. package/templates/nextjs/app/actions.ts +18 -0
  168. package/templates/nextjs/app/api/health/route.ts +6 -0
  169. package/templates/nextjs/app/components/counter.tsx +19 -0
  170. package/templates/nextjs/app/components/greeting-form.tsx +38 -0
  171. package/templates/nextjs/app/globals.css +4 -0
  172. package/templates/nextjs/app/layout.tsx +27 -0
  173. package/templates/nextjs/app/page.tsx +51 -0
  174. package/templates/nextjs/app/providers.tsx +107 -0
  175. package/templates/nextjs/artifact.bundle.yml +55 -0
  176. package/templates/nextjs/next.config.mjs +27 -0
  177. package/templates/nextjs/package.json +32 -0
  178. package/templates/nextjs/pnpm-workspace.yaml +8 -0
  179. package/templates/nextjs/postcss.config.mjs +5 -0
  180. package/templates/nextjs/tsconfig.json +20 -0
  181. package/templates/react-vite/package.json +1 -1
@@ -0,0 +1,229 @@
1
+ import { createNextArtifactBridgeEnvelope, EMPTY_NEXT_ARTIFACT_LATTICE_CONTEXT, isNextArtifactRelativePath, nextArtifactInitialContextSchema, nextArtifactThemeSchema, parseNextArtifactBridgeEnvelope, validateNextArtifactBootstrap, } from './protocol.js';
2
+ import { createNextArtifactRequests } from './requests.js';
3
+ import { createNextArtifactStreams } from './streams.js';
4
+ import { createNextArtifactWindowTransport } from './transport.js';
5
+ import { installArtifactReleaseGuard } from './release.js';
6
+ import { stripArtifactReleaseQuery } from './routes.js';
7
+ /**
8
+ * An SSR-safe external store and DOM transport. Call connect from a client effect,
9
+ * dispose in its cleanup, and routeChanged when App Router's URL changes. Next
10
+ * keeps ownership of links and history; only host.navigate leaves the artifact.
11
+ */
12
+ export function createNextArtifactBridge(options) {
13
+ validateNextArtifactBootstrap({ bootstrap: options.bootstrap });
14
+ // Capture connection identity so a caller cannot mutate an established session.
15
+ const bootstrap = { ...options.bootstrap };
16
+ const initial = nextArtifactInitialContextSchema.parse(options.initialContext ?? {});
17
+ const serverSnapshot = { ...initial, status: 'idle' };
18
+ let snapshot = serverSnapshot;
19
+ let transport = options.transport;
20
+ let unsubscribe;
21
+ let uninstallReleaseGuard;
22
+ let handshakeTimer;
23
+ let ready;
24
+ let resolveReady;
25
+ let rejectReady;
26
+ let lastRoute = initial.route;
27
+ let pendingHostRoute;
28
+ const subscribers = new Set();
29
+ const openOverlays = new Set();
30
+ const timeoutMs = boundedTimeout({ value: options.requestTimeoutMs, fallback: 30_000 });
31
+ const requests = createNextArtifactRequests({ send, timeoutMs });
32
+ const streams = createNextArtifactStreams({ send });
33
+ function update({ next }) {
34
+ snapshot = next;
35
+ for (const subscriber of subscribers)
36
+ subscriber();
37
+ }
38
+ function send(payload) {
39
+ if (!transport || snapshot.status === 'disposed')
40
+ throw new Error('Artifact bridge is not connected');
41
+ transport.send({ message: createNextArtifactBridgeEnvelope({ bootstrap, payload }), targetOrigin: bootstrap.parentOrigin });
42
+ }
43
+ function requireReady() {
44
+ if (snapshot.status !== 'ready')
45
+ throw new Error('Artifact bridge is not ready; await connect()');
46
+ }
47
+ function applyRoute({ path, replace = true }) {
48
+ if (!isNextArtifactRelativePath({ path }))
49
+ return;
50
+ path = stripArtifactReleaseQuery({ path });
51
+ if (path === lastRoute)
52
+ return;
53
+ pendingHostRoute = { path, previous: lastRoute };
54
+ lastRoute = path;
55
+ update({ next: { ...snapshot, route: path } });
56
+ options.onNavigate?.({ path, replace });
57
+ }
58
+ function receiveContext({ payload }) {
59
+ const parsed = nextArtifactInitialContextSchema.safeParse(payload);
60
+ if (!parsed.success)
61
+ return;
62
+ const firstContext = snapshot.status === 'connecting';
63
+ clearTimeout(handshakeTimer);
64
+ update({ next: { ...snapshot, ...parsed.data, status: 'ready' } });
65
+ resolveReady?.();
66
+ if (parsed.data.route)
67
+ applyRoute({ path: parsed.data.route });
68
+ else if (firstContext && lastRoute !== undefined) {
69
+ send({ type: 'artifact-studio-route-change', hash: lastRoute === '/' ? '' : `#${lastRoute}` });
70
+ }
71
+ }
72
+ function receive(event) {
73
+ if (event.origin !== bootstrap.parentOrigin || event.source !== transport?.parent)
74
+ return;
75
+ const envelope = parseNextArtifactBridgeEnvelope({ value: event.data, channelId: bootstrap.channelId, nonce: bootstrap.nonce });
76
+ if (!envelope)
77
+ return;
78
+ const payload = envelope.payload;
79
+ if (payload.type === 'artifact-studio-next-context') {
80
+ receiveContext({ payload });
81
+ return;
82
+ }
83
+ if (snapshot.status !== 'ready')
84
+ return;
85
+ if (payload.type === 'artifact-studio-set-theme') {
86
+ const parsed = nextArtifactThemeSchema.safeParse(payload);
87
+ if (parsed.success)
88
+ update({ next: { ...snapshot, theme: parsed.data } });
89
+ }
90
+ else if (payload.type === 'artifact-studio-set-route' && typeof payload.hash === 'string') {
91
+ const path = payload.hash === '' ? '/' : payload.hash.startsWith('#') ? payload.hash.slice(1) : '';
92
+ applyRoute({ path, replace: payload.replace !== false });
93
+ }
94
+ else {
95
+ requests.receive({ payload });
96
+ streams.receive({ payload });
97
+ }
98
+ }
99
+ const bridge = {
100
+ connect() {
101
+ if (snapshot.status === 'disposed')
102
+ return Promise.reject(new Error('Artifact bridge disposed'));
103
+ if (ready)
104
+ return ready;
105
+ if (!transport) {
106
+ transport = createNextArtifactWindowTransport();
107
+ uninstallReleaseGuard = installArtifactReleaseGuard({ deploymentId: bootstrap.deploymentId ?? null });
108
+ }
109
+ ready = new Promise((resolve, reject) => { resolveReady = resolve; rejectReady = reject; });
110
+ // connect is often invoked by an effect; retain rejection for callers without
111
+ // creating an unhandled rejection before an effect installs its handler.
112
+ void ready.catch(() => undefined);
113
+ update({ next: { ...snapshot, status: 'connecting' } });
114
+ unsubscribe = transport.listen(receive);
115
+ handshakeTimer = setTimeout(() => {
116
+ rejectReady?.(new Error('Artifact host handshake timed out'));
117
+ bridge.dispose();
118
+ }, boundedTimeout({ value: options.handshakeTimeoutMs, fallback: 10_000 }));
119
+ try {
120
+ send({ type: 'artifact-studio-next-ready' });
121
+ }
122
+ catch (error) {
123
+ rejectReady?.(error instanceof Error ? error : new Error('Artifact host handshake failed'));
124
+ bridge.dispose();
125
+ }
126
+ return ready;
127
+ },
128
+ dispose() {
129
+ if (snapshot.status === 'disposed')
130
+ return;
131
+ requests.dispose();
132
+ streams.dispose();
133
+ for (const overlayId of openOverlays) {
134
+ try {
135
+ send({ type: 'artifact-studio-overlay-state', overlayId, scope: 'viewport', open: false });
136
+ }
137
+ catch { /* Host is already detached. */ }
138
+ }
139
+ openOverlays.clear();
140
+ clearTimeout(handshakeTimer);
141
+ unsubscribe?.();
142
+ uninstallReleaseGuard?.();
143
+ rejectReady?.(new Error('Artifact bridge disposed'));
144
+ update({ next: { ...snapshot, status: 'disposed' } });
145
+ subscribers.clear();
146
+ },
147
+ getSnapshot: () => snapshot,
148
+ getServerSnapshot: () => serverSnapshot,
149
+ subscribe(listener) { subscribers.add(listener); return () => { subscribers.delete(listener); }; },
150
+ request(input) {
151
+ try {
152
+ requireReady();
153
+ }
154
+ catch (error) {
155
+ return Promise.reject(error);
156
+ }
157
+ return requests.request(input);
158
+ },
159
+ stream(input) {
160
+ try {
161
+ requireReady();
162
+ }
163
+ catch (error) {
164
+ return Promise.reject(error);
165
+ }
166
+ return streams.open(input);
167
+ },
168
+ routeChanged({ path }) {
169
+ if (!isNextArtifactRelativePath({ path }))
170
+ throw new Error('Artifact route must be a relative path');
171
+ path = stripArtifactReleaseQuery({ path });
172
+ // App Router navigation commits asynchronously. An effect still reporting
173
+ // the old URL must not immediately navigate the shell back to that URL.
174
+ if (pendingHostRoute && path === pendingHostRoute.previous)
175
+ return;
176
+ pendingHostRoute = undefined;
177
+ if (path === lastRoute)
178
+ return;
179
+ lastRoute = path;
180
+ update({ next: { ...snapshot, route: path } });
181
+ if (snapshot.status === 'ready')
182
+ send({ type: 'artifact-studio-route-change', hash: path === '/' ? '' : `#${path}` });
183
+ },
184
+ setDocumentTitle({ label }) {
185
+ requireReady();
186
+ send({ type: 'artifact-studio-document-title', label: label.slice(0, 200) });
187
+ },
188
+ reportRuntimeLog(input) {
189
+ if (snapshot.status !== 'ready')
190
+ return;
191
+ send({ ...input, type: 'artifact-studio-runtime-log', message: input.message.slice(0, 16_384), args: input.args.slice(0, 32) });
192
+ },
193
+ forwardHostShortcut(input) {
194
+ if (snapshot.status !== 'ready' || input.key.toLowerCase() !== 'k' || !(input.metaKey || input.ctrlKey) || input.altKey)
195
+ return false;
196
+ send({ type: 'artifact-studio-host-shortcut', key: input.key, code: input.code, metaKey: input.metaKey, ctrlKey: input.ctrlKey, shiftKey: input.shiftKey, altKey: input.altKey });
197
+ return true;
198
+ },
199
+ lattice: { context: () => ({ ...(snapshot.lattice ?? EMPTY_NEXT_ARTIFACT_LATTICE_CONTEXT) }) },
200
+ host: {
201
+ navigate(path) {
202
+ requireReady();
203
+ if (!isNextArtifactRelativePath({ path }))
204
+ throw new Error('Host navigation requires a relative path');
205
+ send({ type: 'artifact-studio-host-navigate', path: stripArtifactReleaseQuery({ path }) });
206
+ },
207
+ setOverlayState({ overlayId, scope, open }) {
208
+ requireReady();
209
+ if (!overlayId || overlayId.length > 128 || scope !== 'viewport')
210
+ throw new Error('Invalid Artifact overlay');
211
+ if (openOverlays.size >= 100 && open && !openOverlays.has(overlayId))
212
+ throw new Error('Too many Artifact overlays');
213
+ if (open)
214
+ openOverlays.add(overlayId);
215
+ else
216
+ openOverlays.delete(overlayId);
217
+ send({ type: 'artifact-studio-overlay-state', overlayId, scope, open });
218
+ },
219
+ },
220
+ };
221
+ return bridge;
222
+ }
223
+ function boundedTimeout({ value, fallback }) {
224
+ if (value === undefined)
225
+ return fallback;
226
+ if (!Number.isFinite(value) || value < 1 || value > 300_000)
227
+ throw new Error('Artifact timeout must be between 1 and 300000 milliseconds');
228
+ return value;
229
+ }
@@ -0,0 +1,7 @@
1
+ import type { NextArtifactRuntimeLog } from './protocol.js';
2
+ /** Best-effort redaction, not permission to log secrets or request payloads. */
3
+ export declare function createNextArtifactRuntimeLog({ level, source, args }: {
4
+ level: NextArtifactRuntimeLog['level'];
5
+ source: NextArtifactRuntimeLog['source'];
6
+ args: unknown[];
7
+ }): NextArtifactRuntimeLog;
@@ -0,0 +1,51 @@
1
+ const SENSITIVE_KEY = /authorization|cookie|password|secret|token|credential|api[-_]?key/i;
2
+ function safeString({ value }) {
3
+ return value.slice(0, 8192)
4
+ .replace(/\bBearer\s+[A-Za-z0-9._~+\/-]+=*/gi, 'Bearer [REDACTED]')
5
+ .replace(/\b(password|secret|token|api[-_]?key)\s*[:=]\s*[^\s,;]+/gi, '$1=[REDACTED]')
6
+ .replace(/(https?:\/\/[^\s?#]+)\?[^\s#]*/g, '$1?[REDACTED]');
7
+ }
8
+ /** Best-effort redaction, not permission to log secrets or request payloads. */
9
+ export function createNextArtifactRuntimeLog({ level, source, args }) {
10
+ const seen = new WeakSet();
11
+ function serialize({ value, depth }) {
12
+ if (typeof value === 'string')
13
+ return safeString({ value });
14
+ if (value === null || typeof value === 'boolean' || typeof value === 'number')
15
+ return value;
16
+ if (typeof value !== 'object')
17
+ return String(value).slice(0, 128);
18
+ if (seen.has(value))
19
+ return '[Circular]';
20
+ if (depth >= 3)
21
+ return '[Truncated]';
22
+ seen.add(value);
23
+ if (value instanceof Error)
24
+ return { name: value.name, message: safeString({ value: value.message }), stack: safeString({ value: value.stack ?? '' }) };
25
+ if (Array.isArray(value))
26
+ return value.slice(0, 16).map((item) => serialize({ value: item, depth: depth + 1 }));
27
+ const result = {};
28
+ for (const key of Object.keys(value).slice(0, 20)) {
29
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
30
+ // Never invoke application getters to capture a console message.
31
+ result[key] = SENSITIVE_KEY.test(key) ? '[REDACTED]' : descriptor && 'value' in descriptor ? serialize({ value: descriptor.value, depth: depth + 1 }) : '[Accessor]';
32
+ }
33
+ return result;
34
+ }
35
+ const serialized = args.slice(0, 16).map((value) => {
36
+ try {
37
+ return serialize({ value, depth: 0 });
38
+ }
39
+ catch {
40
+ return '[Unserializable]';
41
+ }
42
+ });
43
+ // Preserve the existing host log shape while bounding the whole argument set.
44
+ const encoded = JSON.stringify(serialized);
45
+ const bounded = encoded.length > 16_384 ? [encoded.slice(0, 16_384)] : serialized;
46
+ return {
47
+ level, source, args: bounded,
48
+ message: bounded.map((value) => typeof value === 'string' ? value : JSON.stringify(value)).join(' ').slice(0, 16_384),
49
+ timestamp: new Date().toISOString(),
50
+ };
51
+ }
@@ -0,0 +1,6 @@
1
+ import type { NextArtifactBridge } from './bridge.js';
2
+ /** Install once per mounted provider; cleanup restores the original browser APIs. */
3
+ export declare function installNextArtifactBrowserIntegration({ bridge, captureConsole }: {
4
+ bridge: NextArtifactBridge;
5
+ captureConsole?: boolean;
6
+ }): () => void;
@@ -0,0 +1,57 @@
1
+ import { createNextArtifactRuntimeLog } from './browser-logs.js';
2
+ /** Install once per mounted provider; cleanup restores the original browser APIs. */
3
+ export function installNextArtifactBrowserIntegration({ bridge, captureConsole = true }) {
4
+ if (typeof window === 'undefined')
5
+ return () => undefined;
6
+ const pending = [];
7
+ function emit(input) {
8
+ try {
9
+ const log = createNextArtifactRuntimeLog(input);
10
+ if (bridge.getSnapshot().status === 'ready')
11
+ bridge.reportRuntimeLog(log);
12
+ else if (pending.length < 32 && bridge.getSnapshot().status !== 'disposed')
13
+ pending.push(log);
14
+ }
15
+ catch { /* Logging must never interrupt application behavior. */ }
16
+ }
17
+ const unsubscribe = bridge.subscribe(() => {
18
+ if (bridge.getSnapshot().status !== 'ready')
19
+ return;
20
+ for (const log of pending.splice(0)) {
21
+ try {
22
+ bridge.reportRuntimeLog(log);
23
+ }
24
+ catch { /* The host may have disconnected. */ }
25
+ }
26
+ });
27
+ const restorers = [];
28
+ if (captureConsole) {
29
+ const levels = ['log', 'info', 'warn', 'error'];
30
+ for (const level of levels) {
31
+ const original = console[level];
32
+ function instrumented(...args) {
33
+ emit({ level, source: 'console', args });
34
+ original.apply(console, args);
35
+ }
36
+ console[level] = instrumented;
37
+ restorers.push(() => { if (console[level] === instrumented)
38
+ console[level] = original; });
39
+ }
40
+ }
41
+ const onError = (event) => emit({ level: 'error', source: 'error', args: [event.error ?? event.message] });
42
+ const onRejection = (event) => emit({ level: 'error', source: 'unhandledrejection', args: [event.reason] });
43
+ const onShortcut = (event) => { if (bridge.forwardHostShortcut(event))
44
+ event.preventDefault(); };
45
+ window.addEventListener('error', onError);
46
+ window.addEventListener('unhandledrejection', onRejection);
47
+ window.addEventListener('keydown', onShortcut);
48
+ return () => {
49
+ unsubscribe();
50
+ for (const restore of restorers)
51
+ restore();
52
+ pending.length = 0;
53
+ window.removeEventListener('error', onError);
54
+ window.removeEventListener('unhandledrejection', onRejection);
55
+ window.removeEventListener('keydown', onShortcut);
56
+ };
57
+ }
@@ -0,0 +1,73 @@
1
+ import { z } from 'zod';
2
+ export declare const NEXT_ARTIFACT_BRIDGE_TYPE = "artifact-studio-next";
3
+ export declare const NEXT_ARTIFACT_BRIDGE_VERSION = 1;
4
+ export declare const nextArtifactThemeSchema: z.ZodObject<{
5
+ tokens: z.ZodNullable<z.ZodString>;
6
+ theme: z.ZodNullable<z.ZodString>;
7
+ dark: z.ZodBoolean;
8
+ vars: z.ZodRecord<z.ZodString, z.ZodString>;
9
+ }, z.core.$strip>;
10
+ export type NextArtifactTheme = z.infer<typeof nextArtifactThemeSchema>;
11
+ export declare const nextArtifactLatticeContextSchema: z.ZodObject<{
12
+ runId: z.ZodNullable<z.ZodString>;
13
+ nodeId: z.ZodNullable<z.ZodString>;
14
+ stepId: z.ZodNullable<z.ZodString>;
15
+ processId: z.ZodNullable<z.ZodString>;
16
+ }, z.core.$strip>;
17
+ export type NextArtifactLatticeContext = z.infer<typeof nextArtifactLatticeContextSchema>;
18
+ export declare const nextArtifactInitialContextSchema: z.ZodObject<{
19
+ theme: z.ZodOptional<z.ZodObject<{
20
+ tokens: z.ZodNullable<z.ZodString>;
21
+ theme: z.ZodNullable<z.ZodString>;
22
+ dark: z.ZodBoolean;
23
+ vars: z.ZodRecord<z.ZodString, z.ZodString>;
24
+ }, z.core.$strip>>;
25
+ lattice: z.ZodOptional<z.ZodObject<{
26
+ runId: z.ZodNullable<z.ZodString>;
27
+ nodeId: z.ZodNullable<z.ZodString>;
28
+ stepId: z.ZodNullable<z.ZodString>;
29
+ processId: z.ZodNullable<z.ZodString>;
30
+ }, z.core.$strip>>;
31
+ route: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
32
+ }, z.core.$strip>;
33
+ export type NextArtifactInitialContext = z.infer<typeof nextArtifactInitialContextSchema>;
34
+ /** UI context only. These fields never convey backend identity or authorization. */
35
+ export interface NextArtifactBridgeBootstrap {
36
+ parentOrigin: string;
37
+ channelId: string;
38
+ nonce: string;
39
+ deploymentId?: string | null;
40
+ }
41
+ export interface NextArtifactRuntimeLog {
42
+ level: 'log' | 'info' | 'warn' | 'error';
43
+ source: 'console' | 'error' | 'unhandledrejection' | 'bridge';
44
+ message: string;
45
+ args: unknown[];
46
+ timestamp: string;
47
+ }
48
+ declare const envelopeSchema: z.ZodObject<{
49
+ type: z.ZodLiteral<"artifact-studio-next">;
50
+ version: z.ZodLiteral<1>;
51
+ channelId: z.ZodString;
52
+ nonce: z.ZodString;
53
+ payload: z.ZodRecord<z.ZodString, z.ZodUnknown>;
54
+ }, z.core.$strip>;
55
+ export type NextArtifactBridgeEnvelope = z.infer<typeof envelopeSchema>;
56
+ /** Caller must independently check event.source and event.origin. */
57
+ export declare function parseNextArtifactBridgeEnvelope({ value, channelId, nonce }: {
58
+ value: unknown;
59
+ channelId: string;
60
+ nonce: string;
61
+ }): NextArtifactBridgeEnvelope | null;
62
+ export declare function createNextArtifactBridgeEnvelope({ bootstrap, payload }: {
63
+ bootstrap: Pick<NextArtifactBridgeBootstrap, 'channelId' | 'nonce'>;
64
+ payload: Record<string, unknown>;
65
+ }): NextArtifactBridgeEnvelope;
66
+ export declare function validateNextArtifactBootstrap({ bootstrap }: {
67
+ bootstrap: NextArtifactBridgeBootstrap;
68
+ }): void;
69
+ export declare function isNextArtifactRelativePath({ path }: {
70
+ path: unknown;
71
+ }): boolean;
72
+ export declare const EMPTY_NEXT_ARTIFACT_LATTICE_CONTEXT: Readonly<NextArtifactLatticeContext>;
73
+ export {};
@@ -0,0 +1,56 @@
1
+ import { z } from 'zod';
2
+ import { stripArtifactReleaseQuery } from './routes.js';
3
+ export const NEXT_ARTIFACT_BRIDGE_TYPE = 'artifact-studio-next';
4
+ export const NEXT_ARTIFACT_BRIDGE_VERSION = 1;
5
+ export const nextArtifactThemeSchema = z.object({
6
+ tokens: z.string().max(128).nullable(),
7
+ theme: z.string().max(128).nullable(),
8
+ dark: z.boolean(),
9
+ vars: z.record(z.string().regex(/^--[a-zA-Z0-9_-]{1,100}$/), z.string().max(1024)),
10
+ });
11
+ export const nextArtifactLatticeContextSchema = z.object({
12
+ runId: z.string().max(256).nullable(),
13
+ nodeId: z.string().max(256).nullable(),
14
+ stepId: z.string().max(256).nullable(),
15
+ processId: z.string().max(256).nullable(),
16
+ });
17
+ export const nextArtifactInitialContextSchema = z.object({
18
+ theme: nextArtifactThemeSchema.optional(),
19
+ lattice: nextArtifactLatticeContextSchema.optional(),
20
+ route: z.string().max(8192).refine((path) => isNextArtifactRelativePath({ path })).transform((path) => stripArtifactReleaseQuery({ path })).optional(),
21
+ });
22
+ const envelopeSchema = z.object({
23
+ type: z.literal(NEXT_ARTIFACT_BRIDGE_TYPE),
24
+ version: z.literal(NEXT_ARTIFACT_BRIDGE_VERSION),
25
+ channelId: z.string().min(1).max(256),
26
+ nonce: z.string().min(16).max(256),
27
+ payload: z.record(z.string(), z.unknown()),
28
+ });
29
+ /** Caller must independently check event.source and event.origin. */
30
+ export function parseNextArtifactBridgeEnvelope({ value, channelId, nonce }) {
31
+ const parsed = envelopeSchema.safeParse(value);
32
+ if (!parsed.success || parsed.data.channelId !== channelId || parsed.data.nonce !== nonce)
33
+ return null;
34
+ return parsed.data;
35
+ }
36
+ export function createNextArtifactBridgeEnvelope({ bootstrap, payload }) {
37
+ return { type: NEXT_ARTIFACT_BRIDGE_TYPE, version: NEXT_ARTIFACT_BRIDGE_VERSION, channelId: bootstrap.channelId, nonce: bootstrap.nonce, payload };
38
+ }
39
+ export function validateNextArtifactBootstrap({ bootstrap }) {
40
+ let parsed;
41
+ try {
42
+ parsed = new URL(bootstrap.parentOrigin);
43
+ }
44
+ catch {
45
+ throw new Error('Artifact parent origin must be an exact HTTP(S) origin');
46
+ }
47
+ const local = parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname === '[::1]';
48
+ if (parsed.origin !== bootstrap.parentOrigin || (parsed.protocol !== 'https:' && !(parsed.protocol === 'http:' && local))) {
49
+ throw new Error('Artifact parent origin must be HTTPS (or HTTP loopback), without a path');
50
+ }
51
+ envelopeSchema.parse(createNextArtifactBridgeEnvelope({ bootstrap, payload: {} }));
52
+ }
53
+ export function isNextArtifactRelativePath({ path }) {
54
+ return typeof path === 'string' && path.length <= 8192 && path.startsWith('/') && !path.startsWith('//') && !/[\\\s\x00-\x1f\x7f]/.test(path);
55
+ }
56
+ export const EMPTY_NEXT_ARTIFACT_LATTICE_CONTEXT = Object.freeze({ runId: null, nodeId: null, stepId: null, processId: null });
@@ -0,0 +1,15 @@
1
+ export declare const ARTIFACT_RELEASE_HEADER = "x-sequence-artifact-release";
2
+ export declare const ARTIFACT_RELEASE_FORM_FIELD = "__sequence_release";
3
+ interface ArtifactFetchTarget {
4
+ fetch: typeof globalThis.fetch;
5
+ location: {
6
+ origin: string;
7
+ href: string;
8
+ };
9
+ }
10
+ /** Stamp the document's release, never a cookie shared with another tab. No mutations are retried. */
11
+ export declare function installArtifactReleaseGuard({ deploymentId, target }: {
12
+ deploymentId: string | null;
13
+ target?: ArtifactFetchTarget;
14
+ }): () => void;
15
+ export {};
@@ -0,0 +1,46 @@
1
+ export const ARTIFACT_RELEASE_HEADER = 'x-sequence-artifact-release';
2
+ export const ARTIFACT_RELEASE_FORM_FIELD = '__sequence_release';
3
+ const guards = new WeakMap();
4
+ /** Stamp the document's release, never a cookie shared with another tab. No mutations are retried. */
5
+ export function installArtifactReleaseGuard({ deploymentId, target = typeof window === 'undefined' ? undefined : window }) {
6
+ if (!target || !deploymentId)
7
+ return () => { };
8
+ if (!/^[a-zA-Z0-9_-]{1,255}$/.test(deploymentId))
9
+ throw new Error('Invalid Artifact deployment identifier');
10
+ const existing = guards.get(target);
11
+ if (existing) {
12
+ if (existing.deploymentId !== deploymentId)
13
+ throw new Error('Artifact document release changed; reload before submitting mutations');
14
+ existing.users += 1;
15
+ return cleanup({ target, guard: existing });
16
+ }
17
+ const original = target.fetch;
18
+ const wrapped = (input, init) => {
19
+ const url = new URL(input instanceof Request ? input.url : String(input), target.location.href);
20
+ const method = (init?.method ?? (input instanceof Request ? input.method : 'GET')).toUpperCase();
21
+ if (url.origin !== target.location.origin || ['GET', 'HEAD', 'OPTIONS'].includes(method))
22
+ return original.call(target, input, init);
23
+ const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined));
24
+ headers.set(ARTIFACT_RELEASE_HEADER, deploymentId);
25
+ return original.call(target, input, { ...init, headers });
26
+ };
27
+ const guard = { deploymentId, original, wrapped, users: 1 };
28
+ guards.set(target, guard);
29
+ target.fetch = wrapped;
30
+ return cleanup({ target, guard });
31
+ }
32
+ function cleanup({ target, guard }) {
33
+ let disposed = false;
34
+ return () => {
35
+ if (disposed)
36
+ return;
37
+ disposed = true;
38
+ guard.users -= 1;
39
+ if (guard.users !== 0)
40
+ return;
41
+ // Preserve a later wrapper installed by the application or another framework.
42
+ if (target.fetch === guard.wrapped)
43
+ target.fetch = guard.original;
44
+ guards.delete(target);
45
+ };
46
+ }
@@ -0,0 +1,15 @@
1
+ export interface NextArtifactBridgeRequest {
2
+ method: string;
3
+ args?: unknown[];
4
+ signal?: AbortSignal;
5
+ }
6
+ export declare function createNextArtifactRequests({ send, timeoutMs }: {
7
+ send(payload: Record<string, unknown>): void;
8
+ timeoutMs: number;
9
+ }): {
10
+ request({ method, args, signal }: NextArtifactBridgeRequest): Promise<unknown>;
11
+ receive({ payload }: {
12
+ payload: Record<string, unknown>;
13
+ }): void;
14
+ dispose(): void;
15
+ };
@@ -0,0 +1,59 @@
1
+ export function createNextArtifactRequests({ send, timeoutMs }) {
2
+ const pending = new Map();
3
+ // A React effect remount may reuse the session nonce. Old responses must not
4
+ // settle requests created by the new bridge instance.
5
+ const instanceId = globalThis.crypto.randomUUID();
6
+ let sequence = 0;
7
+ function cancel({ id, error }) {
8
+ const request = pending.get(id);
9
+ if (!request)
10
+ return;
11
+ pending.delete(id);
12
+ request.cleanup();
13
+ request.reject(error);
14
+ try {
15
+ send({ type: 'artifact-studio-bridge-cancel', id });
16
+ }
17
+ catch { /* A lost host cannot accept cancellation. */ }
18
+ }
19
+ return {
20
+ request({ method, args = [], signal }) {
21
+ if (signal?.aborted)
22
+ return Promise.reject(new DOMException('Artifact request aborted', 'AbortError'));
23
+ if (pending.size >= 100)
24
+ return Promise.reject(new Error('Too many pending Artifact requests'));
25
+ if (!method || method.length > 128 || args.length > 20)
26
+ return Promise.reject(new Error('Invalid Artifact bridge request'));
27
+ const id = `artifact-next-${instanceId}-${++sequence}`;
28
+ return new Promise((resolve, reject) => {
29
+ const onAbort = () => cancel({ id, error: new DOMException('Artifact request aborted', 'AbortError') });
30
+ const timer = setTimeout(() => cancel({ id, error: new Error('Artifact bridge request timed out') }), timeoutMs);
31
+ pending.set(id, { resolve, reject, cleanup() { clearTimeout(timer); signal?.removeEventListener('abort', onAbort); } });
32
+ signal?.addEventListener('abort', onAbort, { once: true });
33
+ try {
34
+ send({ type: 'artifact-studio-bridge-request', id, method, args });
35
+ }
36
+ catch (error) {
37
+ cancel({ id, error: error instanceof Error ? error : new Error('Artifact request could not be sent') });
38
+ }
39
+ });
40
+ },
41
+ receive({ payload }) {
42
+ if (payload.type !== 'artifact-studio-bridge-response' || typeof payload.id !== 'string')
43
+ return;
44
+ const request = pending.get(payload.id);
45
+ if (!request)
46
+ return;
47
+ pending.delete(payload.id);
48
+ request.cleanup();
49
+ if (typeof payload.error === 'string')
50
+ request.reject(new Error(payload.error));
51
+ else
52
+ request.resolve(payload.result);
53
+ },
54
+ dispose() {
55
+ for (const id of pending.keys())
56
+ cancel({ id, error: new Error('Artifact bridge disposed') });
57
+ },
58
+ };
59
+ }
@@ -0,0 +1,4 @@
1
+ /** Remove transport metadata without reserializing application query values. */
2
+ export declare function stripArtifactReleaseQuery({ path }: {
3
+ path: string;
4
+ }): string;
@@ -0,0 +1,21 @@
1
+ import { ARTIFACT_RELEASE_FORM_FIELD } from './release.js';
2
+ /** Remove transport metadata without reserializing application query values. */
3
+ export function stripArtifactReleaseQuery({ path }) {
4
+ const hashAt = path.indexOf('#');
5
+ const beforeHash = hashAt < 0 ? path : path.slice(0, hashAt);
6
+ const queryAt = beforeHash.indexOf('?');
7
+ if (queryAt < 0)
8
+ return path;
9
+ const query = beforeHash.slice(queryAt + 1);
10
+ const parts = query.split('&');
11
+ const kept = parts.filter((part) => {
12
+ const key = part.split('=', 1)[0] ?? '';
13
+ // URLSearchParams decodes only the key here. Non-reserved parameters retain
14
+ // their byte-for-byte spelling, duplicate order and original escaping.
15
+ return !new URLSearchParams(`${key}=`).has(ARTIFACT_RELEASE_FORM_FIELD);
16
+ });
17
+ if (kept.length === parts.length)
18
+ return path;
19
+ const remaining = kept.join('&');
20
+ return `${beforeHash.slice(0, queryAt)}${remaining ? `?${remaining}` : ''}${hashAt < 0 ? '' : path.slice(hashAt)}`;
21
+ }
@@ -0,0 +1,16 @@
1
+ import type { SequenceApiFetchOptions } from '../sdk.js';
2
+ export interface NextArtifactStreamRequest {
3
+ path: string;
4
+ options?: SequenceApiFetchOptions;
5
+ signal?: AbortSignal;
6
+ }
7
+ /** One pull grants one <=64KiB chunk; unread responses cannot fill a message queue. */
8
+ export declare function createNextArtifactStreams({ send }: {
9
+ send(payload: Record<string, unknown>): void;
10
+ }): {
11
+ open: ({ path, options, signal }: NextArtifactStreamRequest) => Promise<Response>;
12
+ receive({ payload }: {
13
+ payload: Record<string, unknown>;
14
+ }): void;
15
+ dispose(): void;
16
+ };