@oddington/sdk 0.1.22 → 0.1.23

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/browser.js CHANGED
@@ -285,7 +285,7 @@ function validatePublication(value) {
285
285
  "views",
286
286
  "body",
287
287
  "notifyImmediately",
288
- ...source.kind === "ask" ? ["questions"] : []
288
+ ...source.kind === "ask" ? ["questions", "tags", "recordRefs"] : []
289
289
  ], "publication"), source.notifyImmediately !== void 0 && typeof source.notifyImmediately != "boolean" && fail("notifyImmediately must be a boolean");
290
290
  let itemPresentation = { ...presentation(source), ...source.notifyImmediately === void 0 ? {} : { notifyImmediately: source.notifyImmediately } }, linkedAsks = (itemPresentation.body ?? []).flatMap((block2) => block2.kind === "asks" ? block2.asks : []);
291
291
  if (new Set(linkedAsks).size !== linkedAsks.length && fail("Ask belongs to more than one problem"), source.kind === "digest") return {
@@ -297,10 +297,24 @@ function validatePublication(value) {
297
297
  let questions = array(source.questions === void 0 ? [] : source.questions, "questions").map((value2) => question(value2));
298
298
  new Set(questions.map((entry) => entry.id)).size !== questions.length && fail("Duplicate question ID");
299
299
  let extra = source.recipients === void 0 ? [] : distinctPeople(source.recipients, "recipients", !1);
300
- return !questions.length && !extra.length && fail("Ask requires at least one recipient"), {
300
+ !questions.length && !extra.length && fail("Ask requires at least one recipient");
301
+ let tags = array(source.tags === void 0 ? [] : source.tags, "Ask tags").map((value2) => {
302
+ let tag = text(value2, "Ask tag");
303
+ return new TextEncoder().encode(tag).byteLength > 64 && fail("Ask tag exceeds 64 UTF-8 bytes"), tag;
304
+ });
305
+ (tags.length > 32 || new Set(tags).size !== tags.length) && fail("Ask tags exceed bounds or contain duplicates");
306
+ let recordRefs = array(source.recordRefs === void 0 ? [] : source.recordRefs, "Ask Record references").map((value2) => {
307
+ let row = object(value2, "Ask Record reference");
308
+ keys(row, ["repository", "id"], "Ask Record reference");
309
+ let { repository, ref } = validateRecordReference({ repository: row.repository, ref: row.id });
310
+ return { repository, id: ref };
311
+ });
312
+ return new Set(recordRefs.map((ref) => JSON.stringify([ref.repository, ref.id]))).size !== recordRefs.length && fail("Ask Record references contain duplicates"), {
301
313
  kind: "ask",
302
314
  ...itemPresentation,
303
315
  questions,
316
+ tags,
317
+ recordRefs,
304
318
  recipients: [.../* @__PURE__ */ new Set([...extra, ...questions.flatMap((entry) => [entry.to, ...entry.allowedResponders])])]
305
319
  };
306
320
  }
@@ -335,7 +349,7 @@ function createInboxPublisher(transport) {
335
349
  return {
336
350
  async publishAsk(value, options) {
337
351
  let source = object(value, "ask");
338
- keys(source, ["title", "description", "recipients", "views", "body", "questions", "notifyImmediately"], "ask");
352
+ keys(source, ["title", "description", "recipients", "views", "body", "questions", "notifyImmediately", "tags", "recordRefs"], "ask");
339
353
  let result2 = await transport.publish({
340
354
  requestKey: publicationRequestKey(options),
341
355
  item: validatePublication({ ...source, kind: "ask" })
@@ -517,8 +531,9 @@ function parseAppRoutineContract(value) {
517
531
  case "array":
518
532
  return !fields2(value2, ["type", "items", "maxItems"]) || !Number.isSafeInteger(value2.maxItems) || value2.maxItems < 1 || value2.maxItems > 64 ? invalidContract() : { type: "array", items: schema2(value2.items, depth + 1), maxItems: value2.maxItems };
519
533
  case "boolean":
520
- case "file":
521
534
  return fields2(value2, ["type"]) ? { type: value2.type } : invalidContract();
535
+ case "file":
536
+ return !fields2(value2, ["type"], ["storage"]) || owns(value2, "storage") && value2.storage !== "local" && value2.storage !== "remote" ? invalidContract() : { type: "file", ...owns(value2, "storage") ? { storage: value2.storage } : {} };
522
537
  case "string":
523
538
  return !fields2(value2, ["type"], ["maxLength"]) || owns(value2, "maxLength") && (!Number.isSafeInteger(value2.maxLength) || value2.maxLength < 0 || value2.maxLength > 4096) ? invalidContract() : { type: "string", ...owns(value2, "maxLength") ? { maxLength: value2.maxLength } : {} };
524
539
  case "integer":
@@ -716,7 +731,7 @@ var schema = {
716
731
  boolean() {
717
732
  return declaration({ type: "boolean" });
718
733
  }
719
- }, file = () => declaration({ type: "file" }), definitions = /* @__PURE__ */ new WeakMap(), defineRoutine = (definition) => {
734
+ }, file = (options = {}) => (assertRoutineFields(options, ["storage"]), declaration({ type: "file", ...options })), definitions = /* @__PURE__ */ new WeakMap(), defineRoutine = (definition) => {
720
735
  if (assertRoutineFields(definition, ["id", "title", "input", "steps", "result", "access", "responses", "run"]), !isAppRoutineName(definition.id) || definition.title !== void 0 && !routineTitle(definition.title) || typeof definition.run != "function") return invalidContract3();
721
736
  let input = definition.input === void 0 ? { schema: empty, default: {} } : schemaMetadata(definition.input), result2 = definition.result === void 0 ? void 0 : schemaMetadata(definition.result).schema, access = parseRoutineAccess(definition.access);
722
737
  assertRoutineFields(definition.steps ?? {});
@@ -795,6 +810,12 @@ async function executeRoutineDefinition(definition, input, runtime) {
795
810
  ...guarded({
796
811
  ...runtime,
797
812
  run: freezeRoutineMetadata({ ...runtime.run }),
813
+ connections: { ...runtime.connections, get: async (identity) => {
814
+ if (!active2 || broken || !contract.access.connections?.includes(identity)) throw new Error("Connection unavailable");
815
+ let value = await runtime.connections.get(identity);
816
+ if (!active2 || broken) throw new Error("Run authority ended");
817
+ return value;
818
+ } },
798
819
  inbox: { ...runtime.inbox, publishAsk: (value, options) => runtime.inbox.publishAsk(qualify(value), options) }
799
820
  }),
800
821
  step(id3, ...input2) {
@@ -1029,16 +1050,16 @@ function routineTransport() {
1029
1050
  return installed;
1030
1051
  }
1031
1052
 
1032
- var memberContext;
1033
- function installMemberContext(resolve) {
1034
- return memberContext = resolve, () => {
1035
- memberContext === resolve && (memberContext = void 0);
1053
+ var memberContext, retainedContext;
1054
+ function installMemberContext(resolve, retained = resolve) {
1055
+ return memberContext = resolve, retainedContext = retained, () => {
1056
+ memberContext === resolve && (memberContext = void 0, retainedContext = void 0);
1036
1057
  };
1037
1058
  }
1038
- async function resolveAppContext() {
1059
+ async function resolveAppContext(retained = !1) {
1039
1060
  let host = memberContext;
1040
1061
  if (!host) throw new Error("App context unavailable.");
1041
- let context2 = await host();
1062
+ let context2 = await (retained ? retainedContext : host)();
1042
1063
  if (host !== memberContext || !context2.isCurrent() || context2.expiresAt <= Date.now() || context2.kind !== "local" && (typeof context2.accessToken != "string" || !/^[^\s]{1,8192}$/.test(context2.accessToken)))
1043
1064
  throw new Error("App context unavailable.");
1044
1065
  return context2;
@@ -1063,13 +1084,22 @@ async function resolveFilesContext() {
1063
1084
  let environment = {}, host = memberContext;
1064
1085
  if (host) {
1065
1086
  let context2 = await host();
1066
- if (context2.kind === "local") throw new Error();
1087
+ if (host !== memberContext || !context2.isCurrent() || context2.expiresAt <= Date.now()) throw new Error();
1088
+ if (context2.kind === "local") return {
1089
+ url: `private:app:${context2.workspace}/${context2.memberId}`,
1090
+ headers: {},
1091
+ isCurrent: context2.isCurrent,
1092
+ signal: context2.signal,
1093
+ local: void 0,
1094
+ localFiles: context2.requestFiles
1095
+ };
1067
1096
  if (!Number.isFinite(context2.expiresAt) || context2.expiresAt <= Date.now() || typeof context2.accessToken != "string" || !/^[^\s]{1,8192}$/.test(context2.accessToken)) throw new Error();
1068
1097
  return {
1069
1098
  ...filesContext(context2.workspace, context2.endpoint, { authorization: `Bearer ${context2.accessToken}` }),
1070
1099
  isCurrent: context2.isCurrent,
1071
1100
  local: void 0,
1072
- localFiles: void 0
1101
+ localFiles: void 0,
1102
+ signal: void 0
1073
1103
  };
1074
1104
  }
1075
1105
  throw new Error();
@@ -1182,6 +1212,64 @@ var data = {
1182
1212
  }
1183
1213
  };
1184
1214
 
1215
+ async function hostedContent(url, init2, parent, upstream = fetch, check = async () => {
1216
+ }, failure) {
1217
+ let delivery = new AbortController(), abort = () => delivery.abort(parent.reason);
1218
+ parent.addEventListener("abort", abort, { once: !0 }), parent.aborted && abort();
1219
+ let detach = () => parent.removeEventListener("abort", abort);
1220
+ try {
1221
+ let response2 = await upstream(url, { ...init2, signal: delivery.signal });
1222
+ try {
1223
+ if (await check(), delivery.signal.aborted) throw failure?.() ?? new FileOperationError("unavailable", 503, !0);
1224
+ } catch (error) {
1225
+ throw response2.body?.cancel().catch(() => {
1226
+ }), error;
1227
+ }
1228
+ if (!response2.body)
1229
+ return detach(), response2;
1230
+ let source = response2.body, reader, settled = !1, output, cancel = async (reason) => {
1231
+ if (!settled) {
1232
+ settled = !0, detach(), delivery.signal.removeEventListener("abort", stop), delivery.abort(reason);
1233
+ try {
1234
+ await (reader ? reader.cancel(reason) : source.cancel(reason));
1235
+ } catch (error) {
1236
+ if (error !== delivery.signal.reason && !(error instanceof Error && error.name === "AbortError")) throw error;
1237
+ } finally {
1238
+ reader?.releaseLock();
1239
+ }
1240
+ }
1241
+ }, stop = () => {
1242
+ if (settled) return;
1243
+ let error = failure?.() ?? delivery.signal.reason;
1244
+ cancel(error).catch(() => {
1245
+ }), output.error(error);
1246
+ }, body = new ReadableStream({
1247
+ start(controller) {
1248
+ output = controller, delivery.signal.addEventListener("abort", stop, { once: !0 }), delivery.signal.aborted && stop();
1249
+ },
1250
+ async pull(controller) {
1251
+ try {
1252
+ if (await check(), settled) return;
1253
+ reader ??= source.getReader();
1254
+ let { done, value } = await reader.read();
1255
+ if (await check(), settled) return;
1256
+ done ? (settled = !0, detach(), delivery.signal.removeEventListener("abort", stop), reader.releaseLock(), controller.close()) : controller.enqueue(value);
1257
+ } catch (error) {
1258
+ if (settled) return;
1259
+ controller.error(failure?.() ?? error), await cancel(error).catch(() => {
1260
+ });
1261
+ }
1262
+ },
1263
+ cancel
1264
+ }, { highWaterMark: 0 }), wrapped = new Response(body, { status: response2.status, statusText: response2.statusText, headers: response2.headers });
1265
+ for (let key of ["url", "redirected", "type"])
1266
+ Object.defineProperty(wrapped, key, { value: response2[key] });
1267
+ return wrapped;
1268
+ } catch (error) {
1269
+ throw detach(), delivery.abort(error), error;
1270
+ }
1271
+ }
1272
+
1185
1273
  var MAX_BYTES = 16 * 1024 * 1024, PART_BYTES = 8 * 1024 * 1024, METADATA_BYTES = 4096, RESULT_BYTES = 16 * 1024, CONTROL_RESULT_BYTES = 128 * 1024, TIMEOUT_MS = 3e4, MAX_ACTIVE = 4, ATTEMPTS = 2, active = 0, invalid6 = () => new FileOperationError("invalid_input", 400), unavailable = () => new FileOperationError("unavailable", 503, !0), failures = {
1186
1274
  400: ["invalid_input", "unsupported_context"],
1187
1275
  401: ["unauthenticated"],
@@ -1192,19 +1280,26 @@ var MAX_BYTES = 16 * 1024 * 1024, PART_BYTES = 8 * 1024 * 1024, METADATA_BYTES =
1192
1280
  429: ["capacity"],
1193
1281
  503: ["integrity", "unavailable"]
1194
1282
  };
1195
- async function boundedBytes(stream, limit) {
1283
+ async function boundedBytes(stream, limit, signal) {
1196
1284
  if (!stream) return new Uint8Array();
1197
- let reader = stream.getReader(), bytes = new Uint8Array(limit), size = 0;
1285
+ let reader = stream.getReader(), bytes = new Uint8Array(limit), size = 0, cancel = () => {
1286
+ reader.cancel().catch(() => {
1287
+ });
1288
+ };
1289
+ signal?.addEventListener("abort", cancel, { once: !0 }), signal?.aborted && cancel();
1198
1290
  try {
1199
1291
  for (; ; ) {
1200
1292
  let { done, value } = await reader.read();
1293
+ if (signal?.aborted) throw unavailable();
1201
1294
  if (done) break;
1202
1295
  if (!(value instanceof Uint8Array)) throw invalid6();
1203
1296
  if (size += value.byteLength, size > limit) throw new FileOperationError("request_too_large", 413);
1204
1297
  bytes.set(value, size - value.byteLength);
1205
1298
  }
1299
+ } catch (error) {
1300
+ throw cancel(), error;
1206
1301
  } finally {
1207
- reader.releaseLock();
1302
+ signal?.removeEventListener("abort", cancel), reader.releaseLock();
1208
1303
  }
1209
1304
  return bytes.slice(0, size);
1210
1305
  }
@@ -1228,50 +1323,6 @@ async function result(response2, maximum = RESULT_BYTES) {
1228
1323
  throw unavailable();
1229
1324
  }
1230
1325
  }
1231
- async function hostedContent(url, init2, parent, upstream = fetch) {
1232
- let delivery = new AbortController(), abort = () => delivery.abort(parent.reason);
1233
- parent.addEventListener("abort", abort, { once: !0 }), parent.aborted && abort();
1234
- let detach = () => parent.removeEventListener("abort", abort);
1235
- try {
1236
- let response2 = await upstream(url, { ...init2, signal: delivery.signal });
1237
- if (delivery.signal.aborted)
1238
- throw response2.body?.cancel().catch(() => {
1239
- }), unavailable();
1240
- if (!response2.body)
1241
- return detach(), response2;
1242
- let source = response2.body, reader, settled = !1, cancel = async (reason) => {
1243
- if (!settled) {
1244
- settled = !0, detach(), delivery.abort(reason);
1245
- try {
1246
- await (reader ? reader.cancel(reason) : source.cancel(reason));
1247
- } catch (error) {
1248
- if (error !== delivery.signal.reason && !(error instanceof Error && error.name === "AbortError")) throw error;
1249
- } finally {
1250
- reader?.releaseLock();
1251
- }
1252
- }
1253
- }, body = new ReadableStream({
1254
- async pull(controller) {
1255
- try {
1256
- reader ??= source.getReader();
1257
- let { done, value } = await reader.read();
1258
- if (settled) return;
1259
- done ? (settled = !0, detach(), reader.releaseLock(), controller.close()) : controller.enqueue(value);
1260
- } catch (error) {
1261
- if (settled) return;
1262
- controller.error(error), await cancel(error).catch(() => {
1263
- });
1264
- }
1265
- },
1266
- cancel
1267
- }, { highWaterMark: 0 }), wrapped = new Response(body, { status: response2.status, statusText: response2.statusText, headers: response2.headers });
1268
- for (let key of ["url", "redirected", "type"])
1269
- Object.defineProperty(wrapped, key, { value: response2[key] });
1270
- return wrapped;
1271
- } catch (error) {
1272
- throw detach(), delivery.abort(error), error;
1273
- }
1274
- }
1275
1326
  async function send(suffix, init2, descriptor, initial, parent) {
1276
1327
  let original = initial, checkCurrent = (context2 = original) => {
1277
1328
  if (parent?.aborted) throw unavailable();
@@ -1282,7 +1333,7 @@ async function send(suffix, init2, descriptor, initial, parent) {
1282
1333
  let context2 = await resolveFilesContext();
1283
1334
  checkCurrent(context2);
1284
1335
  try {
1285
- let response2 = await (async () => {
1336
+ let response2 = context2.localFiles ? await context2.localFiles(suffix, init2, parent) : await (async () => {
1286
1337
  let url = `${context2.url}${suffix}`, request2 = {
1287
1338
  ...init2,
1288
1339
  signal: parent,
@@ -1419,13 +1470,15 @@ async function multipartCreate(input, metadata, exchange, release = () => {
1419
1470
  async function operation(work, initial, parent) {
1420
1471
  let context2 = initial ?? await resolveFilesContext();
1421
1472
  if (active >= MAX_ACTIVE) throw new FileOperationError("capacity", 429, !0);
1422
- return active++, timed(async (signal) => {
1473
+ active++;
1474
+ let execute = async (signal2) => {
1423
1475
  try {
1424
- return await work(context2, signal);
1476
+ return await work(context2, signal2);
1425
1477
  } finally {
1426
1478
  active--;
1427
1479
  }
1428
- }, parent).catch((error) => {
1480
+ }, signal = context2.signal && parent ? AbortSignal.any([context2.signal, parent]) : context2.signal ?? parent;
1481
+ return context2.localFiles ? execute(signal ?? new AbortController().signal) : timed(execute, signal).catch((error) => {
1429
1482
  throw error instanceof FileOperationError ? error : unavailable();
1430
1483
  });
1431
1484
  }
@@ -1497,15 +1550,36 @@ var files = {
1497
1550
  ...blob && input.file.type === "application/pdf" ? { inputMediaType: "application/pdf" } : {},
1498
1551
  requestKey: options?.requestKey === void 0 ? routineTransport()?.key("files") ?? crypto.randomUUID() : options.requestKey
1499
1552
  }, FILE_ORIGINAL_MAX_BYTES);
1500
- if (!context2.local && metadata.storage === "local") throw new FileOperationError("unsupported_context", 400);
1553
+ if (!context2.local && !context2.localFiles && metadata.storage === "local") throw new FileOperationError("unsupported_context", 400);
1501
1554
  let header = encodeURIComponent(JSON.stringify(metadata));
1502
1555
  if (new TextEncoder().encode(header).byteLength > METADATA_BYTES) throw invalid6();
1503
1556
  if (active >= MAX_ACTIVE) throw new FileOperationError("capacity", 429, !0);
1504
- return active++, metadata.size > MAX_BYTES ? multipartCreate(input, metadata, (suffix, init2, kind, signal) => kind === !0 ? send(suffix, init2, !0, context2, signal) : send(suffix, init2, "control", context2, signal)) : timed(async (signal) => {
1557
+ if (active++, context2.localFiles && !routineTransport() && metadata.size > MAX_BYTES) {
1558
+ let signal = context2.signal ?? new AbortController().signal, file2 = input.file, offset = 0, body = file2 instanceof Blob ? context2.signal ? file2 : file2.stream() : file2 instanceof ReadableStream ? file2 : new ReadableStream({
1559
+ pull(controller) {
1560
+ if (offset === file2.byteLength) {
1561
+ controller.close();
1562
+ return;
1563
+ }
1564
+ let end = Math.min(offset + 256 * 1024, file2.byteLength);
1565
+ controller.enqueue(file2.subarray(offset, end)), offset = end;
1566
+ }
1567
+ }, { highWaterMark: 0 });
1568
+ try {
1569
+ return await send("", { method: "POST", headers: {
1570
+ "content-type": "application/octet-stream",
1571
+ "x-oddington-file": header
1572
+ }, body }, !0, context2, signal);
1573
+ } finally {
1574
+ active--;
1575
+ }
1576
+ }
1577
+ return metadata.size > MAX_BYTES ? multipartCreate(input, metadata, (suffix, init2, kind, signal) => kind === !0 ? send(suffix, init2, !0, context2, signal) : send(suffix, init2, "control", context2, signal)) : timed(async (signal) => {
1505
1578
  try {
1506
1579
  let bytes = array3 ? input.file.slice() : await boundedBytes(
1507
1580
  blob ? input.file.stream() : input.file,
1508
- metadata.size
1581
+ metadata.size,
1582
+ signal
1509
1583
  );
1510
1584
  if (bytes.byteLength !== metadata.size) throw invalid6();
1511
1585
  if (signal.aborted) throw unavailable();
@@ -1517,7 +1591,7 @@ var files = {
1517
1591
  } finally {
1518
1592
  active--;
1519
1593
  }
1520
- }).catch((error) => {
1594
+ }, context2.signal).catch((error) => {
1521
1595
  throw error instanceof FileOperationError ? error : unavailable();
1522
1596
  });
1523
1597
  },
@@ -1561,6 +1635,24 @@ function validGoogleScopes(value) {
1561
1635
 
1562
1636
  var ROUTINE_CONNECTION_CREDENTIALS_ENV = "ODDINGTON_ROUTINE_CONNECTION_CREDENTIALS";
1563
1637
  var connectionUuid = (v) => typeof v == "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(v);
1638
+ function runtimeConnection(lease) {
1639
+ return lease.provider === "gmail" ? {
1640
+ id: `gmail/${lease.name}`,
1641
+ provider: lease.provider,
1642
+ name: lease.name,
1643
+ accountEmail: lease.accountEmail,
1644
+ scopes: [...lease.scopes],
1645
+ accessToken: lease.accessToken,
1646
+ expiresAt: lease.expiresAt
1647
+ } : {
1648
+ id: `airtable/${lease.name}`,
1649
+ provider: lease.provider,
1650
+ name: lease.name,
1651
+ baseId: lease.baseId,
1652
+ accessToken: lease.accessToken,
1653
+ runExpiresAt: lease.runExpiresAt
1654
+ };
1655
+ }
1564
1656
  function parseConnectionLease(raw, now = Date.now()) {
1565
1657
  let v = raw;
1566
1658
  if (!v || typeof v != "object" || Array.isArray(v) || !connectionUuid(v.connectionId) || !connectionUuid(v.runId) || typeof v.name != "string" || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(v.name) || typeof v.accessToken != "string" || !/^[A-Za-z0-9._~+/-]{1,8192}={0,2}$/.test(v.accessToken))
@@ -1622,7 +1714,9 @@ function equal(a, b) {
1622
1714
  ));
1623
1715
  }
1624
1716
  function createConnections(transport) {
1625
- return { airtable(name) {
1717
+ return { async get() {
1718
+ throw new Error("Connection unavailable");
1719
+ }, airtable(name) {
1626
1720
  (typeof name != "string" || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(name)) && fail2("Invalid Airtable connection name");
1627
1721
  let call = (operation2, input) => transport(name, operation2, input);
1628
1722
  return {
@@ -1804,46 +1898,19 @@ async function reportLocalRoutineEffect(value) {
1804
1898
  await observer?.(value);
1805
1899
  }
1806
1900
 
1807
- var connections = /* @__PURE__ */ createConnections(async (name, operation2, input) => {
1808
- try {
1809
- let installed2 = routineTransport();
1810
- if (installed2) return await installed2.request("airtable", { name, operation: operation2, input: serializable(input) });
1811
- let managed = process.env[MANAGED_CONNECTION_CAPABILITY_ENV];
1812
- if (managed) {
1813
- let client = managedConnections(JSON.parse(managed)).airtable(name);
1814
- return operation2 === "tables.list" ? await client.tables.list() : await client.records[operation2.slice(8)](input);
1815
- }
1816
- let raw = JSON.parse(process.env[ROUTINE_CONNECTION_CREDENTIALS_ENV] ?? "[]");
1817
- if (!Array.isArray(raw)) throw new Error();
1818
- let leases = raw.map((value) => parseConnectionLease(value));
1819
- if (leases.some((lease2) => lease2.provider !== "airtable") || new Set(leases.map((lease2) => lease2.name)).size !== leases.length) throw new Error();
1820
- let lease = leases.find((lease2) => lease2.name === name);
1821
- if (!lease || lease.provider !== "airtable") throw new Error();
1822
- let writing = ["records.create", "records.update", "records.delete"].includes(operation2), captured = serializable(input), result2;
1823
- try {
1824
- result2 = await executeAirtableOperation(lease, operation2, captured);
1825
- } catch (error) {
1826
- throw writing && error instanceof AirtableOperationError && await reportLocalRoutineEffect({
1827
- type: "effect",
1828
- connection: `airtable/${name}`,
1829
- operation: operation2,
1830
- input: captured,
1831
- effect: error.effect,
1832
- receipts: error.receipts
1833
- }), error;
1834
- }
1835
- return writing && await reportLocalRoutineEffect({
1836
- type: "effect",
1837
- connection: `airtable/${name}`,
1838
- operation: operation2,
1839
- input: captured,
1840
- effect: "confirmed",
1841
- receipts: [result2.receipt]
1842
- }), result2;
1843
- } catch (error) {
1844
- throw error instanceof AirtableOperationError ? error : new AirtableOperationError("Airtable connection unavailable");
1845
- }
1901
+ var local;
1902
+ var airtable = /* @__PURE__ */ createConnections(async (name, operation2, input) => {
1903
+ throw new AirtableOperationError("Airtable connection unavailable");
1846
1904
  });
1905
+ function runtimeConnections() {
1906
+ return {
1907
+ ...airtable,
1908
+ async get(identity) {
1909
+ throw new Error("Connection unavailable");
1910
+ }
1911
+ };
1912
+ }
1913
+ var connections = /* @__PURE__ */ runtimeConnections();
1847
1914
 
1848
1915
  var LOCAL_ROUTINE_VALUE_BYTES = 256 * 1024;
1849
1916
 
@@ -2093,7 +2160,7 @@ function parseInboxCurrent(value) {
2093
2160
  }
2094
2161
  function parseInboxItem(value) {
2095
2162
  let item = object4(json(value));
2096
- return assert(id2(item.id) && id2(item.workspaceId) && integer(item.number) && integer(item.revision) && integer(item.activityVersion, 0) && ["ask", "digest"].includes(String(item.kind)) && ["open", "closed"].includes(String(item.lifecycle)) && text3(item.title) && text3(item.description, !0) && text3(item.author) && date(item.publishedAt) && optionalDate(item.seenAt) && optionalDate(item.digestReadAt)), item.outcome !== null && assert(text3(object4(item.outcome).outcome)), "canonicalRoutineId" in item && assert(item.canonicalRoutineId === null || isAppRoutineName(item.canonicalRoutineId)), array2(item.body).forEach(block), array2(item.views).forEach((value2) => {
2163
+ return assert(id2(item.id) && id2(item.workspaceId) && integer(item.number) && integer(item.revision) && integer(item.activityVersion, 0) && ["ask", "digest"].includes(String(item.kind)) && ["open", "closed"].includes(String(item.lifecycle)) && text3(item.title) && text3(item.description, !0) && text3(item.author) && date(item.publishedAt) && optionalDate(item.seenAt) && optionalDate(item.digestReadAt)), item.outcome !== null && assert(text3(object4(item.outcome).outcome)), "canonicalRoutineId" in item && assert(item.canonicalRoutineId === null || isAppRoutineName(item.canonicalRoutineId)), item.kind === "ask" ? askMetadata(item) : assert(!("tags" in item) && !("recordRefs" in item)), array2(item.body).forEach(block), array2(item.views).forEach((value2) => {
2097
2164
  let view = object4(value2);
2098
2165
  assert(text3(view.id) && text3(view.view) && text3(view.implementation)), json(view.input);
2099
2166
  }), array2(item.people).forEach((value2) => {
@@ -2134,19 +2201,23 @@ function parseAnswerableQuestion(value) {
2134
2201
  let entry = object4(json(value));
2135
2202
  keys3(
2136
2203
  entry,
2137
- ["itemId", "revision", "title", "inboxUrl", "questionId", "routine", "prompt", "response", "answerable", "recordRefs", "instructions"],
2138
- ["itemId", "revision", "title", "inboxUrl", "questionId", "routine", "prompt", "response", "answerable", "recordRefs"]
2204
+ ["itemId", "revision", "title", "inboxUrl", "questionId", "routine", "prompt", "response", "answerable", "tags", "recordRefs", "instructions"],
2205
+ ["itemId", "revision", "title", "inboxUrl", "questionId", "routine", "prompt", "response", "answerable"]
2139
2206
  ), assert(id2(entry.itemId) && integer(entry.revision) && text3(entry.title) && text3(entry.inboxUrl));
2140
2207
  let link = new URL(entry.inboxUrl), slug = link.hostname.slice(0, -13);
2141
- assert(link.hostname.endsWith(".oddington.ai") && slug.length <= 63 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug) && !["auth", "www", "api", "os"].includes(slug) && entry.inboxUrl === `https://${slug}.oddington.ai/inbox/${entry.itemId}`), assert(isAppRoutineName(entry.routine) && text3(entry.questionId) && entry.questionId === entry.questionId.trim() && text3(entry.prompt) && (!("instructions" in entry) || text3(entry.instructions, !0)) && typeof entry.answerable == "boolean"), responseDefinition(entry.response);
2142
- let refs = array2(entry.recordRefs);
2143
- return refs.forEach((value2) => {
2144
- let ref = object4(value2);
2145
- keys3(ref, ["repository", "id"]), assert(validRepositoryAlias(ref.repository) && text3(ref.id) && /^[a-zA-Z0-9][a-zA-Z0-9._/-]*$/.test(ref.id) && !ref.id.split("/").some((part) => !part || part === "." || part === ".."));
2146
- }), assert(new Set(refs.map((value2) => {
2147
- let ref = object4(value2);
2208
+ return assert(link.hostname.endsWith(".oddington.ai") && slug.length <= 63 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug) && !["auth", "www", "api", "os"].includes(slug) && entry.inboxUrl === `https://${slug}.oddington.ai/inbox/${entry.itemId}`), assert(isAppRoutineName(entry.routine) && text3(entry.questionId) && entry.questionId === entry.questionId.trim() && text3(entry.prompt) && (!("instructions" in entry) || text3(entry.instructions, !0)) && typeof entry.answerable == "boolean"), responseDefinition(entry.response), askMetadata(entry), entry;
2209
+ }
2210
+ function askMetadata(entry) {
2211
+ let tags = array2(entry.tags === void 0 ? [] : entry.tags);
2212
+ assert(tags.length <= 32 && tags.every((tag) => text3(tag) && new TextEncoder().encode(tag).byteLength <= 64)), assert(new Set(tags).size === tags.length);
2213
+ let refs = array2(entry.recordRefs === void 0 ? [] : entry.recordRefs);
2214
+ refs.forEach((value) => {
2215
+ let ref = object4(value);
2216
+ keys3(ref, ["repository", "id"]), recordReference({ repository: ref.repository, ref: ref.id });
2217
+ }), assert(new Set(refs.map((value) => {
2218
+ let ref = object4(value);
2148
2219
  return JSON.stringify([ref.repository, ref.id]);
2149
- })).size === refs.length), entry;
2220
+ })).size === refs.length), entry.tags = tags, entry.recordRefs = refs;
2150
2221
  }
2151
2222
  function parseInboxAcceptance(value) {
2152
2223
  let accepted = object4(value);
@@ -2376,10 +2447,12 @@ function appContext(origin, active2) {
2376
2447
  credentials: "omit",
2377
2448
  redirect: "error",
2378
2449
  cache: "no-store",
2450
+ ...init2.body instanceof ReadableStream ? { duplex: "half" } : {},
2379
2451
  referrerPolicy: "no-referrer",
2380
2452
  signal,
2381
2453
  headers: {
2382
2454
  ...init2.body === void 0 ? {} : { "content-type": "application/json" },
2455
+ ...Object.fromEntries(new Headers(init2.headers)),
2383
2456
  authorization: `Bearer ${candidate}`
2384
2457
  }
2385
2458
  });
@@ -2545,11 +2618,37 @@ function appContext(origin, active2) {
2545
2618
  async function requestRoutine(key, suffix, init2 = {}) {
2546
2619
  return requestChecked(`routines/${encodeURIComponent(key)}/${suffix}`, init2);
2547
2620
  }
2548
- async function requestChecked(path, init2, inbox2 = !1) {
2621
+ async function requestRetainedRoutine(key, suffix) {
2622
+ return requestChecked(`routines/${encodeURIComponent(key)}/${suffix}`, {}, !1, !0);
2623
+ }
2624
+ async function requestFiles(suffix, init2, parent) {
2625
+ let unavailable3 = () => new FileOperationError("unsupported_context", 400);
2626
+ if (!current2() || !/^(?:|\/[0-9a-f-]{36}(?:\/(?:content|upload|enrichments(?:\/[^/?#]+\/content)?))?)$/i.test(suffix))
2627
+ throw unavailable3();
2628
+ let admitted = credential, signal = parent ? AbortSignal.any([access.signal, parent]) : access.signal, failure = () => current2() && !signal.aborted ? new FileOperationError("unavailable", 503, !0) : unavailable3();
2629
+ try {
2630
+ return await hostedContent("", init2, signal, (_url, request2) => untilAborted(
2631
+ send3(`files${suffix}`, admitted, signal, request2).then((value) => {
2632
+ if (signal.aborted)
2633
+ throw value.body?.cancel().catch(() => {
2634
+ }), unavailable3();
2635
+ return value;
2636
+ }),
2637
+ signal
2638
+ ), () => settledAccess(signal), failure);
2639
+ } catch {
2640
+ throw failure();
2641
+ }
2642
+ }
2643
+ async function requestChecked(path, init2, inbox2 = !1, retained2 = !1) {
2549
2644
  if (!current2()) throw new Error("Local app context unavailable.");
2550
- let admitted = credential, signal = access.signal, response2 = await send3(path, credential, signal, init2);
2551
- return await settledAccess(signal), (response2.status === 401 || !inbox2 && response2.status === 403) && admitted === credential && current2() && invalidate(), responseChecks.set(response2, async (value) => {
2552
- await settledAccess(signal), admitted === credential && current2() && value && typeof value == "object" && "error" in value && value.error === "authority_unavailable" && invalidate();
2645
+ let admitted = credential, signal = access.signal, response2 = await send3(path, credential, signal, init2), check = async () => {
2646
+ if (retained2) {
2647
+ if (signal.aborted || !current2()) throw new Error("Local app context unavailable.");
2648
+ } else await settledAccess(signal);
2649
+ };
2650
+ return await check(), (response2.status === 401 || !inbox2 && response2.status === 403) && admitted === credential && current2() && invalidate(), responseChecks.set(response2, async (value) => {
2651
+ await check(), admitted === credential && current2() && value && typeof value == "object" && "error" in value && value.error === "authority_unavailable" && invalidate();
2553
2652
  }), response2;
2554
2653
  }
2555
2654
  function context2() {
@@ -2566,8 +2665,10 @@ function appContext(origin, active2) {
2566
2665
  issued === credential && current2() && invalidate();
2567
2666
  },
2568
2667
  requestRoutine,
2668
+ requestRetainedRoutine,
2569
2669
  requestData,
2570
2670
  requestInbox,
2671
+ requestFiles,
2571
2672
  requestAvailability: (key) => requestRoutine(key, "availability", { method: "GET" })
2572
2673
  };
2573
2674
  }
@@ -2576,22 +2677,22 @@ function appContext(origin, active2) {
2576
2677
  try {
2577
2678
  if (!alive()) throw new Error();
2578
2679
  if (!pair) {
2579
- let candidate2 = stored(), retained2 = readPair(candidate2);
2580
- pair = retained2, binding = { ...retained2.context }, credential = retained2.credential, serialized = candidate2;
2680
+ let candidate2 = stored(), retained3 = readPair(candidate2);
2681
+ pair = retained3, binding = { ...retained3.context }, credential = retained3.credential, serialized = candidate2;
2581
2682
  }
2582
2683
  if (expireAccess(), binding.expiresAt <= Date.now()) {
2583
2684
  if (await renew(), !current2()) throw new AppRenewalUnavailable();
2584
2685
  return context2();
2585
2686
  }
2586
2687
  await synchronize();
2587
- let candidate = stored(), retained = readPair(candidate);
2688
+ let candidate = stored(), retained2 = readPair(candidate);
2588
2689
  if (attempted = candidate ?? void 0, !current2()) throw new AppRenewalUnavailable();
2589
- let value = await proof(retained.credential);
2690
+ let value = await proof(retained2.credential);
2590
2691
  if (stored() !== candidate || serialized !== candidate)
2591
2692
  return await synchronize(), context2();
2592
2693
  if (!current2()) throw new Error();
2593
2694
  if (expiry && binding && value.expiresAt > binding.expiresAt) throw new Error();
2594
- return (!expiry || value.expiresAt !== binding.expiresAt) && install(value, retained, candidate), context2();
2695
+ return (!expiry || value.expiresAt !== binding.expiresAt) && install(value, retained2, candidate), context2();
2595
2696
  } catch (error) {
2596
2697
  if (alive() && error instanceof AppRenewalUnavailable)
2597
2698
  throw new Error();
@@ -2606,7 +2707,12 @@ function appContext(origin, active2) {
2606
2707
  }, resolve = () => resolution2 || (resolution2 = resolveOnce().finally(() => {
2607
2708
  resolution2 = void 0;
2608
2709
  }), resolution2);
2609
- return { resolve, start: () => {
2710
+ return { resolve, retained: async () => {
2711
+ if (!expiry && alive() && (binding ? current2() && resolution2 : readPair(stored()).context.expiresAt > Date.now()))
2712
+ return resolve();
2713
+ if (!expiry || !current2()) throw new Error("Local app context unavailable.");
2714
+ return context2();
2715
+ }, start: () => {
2610
2716
  startup = setTimeout(() => {
2611
2717
  alive() && !resolution2 && !binding && resolve().catch(() => {
2612
2718
  });
@@ -2832,8 +2938,8 @@ function init({ auth: input }) {
2832
2938
  url.protocol === "https:" && !url.port && !url.username && !url.password && !url.search && !url.hash && url.pathname === "/" && /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(slug) && slug !== "auth" && (endpoint = url.origin, workspace = slug);
2833
2939
  } catch {
2834
2940
  }
2835
- let active2 = !0, revision = 0, person, token, binding = {}, local2 = localOrigin ? appContext(localOrigin, () => active2) : void 0, subscription, stopDisposal, uninstall = installMemberContext(async () => {
2836
- if (local2) return local2.resolve();
2941
+ let active2 = !0, revision = 0, person, token, binding = {}, local3 = localOrigin ? appContext(localOrigin, () => active2) : void 0, subscription, stopDisposal, resolve = async () => {
2942
+ if (local3) return local3.resolve();
2837
2943
  if (!active2 || !endpoint || !workspace || globalThis.location.origin !== endpoint) throw new Error("Hosted Data context unavailable.");
2838
2944
  let started = revision, { data: { session }, error } = await auth2.getSession();
2839
2945
  if (!active2 || globalThis.location.origin !== endpoint || started !== revision || error || !session || person !== void 0 && person !== session.user.id)
@@ -2852,13 +2958,13 @@ function init({ auth: input }) {
2852
2958
  memberId: session.user.id,
2853
2959
  binding
2854
2960
  };
2855
- }), dispose = () => {
2856
- active2 && (active2 = !1, local2?.dispose(), revision++, subscription?.unsubscribe(), stopDisposal?.(), uninstall(), installedDispose === dispose && (installedDispose = void 0));
2961
+ }, uninstall = installMemberContext(resolve, local3 ? local3.retained : resolve), dispose = () => {
2962
+ active2 && (active2 = !1, local3?.dispose(), revision++, subscription?.unsubscribe(), stopDisposal?.(), uninstall(), installedDispose === dispose && (installedDispose = void 0));
2857
2963
  };
2858
2964
  installedDispose = dispose, stopDisposal = registered?.onDispose(dispose), active2 && (subscription = auth2.onAuthStateChange((event, session) => {
2859
2965
  let next = session?.user.id ?? null;
2860
- (event === "SIGNED_OUT" || person !== void 0 && next !== person || registered && person !== void 0 && event === "SIGNED_IN" && token !== session?.access_token) && (revision++, local2?.invalidate()), person = next, token = session?.access_token;
2861
- }).data.subscription, local2?.start());
2966
+ (event === "SIGNED_OUT" || person !== void 0 && next !== person || registered && person !== void 0 && event === "SIGNED_IN" && token !== session?.access_token) && (revision++, local3?.invalidate()), person = next, token = session?.access_token;
2967
+ }).data.subscription, local3?.start());
2862
2968
  }
2863
2969
  var os = { init };
2864
2970
 
@@ -3303,16 +3409,16 @@ function current(context2) {
3303
3409
  function appDescriptors() {
3304
3410
  return globalThis[/* @__PURE__ */ Symbol.for("oddington.app.descriptor")]?.routines ?? [];
3305
3411
  }
3306
- async function local() {
3307
- let context2 = await resolveAppContext().catch(() => fail3("authority_unavailable"));
3412
+ async function local2(retained = !1) {
3413
+ let context2 = await resolveAppContext(retained).catch(() => fail3("authority_unavailable"));
3308
3414
  return context2.kind !== "local" ? fail3("execution_unavailable") : (current(context2), context2);
3309
3415
  }
3310
- async function request(key, suffix, init2, maximum = SUMMARY_BYTES, retained) {
3416
+ async function request(key, suffix, init2, maximum = SUMMARY_BYTES, retained, read2 = !1) {
3311
3417
  (!identifier.test(key) || !appDescriptors().some((item) => item.id === key)) && fail3("authority_unavailable");
3312
- let context2 = retained ?? await local();
3418
+ let context2 = retained ?? await local2(read2);
3313
3419
  current(context2);
3314
3420
  try {
3315
- let response2 = await context2.requestRoutine(key, suffix, init2), value = await appResponse(response2, maximum, context2.signal);
3421
+ let response2 = await (read2 ? context2.requestRetainedRoutine(key, suffix) : context2.requestRoutine(key, suffix, init2)), value = await appResponse(response2, maximum, context2.signal);
3316
3422
  if (current(context2), !response2.ok) {
3317
3423
  let reason = value?.error;
3318
3424
  reason === "authority_unavailable" && (context2.invalidate(), fail3(reason)), (reason === "invalid_input" || reason === "execution_unavailable") && fail3(reason), fail3("execution_unavailable");
@@ -3358,11 +3464,15 @@ function structure(value) {
3358
3464
  return JSON.stringify(value, (_key, item) => item && typeof item == "object" && !Array.isArray(item) ? Object.fromEntries(Object.keys(item).sort().map((key) => [key, item[key]])) : item);
3359
3465
  }
3360
3466
  function handle(key, id3, context2) {
3361
- return (typeof id3 != "string" || !uuid.test(id3)) && fail3("invalid_input"), routineRunHandle({
3362
- current: async () => summary(await request(key, `runs/${id3}`, void 0, SUMMARY_BYTES, context2), key, id3),
3467
+ (!identifier.test(key) || !appDescriptors().some((item) => item.id === key)) && fail3("authority_unavailable"), (typeof id3 != "string" || !uuid.test(id3)) && fail3("invalid_input");
3468
+ let read2 = async () => summary(await request(key, `runs/${id3}`, void 0, SUMMARY_BYTES, context2, !0), key, id3);
3469
+ return routineRunHandle({
3470
+ async current() {
3471
+ return current(context2), read2();
3472
+ },
3363
3473
  async step(step) {
3364
3474
  (typeof step != "string" || !identifier.test(step)) && fail3("invalid_input");
3365
- let value = await request(key, `runs/${id3}/steps/${step}`, void 0, 514 * 1024, context2);
3475
+ let value = await request(key, `runs/${id3}/steps/${step}`, void 0, 514 * 1024, context2, !0);
3366
3476
  return (!only(value, "id,title,status,startedAt,finishedAt,summary,data") || value.id !== step || !validStep(value)) && fail3("execution_unavailable"), value;
3367
3477
  },
3368
3478
  async publish() {
@@ -3401,19 +3511,18 @@ function appRoutineBinding(key) {
3401
3511
  } catch {
3402
3512
  return fail3("invalid_input");
3403
3513
  }
3404
- let context2 = await local(), run = summary(await request(key, "runs", { method: "POST", body: encoded }, SUMMARY_BYTES, context2), key);
3514
+ let context2 = await local2(), run = summary(await request(key, "runs", { method: "POST", body: encoded }, SUMMARY_BYTES, context2), key);
3405
3515
  return handle(key, run.id, context2);
3406
3516
  },
3407
3517
  runs: Object.freeze({
3408
3518
  async list() {
3409
- let value = await request(key, "runs", void 0, 20 * SUMMARY_BYTES);
3519
+ let value = await request(key, "runs", void 0, 20 * SUMMARY_BYTES, void 0, !0);
3410
3520
  if (!Array.isArray(value) || value.length > 20) return fail3("execution_unavailable");
3411
3521
  let runs = value.map((run) => summary(run, key));
3412
3522
  return new Set(runs.map((run) => run.id)).size !== runs.length && fail3("execution_unavailable"), runs;
3413
3523
  },
3414
3524
  async get(id3) {
3415
- let run = handle(key, id3, await local());
3416
- return await run.current(), run;
3525
+ return (typeof id3 != "string" || !uuid.test(id3)) && fail3("invalid_input"), (!identifier.test(key) || !appDescriptors().some((item) => item.id === key)) && fail3("authority_unavailable"), handle(key, id3, await local2(!0));
3417
3526
  }
3418
3527
  })
3419
3528
  });
@@ -3460,7 +3569,7 @@ function routineDataEnvelope(command) {
3460
3569
  }
3461
3570
 
3462
3571
  function routinePublicationClient2(options = {}) {
3463
- let environment = {}, installed2 = routineTransport(), local2 = installed2?.request ?? void 0, workspace = installed2 ? "installed" : options.workspace ?? environment.ODDINGTON_ROUTINE_WORKSPACE, capability = options.capability ?? environment.ODDINGTON_ROUTINE_RUN;
3572
+ let environment = {}, installed2 = routineTransport(), local3 = installed2?.request ?? void 0, workspace = installed2 ? "installed" : options.workspace ?? environment.ODDINGTON_ROUTINE_WORKSPACE, capability = options.capability ?? environment.ODDINGTON_ROUTINE_RUN;
3464
3573
  if (!workspace || !/^[a-z0-9][a-z0-9-]*$/.test(workspace) || capability !== void 0 && !/^[0-9a-f]{64}$/.test(capability))
3465
3574
  throw new Error("A workspace and valid optional routine capability are required");
3466
3575
  let workingRoot = options.workingRoot ?? environment.ODDINGTON_INBOX_WORKING_ROOT ?? void 0, headers = capability ? { "x-oddington-run": capability } : {}, request2 = options.request ?? (capability ? async (path, init2) => {
@@ -3473,7 +3582,7 @@ function routinePublicationClient2(options = {}) {
3473
3582
  throw new Error("Member publication requires a host-supplied request");
3474
3583
  });
3475
3584
  async function send3(operation2, body) {
3476
- if (local2) return await local2(operation2, body);
3585
+ if (local3) return await local3(operation2, body);
3477
3586
  let writing = operation2 === "records/update", reading = operation2 === "records/read", response2 = await request2(writing || reading ? "/api/data" : `/api/inbox/${operation2}`, {
3478
3587
  method: "POST",
3479
3588
  headers: {
@@ -3520,7 +3629,7 @@ function routinePublicationClient2(options = {}) {
3520
3629
  files: {
3521
3630
  async add(value) {
3522
3631
  let input = validateFileAddOptions(value);
3523
- if (local2) return validateInboxFile(await local2("files/add", input));
3632
+ if (local3) return validateInboxFile(await local3("files/add", input));
3524
3633
  throw workingRoot ? new Error("Inbox file reads require a Node working root") : new Error("Inbox file reads require a working root");
3525
3634
  }
3526
3635
  },