deepline 0.1.65 → 0.1.67

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.
@@ -35,7 +35,7 @@ import {
35
35
  const MAX_DIAGNOSTIC_HEADER_LENGTH = 120;
36
36
 
37
37
  interface RequestOptions {
38
- method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
38
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
39
39
  body?: unknown;
40
40
  formData?: FormData | (() => FormData);
41
41
  headers?: Record<string, string>;
@@ -392,6 +392,14 @@ export class HttpClient {
392
392
  });
393
393
  }
394
394
 
395
+ async patch<T = unknown>(
396
+ path: string,
397
+ body: unknown,
398
+ headers?: Record<string, string>,
399
+ ): Promise<T> {
400
+ return this.request<T>(path, { method: 'PATCH', body, headers });
401
+ }
402
+
395
403
  /**
396
404
  * Send a DELETE request.
397
405
  *
@@ -168,6 +168,8 @@ export async function harnessReadSheetDatasetRows(
168
168
  */
169
169
  export async function harnessPrewarmPostgresSessions(input: {
170
170
  executorToken: string;
171
+ orgId: string;
172
+ playName: string;
171
173
  sessions: PreloadedRuntimeDbSessionInput[];
172
174
  }): Promise<{ ok: true; sessions: number }> {
173
175
  return requireBinding().prewarmPostgresSessions(input);
@@ -180,6 +182,7 @@ export async function harnessPrewarmPostgresSessions(input: {
180
182
  export async function harnessStartSheetDataset(input: {
181
183
  baseUrl: string;
182
184
  executorToken: string;
185
+ orgId: string;
183
186
  preloadedDbSessions?: PreloadedRuntimeDbSessionInput[] | null;
184
187
  playName: string;
185
188
  tableNamespace: string;
@@ -206,6 +209,7 @@ export async function harnessStartSheetDataset(input: {
206
209
  export async function harnessPersistCompletedSheetRows(input: {
207
210
  baseUrl: string;
208
211
  executorToken: string;
212
+ orgId: string;
209
213
  preloadedDbSessions?: PreloadedRuntimeDbSessionInput[] | null;
210
214
  playName: string;
211
215
  tableNamespace: string;
@@ -50,10 +50,10 @@ export type SdkRelease = {
50
50
  };
51
51
 
52
52
  export const SDK_RELEASE = {
53
- version: '0.1.65',
53
+ version: '0.1.67',
54
54
  apiContract: '2026-05-play-bootstrap-dataset-summary',
55
55
  supportPolicy: {
56
- latest: '0.1.65',
56
+ latest: '0.1.67',
57
57
  minimumSupported: '0.1.53',
58
58
  deprecatedBelow: '0.1.53',
59
59
  },
@@ -889,3 +889,62 @@ export interface DeletePlayResult {
889
889
  deletedBindingCount: number;
890
890
  deletedRunCount: number;
891
891
  }
892
+
893
+ // ——————————————————————————————————————————————————————————
894
+ // Shareable play pages
895
+ // ——————————————————————————————————————————————————————————
896
+
897
+ /** Owner-facing view of a play's public share page. */
898
+ export interface SharePageOwnerView {
899
+ shareSlug: string;
900
+ publishedRevisionId: string;
901
+ publishedVersion: number;
902
+ visibility: string;
903
+ seoIndexing: 'index' | 'noindex';
904
+ showAverageDeeplineCost: boolean;
905
+ showAverageLatency: boolean;
906
+ /** Stable public path, e.g. `/p/{shareSlug}`. */
907
+ publicPath: string;
908
+ /** Version-pinned canonical path, e.g. `/p/{shareSlug}/v/{version}`. */
909
+ canonicalPath: string;
910
+ createdAt: number;
911
+ updatedAt: number;
912
+ }
913
+
914
+ /** One row in the owner-facing revision picker for sharing. */
915
+ export interface SharePageRevisionOption {
916
+ revisionId: string;
917
+ version: number;
918
+ isLive: boolean;
919
+ isWorking: boolean;
920
+ isPublished: boolean;
921
+ hasMap: boolean;
922
+ hasCard: boolean;
923
+ createdAt: number;
924
+ }
925
+
926
+ /** Status payload from `GET/POST/PATCH /api/v2/plays/:name/share`. */
927
+ export interface SharePageStatus {
928
+ playName: string;
929
+ share: SharePageOwnerView | null;
930
+ publishedCopy: unknown | null;
931
+ revisions: SharePageRevisionOption[];
932
+ /** Present on publish responses when share-card generation was non-strict. */
933
+ warning?: string | null;
934
+ }
935
+
936
+ export interface PublishSharePageRequest {
937
+ /** The revision to publish/repoint the public page to. */
938
+ revisionId: string;
939
+ /** Must be true — acknowledges the page is publicly viewable. */
940
+ acknowledgedUnlisted: true;
941
+ showAverageDeeplineCost?: boolean;
942
+ showAverageLatency?: boolean;
943
+ seoIndexing?: 'index' | 'noindex';
944
+ }
945
+
946
+ export interface UpdateSharePageRequest {
947
+ showAverageDeeplineCost?: boolean;
948
+ showAverageLatency?: boolean;
949
+ seoIndexing?: 'index' | 'noindex';
950
+ }
@@ -0,0 +1,312 @@
1
+ import type { CreateDbSessionResponse } from './db-session';
2
+
3
+ const POSTGRES_URL_ENCRYPTION_ALGORITHM = 'AES-GCM' as const;
4
+ const POSTGRES_URL_ENCRYPTION_KEY_ID =
5
+ 'deepline-runtime-db-session-url:v1' as const;
6
+ const POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_KEY_ID =
7
+ 'deepline-runtime-db-session-url:v2' as const;
8
+ const POSTGRES_URL_ENCRYPTION_LABEL =
9
+ 'deepline:runtime-db-session-postgres-url:v1';
10
+ const POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_ALGORITHM =
11
+ 'RSA-OAEP-256+A256GCM' as const;
12
+ const IV_LENGTH_BYTES = 12;
13
+ const AUTH_TAG_LENGTH_BYTES = 16;
14
+
15
+ export type SharedSecretEncryptedPostgresUrl = {
16
+ alg: 'A256GCM';
17
+ kid: typeof POSTGRES_URL_ENCRYPTION_KEY_ID;
18
+ iv: string;
19
+ ciphertext: string;
20
+ tag: string;
21
+ };
22
+
23
+ export type PublicKeyEncryptedPostgresUrl = {
24
+ alg: typeof POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_ALGORITHM;
25
+ kid: typeof POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_KEY_ID;
26
+ wrappedKey: string;
27
+ iv: string;
28
+ ciphertext: string;
29
+ tag: string;
30
+ };
31
+
32
+ export type EncryptedPostgresUrl =
33
+ | SharedSecretEncryptedPostgresUrl
34
+ | PublicKeyEncryptedPostgresUrl;
35
+
36
+ export type PostgresUrlEncryptionRequest = {
37
+ alg: typeof POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_ALGORITHM;
38
+ publicKeyJwk: JsonWebKey;
39
+ };
40
+
41
+ export type PostgresUrlDecryptionKey = {
42
+ request: PostgresUrlEncryptionRequest;
43
+ privateKey: CryptoKey;
44
+ };
45
+
46
+ function encodeBase64Url(bytes: Uint8Array): string {
47
+ const base64 =
48
+ typeof Buffer !== 'undefined'
49
+ ? Buffer.from(bytes).toString('base64')
50
+ : btoa(String.fromCharCode(...bytes));
51
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
52
+ }
53
+
54
+ function decodeBase64Url(value: string): Uint8Array {
55
+ const padding =
56
+ value.length % 4 === 0 ? '' : '='.repeat(4 - (value.length % 4));
57
+ const normalized = value.replace(/-/g, '+').replace(/_/g, '/') + padding;
58
+ if (typeof Buffer !== 'undefined') {
59
+ return new Uint8Array(Buffer.from(normalized, 'base64'));
60
+ }
61
+ return Uint8Array.from(atob(normalized), (char) => char.charCodeAt(0));
62
+ }
63
+
64
+ function toArrayBuffer(bytes: Uint8Array): ArrayBuffer {
65
+ return bytes.buffer.slice(
66
+ bytes.byteOffset,
67
+ bytes.byteOffset + bytes.byteLength,
68
+ ) as ArrayBuffer;
69
+ }
70
+
71
+ type RsaOaepKeyGenAlgorithm = {
72
+ name: 'RSA-OAEP';
73
+ modulusLength: number;
74
+ publicExponent: Uint8Array;
75
+ hash: string;
76
+ };
77
+
78
+ type RsaOaepImportAlgorithm = {
79
+ name: 'RSA-OAEP';
80
+ hash: string;
81
+ };
82
+
83
+ function rsaOaepAlgorithm(): RsaOaepKeyGenAlgorithm {
84
+ return {
85
+ name: 'RSA-OAEP',
86
+ modulusLength: 2048,
87
+ publicExponent: new Uint8Array([1, 0, 1]),
88
+ hash: 'SHA-256',
89
+ };
90
+ }
91
+
92
+ function rsaOaepImportAlgorithm(): RsaOaepImportAlgorithm {
93
+ return {
94
+ name: 'RSA-OAEP',
95
+ hash: 'SHA-256',
96
+ };
97
+ }
98
+
99
+ async function derivePostgresUrlEncryptionKey(
100
+ secret: string,
101
+ ): Promise<CryptoKey> {
102
+ const normalizedSecret = secret.trim();
103
+ if (!normalizedSecret) {
104
+ throw new Error('Runtime DB session encryption secret is empty.');
105
+ }
106
+ const keyBytes = new Uint8Array(
107
+ await crypto.subtle.digest(
108
+ 'SHA-256',
109
+ new TextEncoder().encode(
110
+ `${POSTGRES_URL_ENCRYPTION_LABEL}:${normalizedSecret}`,
111
+ ),
112
+ ),
113
+ );
114
+ return await crypto.subtle.importKey(
115
+ 'raw',
116
+ toArrayBuffer(keyBytes),
117
+ { name: POSTGRES_URL_ENCRYPTION_ALGORITHM },
118
+ false,
119
+ ['encrypt', 'decrypt'],
120
+ );
121
+ }
122
+
123
+ export function dbSessionPostgresUrlAad(
124
+ session: Omit<
125
+ CreateDbSessionResponse,
126
+ 'postgresUrl' | 'encryptedPostgresUrl'
127
+ >,
128
+ ): string {
129
+ return JSON.stringify({
130
+ sessionId: session.sessionId,
131
+ expiresAt: session.expiresAt,
132
+ playName: session.playName,
133
+ target: session.target,
134
+ operations: [...session.operations].sort(),
135
+ postgres: session.postgres ?? null,
136
+ });
137
+ }
138
+
139
+ export async function generateDbSessionPostgresUrlDecryptionKey(): Promise<PostgresUrlDecryptionKey> {
140
+ const keyPair = (await crypto.subtle.generateKey(rsaOaepAlgorithm(), true, [
141
+ 'encrypt',
142
+ 'decrypt',
143
+ ])) as CryptoKeyPair;
144
+ const publicKeyJwk = (await crypto.subtle.exportKey(
145
+ 'jwk',
146
+ keyPair.publicKey,
147
+ )) as JsonWebKey;
148
+ const serializablePublicKeyJwk: JsonWebKey = {
149
+ kty: publicKeyJwk.kty,
150
+ n: publicKeyJwk.n,
151
+ e: publicKeyJwk.e,
152
+ alg: publicKeyJwk.alg,
153
+ ext: publicKeyJwk.ext,
154
+ key_ops: publicKeyJwk.key_ops ? [...publicKeyJwk.key_ops] : undefined,
155
+ };
156
+ return {
157
+ request: {
158
+ alg: POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_ALGORITHM,
159
+ publicKeyJwk: serializablePublicKeyJwk,
160
+ },
161
+ privateKey: keyPair.privateKey,
162
+ };
163
+ }
164
+
165
+ export async function encryptDbSessionPostgresUrl(input: {
166
+ postgresUrl: string;
167
+ secret: string;
168
+ aad: string;
169
+ }): Promise<SharedSecretEncryptedPostgresUrl> {
170
+ const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH_BYTES));
171
+ const key = await derivePostgresUrlEncryptionKey(input.secret);
172
+ const encrypted = new Uint8Array(
173
+ await crypto.subtle.encrypt(
174
+ {
175
+ name: POSTGRES_URL_ENCRYPTION_ALGORITHM,
176
+ iv: toArrayBuffer(iv),
177
+ additionalData: new TextEncoder().encode(input.aad),
178
+ tagLength: AUTH_TAG_LENGTH_BYTES * 8,
179
+ },
180
+ key,
181
+ new TextEncoder().encode(input.postgresUrl),
182
+ ),
183
+ );
184
+ return {
185
+ alg: 'A256GCM',
186
+ kid: POSTGRES_URL_ENCRYPTION_KEY_ID,
187
+ iv: encodeBase64Url(iv),
188
+ ciphertext: encodeBase64Url(encrypted.slice(0, -AUTH_TAG_LENGTH_BYTES)),
189
+ tag: encodeBase64Url(encrypted.slice(-AUTH_TAG_LENGTH_BYTES)),
190
+ };
191
+ }
192
+
193
+ export async function encryptDbSessionPostgresUrlWithPublicKey(input: {
194
+ postgresUrl: string;
195
+ request: PostgresUrlEncryptionRequest;
196
+ aad: string;
197
+ }): Promise<PublicKeyEncryptedPostgresUrl> {
198
+ if (input.request.alg !== POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_ALGORITHM) {
199
+ throw new Error('Unsupported runtime DB session public-key envelope.');
200
+ }
201
+ const publicKey = (await crypto.subtle.importKey(
202
+ 'jwk',
203
+ input.request.publicKeyJwk,
204
+ rsaOaepImportAlgorithm(),
205
+ false,
206
+ ['encrypt'],
207
+ )) as CryptoKey;
208
+ const contentKey = (await crypto.subtle.generateKey(
209
+ { name: POSTGRES_URL_ENCRYPTION_ALGORITHM, length: 256 },
210
+ true,
211
+ ['encrypt', 'decrypt'],
212
+ )) as CryptoKey;
213
+ const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH_BYTES));
214
+ const encrypted = new Uint8Array(
215
+ await crypto.subtle.encrypt(
216
+ {
217
+ name: POSTGRES_URL_ENCRYPTION_ALGORITHM,
218
+ iv: toArrayBuffer(iv),
219
+ additionalData: new TextEncoder().encode(input.aad),
220
+ tagLength: AUTH_TAG_LENGTH_BYTES * 8,
221
+ },
222
+ contentKey,
223
+ new TextEncoder().encode(input.postgresUrl),
224
+ ),
225
+ );
226
+ const rawContentKey = (await crypto.subtle.exportKey(
227
+ 'raw',
228
+ contentKey,
229
+ )) as ArrayBuffer;
230
+ const wrappedKey = new Uint8Array(
231
+ await crypto.subtle.encrypt({ name: 'RSA-OAEP' }, publicKey, rawContentKey),
232
+ );
233
+ return {
234
+ alg: POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_ALGORITHM,
235
+ kid: POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_KEY_ID,
236
+ wrappedKey: encodeBase64Url(wrappedKey),
237
+ iv: encodeBase64Url(iv),
238
+ ciphertext: encodeBase64Url(encrypted.slice(0, -AUTH_TAG_LENGTH_BYTES)),
239
+ tag: encodeBase64Url(encrypted.slice(-AUTH_TAG_LENGTH_BYTES)),
240
+ };
241
+ }
242
+
243
+ export async function decryptDbSessionPostgresUrl(input: {
244
+ encrypted: EncryptedPostgresUrl;
245
+ secret: string;
246
+ aad: string;
247
+ }): Promise<string> {
248
+ if (
249
+ input.encrypted.alg !== 'A256GCM' ||
250
+ input.encrypted.kid !== POSTGRES_URL_ENCRYPTION_KEY_ID
251
+ ) {
252
+ throw new Error('Unsupported runtime DB session URL encryption envelope.');
253
+ }
254
+ const ciphertext = decodeBase64Url(input.encrypted.ciphertext);
255
+ const tag = decodeBase64Url(input.encrypted.tag);
256
+ const combined = new Uint8Array(ciphertext.byteLength + tag.byteLength);
257
+ combined.set(ciphertext, 0);
258
+ combined.set(tag, ciphertext.byteLength);
259
+ const key = await derivePostgresUrlEncryptionKey(input.secret);
260
+ const plaintext = await crypto.subtle.decrypt(
261
+ {
262
+ name: POSTGRES_URL_ENCRYPTION_ALGORITHM,
263
+ iv: toArrayBuffer(decodeBase64Url(input.encrypted.iv)),
264
+ additionalData: new TextEncoder().encode(input.aad),
265
+ tagLength: AUTH_TAG_LENGTH_BYTES * 8,
266
+ },
267
+ key,
268
+ toArrayBuffer(combined),
269
+ );
270
+ return new TextDecoder().decode(plaintext);
271
+ }
272
+
273
+ export async function decryptDbSessionPostgresUrlWithPrivateKey(input: {
274
+ encrypted: EncryptedPostgresUrl;
275
+ privateKey: CryptoKey;
276
+ aad: string;
277
+ }): Promise<string> {
278
+ if (
279
+ input.encrypted.alg !== POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_ALGORITHM ||
280
+ input.encrypted.kid !== POSTGRES_URL_PUBLIC_KEY_ENCRYPTION_KEY_ID
281
+ ) {
282
+ throw new Error('Unsupported runtime DB session URL public-key envelope.');
283
+ }
284
+ const rawContentKey = await crypto.subtle.decrypt(
285
+ { name: 'RSA-OAEP' },
286
+ input.privateKey,
287
+ toArrayBuffer(decodeBase64Url(input.encrypted.wrappedKey)),
288
+ );
289
+ const contentKey = await crypto.subtle.importKey(
290
+ 'raw',
291
+ rawContentKey,
292
+ { name: POSTGRES_URL_ENCRYPTION_ALGORITHM },
293
+ false,
294
+ ['decrypt'],
295
+ );
296
+ const ciphertext = decodeBase64Url(input.encrypted.ciphertext);
297
+ const tag = decodeBase64Url(input.encrypted.tag);
298
+ const combined = new Uint8Array(ciphertext.byteLength + tag.byteLength);
299
+ combined.set(ciphertext, 0);
300
+ combined.set(tag, ciphertext.byteLength);
301
+ const plaintext = await crypto.subtle.decrypt(
302
+ {
303
+ name: POSTGRES_URL_ENCRYPTION_ALGORITHM,
304
+ iv: toArrayBuffer(decodeBase64Url(input.encrypted.iv)),
305
+ additionalData: new TextEncoder().encode(input.aad),
306
+ tagLength: AUTH_TAG_LENGTH_BYTES * 8,
307
+ },
308
+ contentKey,
309
+ toArrayBuffer(combined),
310
+ );
311
+ return new TextDecoder().decode(plaintext);
312
+ }
@@ -0,0 +1,69 @@
1
+ import {
2
+ flattenStaticPipeline,
3
+ resolveSheetContractForTableNamespace,
4
+ type PlaySheetContract,
5
+ type PlayStaticPipeline,
6
+ } from '../plays/static-pipeline';
7
+ import {
8
+ RUNTIME_SHEET_ROWS_LOGICAL_TABLE,
9
+ RUNTIME_WORK_RECEIPT_LOGICAL_TABLE,
10
+ RUNTIME_WORK_RECEIPT_TABLE_NAMESPACE,
11
+ type DbLogicalTable,
12
+ type DbSessionLimits,
13
+ type DbSessionOperation,
14
+ } from './db-session';
15
+
16
+ export type RuntimeDbSessionRequirement = {
17
+ tableNamespace: string;
18
+ logicalTable: DbLogicalTable;
19
+ operations: DbSessionOperation[];
20
+ limits?: DbSessionLimits;
21
+ sheetContract?: PlaySheetContract | null;
22
+ };
23
+
24
+ export function planRuntimeSheetDbSessionRequirements(
25
+ pipeline: PlayStaticPipeline | null,
26
+ ): RuntimeDbSessionRequirement[] {
27
+ if (!pipeline) {
28
+ return [];
29
+ }
30
+ const byNamespace = new Map<string, PlaySheetContract>();
31
+ for (const substep of flattenStaticPipeline(pipeline)) {
32
+ if (substep.type !== 'map') continue;
33
+ const tableNamespace = (
34
+ substep.tableNamespace ??
35
+ substep.field ??
36
+ ''
37
+ ).trim();
38
+ if (!tableNamespace) continue;
39
+ const sheetContract =
40
+ resolveSheetContractForTableNamespace(pipeline, tableNamespace) ??
41
+ substep.sheetContract ??
42
+ null;
43
+ if (!sheetContract) continue;
44
+ byNamespace.set(tableNamespace, sheetContract);
45
+ }
46
+ return [...byNamespace.entries()].map(([tableNamespace, sheetContract]) => ({
47
+ tableNamespace,
48
+ logicalTable: RUNTIME_SHEET_ROWS_LOGICAL_TABLE,
49
+ operations: ['rows.read', 'rows.upsert'],
50
+ sheetContract,
51
+ }));
52
+ }
53
+
54
+ export function runtimeWorkReceiptDbSessionRequirement(): RuntimeDbSessionRequirement {
55
+ return {
56
+ tableNamespace: RUNTIME_WORK_RECEIPT_TABLE_NAMESPACE,
57
+ logicalTable: RUNTIME_WORK_RECEIPT_LOGICAL_TABLE,
58
+ operations: ['rows.read', 'rows.upsert'],
59
+ };
60
+ }
61
+
62
+ export function planRuntimeDbSessionRequirements(
63
+ pipeline: PlayStaticPipeline | null,
64
+ ): RuntimeDbSessionRequirement[] {
65
+ return [
66
+ ...planRuntimeSheetDbSessionRequirements(pipeline),
67
+ runtimeWorkReceiptDbSessionRequirement(),
68
+ ];
69
+ }