@oddington/sdk 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -21668,7 +21668,7 @@ function managedConnections(capability) {
21668
21668
  }
21669
21669
 
21670
21670
  // src/connections.ts
21671
- var connections = createConnections(async (name, operation, input) => {
21671
+ var connections = /* @__PURE__ */ createConnections(async (name, operation, input) => {
21672
21672
  try {
21673
21673
  const managed = process.env[MANAGED_CONNECTION_CAPABILITY_ENV];
21674
21674
  if (managed) {
@@ -22000,7 +22000,15 @@ function oddingtonOrigin(environment = process.env) {
22000
22000
  }
22001
22001
 
22002
22002
  // src/routine-local-transport.ts
22003
- function privateRoutineRequest(environment = typeof process === "undefined" ? {} : process.env) {
22003
+ var nodeRequest;
22004
+ var nodeReadable;
22005
+ if (typeof __ODDINGTON_BROWSER__ === "undefined" || !__ODDINGTON_BROWSER__) {
22006
+ ({ request: nodeRequest } = await import("node:http"));
22007
+ ({ Readable: nodeReadable } = await import("node:stream"));
22008
+ }
22009
+ function privateRoutineRequest(environment = typeof __ODDINGTON_BROWSER__ !== "undefined" && __ODDINGTON_BROWSER__ ? {} : typeof process === "undefined" ? {} : process.env) {
22010
+ if (typeof __ODDINGTON_BROWSER__ !== "undefined" && __ODDINGTON_BROWSER__)
22011
+ return;
22004
22012
  const prepared = ["ODDINGTON_ROUTINE_LOCAL", "ODDINGTON_ROUTINE_LOCAL_SOCKET", "ODDINGTON_ROUTINE_LOCAL_TOKEN"].some((key) => environment[key] !== undefined);
22005
22013
  const managed = ["ODDINGTON_FILES_SOCKET", "ODDINGTON_FILES_TOKEN"].some((key) => environment[key] !== undefined);
22006
22014
  if (!prepared && !managed)
@@ -22013,83 +22021,302 @@ function privateRoutineRequest(environment = typeof process === "undefined" ? {}
22013
22021
  ODDINGTON_ROUTINE_LOCAL_TOKEN: token
22014
22022
  });
22015
22023
  const isCurrent = () => prepared ? environment.ODDINGTON_ROUTINE_LOCAL === "1" && environment.ODDINGTON_ROUTINE_LOCAL_SOCKET === socket && environment.ODDINGTON_ROUTINE_LOCAL_TOKEN === token : environment.ODDINGTON_FILES_SOCKET === socket && environment.ODDINGTON_FILES_TOKEN === token;
22016
- return { request, isCurrent, identity: `private:${socket}` };
22017
- }
22018
- function localRoutineRequest(environment = typeof process === "undefined" ? {} : process.env) {
22019
- const {
22020
- ODDINGTON_ROUTINE_LOCAL: mode,
22021
- ODDINGTON_ROUTINE_LOCAL_SOCKET: socket,
22022
- ODDINGTON_ROUTINE_LOCAL_TOKEN: token
22023
- } = environment;
22024
- if (mode === undefined && socket === undefined && token === undefined)
22025
- return;
22026
- if (mode !== "1" || !socket || !token || !/^[0-9a-f]{64}$/.test(token))
22027
- throw new Error("Preparation transport unavailable; nothing can publish");
22028
- const tcp = /^tcp:\/\/127\.0\.0\.1:([1-9][0-9]{0,4})$/.exec(socket);
22029
- const endpoint = tcp && tcp[0] === socket && Number(tcp[1]) <= 65535 ? { hostname: "127.0.0.1", port: Number(tcp[1]) } : socket.startsWith("/") && !socket.startsWith("//") && !socket.includes("\x00") ? { socketPath: socket } : undefined;
22030
- if (!endpoint)
22031
- throw new Error("Preparation transport unavailable; nothing can publish");
22032
- return async (operation, input) => {
22033
- const { request } = await import("node:http");
22034
- return await new Promise((resolve, reject) => {
22035
- const call = request({
22024
+ if (!nodeRequest || !nodeReadable)
22025
+ throw new Error("Files transport unavailable");
22026
+ const httpRequest = nodeRequest, Readable = nodeReadable;
22027
+ const files = async (suffix, init, signal) => {
22028
+ if (!isCurrent())
22029
+ throw new FileOperationError("unsupported_context", 400);
22030
+ const tcp = /^tcp:\/\/127\.0\.0\.1:([1-9][0-9]{0,4})$/.exec(socket);
22031
+ const endpoint = tcp ? { hostname: "127.0.0.1", port: Number(tcp[1]) } : { socketPath: socket };
22032
+ const id2 = crypto.randomUUID();
22033
+ if (init.body instanceof ReadableStream || suffix === "" && init.method === "POST") {
22034
+ await new Promise((resolve, reject) => {
22035
+ const admission = httpRequest({
22036
+ ...endpoint,
22037
+ path: "/files/admit",
22038
+ method: "POST",
22039
+ timeout: 30000,
22040
+ signal,
22041
+ agent: false,
22042
+ headers: {
22043
+ ...Object.fromEntries(new Headers(init.headers)),
22044
+ authorization: `Bearer ${token}`,
22045
+ "x-oddington-files-suffix": encodeURIComponent(suffix),
22046
+ "x-oddington-files-method": init.method ?? "GET"
22047
+ }
22048
+ }, (response) => {
22049
+ const bytes = Buffer.alloc(16384);
22050
+ let size = 0;
22051
+ response.on("data", (chunk) => {
22052
+ size += chunk.length;
22053
+ if (size > 16384)
22054
+ response.destroy(new Error("Files admission result exceeds supported size"));
22055
+ else
22056
+ bytes.set(chunk, size - chunk.length);
22057
+ });
22058
+ response.once("error", reject);
22059
+ response.once("end", () => {
22060
+ if (response.statusCode === 204) {
22061
+ resolve();
22062
+ return;
22063
+ }
22064
+ try {
22065
+ const value = JSON.parse(bytes.subarray(0, size).toString());
22066
+ const code = value?.error?.code;
22067
+ const status = response.statusCode ?? 503;
22068
+ if (["invalid_input", "request_too_large", "unsupported_context", "access_denied", "capacity", "unavailable"].includes(code))
22069
+ reject(new FileOperationError(code, status, code === "capacity" || code === "unavailable"));
22070
+ else
22071
+ reject(new FileOperationError("unavailable", 503, true));
22072
+ } catch {
22073
+ reject(new FileOperationError("unavailable", 503, true));
22074
+ }
22075
+ });
22076
+ });
22077
+ admission.once("error", () => reject(new FileOperationError("unavailable", 503, true)));
22078
+ admission.once("timeout", () => admission.destroy(new Error("Files admission stalled")));
22079
+ admission.end();
22080
+ });
22081
+ if (!isCurrent())
22082
+ throw new FileOperationError("unsupported_context", 400);
22083
+ }
22084
+ let cancellation;
22085
+ const cancelCall = () => cancellation ??= new Promise((resolve, reject) => {
22086
+ const cancel = httpRequest({
22036
22087
  ...endpoint,
22037
- path: `/${operation}`,
22088
+ path: "/files/cancel",
22038
22089
  method: "POST",
22039
- headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
22040
- timeout: 120000
22090
+ timeout: 30000,
22091
+ headers: { authorization: `Bearer ${token}`, "x-oddington-files-call": id2 }
22041
22092
  }, (response) => {
22042
- const bytes = Buffer.alloc(33554432);
22043
- let size = 0;
22044
- response.on("data", (chunk) => {
22045
- size += chunk.length;
22046
- if (size > 33554432) {
22093
+ response.resume();
22094
+ response.once("end", resolve);
22095
+ response.once("error", reject);
22096
+ });
22097
+ cancel.once("error", reject);
22098
+ cancel.once("timeout", () => cancel.destroy(new Error("Files cancellation stalled")));
22099
+ cancel.end();
22100
+ });
22101
+ let pumping = Promise.resolve();
22102
+ let sourceReader;
22103
+ const result = new Promise((resolve, reject) => {
22104
+ const call = httpRequest({
22105
+ ...endpoint,
22106
+ path: "/files/stream",
22107
+ method: "POST",
22108
+ headers: {
22109
+ ...Object.fromEntries(new Headers(init.headers)),
22110
+ authorization: `Bearer ${token}`,
22111
+ "x-oddington-files-call": id2,
22112
+ "x-oddington-files-suffix": encodeURIComponent(suffix),
22113
+ "x-oddington-files-method": init.method ?? "GET"
22114
+ },
22115
+ signal,
22116
+ timeout: 30000,
22117
+ agent: false
22118
+ }, async (response) => {
22119
+ try {
22120
+ call.setTimeout(0);
22121
+ if ((response.statusCode ?? 503) >= 400) {
22122
+ await sourceReader?.cancel().catch(() => {});
22123
+ call.end();
22124
+ }
22125
+ await pumping;
22126
+ if (!isCurrent()) {
22047
22127
  response.destroy();
22048
- reject(new Error("Local result exceeds supported size"));
22049
- } else
22050
- bytes.set(chunk, size - chunk.length);
22051
- });
22052
- response.on("error", reject);
22053
- response.on("end", () => {
22054
- try {
22055
- const result = JSON.parse(bytes.subarray(0, size).toString());
22056
- if (response.statusCode !== 200) {
22057
- if (operation === "files/request") {
22058
- const error = result.error;
22059
- const status = response.statusCode ?? 503;
22060
- const codes = [
22061
- "invalid_input",
22062
- "unsupported_context",
22063
- "unauthenticated",
22064
- "access_denied",
22065
- "request_too_large",
22066
- "conflict",
22067
- "local_only",
22068
- "integrity",
22069
- "unavailable",
22070
- "capacity",
22071
- "method_not_allowed"
22072
- ];
22073
- if (error?.code && codes.includes(error.code))
22074
- throw new FileOperationError(error.code, status, error.code === "capacity" || error.code === "unavailable");
22075
- throw new FileOperationError("unavailable", 503, true);
22128
+ reject(new FileOperationError("unsupported_context", 400));
22129
+ return;
22130
+ }
22131
+ const headers = new Headers;
22132
+ for (const [key, value] of Object.entries(response.headers))
22133
+ if (value !== undefined)
22134
+ headers.set(key, Array.isArray(value) ? value.join(", ") : value);
22135
+ const source = Readable.toWeb(response, { strategy: {
22136
+ highWaterMark: 65536,
22137
+ size: (chunk) => chunk.byteLength
22138
+ } });
22139
+ const reader = source.getReader();
22140
+ let closed = false;
22141
+ const body = new ReadableStream({
22142
+ async pull(controller) {
22143
+ let timer;
22144
+ try {
22145
+ if (!isCurrent())
22146
+ throw new FileOperationError("unsupported_context", 400);
22147
+ timer = setTimeout(() => response.destroy(new Error("Files delivery stalled")), 30000);
22148
+ const next = await reader.read();
22149
+ if (closed)
22150
+ return;
22151
+ if (next.done) {
22152
+ closed = true;
22153
+ reader.releaseLock();
22154
+ controller.close();
22155
+ } else
22156
+ controller.enqueue(next.value);
22157
+ } catch (error) {
22158
+ closed = true;
22159
+ controller.error(error);
22160
+ await cancelCall().catch(() => {});
22161
+ response.socket?.destroy();
22162
+ response.destroy();
22163
+ await reader.cancel(error).catch(() => {});
22164
+ reader.releaseLock();
22165
+ } finally {
22166
+ clearTimeout(timer);
22076
22167
  }
22077
- if (operation === "data/query") {
22078
- throw dataTransportError(response.statusCode ?? 0, result);
22168
+ },
22169
+ async cancel(reason) {
22170
+ closed = true;
22171
+ await cancelCall().catch(() => {});
22172
+ response.socket?.destroy();
22173
+ response.destroy();
22174
+ call.destroy();
22175
+ await reader.cancel(reason).catch(() => {});
22176
+ reader.releaseLock();
22177
+ }
22178
+ }, { highWaterMark: 0 });
22179
+ resolve(new Response(body, { status: response.statusCode ?? 503, headers }));
22180
+ } catch (error) {
22181
+ response.destroy();
22182
+ reject(error instanceof FileOperationError ? error : new FileOperationError("unavailable", 503, true));
22183
+ }
22184
+ });
22185
+ call.on("error", (error) => {
22186
+ cancelCall().catch(() => {});
22187
+ reject(error instanceof FileOperationError ? error : new FileOperationError("unavailable", 503, true));
22188
+ });
22189
+ call.on("timeout", () => call.destroy(new Error("Files I/O stalled")));
22190
+ call.on("information", () => call.setTimeout(30000));
22191
+ if (init.body instanceof ReadableStream) {
22192
+ call.flushHeaders();
22193
+ const reader = sourceReader = init.body.getReader();
22194
+ const abort = () => {
22195
+ reader.cancel(signal.reason).catch(() => {});
22196
+ };
22197
+ signal.addEventListener("abort", abort, { once: true });
22198
+ call.once("close", abort);
22199
+ pumping = (async () => {
22200
+ try {
22201
+ for (;; ) {
22202
+ const next = await reader.read();
22203
+ if (next.done)
22204
+ break;
22205
+ if (!(next.value instanceof Uint8Array))
22206
+ throw new FileOperationError("invalid_input", 400);
22207
+ if (!isCurrent())
22208
+ throw new FileOperationError("unsupported_context", 400);
22209
+ for (let offset = 0;offset < next.value.byteLength; offset += 262144) {
22210
+ if (call.destroyed)
22211
+ throw new Error("Files parent disconnected");
22212
+ await new Promise((done, fail3) => {
22213
+ const closed = () => fail3(new Error("Files parent disconnected"));
22214
+ call.once("close", closed);
22215
+ call.write(next.value.subarray(offset, offset + 262144), (error) => {
22216
+ call.removeListener("close", closed);
22217
+ error ? fail3(error) : done();
22218
+ });
22219
+ });
22079
22220
  }
22080
- throw new Error(typeof result.error === "string" ? result.error : "Preparation failed");
22081
22221
  }
22082
- resolve(result);
22222
+ call.end();
22083
22223
  } catch (error) {
22084
- reject(error);
22224
+ call.destroy(error instanceof Error ? error : new Error("Files transport failed"));
22225
+ } finally {
22226
+ await reader.cancel().catch(() => {});
22227
+ signal.removeEventListener("abort", abort);
22228
+ call.removeListener("close", abort);
22229
+ reader.releaseLock();
22085
22230
  }
22086
- });
22087
- });
22088
- call.on("error", () => reject(new Error("Preparation transport unavailable; nothing was published")));
22089
- call.on("timeout", () => call.destroy(new Error("Preparation timed out")));
22090
- call.end(JSON.stringify(input));
22231
+ })();
22232
+ } else
22233
+ call.end(init.body === undefined ? undefined : Buffer.from(init.body));
22091
22234
  });
22235
+ try {
22236
+ return await result;
22237
+ } finally {
22238
+ await pumping;
22239
+ }
22092
22240
  };
22241
+ return { request, files, isCurrent, identity: `private:${socket}` };
22242
+ }
22243
+ function localRoutineRequest(environment = typeof __ODDINGTON_BROWSER__ !== "undefined" && __ODDINGTON_BROWSER__ ? {} : typeof process === "undefined" ? {} : process.env) {
22244
+ if (typeof __ODDINGTON_BROWSER__ === "undefined" || !__ODDINGTON_BROWSER__) {
22245
+ const {
22246
+ ODDINGTON_ROUTINE_LOCAL: mode,
22247
+ ODDINGTON_ROUTINE_LOCAL_SOCKET: socket,
22248
+ ODDINGTON_ROUTINE_LOCAL_TOKEN: token
22249
+ } = environment;
22250
+ if (mode === undefined && socket === undefined && token === undefined)
22251
+ return;
22252
+ if (mode !== "1" || !socket || !token || !/^[0-9a-f]{64}$/.test(token))
22253
+ throw new Error("Preparation transport unavailable; nothing can publish");
22254
+ const tcp = /^tcp:\/\/127\.0\.0\.1:([1-9][0-9]{0,4})$/.exec(socket);
22255
+ const endpoint = tcp && tcp[0] === socket && Number(tcp[1]) <= 65535 ? { hostname: "127.0.0.1", port: Number(tcp[1]) } : socket.startsWith("/") && !socket.startsWith("//") && !socket.includes("\x00") ? { socketPath: socket } : undefined;
22256
+ if (!endpoint)
22257
+ throw new Error("Preparation transport unavailable; nothing can publish");
22258
+ return async (operation, input) => {
22259
+ const { request } = await import("node:http");
22260
+ return await new Promise((resolve, reject) => {
22261
+ const call = request({
22262
+ ...endpoint,
22263
+ path: `/${operation}`,
22264
+ method: "POST",
22265
+ headers: { "content-type": "application/json", authorization: `Bearer ${token}` },
22266
+ timeout: 120000
22267
+ }, (response) => {
22268
+ const bytes = Buffer.alloc(33554432);
22269
+ let size = 0;
22270
+ response.on("data", (chunk) => {
22271
+ size += chunk.length;
22272
+ if (size > 33554432) {
22273
+ response.destroy();
22274
+ reject(new Error("Local result exceeds supported size"));
22275
+ } else
22276
+ bytes.set(chunk, size - chunk.length);
22277
+ });
22278
+ response.on("error", reject);
22279
+ response.on("end", () => {
22280
+ try {
22281
+ const result = JSON.parse(bytes.subarray(0, size).toString());
22282
+ if (response.statusCode !== 200) {
22283
+ if (operation === "files/request") {
22284
+ const error = result.error;
22285
+ const status = response.statusCode ?? 503;
22286
+ const codes = [
22287
+ "invalid_input",
22288
+ "unsupported_context",
22289
+ "unauthenticated",
22290
+ "access_denied",
22291
+ "request_too_large",
22292
+ "conflict",
22293
+ "local_only",
22294
+ "integrity",
22295
+ "unavailable",
22296
+ "capacity",
22297
+ "method_not_allowed"
22298
+ ];
22299
+ if (error?.code && codes.includes(error.code))
22300
+ throw new FileOperationError(error.code, status, error.code === "capacity" || error.code === "unavailable");
22301
+ throw new FileOperationError("unavailable", 503, true);
22302
+ }
22303
+ if (operation === "data/query") {
22304
+ throw dataTransportError(response.statusCode ?? 0, result);
22305
+ }
22306
+ throw new Error(typeof result.error === "string" ? result.error : "Preparation failed");
22307
+ }
22308
+ resolve(result);
22309
+ } catch (error) {
22310
+ reject(error);
22311
+ }
22312
+ });
22313
+ });
22314
+ call.on("error", () => reject(new Error("Preparation transport unavailable; nothing was published")));
22315
+ call.on("timeout", () => call.destroy(new Error("Preparation timed out")));
22316
+ call.end(JSON.stringify(input));
22317
+ });
22318
+ };
22319
+ }
22093
22320
  }
22094
22321
 
22095
22322
  // src/data-context.ts
@@ -22116,12 +22343,14 @@ async function resolveAppContext() {
22116
22343
  }
22117
22344
  async function resolveDataContext() {
22118
22345
  try {
22119
- const local = localRoutineRequest();
22120
- if (local)
22121
- return { local };
22122
- const managed = privateRoutineRequest();
22123
- if (managed)
22124
- return { local: managed.request };
22346
+ if (typeof __ODDINGTON_BROWSER__ === "undefined" || !__ODDINGTON_BROWSER__) {
22347
+ const local = localRoutineRequest();
22348
+ if (local)
22349
+ return { local };
22350
+ const managed = privateRoutineRequest();
22351
+ if (managed)
22352
+ return { local: managed.request };
22353
+ }
22125
22354
  const host = memberContext;
22126
22355
  if (host !== undefined) {
22127
22356
  const context = await host();
@@ -22133,25 +22362,31 @@ async function resolveDataContext() {
22133
22362
  throw new Error;
22134
22363
  return checked(context.workspace, context.endpoint, { authorization: `Bearer ${context.accessToken}` });
22135
22364
  }
22136
- const environment = typeof process === "undefined" ? undefined : process.env;
22137
- if (!environment || !/^[0-9a-f]{64}$/.test(environment.ODDINGTON_ROUTINE_RUN ?? ""))
22138
- throw new Error;
22139
- return checked(environment.ODDINGTON_ROUTINE_WORKSPACE, oddingtonOrigin(environment), { "x-oddington-run": environment.ODDINGTON_ROUTINE_RUN });
22365
+ if (typeof __ODDINGTON_BROWSER__ === "undefined" || !__ODDINGTON_BROWSER__) {
22366
+ const environment = typeof process === "undefined" ? undefined : process.env;
22367
+ if (!environment || !/^[0-9a-f]{64}$/.test(environment.ODDINGTON_ROUTINE_RUN ?? ""))
22368
+ throw new Error;
22369
+ return checked(environment.ODDINGTON_ROUTINE_WORKSPACE, oddingtonOrigin(environment), { "x-oddington-run": environment.ODDINGTON_ROUTINE_RUN });
22370
+ }
22371
+ throw new Error;
22140
22372
  } catch {
22141
22373
  throw new DataOperationError("context_unavailable");
22142
22374
  }
22143
22375
  }
22144
22376
  async function resolveFilesContext() {
22145
22377
  try {
22146
- const environment = typeof process === "undefined" ? {} : process.env;
22147
- const local = privateRoutineRequest(environment);
22148
- if (local)
22149
- return {
22150
- url: local.identity,
22151
- headers: {},
22152
- isCurrent: local.isCurrent,
22153
- local: local.request
22154
- };
22378
+ const environment = typeof __ODDINGTON_BROWSER__ !== "undefined" && __ODDINGTON_BROWSER__ ? {} : typeof process === "undefined" ? {} : process.env;
22379
+ if (typeof __ODDINGTON_BROWSER__ === "undefined" || !__ODDINGTON_BROWSER__) {
22380
+ const local = privateRoutineRequest(environment);
22381
+ if (local)
22382
+ return {
22383
+ url: local.identity,
22384
+ headers: {},
22385
+ isCurrent: local.isCurrent,
22386
+ local: local.request,
22387
+ localFiles: local.files
22388
+ };
22389
+ }
22155
22390
  const host = memberContext;
22156
22391
  if (host) {
22157
22392
  const context = await host();
@@ -22162,13 +22397,17 @@ async function resolveFilesContext() {
22162
22397
  return {
22163
22398
  ...filesContext(context.workspace, context.endpoint, { authorization: `Bearer ${context.accessToken}` }),
22164
22399
  isCurrent: context.isCurrent,
22165
- local: undefined
22400
+ local: undefined,
22401
+ localFiles: undefined
22166
22402
  };
22167
22403
  }
22168
- if (!/^[0-9a-f]{64}$/.test(environment.ODDINGTON_ROUTINE_RUN ?? ""))
22169
- throw new Error;
22170
- const workspace = environment.ODDINGTON_ROUTINE_WORKSPACE;
22171
- return { ...filesContext(workspace, `https://${workspace}.oddington.ai`, { "x-oddington-run": environment.ODDINGTON_ROUTINE_RUN }), local: undefined };
22404
+ if (typeof __ODDINGTON_BROWSER__ === "undefined" || !__ODDINGTON_BROWSER__) {
22405
+ if (!/^[0-9a-f]{64}$/.test(environment.ODDINGTON_ROUTINE_RUN ?? ""))
22406
+ throw new Error;
22407
+ const workspace = environment.ODDINGTON_ROUTINE_WORKSPACE;
22408
+ return { ...filesContext(workspace, `https://${workspace}.oddington.ai`, { "x-oddington-run": environment.ODDINGTON_ROUTINE_RUN }), local: undefined, localFiles: undefined };
22409
+ }
22410
+ throw new Error;
22172
22411
  } catch {
22173
22412
  throw new FileOperationError("unsupported_context", 400);
22174
22413
  }
@@ -22330,6 +22569,7 @@ var MAX_BYTES = 16 * 1024 * 1024;
22330
22569
  var PART_BYTES = 8 * 1024 * 1024;
22331
22570
  var METADATA_BYTES = 4096;
22332
22571
  var RESULT_BYTES = 16 * 1024;
22572
+ var CONTROL_RESULT_BYTES = 128 * 1024;
22333
22573
  var TIMEOUT_MS = 30000;
22334
22574
  var MAX_ACTIVE = 4;
22335
22575
  var ATTEMPTS = 2;
@@ -22390,15 +22630,15 @@ async function timed(work, parent, timeout = TIMEOUT_MS) {
22390
22630
  parent?.removeEventListener("abort", abort);
22391
22631
  }
22392
22632
  }
22393
- async function result(response) {
22633
+ async function result(response, maximum = RESULT_BYTES) {
22394
22634
  try {
22395
- const bytes = await boundedBytes(response.body, RESULT_BYTES);
22635
+ const bytes = await boundedBytes(response.body, maximum);
22396
22636
  return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
22397
22637
  } catch {
22398
22638
  throw unavailable();
22399
22639
  }
22400
22640
  }
22401
- async function hostedContent(url, init, parent) {
22641
+ async function hostedContent(url, init, parent, upstream = fetch) {
22402
22642
  const delivery = new AbortController;
22403
22643
  const abort = () => delivery.abort(parent.reason);
22404
22644
  parent.addEventListener("abort", abort, { once: true });
@@ -22406,7 +22646,7 @@ async function hostedContent(url, init, parent) {
22406
22646
  abort();
22407
22647
  const detach = () => parent.removeEventListener("abort", abort);
22408
22648
  try {
22409
- const response = await fetch(url, { ...init, signal: delivery.signal });
22649
+ const response = await upstream(url, { ...init, signal: delivery.signal });
22410
22650
  if (delivery.signal.aborted) {
22411
22651
  response.body?.cancel().catch(() => {});
22412
22652
  throw unavailable();
@@ -22482,26 +22722,7 @@ async function send(suffix, init, descriptor, initial, parent) {
22482
22722
  throw new FileOperationError("unsupported_context", 400);
22483
22723
  try {
22484
22724
  return await (async () => {
22485
- const response = context.local ? await (async () => {
22486
- const body = init.body instanceof ArrayBuffer ? new Uint8Array(init.body) : undefined;
22487
- let binary = "";
22488
- if (body)
22489
- for (let offset = 0;offset < body.length; offset += 8192)
22490
- binary += String.fromCharCode(...body.subarray(offset, offset + 8192));
22491
- const value2 = await context.local("files/request", {
22492
- suffix,
22493
- method: init.method,
22494
- headers: Object.fromEntries(new Headers(init.headers)),
22495
- ...body ? { bytes: btoa(binary) } : {}
22496
- });
22497
- if (value2.bytes !== undefined) {
22498
- if (value2.bytes.length > Math.ceil(MAX_BYTES / 3) * 4)
22499
- throw unavailable();
22500
- const data2 = atob(value2.bytes);
22501
- return new Response(Uint8Array.from(data2, (c) => c.charCodeAt(0)), { status: value2.status, headers: value2.headers });
22502
- }
22503
- return Response.json(value2.json, { status: value2.status, headers: value2.headers });
22504
- })() : await (async () => {
22725
+ const response = context.localFiles ? await context.localFiles(suffix, init, parent) : await (async () => {
22505
22726
  const url = `${context.url}${suffix}`;
22506
22727
  const request = {
22507
22728
  ...init,
@@ -22531,7 +22752,7 @@ async function send(suffix, init, descriptor, initial, parent) {
22531
22752
  }
22532
22753
  if (!descriptor)
22533
22754
  return response;
22534
- const value = await result(response);
22755
+ const value = await result(response, descriptor === "control" ? CONTROL_RESULT_BYTES : RESULT_BYTES);
22535
22756
  if (descriptor === "control") {
22536
22757
  checkCurrent();
22537
22758
  return value;
@@ -22551,7 +22772,7 @@ async function send(suffix, init, descriptor, initial, parent) {
22551
22772
  })();
22552
22773
  } catch (error) {
22553
22774
  const failure = error instanceof FileOperationError ? error : unavailable();
22554
- if (!failure.retryable || attempt + 1 === ATTEMPTS)
22775
+ if (init.body instanceof ReadableStream || !failure.retryable || attempt + 1 === ATTEMPTS)
22555
22776
  throw failure;
22556
22777
  }
22557
22778
  }
@@ -22562,7 +22783,10 @@ function idPath(id2) {
22562
22783
  throw invalid2();
22563
22784
  return `/${id2}`;
22564
22785
  }
22565
- async function multipartCreate(input, metadata, context) {
22786
+ async function multipartCreate(input, metadata, exchange, release = () => {
22787
+ active--;
22788
+ }, upload) {
22789
+ const route = upload ? `${idPath(upload.file.id)}/upload/multipart` : "/multipart";
22566
22790
  let ticket;
22567
22791
  let reader;
22568
22792
  let chunk, offset = 0;
@@ -22580,11 +22804,11 @@ async function multipartCreate(input, metadata, context) {
22580
22804
  body: JSON.stringify(body)
22581
22805
  });
22582
22806
  try {
22583
- const start = await request((signal) => send("/multipart/start", control(metadata), "control", context, signal));
22807
+ const start = await request((signal) => exchange(`${route}/start`, control(upload?.file ?? metadata), "control", signal));
22584
22808
  if (!start || typeof start !== "object" || Array.isArray(start))
22585
22809
  throw unavailable();
22586
22810
  const session = start;
22587
- if (Object.keys(session).sort().join(",") !== "partBytes,ticket" || session.partBytes !== PART_BYTES || typeof session.ticket !== "string" || !session.ticket || session.ticket.length > 8192)
22811
+ if (Object.keys(session).sort().join(",") !== "partBytes,ticket" || session.partBytes !== PART_BYTES || typeof session.ticket !== "string" || !session.ticket || session.ticket.length > 64 * 1024)
22588
22812
  throw unavailable();
22589
22813
  ticket = session.ticket;
22590
22814
  if (input.file instanceof ReadableStream)
@@ -22603,7 +22827,7 @@ async function multipartCreate(input, metadata, context) {
22603
22827
  throw error;
22604
22828
  }
22605
22829
  };
22606
- for (let sent = 0, partNumber = 1;sent < metadata.size; partNumber++) {
22830
+ for (let sent = 0, partNumber = 1;sent < metadata.size || metadata.size === 0 && partNumber === 1; partNumber++) {
22607
22831
  const length = Math.min(PART_BYTES, metadata.size - sent);
22608
22832
  let part;
22609
22833
  if (input.file instanceof Uint8Array)
@@ -22649,11 +22873,12 @@ async function multipartCreate(input, metadata, context) {
22649
22873
  throw invalid2();
22650
22874
  }
22651
22875
  }
22652
- const value = await request((signal) => send("/multipart/part", {
22876
+ const value = await request((signal) => exchange(`${route}/part`, {
22653
22877
  method: "POST",
22654
22878
  headers: { "content-type": "application/octet-stream", "x-oddington-transfer": ticket, "x-oddington-part": String(partNumber) },
22655
22879
  body: part.buffer
22656
- }, "control", context, signal));
22880
+ }, "control", signal));
22881
+ part = undefined;
22657
22882
  const receipt = value;
22658
22883
  if (!receipt || typeof receipt !== "object" || Object.keys(receipt).sort().join(",") !== "etag,partNumber" || receipt.partNumber !== partNumber || typeof receipt.etag !== "string" || !receipt.etag || receipt.etag.length > 256 || /[\u0000-\u001f\u007f]/.test(receipt.etag))
22659
22884
  throw unavailable();
@@ -22661,25 +22886,35 @@ async function multipartCreate(input, metadata, context) {
22661
22886
  sent += length;
22662
22887
  lastProgress = Date.now();
22663
22888
  }
22889
+ if (upload)
22890
+ for (const name of Object.keys(upload.file.enrichments)) {
22891
+ const bytes = await request(() => upload.enrichment(name));
22892
+ if (bytes.byteLength !== upload.file.enrichments[name].size || bytes.byteLength > MAX_BYTES)
22893
+ throw invalid2();
22894
+ await request((signal) => exchange(`${route}/enrichments/${encodeURIComponent(name)}`, {
22895
+ method: "POST",
22896
+ headers: { "content-type": "application/octet-stream", "x-oddington-transfer": ticket },
22897
+ body: bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)
22898
+ }, "control", signal));
22899
+ }
22900
+ await upload?.beforeComplete();
22664
22901
  const completion = control({ parts: receipts });
22665
22902
  if (new TextEncoder().encode(completion.body).byteLength > 128 * 1024)
22666
22903
  throw unavailable();
22667
- return await request((signal) => send("/multipart/complete", completion, true, context, signal), TIMEOUT_MS + Math.ceil(metadata.size / (1024 * 1024)) * 1000);
22904
+ return parseFileDescriptor(await request((signal) => exchange(`${route}/complete`, completion, true, signal), TIMEOUT_MS + Math.ceil(metadata.size / (1024 * 1024)) * 1000));
22668
22905
  } catch (error) {
22669
22906
  if (reader)
22670
22907
  await reader.cancel(error).catch(() => {});
22671
22908
  if (ticket)
22672
- await request((signal) => send("/multipart/abort", control({}), "control", context, signal)).catch(() => {});
22909
+ await request((signal) => exchange(`${route}/abort`, control({}), "control", signal)).catch(() => {});
22673
22910
  throw error instanceof FileOperationError ? error : unavailable();
22674
22911
  } finally {
22675
22912
  chunk = undefined;
22676
22913
  reader?.releaseLock();
22677
22914
  if (pending.size)
22678
- Promise.allSettled([...pending]).then(() => {
22679
- active--;
22680
- });
22915
+ Promise.allSettled([...pending]).then(release);
22681
22916
  else
22682
- active--;
22917
+ release();
22683
22918
  }
22684
22919
  }
22685
22920
  async function operation(work, initial, parent) {
@@ -22687,6 +22922,13 @@ async function operation(work, initial, parent) {
22687
22922
  if (active >= MAX_ACTIVE)
22688
22923
  throw new FileOperationError("capacity", 429, true);
22689
22924
  active++;
22925
+ if (context.localFiles) {
22926
+ try {
22927
+ return await work(context, parent ?? new AbortController().signal);
22928
+ } finally {
22929
+ active--;
22930
+ }
22931
+ }
22690
22932
  return timed(async (signal) => {
22691
22933
  try {
22692
22934
  return await work(context, signal);
@@ -22776,7 +23018,7 @@ var files = {
22776
23018
  storage: input.storage,
22777
23019
  csv: input.csv,
22778
23020
  requestKey: options?.requestKey === undefined ? crypto.randomUUID() : options.requestKey
22779
- }, context.local ? MAX_BYTES : FILE_ORIGINAL_MAX_BYTES);
23021
+ }, FILE_ORIGINAL_MAX_BYTES);
22780
23022
  if (!context.local && metadata.storage === "local")
22781
23023
  throw new FileOperationError("unsupported_context", 400);
22782
23024
  const header = encodeURIComponent(JSON.stringify(metadata));
@@ -22785,8 +23027,32 @@ var files = {
22785
23027
  if (active >= MAX_ACTIVE)
22786
23028
  throw new FileOperationError("capacity", 429, true);
22787
23029
  active++;
23030
+ if (context.localFiles && metadata.size > MAX_BYTES) {
23031
+ const controller = new AbortController;
23032
+ const file = input.file;
23033
+ let offset = 0;
23034
+ const body = file instanceof ReadableStream ? file : file instanceof Blob ? file.stream() : new ReadableStream({
23035
+ pull(controller2) {
23036
+ if (offset === file.byteLength) {
23037
+ controller2.close();
23038
+ return;
23039
+ }
23040
+ const end = Math.min(offset + 256 * 1024, file.byteLength);
23041
+ controller2.enqueue(file.subarray(offset, end));
23042
+ offset = end;
23043
+ }
23044
+ }, { highWaterMark: 0 });
23045
+ try {
23046
+ return await send("", { method: "POST", headers: {
23047
+ "content-type": "application/octet-stream",
23048
+ "x-oddington-file": header
23049
+ }, body }, true, context, controller.signal);
23050
+ } finally {
23051
+ active--;
23052
+ }
23053
+ }
22788
23054
  if (metadata.size > MAX_BYTES)
22789
- return multipartCreate(input, metadata, context);
23055
+ return multipartCreate(input, metadata, (suffix, init, kind, signal) => kind === true ? send(suffix, init, true, context, signal) : send(suffix, init, "control", context, signal));
22790
23056
  return timed(async (signal) => {
22791
23057
  try {
22792
23058
  const bytes = array2 ? input.file.slice() : await boundedBytes(blob ? input.file.stream() : input.file, metadata.size);
@@ -22848,9 +23114,16 @@ var files = {
22848
23114
  };
22849
23115
  // src/app-context.ts
22850
23116
  var sessionKey = "_os.app.session";
23117
+ var responseChecks = new WeakMap;
22851
23118
 
22852
23119
  class AppResponseReadError extends Error {
22853
23120
  }
23121
+
23122
+ class AppContextDenied extends Error {
23123
+ }
23124
+
23125
+ class AppRenewalUnavailable extends Error {
23126
+ }
22854
23127
  async function appResponse(response, maximum, signal) {
22855
23128
  if (signal.aborted || !response.body || response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() !== "application/json")
22856
23129
  throw new Error;
@@ -22871,8 +23144,13 @@ async function appResponse(response, maximum, signal) {
22871
23144
  }
22872
23145
  if (signal.aborted)
22873
23146
  throw new Error;
22874
- if (chunk.done)
22875
- return JSON.parse(text3 + decoder.decode());
23147
+ if (chunk.done) {
23148
+ const value = JSON.parse(text3 + decoder.decode());
23149
+ await responseChecks.get(response)?.(value);
23150
+ if (signal.aborted)
23151
+ throw new Error;
23152
+ return value;
23153
+ }
22876
23154
  bytes += chunk.value.byteLength;
22877
23155
  if (bytes > maximum) {
22878
23156
  abort();
@@ -22899,38 +23177,239 @@ function appContext(origin, active2) {
22899
23177
  let denied = false;
22900
23178
  const controller = new AbortController;
22901
23179
  let expiry;
22902
- const invalidate = () => {
22903
- denied = true;
23180
+ let startup;
23181
+ let renewalTimer;
23182
+ let renewal;
23183
+ let observation;
23184
+ let resolution2;
23185
+ let disposed = false;
23186
+ const stored = () => globalThis.localStorage.getItem(sessionKey);
23187
+ const alive = () => {
23188
+ try {
23189
+ return !denied && !disposed && active2() && globalThis.location.origin === origin && (!binding || binding.expiresAt > Date.now());
23190
+ } catch {
23191
+ return false;
23192
+ }
23193
+ };
23194
+ const cancel = () => {
22904
23195
  controller.abort();
22905
23196
  clearTimeout(expiry);
23197
+ clearTimeout(startup);
23198
+ clearTimeout(renewalTimer);
22906
23199
  clearInterval(monitor);
23200
+ globalThis.removeEventListener?.("storage", storageChanged);
23201
+ };
23202
+ const invalidate = () => {
23203
+ denied = true;
23204
+ cancel();
22907
23205
  try {
22908
- if (active2() && globalThis.location.origin === origin && (!credential || globalThis.localStorage.getItem(sessionKey) === credential)) {
23206
+ if (!disposed && active2() && globalThis.location.origin === origin && (!credential || globalThis.localStorage.getItem(sessionKey) === credential)) {
22909
23207
  globalThis.localStorage.removeItem(sessionKey);
22910
23208
  }
22911
23209
  } catch {}
22912
23210
  };
22913
23211
  const current = () => {
22914
23212
  try {
22915
- return !denied && active2() && globalThis.location.origin === origin && globalThis.localStorage.getItem(sessionKey) === credential && (!binding || binding.expiresAt > Date.now());
23213
+ return alive() && stored() === credential;
22916
23214
  } catch {
22917
23215
  return false;
22918
23216
  }
22919
23217
  };
22920
- const monitor = setInterval(() => {
22921
- if (credential && !current())
23218
+ const sameSession = (value) => !binding || value.appId === binding.appId && value.memberId === binding.memberId && value.workspace === binding.workspace && value.sessionId === binding.sessionId;
23219
+ const bounded = (value) => validBinding(value) && value.expiresAt > Date.now() && value.expiresAt <= Date.now() + 600000 && sameSession(value);
23220
+ async function proof(candidate, signal = controller.signal) {
23221
+ if (!alive() || !/^[A-Za-z0-9_-]{43}$/.test(candidate))
23222
+ throw new Error;
23223
+ const url = `${origin}/_os/app/context`;
23224
+ const response = await fetch(url, {
23225
+ credentials: "omit",
23226
+ redirect: "error",
23227
+ cache: "no-store",
23228
+ referrerPolicy: "no-referrer",
23229
+ headers: { authorization: `Bearer ${candidate}` },
23230
+ signal
23231
+ });
23232
+ if (response.redirected || response.url && response.url !== url)
23233
+ throw new Error;
23234
+ if (response.status === 401 || response.status === 403)
23235
+ throw new AppContextDenied;
23236
+ if (!response.ok)
23237
+ throw new Error;
23238
+ const value = await appResponse(response, 4096, signal);
23239
+ if (!alive() || !bounded(value))
23240
+ throw new Error;
23241
+ return value;
23242
+ }
23243
+ function install(value, candidate) {
23244
+ binding = { ...value };
23245
+ credential = candidate;
23246
+ clearTimeout(expiry);
23247
+ clearTimeout(renewalTimer);
23248
+ const remaining = binding.expiresAt - Date.now();
23249
+ expiry = setTimeout(invalidate, remaining);
23250
+ renewalTimer = setTimeout(() => {
23251
+ renew();
23252
+ }, remaining - Math.min(60000, remaining / 5));
23253
+ }
23254
+ async function synchronize() {
23255
+ if (!alive())
23256
+ throw new Error;
23257
+ if (!credential)
23258
+ return;
23259
+ if (stored() === credential)
23260
+ return;
23261
+ if (!binding)
23262
+ throw new Error;
23263
+ if (observation)
23264
+ return observation;
23265
+ observation = (async () => {
23266
+ while (alive()) {
23267
+ const candidate = stored();
23268
+ if (!candidate)
23269
+ throw new Error;
23270
+ let value;
23271
+ try {
23272
+ value = await proof(candidate);
23273
+ } catch {
23274
+ if (stored() !== candidate)
23275
+ continue;
23276
+ throw new Error;
23277
+ }
23278
+ if (stored() !== candidate)
23279
+ continue;
23280
+ install(value, candidate);
23281
+ return;
23282
+ }
23283
+ throw new Error;
23284
+ })();
23285
+ try {
23286
+ await observation;
23287
+ } catch {
23288
+ invalidate();
23289
+ throw new Error("Local app context unavailable.");
23290
+ } finally {
23291
+ observation = undefined;
23292
+ }
23293
+ }
23294
+ async function renew() {
23295
+ if (renewal || !binding || !alive())
23296
+ return;
23297
+ renewal = (async () => {
23298
+ const deadline = binding.expiresAt;
23299
+ const requestController = new AbortController;
23300
+ const abort = () => requestController.abort();
23301
+ controller.signal.addEventListener("abort", abort, { once: true });
23302
+ const timeout = setTimeout(abort, Math.min(20000, deadline - Date.now()));
23303
+ const previous = credential;
23304
+ try {
23305
+ await synchronize();
23306
+ if (credential !== previous)
23307
+ return;
23308
+ const url = `${origin}/_os/app/renew`;
23309
+ let response;
23310
+ try {
23311
+ response = await fetch(url, {
23312
+ method: "POST",
23313
+ credentials: "omit",
23314
+ redirect: "error",
23315
+ cache: "no-store",
23316
+ referrerPolicy: "no-referrer",
23317
+ signal: requestController.signal,
23318
+ headers: { authorization: `Bearer ${previous}` }
23319
+ });
23320
+ } catch {
23321
+ throw new AppRenewalUnavailable;
23322
+ }
23323
+ if (response.redirected || response.url && response.url !== url)
23324
+ throw new Error;
23325
+ const value = await appResponse(response, 4096, requestController.signal);
23326
+ if (response.status === 503 && value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).join(",") === "error" && "error" in value && value.error === "renewal_unavailable")
23327
+ throw new AppRenewalUnavailable;
23328
+ if (response.status === 403 && value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).join(",") === "error" && "error" in value && value.error === "renewal_conflict") {
23329
+ while (alive() && stored() === previous && !requestController.signal.aborted) {
23330
+ await new Promise((resolve2, reject) => {
23331
+ const abortWait = () => {
23332
+ clearTimeout(timer);
23333
+ reject(new Error);
23334
+ };
23335
+ const timer = setTimeout(() => {
23336
+ requestController.signal.removeEventListener("abort", abortWait);
23337
+ resolve2();
23338
+ }, 100);
23339
+ requestController.signal.addEventListener("abort", abortWait, { once: true });
23340
+ });
23341
+ }
23342
+ if (requestController.signal.aborted)
23343
+ throw new Error;
23344
+ await synchronize();
23345
+ return;
23346
+ }
23347
+ if (!response.ok)
23348
+ throw new Error;
23349
+ if (!alive() || Date.now() >= deadline || !value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).sort().join(",") !== "context,credential")
23350
+ throw new Error;
23351
+ const result2 = value;
23352
+ if (!bounded(result2.context) || typeof result2.credential !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(result2.credential) || result2.credential === previous)
23353
+ throw new Error;
23354
+ if (stored() !== previous) {
23355
+ await synchronize();
23356
+ return;
23357
+ }
23358
+ globalThis.localStorage.setItem(sessionKey, result2.credential);
23359
+ install(result2.context, result2.credential);
23360
+ } catch (error) {
23361
+ try {
23362
+ if (alive() && stored() && stored() !== previous) {
23363
+ await synchronize();
23364
+ return;
23365
+ }
23366
+ } catch {}
23367
+ try {
23368
+ if (alive() && stored() && stored() !== previous) {
23369
+ await synchronize();
23370
+ return;
23371
+ }
23372
+ } catch {}
23373
+ if ((error instanceof AppRenewalUnavailable || error instanceof AppResponseReadError || requestController.signal.aborted) && current() && Date.now() < deadline) {
23374
+ renewalTimer = setTimeout(() => {
23375
+ renew();
23376
+ }, Math.min(5000, (deadline - Date.now()) / 5));
23377
+ } else
23378
+ invalidate();
23379
+ } finally {
23380
+ clearTimeout(timeout);
23381
+ controller.signal.removeEventListener("abort", abort);
23382
+ }
23383
+ })();
23384
+ try {
23385
+ await renewal;
23386
+ } finally {
23387
+ renewal = undefined;
23388
+ }
23389
+ }
23390
+ const storageChanged = () => {
23391
+ if (!credential)
23392
+ return;
23393
+ if (!alive()) {
22922
23394
  invalidate();
23395
+ return;
23396
+ }
23397
+ synchronize().catch(invalidate);
23398
+ };
23399
+ const monitor = setInterval(() => {
23400
+ storageChanged();
22923
23401
  }, 100);
23402
+ globalThis.addEventListener?.("storage", storageChanged);
22924
23403
  const dispose = () => {
22925
- controller.abort();
22926
- clearTimeout(expiry);
22927
- clearInterval(monitor);
23404
+ disposed = true;
23405
+ cancel();
22928
23406
  };
22929
23407
  async function requestData(operation2, input) {
22930
23408
  try {
22931
23409
  if (operation2 !== "data/query" || !current())
22932
23410
  throw new Error;
22933
23411
  const url = `${origin}/_os/app/data`;
23412
+ const admitted = credential;
22934
23413
  const response = await fetch(url, {
22935
23414
  method: "POST",
22936
23415
  credentials: "omit",
@@ -22941,13 +23420,20 @@ function appContext(origin, active2) {
22941
23420
  headers: { "content-type": "application/json", authorization: `Bearer ${credential}` },
22942
23421
  body: JSON.stringify(input)
22943
23422
  });
23423
+ await synchronize();
23424
+ while (alive() && stored() !== credential)
23425
+ await synchronize();
22944
23426
  if (!current() || response.redirected || response.url && response.url !== url)
22945
23427
  throw new Error;
22946
23428
  if (response.status === 401 || response.status === 403) {
22947
- invalidate();
23429
+ if (admitted === credential)
23430
+ invalidate();
22948
23431
  throw new DataOperationError("access_denied");
22949
23432
  }
22950
23433
  const result2 = await appResponse(response, Number.MAX_SAFE_INTEGER, controller.signal);
23434
+ await synchronize();
23435
+ while (alive() && stored() !== credential)
23436
+ await synchronize();
22951
23437
  if (!current())
22952
23438
  throw new Error;
22953
23439
  if (!response.ok)
@@ -22967,6 +23453,7 @@ function appContext(origin, active2) {
22967
23453
  throw new Error("Local app context unavailable.");
22968
23454
  }
22969
23455
  const url = `${origin}/_os/app/routines/${encodeURIComponent(key)}/${suffix}`;
23456
+ const admitted = credential;
22970
23457
  const response = await fetch(url, {
22971
23458
  ...init,
22972
23459
  credentials: "omit",
@@ -22976,61 +23463,107 @@ function appContext(origin, active2) {
22976
23463
  signal: controller.signal,
22977
23464
  headers: { ...init.body === undefined ? {} : { "content-type": "application/json" }, authorization: `Bearer ${credential}` }
22978
23465
  });
23466
+ await synchronize();
23467
+ while (alive() && stored() !== credential)
23468
+ await synchronize();
22979
23469
  if (!current() || response.redirected || response.url && response.url !== url) {
22980
23470
  invalidate();
22981
23471
  throw new Error("Local app context unavailable.");
22982
23472
  }
22983
- if (response.status === 401 || response.status === 403)
23473
+ if ((response.status === 401 || response.status === 403) && admitted === credential)
22984
23474
  invalidate();
23475
+ responseChecks.set(response, async (value) => {
23476
+ await synchronize();
23477
+ while (alive() && stored() !== credential)
23478
+ await synchronize();
23479
+ if (admitted === credential && value && typeof value === "object" && "error" in value && value.error === "authority_unavailable")
23480
+ invalidate();
23481
+ });
22985
23482
  return response;
22986
23483
  }
22987
- const resolve = async () => {
23484
+ function context() {
23485
+ const issued = credential;
23486
+ return {
23487
+ kind: "local",
23488
+ ...binding,
23489
+ get expiresAt() {
23490
+ return binding.expiresAt;
23491
+ },
23492
+ isCurrent: current,
23493
+ signal: controller.signal,
23494
+ invalidate: () => {
23495
+ if (issued === credential)
23496
+ invalidate();
23497
+ },
23498
+ requestRoutine,
23499
+ requestData,
23500
+ requestAvailability: (key) => requestRoutine(key, "availability", { method: "GET" })
23501
+ };
23502
+ }
23503
+ const resolveOnce = async () => {
23504
+ let attempted;
22988
23505
  try {
22989
23506
  if (denied || !active2() || globalThis.location.origin !== origin)
22990
23507
  throw new Error;
22991
- const stored = globalThis.localStorage.getItem(sessionKey);
22992
- if (!stored || !/^[A-Za-z0-9_-]{43}$/.test(stored) || credential && stored !== credential)
23508
+ if (credential)
23509
+ await synchronize();
23510
+ const candidate = stored();
23511
+ if (!candidate || !/^[A-Za-z0-9_-]{43}$/.test(candidate))
22993
23512
  throw new Error;
22994
- credential = stored;
23513
+ credential = candidate;
23514
+ attempted = candidate;
22995
23515
  if (!current())
22996
23516
  throw new Error;
22997
- const response = await fetch(`${origin}/_os/app/context`, {
22998
- credentials: "omit",
22999
- redirect: "error",
23000
- referrerPolicy: "no-referrer",
23001
- headers: { authorization: `Bearer ${credential}` },
23002
- signal: controller.signal
23003
- });
23004
- if (!response.ok || response.redirected || response.url && response.url !== `${origin}/_os/app/context` || !response.headers.get("content-type")?.startsWith("application/json"))
23517
+ const value = await proof(candidate);
23518
+ if (stored() !== candidate || credential !== candidate) {
23519
+ if (!binding)
23520
+ binding = { ...value };
23521
+ await synchronize();
23522
+ return context();
23523
+ }
23524
+ if (!current())
23005
23525
  throw new Error;
23006
- const value = await appResponse(response, 4096, controller.signal);
23007
- if (!validBinding(value) || !current() || value.expiresAt <= Date.now() || value.expiresAt > Date.now() + 600000 || binding && (value.appId !== binding.appId || value.memberId !== binding.memberId || value.workspace !== binding.workspace || value.expiresAt > binding.expiresAt))
23526
+ if (binding && value.expiresAt > binding.expiresAt)
23008
23527
  throw new Error;
23009
- binding = { ...value };
23010
- clearTimeout(expiry);
23011
- expiry = setTimeout(invalidate, binding.expiresAt - Date.now());
23012
- return {
23013
- kind: "local",
23014
- ...binding,
23015
- isCurrent: current,
23016
- signal: controller.signal,
23017
- invalidate,
23018
- requestRoutine,
23019
- requestData,
23020
- requestAvailability: (key) => requestRoutine(key, "availability", { method: "GET" })
23021
- };
23022
- } catch {
23528
+ if (!binding || value.expiresAt !== binding.expiresAt)
23529
+ install(value, candidate);
23530
+ return context();
23531
+ } catch (error) {
23532
+ try {
23533
+ if (binding && current() && attempted && attempted !== credential)
23534
+ return context();
23535
+ if (binding && alive() && stored() && stored() !== credential) {
23536
+ await synchronize();
23537
+ return context();
23538
+ }
23539
+ } catch {}
23540
+ if (error instanceof AppContextDenied && binding && alive() && renewal)
23541
+ throw new Error("Local app context unavailable.");
23023
23542
  invalidate();
23024
23543
  throw new Error("Local app context unavailable.");
23025
23544
  }
23026
23545
  };
23027
- return { resolve, invalidate, dispose };
23546
+ const resolve = () => {
23547
+ if (resolution2)
23548
+ return resolution2;
23549
+ resolution2 = resolveOnce().finally(() => {
23550
+ resolution2 = undefined;
23551
+ });
23552
+ return resolution2;
23553
+ };
23554
+ const start = () => {
23555
+ startup = setTimeout(() => {
23556
+ if (alive() && !resolution2 && !binding)
23557
+ resolve().catch(() => {});
23558
+ }, 0);
23559
+ };
23560
+ return { resolve, start, invalidate, dispose };
23028
23561
  }
23029
23562
  function validBinding(value) {
23030
- if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).sort().join(",") !== "appId,expiresAt,memberId,workspace")
23563
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).sort().join(",") !== "appId,expiresAt,memberId,sessionId,workspace")
23031
23564
  return false;
23032
23565
  const binding = value;
23033
- return typeof binding.appId === "string" && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(binding.appId) && typeof binding.workspace === "string" && /^[a-z0-9][a-z0-9-]{0,62}$/.test(binding.workspace) && typeof binding.memberId === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(binding.memberId) && typeof binding.expiresAt === "number" && Number.isFinite(binding.expiresAt);
23566
+ return typeof binding.appId === "string" && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(binding.appId) && typeof binding.workspace === "string" && /^[a-z0-9][a-z0-9-]{0,62}$/.test(binding.workspace) && typeof binding.memberId === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(binding.memberId) && typeof binding.sessionId === "string" && /^[A-Za-z0-9_-]{43}$/.test(binding.sessionId) && typeof binding.expiresAt === "number" && Number.isFinite(binding.expiresAt);
23034
23567
  }
23035
23568
 
23036
23569
  // src/os.ts
@@ -23098,6 +23631,7 @@ function init({ auth }) {
23098
23631
  installedDispose = undefined;
23099
23632
  };
23100
23633
  installedDispose = dispose;
23634
+ local?.start();
23101
23635
  }
23102
23636
  var os = { init };
23103
23637
  // src/app-routine-browser.ts
@@ -23561,7 +24095,7 @@ function freeze(value) {
23561
24095
  }
23562
24096
  return value;
23563
24097
  }
23564
- if (typeof process !== "undefined" && process.env.ODDINGTON_APP_RUN !== undefined) {
24098
+ if ((typeof __ODDINGTON_BROWSER__ === "undefined" || !__ODDINGTON_BROWSER__) && typeof process !== "undefined" && process.env.ODDINGTON_APP_RUN !== undefined) {
23565
24099
  let fail4 = function() {
23566
24100
  broken = true;
23567
24101
  pending?.reject(unavailable2());
@@ -23712,13 +24246,13 @@ init_inbox_contract();
23712
24246
  init_inbox_contract();
23713
24247
  init_inbox_files();
23714
24248
  function routinePublicationClient(options = {}) {
23715
- const environment = typeof process === "undefined" ? {} : process.env;
24249
+ const environment = typeof __ODDINGTON_BROWSER__ !== "undefined" && __ODDINGTON_BROWSER__ ? {} : typeof process === "undefined" ? {} : process.env;
23716
24250
  const local = localRoutineRequest(environment);
23717
24251
  const workspace = options.workspace ?? environment.ODDINGTON_ROUTINE_WORKSPACE;
23718
24252
  const capability = options.capability ?? environment.ODDINGTON_ROUTINE_RUN;
23719
24253
  if (!workspace || !/^[a-z0-9][a-z0-9-]*$/.test(workspace) || capability !== undefined && !/^[0-9a-f]{64}$/.test(capability))
23720
24254
  throw new Error("A workspace and valid optional routine capability are required");
23721
- const workingRoot = options.workingRoot ?? environment.ODDINGTON_INBOX_WORKING_ROOT ?? (typeof process === "undefined" ? undefined : process.cwd());
24255
+ const workingRoot = options.workingRoot ?? environment.ODDINGTON_INBOX_WORKING_ROOT ?? (typeof __ODDINGTON_BROWSER__ !== "undefined" && __ODDINGTON_BROWSER__ ? undefined : typeof process === "undefined" ? undefined : process.cwd());
23722
24256
  const headers = capability ? { "x-oddington-run": capability } : {};
23723
24257
  const request = options.request ?? (capability ? async (path, init2) => {
23724
24258
  const origin = oddingtonOrigin();
@@ -23783,22 +24317,25 @@ function routinePublicationClient(options = {}) {
23783
24317
  return validateInboxFile(await local("files/add", input));
23784
24318
  if (!workingRoot)
23785
24319
  throw new Error("Inbox file reads require a working root");
23786
- const { readInboxPublicationFile: readInboxPublicationFile2 } = await Promise.resolve().then(() => (init_inbox_publication_file(), exports_inbox_publication_file));
23787
- const bytes = await readInboxPublicationFile2(workingRoot, input);
23788
- const name = input.name ?? input.path.split("/").at(-1);
23789
- const response = await request(`/api/cli/workspaces/${workspace}/inbox/files/upload`, {
23790
- method: "POST",
23791
- headers: {
23792
- "content-type": "application/octet-stream",
23793
- ...headers,
23794
- "x-oddington-file": JSON.stringify({ name, ...input.csv ? { csv: input.csv } : {} })
23795
- },
23796
- body: Uint8Array.from(bytes).buffer
23797
- });
23798
- const result2 = await response.json();
23799
- if (!response.ok)
23800
- throw new Error(result2.error ?? "Could not retain file");
23801
- return await verifyInboxFile(bytes, validateInboxFile(result2));
24320
+ if (typeof __ODDINGTON_BROWSER__ === "undefined" || !__ODDINGTON_BROWSER__) {
24321
+ const { readInboxPublicationFile: readInboxPublicationFile2 } = await Promise.resolve().then(() => (init_inbox_publication_file(), exports_inbox_publication_file));
24322
+ const bytes = await readInboxPublicationFile2(workingRoot, input);
24323
+ const name = input.name ?? input.path.split("/").at(-1);
24324
+ const response = await request(`/api/cli/workspaces/${workspace}/inbox/files/upload`, {
24325
+ method: "POST",
24326
+ headers: {
24327
+ "content-type": "application/octet-stream",
24328
+ ...headers,
24329
+ "x-oddington-file": JSON.stringify({ name, ...input.csv ? { csv: input.csv } : {} })
24330
+ },
24331
+ body: Uint8Array.from(bytes).buffer
24332
+ });
24333
+ const result2 = await response.json();
24334
+ if (!response.ok)
24335
+ throw new Error(result2.error ?? "Could not retain file");
24336
+ return await verifyInboxFile(bytes, validateInboxFile(result2));
24337
+ }
24338
+ throw new Error("Inbox file reads require a Node working root");
23802
24339
  }
23803
24340
  },
23804
24341
  async check(body2) {