@oddington/sdk 0.1.10 → 0.1.12

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
@@ -131,8 +131,8 @@ function validateBlock(value) {
131
131
  });
132
132
  return (!records.length || new Set(records.map((r) => r.ref)).size !== records.length) && fail("Empty or duplicate records"), { kind: "records", label, records, ...target, ...expanded };
133
133
  }
134
- let files2 = array(b.files, "files").map(validateInboxFile);
135
- return (!files2.length || new Set(files2.map((file) => file.id)).size !== files2.length) && fail("Empty or duplicate files"), { kind: "files", label, files: files2, ...expanded };
134
+ let files3 = array(b.files, "files").map(validateInboxFile);
135
+ return (!files3.length || new Set(files3.map((file2) => file2.id)).size !== files3.length) && fail("Empty or duplicate files"), { kind: "files", label, files: files3, ...expanded };
136
136
  }
137
137
  function richTextBlock(value) {
138
138
  return validateBlock({ ...value, kind: "rich-text" });
@@ -161,8 +161,8 @@ function validateAuthoredSource(value) {
161
161
  keys(option, ["value", "label", "description", "note"], "choice");
162
162
  let note;
163
163
  if (option.note !== void 0) {
164
- let declaration = object(option.note, "choice note");
165
- keys(declaration, ["placeholder"], "choice note"), note = { placeholder: text(declaration.placeholder, "note placeholder") };
164
+ let declaration2 = object(option.note, "choice note");
165
+ keys(declaration2, ["placeholder"], "choice note"), note = { placeholder: text(declaration2.placeholder, "note placeholder") };
166
166
  }
167
167
  return {
168
168
  value: id(option.value, "choice value"),
@@ -208,8 +208,8 @@ function validateRecordSource(value) {
208
208
  let suggestions = array(source.suggestions ?? [], "suggestions").map((value2) => {
209
209
  let entry = object(value2, "suggestion");
210
210
  keys(entry, ["record", "reason"], "suggestion");
211
- let record2 = validateRecordReference(entry.record);
212
- return record2.repository !== repository && fail("Suggestion belongs to another repository"), { record: record2, ...entry.reason === void 0 ? {} : { reason: text(entry.reason, "suggestion reason") } };
211
+ let record3 = validateRecordReference(entry.record);
212
+ return record3.repository !== repository && fail("Suggestion belongs to another repository"), { record: record3, ...entry.reason === void 0 ? {} : { reason: text(entry.reason, "suggestion reason") } };
213
213
  });
214
214
  return (suggestions.length > 8 || new Set(suggestions.map((entry) => entry.record.ref)).size !== suggestions.length) && fail("Record selection permits at most eight unique suggestions"), {
215
215
  type: "records",
@@ -239,7 +239,11 @@ function handlerReference(value) {
239
239
  }
240
240
  function defineHandler(value) {
241
241
  let source = object(value, "handler");
242
- return keys(source, ["id", "run"], "handler"), id(source.id, "handler ID"), typeof source.run != "function" && fail("Handler requires a run function"), Object.freeze({ id: value.id, run: value.run });
242
+ keys(source, ["id", "results", "run"], "handler");
243
+ let stable = (value2) => typeof value2 == "string" && /^[a-zA-Z0-9][a-zA-Z0-9_./-]{0,127}$/.test(value2);
244
+ stable(source.id) || fail("Handler ID must be stable");
245
+ let results = source.results === void 0 ? void 0 : distinctIds(source.results, "handler results", !1);
246
+ return results && (results.length > 64 || results.some((result2) => !stable(result2))) && fail("Handler results must be stable IDs"), typeof source.run != "function" && fail("Handler requires a run function"), Object.freeze({ id: value.id, ...results === void 0 ? {} : { results: Object.freeze(results.sort()) }, run: value.run });
243
247
  }
244
248
  function respond(handler, input) {
245
249
  return defineHandler(handler), handlerReference({ id: handler.id, input });
@@ -266,7 +270,7 @@ function presentation(source) {
266
270
  new Set(views.map((view) => view.id)).size !== views.length && fail("Duplicate view ID");
267
271
  let body2 = source.body === void 0 ? [] : array(source.body, "body").map(validateBlock);
268
272
  for (let block of body2) {
269
- let targets = block.kind === "record" ? [block.view] : block.kind === "records" ? [block.view, ...block.records.map((record2) => record2.view)] : [];
273
+ let targets = block.kind === "record" ? [block.view] : block.kind === "records" ? [block.view, ...block.records.map((record3) => record3.view)] : [];
270
274
  for (let target of targets) target !== void 0 && !views.some((view) => view.id === target) && fail(`Unknown Block View target: ${target}`);
271
275
  }
272
276
  return { title: text(source.title, "title"), description: text(source.description, "description"), views, body: body2 };
@@ -375,1163 +379,1404 @@ function validateRecordRead(value) {
375
379
  };
376
380
  }
377
381
 
378
- var RECORD_WRITE_INPUT_LIMIT = 8 * 1024 * 1024, RoutineRecordWriteError = class extends Error {
379
- constructor(message, effect) {
380
- super(message);
381
- this.effect = effect;
382
- this.name = "RoutineRecordWriteError";
382
+ var FileOperationError = class extends Error {
383
+ constructor(code, status, retryable = !1) {
384
+ super(code);
385
+ this.code = code;
386
+ this.status = status;
387
+ this.retryable = retryable;
383
388
  }
384
- effect;
389
+ code;
390
+ status;
391
+ retryable;
392
+ }, FILE_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/, FILE_ORIGINAL_MAX_BYTES = 5 * 1024 ** 3;
393
+ function parseFileDescriptor(value) {
394
+ let invalid5 = () => {
395
+ throw new FileOperationError("unavailable", 503, !0);
396
+ };
397
+ if (!value || typeof value != "object" || Array.isArray(value)) return invalid5();
398
+ let f = value;
399
+ if (Object.keys(f).sort().join(",") !== "details,enrichments,id,mediaType,name,path,sha256,size" || typeof f.id != "string" || !FILE_ID_PATTERN.test(f.id) || !safeFileName(f.name) || !safeFilePath(f.path) || !Number.isSafeInteger(f.size) || f.size < 0 || f.size > FILE_ORIGINAL_MAX_BYTES || typeof f.sha256 != "string" || !/^[0-9a-f]{64}$/.test(f.sha256) || !f.enrichments || typeof f.enrichments != "object" || Array.isArray(f.enrichments) || !f.details || typeof f.details != "object" || Array.isArray(f.details)) return invalid5();
400
+ let d = f.details;
401
+ if (d.kind === "other") {
402
+ if (Object.keys(d).length !== 1 || f.mediaType !== "application/octet-stream") return invalid5();
403
+ } else if (d.kind === "csv") {
404
+ if (Object.keys(d).sort().join(",") !== "dataRows,delimiter,encoding,headerRow,kind" || f.mediaType !== "text/csv" || !["utf-8", "windows-1252"].includes(d.encoding) || ![",", ";", " "].includes(d.delimiter) || !Number.isSafeInteger(d.headerRow) || d.headerRow < 1 || !Number.isSafeInteger(d.dataRows) || d.dataRows < 0) return invalid5();
405
+ } else if (d.kind === "pdf") {
406
+ if (f.mediaType !== "application/pdf" || Object.keys(d).some((key) => !["kind", "pages"].includes(key)) || "pages" in d && (!Number.isSafeInteger(d.pages) || d.pages < 1)) return invalid5();
407
+ } else return invalid5();
408
+ for (let [name, descriptor2] of Object.entries(f.enrichments)) {
409
+ if (!safeEnrichmentName(name)) return invalid5();
410
+ parseEnrichmentDescriptor(descriptor2);
411
+ }
412
+ return new TextEncoder().encode(JSON.stringify(f)).byteLength > 16 * 1024 ? invalid5() : f;
413
+ }
414
+ function parseEnrichmentDescriptor(value) {
415
+ let invalid5 = () => {
416
+ throw new FileOperationError("unavailable", 503, !0);
417
+ };
418
+ if (!value || typeof value != "object" || Array.isArray(value)) return invalid5();
419
+ let d = value;
420
+ return Object.keys(d).sort().join(",") !== "mediaType,sha256,size" || !["application/octet-stream", "text/plain", "text/markdown", "application/json"].includes(d.mediaType) || !Number.isSafeInteger(d.size) || d.size < 0 || d.size > 16 * 1024 * 1024 || typeof d.sha256 != "string" || !/^[0-9a-f]{64}$/.test(d.sha256) ? invalid5() : d;
421
+ }
422
+ function safeEnrichmentName(value) {
423
+ return safeFileName(value) && !/^original(?:\.|$)/i.test(value);
424
+ }
425
+ function parseFileEnrichMetadata(value, maxBytes) {
426
+ let invalid5 = () => {
427
+ throw new FileOperationError("invalid_input", 400);
428
+ };
429
+ if (!value || typeof value != "object" || Array.isArray(value)) return invalid5();
430
+ let d = value;
431
+ if (Object.keys(d).some((key) => !["name", "size", "mediaType", "replace"].includes(key)) || !safeEnrichmentName(d.name) || !Number.isSafeInteger(d.size) || d.size < 0 || d.mediaType !== void 0 && (typeof d.mediaType != "string" || d.mediaType.length > 255 || /[\u0000-\u001f\u007f]/.test(d.mediaType)) || d.replace !== void 0 && (typeof d.replace != "string" || !/^[0-9a-f]{64}$/.test(d.replace))) return invalid5();
432
+ if (d.size > maxBytes) throw new FileOperationError("request_too_large", 413);
433
+ return d;
434
+ }
435
+ function safeFileName(value) {
436
+ return typeof value == "string" && value.length > 0 && value.length <= 255 && value === value.trim() && value !== "." && value !== ".." && !/[\u0000-\u001f\u007f-\u009f/\\:*?"<>|]/u.test(value) && !/[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/.test(value) && !/[. ]$/.test(value) && !/^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(value);
437
+ }
438
+ function safeFilePath(value) {
439
+ return typeof value == "string" && value.length <= 1024 && value.split("/").every(safeFileName);
440
+ }
441
+ function parseFileCreateMetadata(value, maxBytes) {
442
+ let invalid5 = () => {
443
+ throw new FileOperationError("invalid_input", 400);
444
+ };
445
+ if (!value || typeof value != "object" || Array.isArray(value)) return invalid5();
446
+ let input = value;
447
+ if (Object.keys(input).some((key) => !["name", "path", "storage", "csv", "inputMediaType", "size", "requestKey"].includes(key))) return invalid5();
448
+ let path = input.path === void 0 ? "Files" : input.path;
449
+ if (!safeFileName(input.name) || !safeFilePath(path) || path.split("/")[0].toLowerCase() === ".os" || input.storage !== void 0 && input.storage !== "remote" && input.storage !== "local" || input.inputMediaType !== void 0 && input.inputMediaType !== "application/pdf" || !Number.isSafeInteger(input.size) || input.size < 0) return invalid5();
450
+ if (input.size > maxBytes) throw new FileOperationError("request_too_large", 413);
451
+ if (typeof input.requestKey != "string" || input.requestKey.length < 1 || input.requestKey.length > 256 || input.requestKey !== input.requestKey.trim() || /[\u0000-\u001f\u007f]/.test(input.requestKey)) return invalid5();
452
+ let csv;
453
+ if (input.csv !== void 0) {
454
+ if (!input.csv || typeof input.csv != "object" || Array.isArray(input.csv)) return invalid5();
455
+ let hints = input.csv;
456
+ if (Object.keys(hints).some((key) => !["encoding", "delimiter", "headerRow"].includes(key))) return invalid5();
457
+ let encoding = hints.encoding === void 0 ? "utf-8" : hints.encoding, delimiter = hints.delimiter === void 0 ? "," : hints.delimiter, headerRow = hints.headerRow === void 0 ? 1 : hints.headerRow;
458
+ if (encoding !== "utf-8" && encoding !== "windows-1252" || ![",", ";", " "].includes(delimiter) || !Number.isSafeInteger(headerRow) || headerRow < 1 || headerRow > 1e5) return invalid5();
459
+ csv = { encoding, delimiter, headerRow };
460
+ }
461
+ return {
462
+ name: input.name,
463
+ path,
464
+ ...input.storage ? { storage: input.storage } : {},
465
+ ...input.inputMediaType === "application/pdf" ? { inputMediaType: input.inputMediaType } : {},
466
+ ...csv ? { csv } : {},
467
+ size: input.size,
468
+ requestKey: input.requestKey
469
+ };
470
+ }
471
+
472
+ var AppRoutineContractError = class extends Error {
473
+ constructor(code) {
474
+ super(code);
475
+ this.code = code;
476
+ }
477
+ code;
478
+ }, propertyName = /^[a-z][A-Za-z0-9]*(?![\s\S])/, stepId = /^[a-z0-9]+(?:-[a-z0-9]+)*(?![\s\S])/, routineName = /^[a-z0-9][a-z0-9-]{0,62}(?![\s\S])/, owns = (value, key) => Object.hasOwn(value, key), invalidContract = () => {
479
+ throw new AppRoutineContractError("invalid_contract");
480
+ }, invalidValue = () => {
481
+ throw new AppRoutineContractError("invalid_value");
385
482
  };
386
- function validateRecordWrite(value) {
387
- let input = serializable(value);
388
- if (!input || Array.isArray(input) || typeof input != "object" || Object.keys(input).sort().join(",") !== "records,repository" || !validRepositoryAlias(input.repository) || !Array.isArray(input.records) || !input.records.length || input.records.length > 1e4 || new Set(input.records.map((edit) => edit?.ref)).size !== input.records.length)
389
- throw new InboxContractError("Invalid record update batch");
390
- for (let edit of input.records) {
391
- if (!edit || Array.isArray(edit) || typeof edit != "object" || !validRecordRef(edit.ref))
392
- throw new InboxContractError("Invalid record edit");
393
- if ("expectedVersion" in edit) {
394
- if (Object.keys(edit).sort().join(",") !== "expectedVersion,ref,set" || typeof edit.expectedVersion != "string" || !/^record-v1:[0-9a-f]{64}$/.test(edit.expectedVersion) || !edit.set || Array.isArray(edit.set) || typeof edit.set != "object" || !Object.keys(edit.set).length)
395
- throw new InboxContractError("Invalid record edit");
396
- continue;
483
+ function record(value) {
484
+ return value !== null && typeof value == "object" && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
485
+ }
486
+ function fields2(value, required, optional = []) {
487
+ return required.every((key) => owns(value, key)) && Object.keys(value).every((key) => required.includes(key) || optional.includes(key));
488
+ }
489
+ function denseArray(value) {
490
+ return Array.isArray(value) && Object.keys(value).length === value.length && Array.from({ length: value.length }, (_, index) => owns(value, String(index))).every(Boolean);
491
+ }
492
+ function parseAppRoutineContract(value) {
493
+ let properties = 0;
494
+ function schema2(value2, depth = 1) {
495
+ if (depth > 8 || !record(value2)) return invalidContract();
496
+ switch (value2.type) {
497
+ case "object": {
498
+ if (!fields2(value2, ["type", "properties", "additionalProperties"], ["required"]) || value2.additionalProperties !== !1 || !record(value2.properties)) return invalidContract();
499
+ let entries = Object.entries(value2.properties);
500
+ if (properties += entries.length, properties > 64 || entries.some(([key]) => !propertyName.test(key))) return invalidContract();
501
+ let required;
502
+ if (owns(value2, "required")) {
503
+ if (!denseArray(value2.required) || value2.required.some((key) => typeof key != "string" || !owns(value2.properties, key)) || new Set(value2.required).size !== value2.required.length) return invalidContract();
504
+ required = [...value2.required];
505
+ }
506
+ return {
507
+ type: "object",
508
+ properties: Object.fromEntries(entries.map(([key, child]) => [key, schema2(child, depth + 1)])),
509
+ ...required === void 0 ? {} : { required },
510
+ additionalProperties: !1
511
+ };
512
+ }
513
+ case "array":
514
+ 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 };
515
+ case "boolean":
516
+ case "file":
517
+ return fields2(value2, ["type"]) ? { type: value2.type } : invalidContract();
518
+ case "string":
519
+ 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 } : {} };
520
+ case "integer":
521
+ case "number": {
522
+ let validNumber = value2.type === "integer" ? Number.isSafeInteger : Number.isFinite;
523
+ return !fields2(value2, ["type"], ["minimum", "maximum"]) || owns(value2, "minimum") && !validNumber(value2.minimum) || owns(value2, "maximum") && !validNumber(value2.maximum) || owns(value2, "minimum") && owns(value2, "maximum") && value2.minimum > value2.maximum ? invalidContract() : {
524
+ type: value2.type,
525
+ ...owns(value2, "minimum") ? { minimum: value2.minimum } : {},
526
+ ...owns(value2, "maximum") ? { maximum: value2.maximum } : {}
527
+ };
528
+ }
529
+ default:
530
+ return invalidContract();
397
531
  }
398
- if (Object.keys(edit).sort().join(",") !== "fields,path,ref" || typeof edit.path != "string" || !validCreatePath(edit.path) || !edit.fields || Array.isArray(edit.fields) || typeof edit.fields != "object" || !Object.keys(edit.fields).length)
399
- throw new InboxContractError("Invalid record creation");
400
532
  }
401
- return structuredClone(input);
533
+ if (!record(value) || !fields2(value, ["routine", "input", "steps"], ["result"]) || typeof value.routine != "string" || !routineName.test(value.routine) || !denseArray(value.steps) || value.steps.length < 1 || value.steps.length > 64) return invalidContract();
534
+ let input = schema2(value.input), result2 = owns(value, "result") ? schema2(value.result) : void 0, ids = /* @__PURE__ */ new Set(), steps = value.steps.map((step) => !record(step) || !fields2(step, ["id"], ["summary"]) || typeof step.id != "string" || !stepId.test(step.id) || ids.has(step.id) ? invalidContract() : (ids.add(step.id), { id: step.id, ...owns(step, "summary") ? { summary: schema2(step.summary) } : {} }));
535
+ return { routine: value.routine, input, steps, ...result2 === void 0 ? {} : { result: result2 } };
402
536
  }
403
- function validateRecordUpdate(value) {
404
- let input = validateRecordWrite(value);
405
- if (input.records.some((record2) => !("expectedVersion" in record2)))
406
- throw new InboxContractError("Record creation is unavailable for this operation");
407
- return input;
537
+ function jsonData(value, ancestors = /* @__PURE__ */ new Set()) {
538
+ if (!(value === null || typeof value == "string" || typeof value == "boolean" || typeof value == "number" && Number.isFinite(value))) {
539
+ if (!record(value) && !denseArray(value) || ancestors.has(value)) return invalidValue();
540
+ ancestors.add(value);
541
+ for (let child of Object.values(value)) jsonData(child, ancestors);
542
+ ancestors.delete(value);
543
+ }
408
544
  }
409
- function validateRecordDelete(value) {
410
- let input = serializable(value);
411
- if (!input || Array.isArray(input) || typeof input != "object" || Object.keys(input).sort().join(",") !== "records,repository" || !validRepositoryAlias(input.repository) || !Array.isArray(input.records) || !input.records.length || input.records.length > 1e4 || new Set(input.records.map((record2) => record2?.ref)).size !== input.records.length)
412
- throw new InboxContractError("Invalid record deletion batch");
413
- for (let record2 of input.records)
414
- if (!record2 || Array.isArray(record2) || typeof record2 != "object" || Object.keys(record2).sort().join(",") !== "expectedVersion,ref" || !validRecordRef(record2.ref) || typeof record2.expectedVersion != "string" || !/^record-v1:[0-9a-f]{64}$/.test(record2.expectedVersion))
415
- throw new InboxContractError("Invalid record deletion");
416
- return structuredClone(input);
545
+ function validateRoutineSchemaValue(schema2, value) {
546
+ switch (schema2.type) {
547
+ case "object":
548
+ if (!record(value) || (schema2.required ?? []).some((key) => !owns(value, key)) || Object.keys(value).some((key) => !owns(schema2.properties, key))) return invalidValue();
549
+ for (let [key, child] of Object.entries(value)) validateRoutineSchemaValue(schema2.properties[key], child);
550
+ return;
551
+ case "array":
552
+ if (!denseArray(value) || value.length > schema2.maxItems) return invalidValue();
553
+ for (let child of value) validateRoutineSchemaValue(schema2.items, child);
554
+ return;
555
+ case "boolean":
556
+ return typeof value != "boolean" ? invalidValue() : void 0;
557
+ case "string":
558
+ return typeof value != "string" || Array.from(value).length > (schema2.maxLength ?? 4096) ? invalidValue() : void 0;
559
+ case "integer":
560
+ case "number":
561
+ return typeof value != "number" || !(schema2.type === "integer" ? Number.isSafeInteger(value) : Number.isFinite(value)) || schema2.minimum !== void 0 && value < schema2.minimum || schema2.maximum !== void 0 && value > schema2.maximum ? invalidValue() : void 0;
562
+ case "file":
563
+ try {
564
+ jsonData(value), parseFileDescriptor(value);
565
+ } catch {
566
+ return invalidValue();
567
+ }
568
+ }
417
569
  }
418
- function validateRecordChange(value) {
419
- let input = serializable(value);
420
- if (!input || Array.isArray(input) || typeof input != "object" || Object.keys(input).sort().join(",") !== "records,repository" || !validRepositoryAlias(input.repository) || !Array.isArray(input.records) || !input.records.length || input.records.length > 1e4 || new Set(input.records.map((record2) => record2?.ref)).size !== input.records.length || new TextEncoder().encode(JSON.stringify(input)).byteLength > RECORD_WRITE_INPUT_LIMIT)
421
- throw new InboxContractError("Invalid record change batch");
422
- let writes = [], deletes = [];
423
- for (let record2 of input.records) {
424
- if (!record2 || typeof record2 != "object" || Array.isArray(record2))
425
- throw new InboxContractError("Invalid record change");
426
- "set" in record2 || "fields" in record2 ? writes.push(record2) : deletes.push(record2);
570
+
571
+ var LIMIT = 256 * 1024, invalid = () => {
572
+ throw new AppRoutineContractError("invalid_value");
573
+ }, invalidContract2 = () => {
574
+ throw new AppRoutineContractError("invalid_contract");
575
+ }, empty = { type: "object", properties: {}, required: [], additionalProperties: !1 };
576
+ function freeze(value) {
577
+ if (value !== null && typeof value == "object") {
578
+ for (let child of Object.values(value)) freeze(child);
579
+ Object.freeze(value);
427
580
  }
428
- return writes.length && validateRecordWrite({ repository: input.repository, records: writes }), deletes.length && validateRecordDelete({ repository: input.repository, records: deletes }), structuredClone(input);
581
+ return value;
429
582
  }
430
- function validateRoutineRecordWrite(value) {
431
- let body2 = serializable(value);
432
- if (!body2 || typeof body2 != "object" || Array.isArray(body2) || Object.keys(body2).sort().join(",") !== "input,requestKey" || typeof body2.requestKey != "string" || !body2.requestKey.trim() || body2.requestKey !== body2.requestKey.trim() || body2.requestKey.length > 512)
433
- throw new InboxContractError("A stable routine request key is required");
434
- return { input: validateRecordChange(body2.input), requestKey: body2.requestKey };
583
+ function json(value, ancestors = /* @__PURE__ */ new Set()) {
584
+ if (value === null || typeof value == "string" || typeof value == "boolean" || typeof value == "number" && Number.isFinite(value)) return;
585
+ if (!value || typeof value != "object" || ancestors.has(value)) return invalid();
586
+ let array2 = Array.isArray(value);
587
+ if (!array2 && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return invalid();
588
+ let keys4 = Reflect.ownKeys(value);
589
+ if (array2 && (keys4.length !== value.length + 1 || !Array.from({ length: value.length }, (_, index) => Object.hasOwn(value, index)).every(Boolean))) return invalid();
590
+ ancestors.add(value);
591
+ for (let key of keys4) {
592
+ if (array2 && key === "length") continue;
593
+ if (typeof key != "string") return invalid();
594
+ let descriptor2 = Object.getOwnPropertyDescriptor(value, key);
595
+ if (!descriptor2.enumerable || !Object.hasOwn(descriptor2, "value")) return invalid();
596
+ json(descriptor2.value, ancestors);
597
+ }
598
+ ancestors.delete(value);
435
599
  }
436
- function validCreatePath(value) {
437
- return value === value.trim() && value.endsWith(".md") && !value.startsWith("/") && value.split("/").every((segment) => !!segment && ![".", ".."].includes(segment) && /^[a-zA-Z0-9_.-]+$/.test(segment));
600
+ function capture(schema2, value) {
601
+ json(value), validateRoutineSchemaValue(schema2, value);
602
+ let encoded = JSON.stringify(value);
603
+ return new TextEncoder().encode(encoded).byteLength > LIMIT ? invalid() : freeze(JSON.parse(encoded));
438
604
  }
439
-
440
- function validGoogleScopes(value) {
441
- return Array.isArray(value) && value.length > 0 && value.length <= 32 && value.every((scope) => typeof scope == "string" && scope.length <= 256 && /^(https:\/\/www\.googleapis\.com\/auth\/[A-Za-z0-9._-]+|https:\/\/mail\.google\.com\/|openid|email|profile)$/.test(scope)) && new Set(value).size === value.length;
605
+ function declaration(value) {
606
+ try {
607
+ json(value);
608
+ } catch {
609
+ return invalidContract2();
610
+ }
611
+ return freeze(parseAppRoutineContract({ routine: "definition", input: value, steps: [{ id: "inspect" }] }).input);
442
612
  }
443
-
444
- var ROUTINE_CONNECTION_CREDENTIALS_ENV = "ODDINGTON_ROUTINE_CONNECTION_CREDENTIALS";
445
- 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);
446
- function parseConnectionLease(raw, now = Date.now()) {
447
- let v = raw;
448
- 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))
449
- throw new Error("Invalid connection credential");
450
- if (v.provider === "gmail") {
451
- if (Object.keys(v).sort().join(",") !== "accessToken,accountEmail,connectionId,expiresAt,name,provider,runId,scopes" || typeof v.accountEmail != "string" || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v.accountEmail) || !validGoogleScopes(v.scopes) || typeof v.expiresAt != "string" || !Number.isFinite(Date.parse(v.expiresAt)) || Date.parse(v.expiresAt) <= now + 6e4 || Date.parse(v.expiresAt) > now + 36e5)
452
- throw new Error("Invalid connection credential");
453
- return v;
613
+ var schema = {
614
+ object(properties) {
615
+ return declaration({ type: "object", properties, required: Object.keys(properties), additionalProperties: !1 });
616
+ },
617
+ array(items, options) {
618
+ return declaration({ type: "array", items, maxItems: options.maxItems });
619
+ },
620
+ string(options = {}) {
621
+ return declaration({ type: "string", ...options });
622
+ },
623
+ integer(options = {}) {
624
+ return declaration({ type: "integer", ...options });
625
+ },
626
+ number(options = {}) {
627
+ return declaration({ type: "number", ...options });
628
+ },
629
+ boolean() {
630
+ return declaration({ type: "boolean" });
631
+ }
632
+ }, file = () => declaration({ type: "file" }), definitions = /* @__PURE__ */ new WeakMap(), defineRoutine = (definition) => {
633
+ if (!definition || typeof definition != "object" || Object.getPrototypeOf(definition) !== Object.prototype || Reflect.ownKeys(definition).some((key) => typeof key != "string" || !["input", "steps", "result", "run"].includes(key) || !Object.getOwnPropertyDescriptor(definition, key).enumerable || !Object.hasOwn(Object.getOwnPropertyDescriptor(definition, key), "value")) || typeof definition.run != "function") return invalidContract2();
634
+ try {
635
+ for (let key of ["input", "steps", "result"])
636
+ definition[key] !== void 0 && json(definition[key]);
637
+ } catch {
638
+ return invalidContract2();
639
+ }
640
+ if (definition.steps !== void 0 && (!definition.steps || typeof definition.steps != "object" || Array.isArray(definition.steps))) return invalidContract2();
641
+ let steps = definition.steps === void 0 ? [] : Object.entries(definition.steps).map(([id2, summary]) => ({ id: id2, ...summary === null ? {} : { summary } })), parsed = parseAppRoutineContract({
642
+ routine: "definition",
643
+ input: definition.input === void 0 ? empty : definition.input,
644
+ steps: steps.length ? steps : [{ id: "inspect" }],
645
+ ...definition.result === void 0 ? {} : { result: definition.result }
646
+ }), contract = freeze({
647
+ input: parsed.input,
648
+ steps: steps.length ? parsed.steps : [],
649
+ ...parsed.result === void 0 ? {} : { result: parsed.result }
650
+ }), captured = Object.freeze({
651
+ ...definition,
652
+ ...definition.input === void 0 ? {} : { input: contract.input },
653
+ ...definition.steps === void 0 ? {} : { steps: freeze(Object.fromEntries(contract.steps.map((step) => [step.id, step.summary ?? null]))) },
654
+ ...definition.result === void 0 ? {} : { result: contract.result },
655
+ // Only this authored definition is callable; no generic runner is exported.
656
+ run: (input, os2) => executeRoutineDefinition(captured, input, os2)
657
+ });
658
+ return definitions.set(captured, { contract, run: definition.run }), captured;
659
+ };
660
+ function inspectRoutineDefinition(definition) {
661
+ if (!definition || typeof definition != "object") return invalidContract2();
662
+ let registered = definitions.get(definition);
663
+ return registered ? registered.contract : invalidContract2();
664
+ }
665
+ function captureRoutineInput(definition, input) {
666
+ return capture(inspectRoutineDefinition(definition).input, input);
667
+ }
668
+ async function executeRoutineDefinition(definition, input, runtime) {
669
+ let captured = captureRoutineInput(definition, input), { contract, run } = definitions.get(definition), active2 = !0, broken = !1, reports = Promise.resolve(), os2 = Object.freeze({
670
+ ...runtime,
671
+ run: freeze({ ...runtime.run }),
672
+ step(id2, ...summary) {
673
+ if (!active2 || broken || summary.length > 1)
674
+ return broken = !0, Promise.reject(new AppRoutineContractError("invalid_value"));
675
+ let value;
676
+ try {
677
+ let step = contract.steps.find((step2) => step2.id === id2);
678
+ if (!step) return invalid();
679
+ if (step.summary === void 0) {
680
+ if (summary.length) return invalid();
681
+ } else value = capture(step.summary, summary[0]);
682
+ } catch (error) {
683
+ return broken = !0, Promise.reject(error);
684
+ }
685
+ let report = reports.then(() => broken ? invalid() : runtime.step(id2, value));
686
+ return reports = report.catch(() => {
687
+ broken = !0;
688
+ }), report;
689
+ }
690
+ });
691
+ try {
692
+ let result2 = await run(captured, os2);
693
+ return active2 = !1, await reports, broken ? invalid() : contract.result !== void 0 ? capture(contract.result, result2) : result2 !== void 0 ? invalid() : void 0;
694
+ } finally {
695
+ active2 = !1;
454
696
  }
455
- if (v.provider === "airtable" && Object.keys(v).sort().join(",") === "accessToken,baseId,connectionId,name,provider,runExpiresAt,runId" && typeof v.baseId == "string" && /^app[A-Za-z0-9]+$/.test(v.baseId) && typeof v.runExpiresAt == "string" && Number.isFinite(Date.parse(v.runExpiresAt)) && Date.parse(v.runExpiresAt) > now + 6e4 && Date.parse(v.runExpiresAt) <= now + 15 * 6e4)
456
- return v;
457
- throw new Error("Invalid connection credential");
458
697
  }
459
698
 
460
- var AirtableOperationError = class extends Error {
461
- constructor(message, effect = "unapplied", receipts = []) {
462
- super(message);
699
+ var DataOperationError = class extends Error {
700
+ constructor(code, retryable = !1, effect, component) {
701
+ super(`Data operation failed: ${code}`);
702
+ this.code = code;
703
+ this.retryable = retryable;
463
704
  this.effect = effect;
464
- this.receipts = receipts;
465
- this.name = "AirtableOperationError";
705
+ this.component = component;
706
+ this.name = "DataOperationError";
466
707
  }
708
+ code;
709
+ retryable;
467
710
  effect;
468
- receipts;
711
+ component;
469
712
  };
470
713
 
471
- var recordId = (v) => typeof v == "string" && /^rec[A-Za-z0-9]+$/.test(v), tableId = (v) => typeof v == "string" && /^tbl[A-Za-z0-9]+$/.test(v);
472
- function fail2(message) {
473
- throw new AirtableOperationError(message);
714
+ var invalid2 = () => {
715
+ throw new DataOperationError("invalid_query");
716
+ }, historyText = (value) => typeof value == "string" && !/[\u0000\uD800-\uDFFF]/u.test(value);
717
+ function historyVersion(value) {
718
+ return historyText(value) && value.length > 0 && new TextEncoder().encode(value).length <= 1024 && !/[\u0000-\u001f\u007f-\u009f]/.test(value) && !/^[0-9a-f]{40}$/i.test(value);
474
719
  }
475
- function object2(v) {
476
- return (!v || typeof v != "object" || Array.isArray(v) || ![null, Object.prototype].includes(Object.getPrototypeOf(v))) && fail2("Invalid Airtable data"), v;
720
+ function historyProperties(value) {
721
+ if (!value || typeof value != "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return invalid2();
722
+ let descriptors = Object.getOwnPropertyDescriptors(value);
723
+ return Reflect.ownKeys(value).some((key) => typeof key != "string" || !descriptors[key]?.enumerable || !Object.hasOwn(descriptors[key], "value")) ? invalid2() : descriptors;
477
724
  }
478
- function keys2(v, allowed, required = allowed) {
479
- (Object.keys(v).some((k) => !allowed.includes(k)) || required.some((k) => !Object.hasOwn(v, k))) && fail2("Invalid Airtable input");
725
+ var historyCollection = (value) => typeof value == "string" && value.length <= 512 && /^[_a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value), historyCursor = (value) => typeof value == "string" && value.length <= 128 * 1024 && /^history-cursor-v1\.[A-Za-z0-9_-]{1,64}\.[A-Za-z0-9_-]{16}\.[A-Za-z0-9_-]+$/.test(value);
726
+ function historyArray(value) {
727
+ if (!Array.isArray(value) || Object.getPrototypeOf(value) !== Array.prototype) return invalid2();
728
+ let descriptors = Object.getOwnPropertyDescriptors(value);
729
+ if (Reflect.ownKeys(value).length !== value.length + 1) return invalid2();
730
+ let result2 = [];
731
+ for (let i = 0; i < value.length; i++) {
732
+ let descriptor2 = descriptors[i];
733
+ if (!descriptor2?.enumerable || !Object.hasOwn(descriptor2, "value")) return invalid2();
734
+ result2.push(descriptor2.value);
735
+ }
736
+ return result2;
480
737
  }
481
- function text2(v) {
482
- return typeof v == "string" && v.length > 0 && v.length <= 1024;
738
+ function captureOptions(value, changes) {
739
+ if (value === void 0) return {};
740
+ let descriptors = historyProperties(value), result2 = {}, allowed = changes ? ["collections", "refs", "from", "through", "limit", "after"] : ["limit", "before"];
741
+ if (Object.keys(descriptors).some((key) => !allowed.includes(key))) return invalid2();
742
+ for (let key of allowed) {
743
+ if (!Object.hasOwn(descriptors, key)) continue;
744
+ let scalar = descriptors[key].value;
745
+ if (key === "collections" || key === "refs") {
746
+ let values = historyArray(scalar);
747
+ if (!values.length || values.length > (key === "collections" ? 16 : 100) || !values.every(key === "collections" ? historyCollection : validRecordRef) || new Set(values).size !== values.length) return invalid2();
748
+ result2[key] = values;
749
+ } else if (key === "limit") {
750
+ if (!Number.isInteger(scalar) || scalar < 1 || scalar > (changes ? 500 : 100)) return invalid2();
751
+ result2.limit = scalar;
752
+ } else {
753
+ if (!(key === "after" ? historyCursor(scalar) : historyVersion(scalar))) return invalid2();
754
+ result2[key] = scalar;
755
+ }
756
+ }
757
+ return result2;
483
758
  }
484
- function fields2(v, nonempty = !1) {
485
- let result2 = object2(v);
486
- (nonempty && !Object.keys(result2).length || Object.keys(result2).some((k) => !text2(k))) && fail2("Invalid Airtable fields");
759
+ var captureHistoryListOptions = (value) => captureOptions(value, !1), captureHistoryChangesOptions = (value) => captureOptions(value, !0);
760
+ function captureHistoryInstant(value) {
761
+ if (!historyText(value) || value.length > 1024) return invalid2();
762
+ let match = /^(\d{4})-(\d{2})-(\d{2})[Tt](\d{2}):(\d{2}):(\d{2})(?:\.\d+)?([Zz]|([+-])(\d{2}):(\d{2}))$/.exec(value);
763
+ if (!match) return invalid2();
764
+ let [year, month, day, hour, minute, second] = match.slice(1, 7).map(Number), days = new Date(Date.UTC(year, month, 0));
765
+ return days.setUTCFullYear(year), year < 1 || month < 1 || month > 12 || day < 1 || day > days.getUTCDate() || hour > 23 || minute > 59 || second > 59 || Number(match[9] ?? 0) > 23 || Number(match[10] ?? 0) > 59 ? invalid2() : value;
766
+ }
767
+
768
+ var invalid3 = () => {
769
+ throw new DataOperationError("invalid_query");
770
+ };
771
+ function captureRecordsSearchInput(query, options) {
772
+ if (!historyText(query)) return invalid3();
773
+ let length = [...query.trim()].length;
774
+ if (length < 1 || length > 256) return invalid3();
775
+ if (options === void 0) return { query };
776
+ let descriptors = historyProperties(options);
777
+ if (Object.keys(descriptors).some((key) => !["collections", "limit"].includes(key))) return invalid3();
778
+ let captured = {};
779
+ if (Object.hasOwn(descriptors, "collections")) {
780
+ let supplied = descriptors.collections.value;
781
+ if (!Array.isArray(supplied) || supplied.length < 1 || supplied.length > 16 || Reflect.ownKeys(supplied).length !== supplied.length + 1) return invalid3();
782
+ let collections = [];
783
+ for (let index = 0; index < supplied.length; index++) {
784
+ let descriptor2 = Object.getOwnPropertyDescriptor(supplied, String(index));
785
+ if (!descriptor2?.enumerable || !Object.hasOwn(descriptor2, "value") || !historyText(descriptor2.value)) return invalid3();
786
+ collections.push(descriptor2.value);
787
+ }
788
+ if (new Set(collections).size !== collections.length) return invalid3();
789
+ captured.collections = collections;
790
+ }
791
+ if (Object.hasOwn(descriptors, "limit")) {
792
+ let supplied = descriptors.limit.value;
793
+ if (!Number.isInteger(supplied) || supplied < 1 || supplied > 100) return invalid3();
794
+ captured.limit = supplied;
795
+ }
796
+ return { query, options: captured };
797
+ }
798
+
799
+ var DATA_REQUEST_LIMIT = 256 * 1024, invalidChange = () => {
800
+ throw new DataOperationError("invalid_change", !1, "unapplied");
801
+ };
802
+ function mapping(value) {
487
803
  try {
488
- return serializable(result2);
804
+ return historyProperties(value), value;
489
805
  } catch {
490
- return fail2("Invalid Airtable fields");
806
+ return invalidChange();
491
807
  }
492
808
  }
493
- function record(v) {
494
- let r = object2(v);
495
- return (!recordId(r.id) || r.createdTime !== void 0 && (typeof r.createdTime != "string" || !Number.isFinite(Date.parse(r.createdTime)))) && fail2("Malformed Airtable record"), { id: r.id, ...r.createdTime === void 0 ? {} : { createdTime: r.createdTime }, fields: fields2(r.fields) };
809
+ function captureValue(value, ancestors = /* @__PURE__ */ new Set(), depth = 0) {
810
+ if (value === null || typeof value == "string" || typeof value == "boolean" || typeof value == "number" && Number.isFinite(value)) return value;
811
+ if (!value || typeof value != "object" || ancestors.has(value) || depth > 32) return invalidChange();
812
+ ancestors.add(value);
813
+ try {
814
+ return Array.isArray(value) ? value.length > 1e5 || Object.keys(value).length !== value.length || Object.getOwnPropertySymbols(value).length ? invalidChange() : Array.from(value, (item) => captureValue(item, ancestors, depth + 1)) : Object.fromEntries(Object.entries(mapping(value)).map(([key, item]) => ["__proto__", "constructor", "prototype"].includes(key) ? invalidChange() : [key, captureValue(item, ancestors, depth + 1)]));
815
+ } finally {
816
+ ancestors.delete(value);
817
+ }
496
818
  }
497
- function equal(a, b) {
498
- if (a === b) return !0;
499
- if (!a || !b || typeof a != "object" || typeof b != "object" || Array.isArray(a) !== Array.isArray(b)) return !1;
500
- let left = Object.keys(a), right = Object.keys(b);
501
- return left.length === right.length && left.every((k) => Object.hasOwn(b, k) && equal(
502
- a[k],
503
- b[k]
504
- ));
819
+ function captureDataDelete(record3) {
820
+ let identity = mapping(record3);
821
+ return !validRecordRef(identity.$ref) || typeof identity.$version != "string" || !identity.$version.length || identity.$version.length > 512 ? invalidChange() : { ref: identity.$ref, expectedVersion: identity.$version };
505
822
  }
506
- function createConnections(transport) {
507
- return { airtable(name) {
508
- (typeof name != "string" || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(name)) && fail2("Invalid Airtable connection name");
509
- let call = (operation2, input) => transport(name, operation2, input);
510
- return {
511
- tables: { list: async () => await call("tables.list", {}) },
512
- records: {
513
- list: async (input) => await call("records.list", input),
514
- get: async (input) => await call("records.get", input),
515
- create: async (input) => await call("records.create", input),
516
- update: async (input) => await call("records.update", input),
517
- delete: async (input) => await call("records.delete", input)
518
- }
519
- };
520
- } };
823
+ function captureDataUpdate(record3, set) {
824
+ let identity = captureDataDelete(record3), fields4 = mapping(set);
825
+ return !Object.keys(fields4).length || Object.keys(fields4).some((key) => key.startsWith("$") || ["id", "type"].includes(key)) ? invalidChange() : { ...identity, set: captureValue(fields4) };
521
826
  }
522
- async function executeAirtableOperation(authority, operation2, raw, options = {}) {
523
- let { name, baseId, accessToken } = authority;
524
- (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(name) || !/^app[A-Za-z0-9]+$/.test(baseId) || !/^[A-Za-z0-9._~+/-]{1,8192}={0,2}$/.test(accessToken)) && fail2("Airtable connection unavailable");
525
- let input;
526
- try {
527
- input = object2(serializable(raw));
528
- } catch {
529
- return fail2("Invalid Airtable input");
530
- }
531
- let mutation = ["records.create", "records.update", "records.delete"].includes(operation2), receipt = (ids2) => ({
532
- provider: "airtable",
533
- connection: `airtable/${name}`,
534
- baseId,
535
- table: input.table,
536
- operation: operation2.slice(8),
537
- records: ids2.map((id2) => ({ id: id2 }))
827
+ function captureIdentities(value, update, allowEmpty = !0) {
828
+ if (!Array.isArray(value) || !allowEmpty && !value.length || value.length > 1e4) return invalidChange();
829
+ let captured = value.map((item) => {
830
+ let row = mapping(item);
831
+ if (Object.keys(row).sort().join(",") !== (update ? "expectedVersion,ref,set" : "expectedVersion,ref")) return invalidChange();
832
+ let identity = { $ref: row.ref, $version: row.expectedVersion };
833
+ return update ? captureDataUpdate(identity, row.set) : captureDataDelete(identity);
538
834
  });
539
- async function request(path2, init2 = {}, write = !1) {
540
- let response;
541
- try {
542
- response = await (options.fetch ?? fetch)(`https://api.airtable.com/v0/${path2}`, {
543
- ...init2,
544
- headers: { authorization: `Bearer ${accessToken}`, "content-type": "application/json" },
545
- redirect: "manual",
546
- signal: options.signal ? AbortSignal.any([options.signal, AbortSignal.timeout(15e3)]) : AbortSignal.timeout(15e3)
547
- });
548
- } catch {
549
- throw new AirtableOperationError("Airtable request acknowledgment unavailable", write ? "uncertain" : "unapplied");
550
- }
551
- if (!response.ok)
552
- throw await response.body?.cancel().catch(() => {
553
- }), new AirtableOperationError("Airtable request rejected", write && (response.status >= 500 || response.status === 408 || response.status < 400) ? "uncertain" : "unapplied");
554
- try {
555
- if (!response.headers.get("content-type")?.toLowerCase().includes("application/json")) throw new Error();
556
- let value = object2(await response.json()), safe = serializable(value);
557
- if (Object.hasOwn(value, "error") || JSON.stringify(safe).includes(accessToken)) throw new Error();
558
- return value;
559
- } catch {
560
- throw new AirtableOperationError("Malformed Airtable acknowledgment", write ? "uncertain" : "unapplied");
561
- }
562
- }
563
- if (operation2 === "tables.list") {
564
- keys2(input, []);
565
- let tables = [], seen = /* @__PURE__ */ new Set(), offset;
566
- do {
567
- let query2 = new URLSearchParams(offset ? { offset } : {}), page = await request(`meta/bases/${baseId}/tables?${query2}`);
568
- Array.isArray(page.tables) || fail2("Malformed Airtable tables");
569
- for (let value of page.tables) {
570
- let t = object2(value);
571
- (!tableId(t.id) || !text2(t.name) || !text2(t.primaryFieldId) || !Array.isArray(t.fields) || tables.some((existing) => existing.id === t.id)) && fail2("Malformed Airtable table");
572
- let metadata = t.fields.map((value2) => {
573
- let f = object2(value2);
574
- return (typeof f.id != "string" || !/^fld[A-Za-z0-9]+$/.test(f.id) || !text2(f.name) || !text2(f.type)) && fail2("Malformed Airtable field"), { id: f.id, name: f.name, type: f.type, ...f.options === void 0 ? {} : { options: serializable(f.options) } };
575
- });
576
- (!metadata.some((f) => f.id === t.primaryFieldId) || new Set(metadata.map((f) => f.id)).size !== metadata.length) && fail2("Malformed Airtable fields"), tables.push({ id: t.id, name: t.name, primaryFieldId: t.primaryFieldId, fields: metadata });
577
- }
578
- offset = nextOffset(page.offset, seen);
579
- } while (offset);
580
- return tables;
581
- }
582
- (!text2(input.table) || input.table === "." || input.table === "..") && fail2("Invalid Airtable table"), mutation && !tableId(input.table) && fail2("Airtable writes require a stable table ID");
583
- let path = `${baseId}/${encodeURIComponent(input.table)}`;
584
- if (operation2 === "records.get") {
585
- keys2(input, ["table", "id"]), recordId(input.id) || fail2("Invalid Airtable record ID");
586
- let result3 = record(await request(`${path}/${input.id}`));
587
- return result3.id !== input.id && fail2("Malformed Airtable record"), result3;
588
- }
589
- if (operation2 === "records.list") {
590
- keys2(input, ["table", "fields"], ["table"]), input.fields !== void 0 && (!Array.isArray(input.fields) || !input.fields.length || input.fields.some((f) => !text2(f)) || new Set(input.fields).size !== input.fields.length) && fail2("Invalid Airtable field selection");
591
- let records2 = [], ids2 = /* @__PURE__ */ new Set(), seen = /* @__PURE__ */ new Set(), offset;
592
- do {
593
- let query2 = new URLSearchParams();
594
- for (let field of input.fields ?? []) query2.append("fields[]", field);
595
- offset && query2.set("offset", offset);
596
- let page = await request(`${path}?${query2}`);
597
- Array.isArray(page.records) || fail2("Malformed Airtable records");
598
- for (let value of page.records) {
599
- let r = record(value);
600
- ids2.has(r.id) && fail2("Malformed Airtable pagination"), ids2.add(r.id), records2.push(r);
601
- }
602
- offset = nextOffset(page.offset, seen);
603
- } while (offset);
604
- return records2;
835
+ return new Set(captured.map((item) => item.ref)).size !== captured.length ? invalidChange() : captured;
836
+ }
837
+ function captureDataUpdates(value, allowEmpty = !1) {
838
+ return captureIdentities(value, !0, allowEmpty);
839
+ }
840
+ function captureDataCreate(value) {
841
+ let row = mapping(value);
842
+ if (!validRecordRef(row.$ref) || Object.keys(row).some((key) => key.startsWith("$") && !["$ref", "$filing"].includes(key)))
843
+ return invalidChange();
844
+ let { $ref: ref, $filing, ...fields4 } = row, filing;
845
+ if ($filing !== void 0) {
846
+ let components = mapping($filing);
847
+ if (Object.entries(components).some(([name, value2]) => !/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name) || ["constructor", "prototype"].includes(name) || typeof value2 != "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value2)))
848
+ return invalidChange();
849
+ filing = { ...components };
605
850
  }
606
- mutation || fail2("Unsupported Airtable operation"), keys2(input, ["table", "records"]), (!Array.isArray(input.records) || !input.records.length || input.records.length > 10) && fail2("Invalid Airtable batch");
607
- let kind = operation2.slice(8), ids = /* @__PURE__ */ new Set(), records = input.records.map((value) => {
608
- let r = object2(value);
609
- if (keys2(r, kind === "create" ? ["fields"] : kind === "update" ? ["id", "expected", "set"] : ["id", "expected"]), kind === "create") return { fields: fields2(r.fields, !0) };
610
- (!recordId(r.id) || ids.has(r.id)) && fail2("Invalid Airtable batch record ID"), ids.add(r.id);
611
- let set = kind === "update" ? fields2(r.set, !0) : void 0;
612
- return set && Object.values(set).some((value2) => value2 === null) && fail2("Airtable guard null cannot be an update value"), { id: r.id, expected: fields2(r.expected), ...set ? { set } : {} };
613
- });
614
- for (let r of records) {
615
- if (!("id" in r)) continue;
616
- let current2 = record(await request(`${path}/${r.id}`));
617
- current2.id !== r.id && fail2("Malformed Airtable record");
618
- for (let [key, value] of Object.entries(r.expected))
619
- (value === null ? Object.hasOwn(current2.fields, key) : !Object.hasOwn(current2.fields, key) || !equal(current2.fields[key], value)) && fail2("Airtable reviewed values are stale");
851
+ return { ref, fields: captureValue(fields4), ...filing ? { filing } : {} };
852
+ }
853
+ function captureDataChanges(updatesValue, createsValue = [], deletesValue = []) {
854
+ let updates = captureDataUpdates(updatesValue, !0);
855
+ if (!Array.isArray(createsValue) || createsValue.length > 1e4) return invalidChange();
856
+ let creates = createsValue.map((item) => {
857
+ let row = mapping(item);
858
+ if (Object.keys(row).some((key) => !["ref", "fields", "filing"].includes(key)) || !Object.hasOwn(row, "ref") || !Object.hasOwn(row, "fields")) return invalidChange();
859
+ let fields4 = mapping(row.fields);
860
+ return Object.keys(fields4).some((key) => key.startsWith("$")) ? invalidChange() : captureDataCreate({ ...fields4, $ref: row.ref, ...Object.hasOwn(row, "filing") ? { $filing: row.filing } : {} });
861
+ }), deletes = captureIdentities(deletesValue, !1), refs = [...updates, ...creates, ...deletes].map((item) => item.ref);
862
+ return !refs.length || refs.length > 1e4 || new Set(refs).size !== refs.length ? invalidChange() : { updates, creates, deletes };
863
+ }
864
+ function checkDataRequestSize(input) {
865
+ if (new TextEncoder().encode(JSON.stringify(input)).byteLength > DATA_REQUEST_LIMIT)
866
+ throw new DataOperationError("request_too_large", !1, "unapplied");
867
+ }
868
+ function dataTransportError(status, value) {
869
+ let failures2 = {
870
+ 400: ["invalid_query", "invalid_change"],
871
+ 401: ["unauthenticated"],
872
+ 403: ["access_denied"],
873
+ 409: ["conflict", "publication_pending"],
874
+ 413: ["request_too_large"],
875
+ 429: ["capacity"],
876
+ 503: ["unavailable", "publication_pending"]
877
+ }, error = value && typeof value == "object" && "error" in value ? value.error : void 0, row = error && typeof error == "object" ? error : void 0, code = failures2[status]?.find((allowed) => allowed === row?.code) ?? "unavailable", effect = ["unapplied", "uncertain", "applied"].includes(String(row?.effect)) ? row.effect : void 0;
878
+ return new DataOperationError(
879
+ code,
880
+ ["capacity", "unavailable", "publication_pending"].includes(code),
881
+ code === "conflict" ? "unapplied" : effect,
882
+ code === "invalid_change" && typeof row?.component == "string" && /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(row.component) ? row.component : void 0
883
+ );
884
+ }
885
+
886
+ var AUTH_ORIGIN = "https://auth.oddington.ai";
887
+ function oddingtonOrigin(environment = process.env) {
888
+ let value = environment.ODDINGTON_OS_ORIGIN ?? AUTH_ORIGIN, url = new URL(value);
889
+ if (url.protocol !== "https:" && url.hostname !== "127.0.0.1")
890
+ throw new Error("ODDINGTON_OS_ORIGIN must use HTTPS");
891
+ return url.origin;
892
+ }
893
+
894
+ var memberContext;
895
+ function installMemberContext(resolve) {
896
+ return memberContext = resolve, () => {
897
+ memberContext === resolve && (memberContext = void 0);
898
+ };
899
+ }
900
+ async function resolveAppContext() {
901
+ let host = memberContext;
902
+ if (!host) throw new Error("App context unavailable.");
903
+ let context2 = await host();
904
+ if (host !== memberContext || !context2.isCurrent() || context2.expiresAt <= Date.now() || context2.kind !== "local" && (typeof context2.accessToken != "string" || !/^[^\s]{1,8192}$/.test(context2.accessToken)))
905
+ throw new Error("App context unavailable.");
906
+ return context2;
907
+ }
908
+ async function resolveDataContext() {
909
+ try {
910
+ let host = memberContext;
911
+ if (host !== void 0) {
912
+ let context2 = await host();
913
+ if (host !== memberContext || !context2.isCurrent() || context2.expiresAt <= Date.now()) throw new Error();
914
+ if (context2.kind === "local") return { local: context2.requestData };
915
+ if (!context2 || !Number.isFinite(context2.expiresAt) || context2.expiresAt <= Date.now() || typeof context2.accessToken != "string" || !/^[^\s]{1,8192}$/.test(context2.accessToken)) throw new Error();
916
+ return checked(context2.workspace, context2.endpoint, { authorization: `Bearer ${context2.accessToken}` });
917
+ }
918
+ throw new Error();
919
+ } catch {
920
+ throw new DataOperationError("context_unavailable");
620
921
  }
621
- await options.beforeMutation?.();
622
- let query = new URLSearchParams();
623
- if (kind === "delete") for (let id2 of ids) query.append("records[]", id2);
624
- let result2 = await request(`${path}${kind === "delete" ? `?${query}` : ""}`, {
625
- method: kind === "create" ? "POST" : kind === "update" ? "PATCH" : "DELETE",
626
- ...kind === "delete" ? {} : { body: JSON.stringify({ records: records.map((r) => "id" in r ? { id: r.id, fields: r.set } : r) }) }
627
- }, !0), acknowledged = [], returned = [];
922
+ }
923
+ async function resolveFilesContext() {
628
924
  try {
629
- if (!Array.isArray(result2.records)) throw new Error();
630
- for (let value of result2.records) {
631
- let r = object2(value);
632
- if (!recordId(r.id) || acknowledged.includes(r.id) || kind !== "create" && !ids.has(r.id) || kind === "delete" && r.deleted !== !0) throw new Error();
633
- acknowledged.push(r.id), kind !== "delete" && returned.push(record(r));
925
+ let environment = {}, host = memberContext;
926
+ if (host) {
927
+ let context2 = await host();
928
+ if (context2.kind === "local") throw new Error();
929
+ if (!Number.isFinite(context2.expiresAt) || context2.expiresAt <= Date.now() || typeof context2.accessToken != "string" || !/^[^\s]{1,8192}$/.test(context2.accessToken)) throw new Error();
930
+ return {
931
+ ...filesContext(context2.workspace, context2.endpoint, { authorization: `Bearer ${context2.accessToken}` }),
932
+ isCurrent: context2.isCurrent,
933
+ local: void 0,
934
+ localFiles: void 0
935
+ };
634
936
  }
635
- if (acknowledged.length !== records.length) throw new Error();
937
+ throw new Error();
636
938
  } catch {
637
- throw new AirtableOperationError(
638
- "Incomplete Airtable mutation acknowledgment",
639
- acknowledged.length ? "partial" : "uncertain",
640
- acknowledged.length ? [receipt(acknowledged)] : []
641
- );
939
+ throw new FileOperationError("unsupported_context", 400);
642
940
  }
643
- return { receipt: receipt(acknowledged), ...kind === "delete" ? {} : { records: returned } };
644
941
  }
645
- function nextOffset(value, seen) {
646
- if (value !== void 0)
647
- return (!text2(value) || seen.has(value)) && fail2("Malformed Airtable pagination"), seen.add(value), value;
942
+ function filesContext(workspace, endpoint, headers) {
943
+ if (!workspace || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(workspace) || workspace.length > 63 || ["auth", "www", "api", "os"].includes(workspace) || endpoint !== `https://${workspace}.oddington.ai`) throw new Error();
944
+ return { url: `${endpoint}/api/files`, headers: { ...headers, "x-oddington-workspace": workspace }, isCurrent: () => !0 };
945
+ }
946
+ function checked(workspace, endpoint, headers) {
947
+ if (!workspace || !/^[a-z0-9][a-z0-9-]{0,127}$/.test(workspace)) throw new Error();
948
+ let origin = new URL(endpoint);
949
+ if (origin.protocol !== "https:" || origin.username || origin.password || origin.search || origin.hash || origin.pathname !== "/")
950
+ throw new Error();
951
+ return { url: `${origin.origin}/api/data`, headers: { ...headers, "x-oddington-workspace": workspace } };
648
952
  }
649
953
 
650
- var MANAGED_CONNECTION_CAPABILITY_ENV = "ODDINGTON_MANAGED_CONNECTION_CAPABILITY";
651
- function managedConnections(capability) {
652
- let endpoint = new URL(capability.endpoint);
653
- if (endpoint.protocol !== "http:" || endpoint.hostname !== "127.0.0.1" || !endpoint.port || endpoint.pathname !== "/" || endpoint.search || endpoint.hash || endpoint.username || endpoint.password || !/^[0-9a-f-]{36}$/.test(capability.token)) throw new AirtableOperationError("Airtable capability unavailable");
654
- return createConnections(async (name, operation2, input) => {
655
- let safeInput;
656
- try {
657
- safeInput = serializable(input);
658
- } catch {
659
- safeInput = null;
660
- }
661
- try {
662
- let response = await fetch(new URL("airtable", endpoint), {
663
- method: "POST",
664
- redirect: "error",
665
- headers: { authorization: `Bearer ${capability.token}`, "content-type": "application/json" },
666
- body: JSON.stringify({ name, operation: operation2, input: safeInput }),
667
- signal: AbortSignal.timeout(125e3)
668
- }), value = await response.json();
669
- if (!response.ok) throw new AirtableOperationError(
670
- "Airtable operation failed; the execution retains its evidence",
671
- value.effect === "partial" || value.effect === "uncertain" ? value.effect : "unapplied",
672
- value.receipts ?? []
673
- );
674
- return value;
675
- } catch (error) {
676
- throw error instanceof AirtableOperationError ? error : new AirtableOperationError(
677
- "Airtable parent acknowledgment unavailable",
678
- ["records.create", "records.update", "records.delete"].includes(operation2) ? "uncertain" : "unapplied"
679
- );
954
+ var data = {
955
+ repository(repository) {
956
+ async function send2(input) {
957
+ let context2 = await resolveDataContext();
958
+ try {
959
+ let envelope = { repository, ...input };
960
+ if (checkDataRequestSize(envelope), "local" in context2) return await context2.local("data/query", envelope);
961
+ let response = await fetch(context2.url, {
962
+ method: "POST",
963
+ redirect: "error",
964
+ cache: "no-store",
965
+ headers: { "content-type": "application/json", ...context2.headers },
966
+ body: JSON.stringify(envelope)
967
+ }), result2 = await response.json();
968
+ if (!response.ok)
969
+ throw dataTransportError(response.status, result2);
970
+ return result2;
971
+ } catch (error) {
972
+ throw error instanceof DataOperationError ? error : new DataOperationError("unavailable", !0);
973
+ }
680
974
  }
681
- });
682
- }
975
+ return {
976
+ collections: {
977
+ list: () => send2({ operation: "collections.list" }),
978
+ schema: (name) => send2({ operation: "collections.schema", name })
979
+ },
980
+ records: {
981
+ select: (query) => send2({ operation: "records.select", query }),
982
+ search: async (query, options) => send2({ operation: "records.search", ...captureRecordsSearchInput(query, options) })
983
+ },
984
+ history: {
985
+ list: async (options) => send2({ operation: "history.list", options: captureHistoryListOptions(options) }),
986
+ at: async (instant) => send2({ operation: "history.at", instant: captureHistoryInstant(instant) }),
987
+ changes: async (options) => send2({ operation: "history.changes", options: captureHistoryChangesOptions(options) })
988
+ },
989
+ changes() {
990
+ let updates = [], creates = [], deletes = [], publicationId, pending, confirmed, assertMutable = () => {
991
+ if (publicationId) throw new DataOperationError("invalid_change", !1, "unapplied");
992
+ }, snapshot = (nextUpdates = updates, nextCreates = creates, nextDeletes = deletes) => {
993
+ let { updates: updates2, creates: creates2, deletes: deletes2 } = captureDataChanges(nextUpdates, nextCreates, nextDeletes), input = { updates: updates2, ...creates2.length ? { creates: creates2 } : {}, ...deletes2.length ? { deletes: deletes2 } : {} };
994
+ return checkDataRequestSize({
995
+ repository,
996
+ operation: "changes.publish",
997
+ ...input,
998
+ publicationId: publicationId ?? "00000000-0000-4000-8000-000000000000"
999
+ }), input;
1000
+ };
1001
+ return {
1002
+ create(record3) {
1003
+ assertMutable();
1004
+ let next = [...creates, captureDataCreate(record3)];
1005
+ snapshot(updates, next, deletes), creates = next;
1006
+ },
1007
+ update(record3, set) {
1008
+ assertMutable();
1009
+ let next = [...updates, captureDataUpdate(record3, set)];
1010
+ snapshot(next, creates, deletes), updates = next;
1011
+ },
1012
+ delete(record3) {
1013
+ assertMutable();
1014
+ let next = [...deletes, captureDataDelete(record3)];
1015
+ snapshot(updates, creates, next), deletes = next;
1016
+ },
1017
+ async validate() {
1018
+ await send2({ operation: "changes.validate", ...snapshot() });
1019
+ },
1020
+ publish() {
1021
+ if (confirmed) return Promise.resolve(structuredClone(confirmed));
1022
+ if (!pending) {
1023
+ let captured;
1024
+ try {
1025
+ captured = snapshot();
1026
+ } catch (error) {
1027
+ return Promise.reject(error);
1028
+ }
1029
+ publicationId ??= crypto.randomUUID(), pending = send2({ operation: "changes.publish", ...captured, publicationId }).then((result2) => (confirmed = structuredClone(result2), result2)).catch((error) => {
1030
+ throw error instanceof DataOperationError ? new DataOperationError(
1031
+ error.code,
1032
+ error.retryable,
1033
+ error.effect ?? (["unavailable", "publication_pending"].includes(error.code) ? "uncertain" : "unapplied"),
1034
+ error.component
1035
+ ) : new DataOperationError("unavailable", !0, "uncertain");
1036
+ }).finally(() => {
1037
+ pending = void 0;
1038
+ });
1039
+ }
1040
+ return pending.then((result2) => structuredClone(result2));
1041
+ }
1042
+ };
1043
+ }
1044
+ };
1045
+ }
1046
+ };
683
1047
 
684
- var connections = /* @__PURE__ */ createConnections(async (name, operation2, input) => {
1048
+ 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, invalid4 = () => new FileOperationError("invalid_input", 400), unavailable = () => new FileOperationError("unavailable", 503, !0), failures = {
1049
+ 400: ["invalid_input", "unsupported_context"],
1050
+ 401: ["unauthenticated"],
1051
+ 403: ["access_denied"],
1052
+ 405: ["method_not_allowed"],
1053
+ 409: ["conflict", "local_only"],
1054
+ 413: ["request_too_large"],
1055
+ 429: ["capacity"],
1056
+ 503: ["integrity", "unavailable"]
1057
+ };
1058
+ async function boundedBytes(stream, limit) {
1059
+ if (!stream) return new Uint8Array();
1060
+ let reader = stream.getReader(), bytes = new Uint8Array(limit), size = 0;
685
1061
  try {
686
- let managed = process.env[MANAGED_CONNECTION_CAPABILITY_ENV];
687
- if (managed) {
688
- let client = managedConnections(JSON.parse(managed)).airtable(name);
689
- return operation2 === "tables.list" ? await client.tables.list() : await client.records[operation2.slice(8)](input);
1062
+ for (; ; ) {
1063
+ let { done, value } = await reader.read();
1064
+ if (done) break;
1065
+ if (!(value instanceof Uint8Array)) throw invalid4();
1066
+ if (size += value.byteLength, size > limit) throw new FileOperationError("request_too_large", 413);
1067
+ bytes.set(value, size - value.byteLength);
690
1068
  }
691
- let raw = JSON.parse(process.env[ROUTINE_CONNECTION_CREDENTIALS_ENV] ?? "[]");
692
- if (!Array.isArray(raw)) throw new Error();
693
- let leases = raw.map((value) => parseConnectionLease(value));
694
- if (leases.some((lease2) => lease2.provider !== "airtable") || new Set(leases.map((lease2) => lease2.name)).size !== leases.length) throw new Error();
695
- let lease = leases.find((lease2) => lease2.name === name);
696
- if (!lease || lease.provider !== "airtable") throw new Error();
697
- return await executeAirtableOperation(lease, operation2, input);
698
- } catch (error) {
699
- throw error instanceof AirtableOperationError ? error : new AirtableOperationError("Airtable connection unavailable");
700
- }
701
- });
702
-
703
- var DataOperationError = class extends Error {
704
- constructor(code, retryable = !1, effect, component) {
705
- super(`Data operation failed: ${code}`);
706
- this.code = code;
707
- this.retryable = retryable;
708
- this.effect = effect;
709
- this.component = component;
710
- this.name = "DataOperationError";
1069
+ } finally {
1070
+ reader.releaseLock();
711
1071
  }
712
- code;
713
- retryable;
714
- effect;
715
- component;
716
- };
717
-
718
- var invalid = () => {
719
- throw new DataOperationError("invalid_query");
720
- }, historyText = (value) => typeof value == "string" && !/[\u0000\uD800-\uDFFF]/u.test(value);
721
- function historyVersion(value) {
722
- return historyText(value) && value.length > 0 && new TextEncoder().encode(value).length <= 1024 && !/[\u0000-\u001f\u007f-\u009f]/.test(value) && !/^[0-9a-f]{40}$/i.test(value);
723
- }
724
- function historyProperties(value) {
725
- if (!value || typeof value != "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) return invalid();
726
- let descriptors = Object.getOwnPropertyDescriptors(value);
727
- return Reflect.ownKeys(value).some((key) => typeof key != "string" || !descriptors[key]?.enumerable || !Object.hasOwn(descriptors[key], "value")) ? invalid() : descriptors;
1072
+ return bytes.slice(0, size);
728
1073
  }
729
- function captureHistoryListOptions(value) {
730
- if (value === void 0) return {};
731
- let descriptors = historyProperties(value), result2 = {};
732
- if (Object.keys(descriptors).some((key) => !["limit", "before"].includes(key))) return invalid();
733
- if (Object.hasOwn(descriptors, "limit")) {
734
- let limit = descriptors.limit.value;
735
- if (!Number.isInteger(limit) || limit < 1 || limit > 100) return invalid();
736
- result2.limit = limit;
1074
+ async function timed(work, parent, timeout = TIMEOUT_MS) {
1075
+ let controller = new AbortController(), timer, abort, expiry = new Promise((_, reject) => {
1076
+ abort = () => {
1077
+ controller.abort(), reject(unavailable());
1078
+ }, timer = setTimeout(abort, timeout), parent?.addEventListener("abort", abort, { once: !0 }), parent?.aborted && abort();
1079
+ });
1080
+ try {
1081
+ return await Promise.race([work(controller.signal), expiry]);
1082
+ } finally {
1083
+ clearTimeout(timer), parent?.removeEventListener("abort", abort);
737
1084
  }
738
- if (Object.hasOwn(descriptors, "before")) {
739
- let before = descriptors.before.value;
740
- if (!historyVersion(before)) return invalid();
741
- result2.before = before;
1085
+ }
1086
+ async function result(response, maximum = RESULT_BYTES) {
1087
+ try {
1088
+ let bytes = await boundedBytes(response.body, maximum);
1089
+ return JSON.parse(new TextDecoder("utf-8", { fatal: !0 }).decode(bytes));
1090
+ } catch {
1091
+ throw unavailable();
742
1092
  }
743
- return result2;
744
1093
  }
745
- function captureHistoryInstant(value) {
746
- if (!historyText(value) || value.length > 1024) return invalid();
747
- let match = /^(\d{4})-(\d{2})-(\d{2})[Tt](\d{2}):(\d{2}):(\d{2})(?:\.\d+)?([Zz]|([+-])(\d{2}):(\d{2}))$/.exec(value);
748
- if (!match) return invalid();
749
- let [year, month, day, hour, minute, second] = match.slice(1, 7).map(Number), days = new Date(Date.UTC(year, month, 0));
750
- return days.setUTCFullYear(year), year < 1 || month < 1 || month > 12 || day < 1 || day > days.getUTCDate() || hour > 23 || minute > 59 || second > 59 || Number(match[9] ?? 0) > 23 || Number(match[10] ?? 0) > 59 ? invalid() : value;
1094
+ async function hostedContent(url, init2, parent, upstream = fetch) {
1095
+ let delivery = new AbortController(), abort = () => delivery.abort(parent.reason);
1096
+ parent.addEventListener("abort", abort, { once: !0 }), parent.aborted && abort();
1097
+ let detach = () => parent.removeEventListener("abort", abort);
1098
+ try {
1099
+ let response = await upstream(url, { ...init2, signal: delivery.signal });
1100
+ if (delivery.signal.aborted)
1101
+ throw response.body?.cancel().catch(() => {
1102
+ }), unavailable();
1103
+ if (!response.body)
1104
+ return detach(), response;
1105
+ let source = response.body, reader, settled = !1, cancel = async (reason) => {
1106
+ if (!settled) {
1107
+ settled = !0, detach(), delivery.abort(reason);
1108
+ try {
1109
+ await (reader ? reader.cancel(reason) : source.cancel(reason));
1110
+ } catch (error) {
1111
+ if (error !== delivery.signal.reason && !(error instanceof Error && error.name === "AbortError")) throw error;
1112
+ } finally {
1113
+ reader?.releaseLock();
1114
+ }
1115
+ }
1116
+ }, body2 = new ReadableStream({
1117
+ async pull(controller) {
1118
+ try {
1119
+ reader ??= source.getReader();
1120
+ let { done, value } = await reader.read();
1121
+ if (settled) return;
1122
+ done ? (settled = !0, detach(), reader.releaseLock(), controller.close()) : controller.enqueue(value);
1123
+ } catch (error) {
1124
+ if (settled) return;
1125
+ controller.error(error), await cancel(error).catch(() => {
1126
+ });
1127
+ }
1128
+ },
1129
+ cancel
1130
+ }, { highWaterMark: 0 }), wrapped = new Response(body2, { status: response.status, statusText: response.statusText, headers: response.headers });
1131
+ for (let key of ["url", "redirected", "type"])
1132
+ Object.defineProperty(wrapped, key, { value: response[key] });
1133
+ return wrapped;
1134
+ } catch (error) {
1135
+ throw detach(), delivery.abort(error), error;
1136
+ }
751
1137
  }
752
-
753
- var invalid2 = () => {
754
- throw new DataOperationError("invalid_query");
755
- };
756
- function captureRecordsSearchInput(query, options) {
757
- if (!historyText(query)) return invalid2();
758
- let length = [...query.trim()].length;
759
- if (length < 1 || length > 256) return invalid2();
760
- if (options === void 0) return { query };
761
- let descriptors = historyProperties(options);
762
- if (Object.keys(descriptors).some((key) => !["collections", "limit"].includes(key))) return invalid2();
763
- let captured = {};
764
- if (Object.hasOwn(descriptors, "collections")) {
765
- let supplied = descriptors.collections.value;
766
- if (!Array.isArray(supplied) || supplied.length < 1 || supplied.length > 16 || Reflect.ownKeys(supplied).length !== supplied.length + 1) return invalid2();
767
- let collections = [];
768
- for (let index = 0; index < supplied.length; index++) {
769
- let descriptor2 = Object.getOwnPropertyDescriptor(supplied, String(index));
770
- if (!descriptor2?.enumerable || !Object.hasOwn(descriptor2, "value") || !historyText(descriptor2.value)) return invalid2();
771
- collections.push(descriptor2.value);
1138
+ async function send(suffix, init2, descriptor2, initial, parent) {
1139
+ let original = initial, checkCurrent = (context2 = original) => {
1140
+ if (parent?.aborted) throw unavailable();
1141
+ if (!original.isCurrent() || context2.url !== original.url) throw new FileOperationError("unsupported_context", 400);
1142
+ };
1143
+ for (let attempt = 0; attempt < ATTEMPTS; attempt++) {
1144
+ checkCurrent();
1145
+ let context2 = await resolveFilesContext();
1146
+ checkCurrent(context2);
1147
+ try {
1148
+ let response = await (async () => {
1149
+ let url = `${context2.url}${suffix}`, request = {
1150
+ ...init2,
1151
+ signal: parent,
1152
+ headers: { ...context2.headers, ...init2.headers },
1153
+ redirect: "error",
1154
+ cache: "no-store"
1155
+ };
1156
+ return descriptor2 ? fetch(url, request) : hostedContent(url, request, parent);
1157
+ })();
1158
+ if (!response.ok) {
1159
+ let value2 = await result(response), error = value2 && typeof value2 == "object" && "error" in value2 ? value2.error : void 0, received = error && typeof error == "object" && "code" in error ? error.code : void 0, code = failures[response.status]?.find((code2) => code2 === received);
1160
+ throw code ? new FileOperationError(code, response.status, code === "capacity" || code === "unavailable") : unavailable();
1161
+ }
1162
+ if (response.status !== (descriptor2 === 204 ? 204 : 200) || descriptor2 === 204 && response.body !== null)
1163
+ throw response.body?.cancel().catch(() => {
1164
+ }), unavailable();
1165
+ try {
1166
+ checkCurrent();
1167
+ } catch (error) {
1168
+ throw response.body?.cancel().catch(() => {
1169
+ }), error;
1170
+ }
1171
+ if (descriptor2 === 204) return;
1172
+ if (!descriptor2) return response;
1173
+ let value = await result(response, descriptor2 === "control" ? CONTROL_RESULT_BYTES : RESULT_BYTES);
1174
+ if (descriptor2 === "control")
1175
+ return checkCurrent(), value;
1176
+ let parsed;
1177
+ if (descriptor2 === "enrichment") {
1178
+ if (!value || typeof value != "object" || Array.isArray(value)) throw unavailable();
1179
+ let { name, ...metadata } = value;
1180
+ if (!safeEnrichmentName(name)) throw unavailable();
1181
+ parsed = { name, ...parseEnrichmentDescriptor(metadata) };
1182
+ } else parsed = parseFileDescriptor(value);
1183
+ return checkCurrent(), parsed;
1184
+ } catch (error) {
1185
+ let failure = error instanceof FileOperationError ? error : unavailable();
1186
+ if (init2.body instanceof ReadableStream || !failure.retryable || attempt + 1 === ATTEMPTS) throw failure;
772
1187
  }
773
- if (new Set(collections).size !== collections.length) return invalid2();
774
- captured.collections = collections;
775
- }
776
- if (Object.hasOwn(descriptors, "limit")) {
777
- let supplied = descriptors.limit.value;
778
- if (!Number.isInteger(supplied) || supplied < 1 || supplied > 100) return invalid2();
779
- captured.limit = supplied;
780
1188
  }
781
- return { query, options: captured };
1189
+ throw unavailable();
782
1190
  }
783
-
784
- var DATA_REQUEST_LIMIT = 256 * 1024, invalidChange = () => {
785
- throw new DataOperationError("invalid_change", !1, "unapplied");
786
- };
787
- function mapping(value) {
788
- return !value || typeof value != "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value)) || Reflect.ownKeys(value).some((key) => typeof key != "string" || !Object.getOwnPropertyDescriptor(value, key)?.enumerable || !Object.hasOwn(Object.getOwnPropertyDescriptor(value, key), "value")) ? invalidChange() : value;
1191
+ function idPath(id2) {
1192
+ if (typeof id2 != "string" || !FILE_ID_PATTERN.test(id2)) throw invalid4();
1193
+ return `/${id2}`;
789
1194
  }
790
- function captureValue(value, ancestors = /* @__PURE__ */ new Set(), depth = 0) {
791
- if (value === null || typeof value == "string" || typeof value == "boolean" || typeof value == "number" && Number.isFinite(value)) return value;
792
- if (!value || typeof value != "object" || ancestors.has(value) || depth > 32) return invalidChange();
793
- ancestors.add(value);
1195
+ async function multipartCreate(input, metadata, exchange, release = () => {
1196
+ active--;
1197
+ }, upload) {
1198
+ let route = upload ? `${idPath(upload.file.id)}/upload/multipart` : "/multipart", ticket, reader, chunk, offset = 0, receipts = [], pending = /* @__PURE__ */ new Set(), request = (work, timeout = TIMEOUT_MS) => timed((signal) => {
1199
+ let task = work(signal);
1200
+ return pending.add(task), task.then(() => pending.delete(task), () => pending.delete(task)), task;
1201
+ }, void 0, timeout), control = (body2) => ({
1202
+ method: "POST",
1203
+ headers: { "content-type": "application/json", ...ticket ? { "x-oddington-transfer": ticket } : {} },
1204
+ body: JSON.stringify(body2)
1205
+ });
794
1206
  try {
795
- return Array.isArray(value) ? value.length > 1e5 || Object.keys(value).length !== value.length || Object.getOwnPropertySymbols(value).length ? invalidChange() : Array.from(value, (item) => captureValue(item, ancestors, depth + 1)) : Object.fromEntries(Object.entries(mapping(value)).map(([key, item]) => ["__proto__", "constructor", "prototype"].includes(key) ? invalidChange() : [key, captureValue(item, ancestors, depth + 1)]));
1207
+ let start = await request((signal) => exchange(`${route}/start`, control(upload?.file ?? metadata), "control", signal));
1208
+ if (!start || typeof start != "object" || Array.isArray(start)) throw unavailable();
1209
+ let session = start;
1210
+ if (Object.keys(session).sort().join(",") !== "partBytes,ticket" || session.partBytes !== PART_BYTES || typeof session.ticket != "string" || !session.ticket || session.ticket.length > 64 * 1024) throw unavailable();
1211
+ ticket = session.ticket, input.file instanceof ReadableStream && (reader = input.file.getReader());
1212
+ let lastProgress = Date.now(), next = async () => {
1213
+ try {
1214
+ if (Date.now() - lastProgress >= TIMEOUT_MS) throw unavailable();
1215
+ let value = await request(() => reader.read(), Math.max(1, TIMEOUT_MS - (Date.now() - lastProgress)));
1216
+ return value.value?.byteLength && (lastProgress = Date.now()), value;
1217
+ } catch (error) {
1218
+ throw await reader.cancel(error).catch(() => {
1219
+ }), error;
1220
+ }
1221
+ };
1222
+ for (let sent = 0, partNumber = 1; sent < metadata.size || metadata.size === 0 && partNumber === 1; partNumber++) {
1223
+ let length = Math.min(PART_BYTES, metadata.size - sent), part;
1224
+ if (input.file instanceof Uint8Array) part = input.file.subarray(sent, sent + length).slice();
1225
+ else if (input.file instanceof Blob) {
1226
+ let slice = input.file.slice(sent, sent + length);
1227
+ part = new Uint8Array(await request(() => slice.arrayBuffer()));
1228
+ } else {
1229
+ part = new Uint8Array(length);
1230
+ let filled = 0;
1231
+ for (; filled < length; ) {
1232
+ if (!chunk || offset === chunk.byteLength) {
1233
+ chunk = void 0, offset = 0;
1234
+ let value2 = await next();
1235
+ if (value2.done || !(value2.value instanceof Uint8Array) || (chunk = value2.value, chunk.byteLength > metadata.size - sent - filled)) throw invalid4();
1236
+ }
1237
+ let take = Math.min(length - filled, chunk.byteLength - offset);
1238
+ part.set(chunk.subarray(offset, offset + take), filled), filled += take, offset += take, offset === chunk.byteLength && (chunk = void 0, offset = 0);
1239
+ }
1240
+ }
1241
+ if (part.byteLength !== length) throw invalid4();
1242
+ if (reader && sent + length === metadata.size)
1243
+ for (lastProgress = Date.now(); ; ) {
1244
+ let value2 = await next();
1245
+ if (value2.done) break;
1246
+ if (!(value2.value instanceof Uint8Array) || value2.value.byteLength) throw invalid4();
1247
+ }
1248
+ let value = await request((signal) => exchange(`${route}/part`, {
1249
+ method: "POST",
1250
+ headers: { "content-type": "application/octet-stream", "x-oddington-transfer": ticket, "x-oddington-part": String(partNumber) },
1251
+ body: part.buffer
1252
+ }, "control", signal));
1253
+ part = void 0;
1254
+ let receipt = value;
1255
+ 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)) throw unavailable();
1256
+ receipts.push({ partNumber, etag: receipt.etag }), sent += length, lastProgress = Date.now();
1257
+ }
1258
+ if (upload) for (let name of Object.keys(upload.file.enrichments)) {
1259
+ let bytes = await request(() => upload.enrichment(name));
1260
+ if (bytes.byteLength !== upload.file.enrichments[name].size || bytes.byteLength > MAX_BYTES) throw invalid4();
1261
+ await request((signal) => exchange(`${route}/enrichments/${encodeURIComponent(name)}`, {
1262
+ method: "POST",
1263
+ headers: { "content-type": "application/octet-stream", "x-oddington-transfer": ticket },
1264
+ body: bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)
1265
+ }, "control", signal));
1266
+ }
1267
+ await upload?.beforeComplete();
1268
+ let completion = control({ parts: receipts });
1269
+ if (new TextEncoder().encode(completion.body).byteLength > 128 * 1024) throw unavailable();
1270
+ return parseFileDescriptor(await request(
1271
+ (signal) => exchange(`${route}/complete`, completion, !0, signal),
1272
+ TIMEOUT_MS + Math.ceil(metadata.size / (1024 * 1024)) * 1e3
1273
+ ));
1274
+ } catch (error) {
1275
+ throw reader && await reader.cancel(error).catch(() => {
1276
+ }), ticket && await request((signal) => exchange(`${route}/abort`, control({}), "control", signal)).catch(() => {
1277
+ }), error instanceof FileOperationError ? error : unavailable();
796
1278
  } finally {
797
- ancestors.delete(value);
798
- }
799
- }
800
- function captureDataDelete(record2) {
801
- let identity = mapping(record2);
802
- return !validRecordRef(identity.$ref) || typeof identity.$version != "string" || !identity.$version.length || identity.$version.length > 512 ? invalidChange() : { ref: identity.$ref, expectedVersion: identity.$version };
803
- }
804
- function captureDataUpdate(record2, set) {
805
- let identity = captureDataDelete(record2), fields3 = mapping(set);
806
- return !Object.keys(fields3).length || Object.keys(fields3).some((key) => key.startsWith("$") || ["id", "type"].includes(key)) ? invalidChange() : { ...identity, set: captureValue(fields3) };
807
- }
808
- function captureDataUpdates(value, allowEmpty = !1) {
809
- if (!Array.isArray(value) || !allowEmpty && !value.length || value.length > 1e4) return invalidChange();
810
- let updates = value.map((item) => {
811
- let row = mapping(item);
812
- return Object.keys(row).sort().join(",") !== "expectedVersion,ref,set" ? invalidChange() : captureDataUpdate({ $ref: row.ref, $version: row.expectedVersion }, row.set);
813
- });
814
- return new Set(updates.map((update) => update.ref)).size !== updates.length ? invalidChange() : updates;
815
- }
816
- function captureDataCreate(value) {
817
- let row = mapping(value);
818
- if (!validRecordRef(row.$ref) || Object.keys(row).some((key) => key.startsWith("$") && !["$ref", "$filing"].includes(key)))
819
- return invalidChange();
820
- let { $ref: ref, $filing, ...fields3 } = row, filing;
821
- if ($filing !== void 0) {
822
- let components = mapping($filing);
823
- if (Object.entries(components).some(([name, value2]) => !/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name) || ["constructor", "prototype"].includes(name) || typeof value2 != "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value2)))
824
- return invalidChange();
825
- filing = { ...components };
1279
+ chunk = void 0, reader?.releaseLock(), pending.size ? Promise.allSettled([...pending]).then(release) : release();
826
1280
  }
827
- return { ref, fields: captureValue(fields3), ...filing ? { filing } : {} };
828
1281
  }
829
- function captureDataChanges(updatesValue, createsValue = [], deletesValue = []) {
830
- let updates = captureDataUpdates(updatesValue, !0);
831
- if (!Array.isArray(createsValue) || createsValue.length > 1e4) return invalidChange();
832
- let creates = createsValue.map((item) => {
833
- let row = mapping(item);
834
- if (Object.keys(row).some((key) => !["ref", "fields", "filing"].includes(key)) || !Object.hasOwn(row, "ref") || !Object.hasOwn(row, "fields")) return invalidChange();
835
- let fields3 = mapping(row.fields);
836
- return Object.keys(fields3).some((key) => key.startsWith("$")) ? invalidChange() : captureDataCreate({ ...fields3, $ref: row.ref, ...Object.hasOwn(row, "filing") ? { $filing: row.filing } : {} });
1282
+ async function operation(work, initial, parent) {
1283
+ let context2 = initial ?? await resolveFilesContext();
1284
+ if (active >= MAX_ACTIVE) throw new FileOperationError("capacity", 429, !0);
1285
+ return active++, timed(async (signal) => {
1286
+ try {
1287
+ return await work(context2, signal);
1288
+ } finally {
1289
+ active--;
1290
+ }
1291
+ }, parent).catch((error) => {
1292
+ throw error instanceof FileOperationError ? error : unavailable();
837
1293
  });
838
- if (!Array.isArray(deletesValue) || deletesValue.length > 1e4) return invalidChange();
839
- let deletes = deletesValue.map((item) => {
840
- let row = mapping(item);
841
- return Object.keys(row).sort().join(",") !== "expectedVersion,ref" ? invalidChange() : captureDataDelete({ $ref: row.ref, $version: row.expectedVersion });
842
- }), refs = [...updates, ...creates, ...deletes].map((item) => item.ref);
843
- return !refs.length || refs.length > 1e4 || new Set(refs).size !== refs.length ? invalidChange() : { updates, creates, deletes };
844
- }
845
- function checkDataRequestSize(input) {
846
- if (new TextEncoder().encode(JSON.stringify(input)).byteLength > DATA_REQUEST_LIMIT)
847
- throw new DataOperationError("request_too_large", !1, "unapplied");
848
1294
  }
849
- function dataTransportError(status, value) {
850
- let failures2 = {
851
- 400: ["invalid_query", "invalid_change"],
852
- 401: ["unauthenticated"],
853
- 403: ["access_denied"],
854
- 409: ["conflict", "publication_pending"],
855
- 413: ["request_too_large"],
856
- 429: ["capacity"],
857
- 503: ["unavailable", "publication_pending"]
858
- }, error = value && typeof value == "object" && "error" in value ? value.error : void 0, row = error && typeof error == "object" ? error : void 0, code = failures2[status]?.find((allowed) => allowed === row?.code) ?? "unavailable", effect = ["unapplied", "uncertain", "applied"].includes(String(row?.effect)) ? row.effect : void 0;
859
- return new DataOperationError(
860
- code,
861
- ["capacity", "unavailable", "publication_pending"].includes(code),
862
- code === "conflict" ? "unapplied" : effect,
863
- code === "invalid_change" && typeof row?.component == "string" && /^[a-zA-Z][a-zA-Z0-9_-]{0,127}$/.test(row.component) ? row.component : void 0
864
- );
1295
+ function contentPath(id2, name) {
1296
+ let path = idPath(id2);
1297
+ if (name === void 0) return `${path}/content`;
1298
+ if (!safeEnrichmentName(name)) throw invalid4();
1299
+ return `${path}/enrichments/${encodeURIComponent(name)}/content`;
865
1300
  }
866
-
867
- var FileOperationError = class extends Error {
868
- constructor(code, status, retryable = !1) {
869
- super(code);
870
- this.code = code;
871
- this.status = status;
872
- this.retryable = retryable;
1301
+ async function read(id2, names) {
1302
+ let context2 = await resolveFilesContext();
1303
+ if (idPath(id2), names === void 0 || typeof names == "string") {
1304
+ let path = contentPath(id2, names);
1305
+ return operation((context3, signal) => send(path, { method: "GET" }, !1, context3, signal), context2);
873
1306
  }
874
- code;
875
- status;
876
- retryable;
877
- }, FILE_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/, FILE_ORIGINAL_MAX_BYTES = 5 * 1024 ** 3;
878
- function parseFileDescriptor(value) {
879
- let invalid4 = () => {
880
- throw new FileOperationError("unavailable", 503, !0);
881
- };
882
- if (!value || typeof value != "object" || Array.isArray(value)) return invalid4();
883
- let f = value;
884
- if (Object.keys(f).sort().join(",") !== "details,enrichments,id,mediaType,name,path,sha256,size" || typeof f.id != "string" || !FILE_ID_PATTERN.test(f.id) || !safeFileName(f.name) || !safeFilePath(f.path) || !Number.isSafeInteger(f.size) || f.size < 0 || f.size > FILE_ORIGINAL_MAX_BYTES || typeof f.sha256 != "string" || !/^[0-9a-f]{64}$/.test(f.sha256) || !f.enrichments || typeof f.enrichments != "object" || Array.isArray(f.enrichments) || !f.details || typeof f.details != "object" || Array.isArray(f.details)) return invalid4();
885
- let d = f.details;
886
- if (d.kind === "other") {
887
- if (Object.keys(d).length !== 1 || f.mediaType !== "application/octet-stream") return invalid4();
888
- } else if (d.kind === "csv") {
889
- if (Object.keys(d).sort().join(",") !== "dataRows,delimiter,encoding,headerRow,kind" || f.mediaType !== "text/csv" || !["utf-8", "windows-1252"].includes(d.encoding) || ![",", ";", " "].includes(d.delimiter) || !Number.isSafeInteger(d.headerRow) || d.headerRow < 1 || !Number.isSafeInteger(d.dataRows) || d.dataRows < 0) return invalid4();
890
- } else return invalid4();
891
- for (let [name, descriptor2] of Object.entries(f.enrichments)) {
892
- if (!safeEnrichmentName(name)) return invalid4();
893
- parseEnrichmentDescriptor(descriptor2);
1307
+ if (!Array.isArray(names)) throw invalid4();
1308
+ let distinct = [], seen = /* @__PURE__ */ new Set();
1309
+ for (let name of names) {
1310
+ if (typeof name != "string" || !safeEnrichmentName(name)) throw invalid4();
1311
+ if (!seen.has(name)) {
1312
+ if (seen.size === 16) throw invalid4();
1313
+ seen.add(name), distinct.push(name);
1314
+ }
894
1315
  }
895
- return new TextEncoder().encode(JSON.stringify(f)).byteLength > 16 * 1024 ? invalid4() : f;
896
- }
897
- function parseEnrichmentDescriptor(value) {
898
- let invalid4 = () => {
899
- throw new FileOperationError("unavailable", 503, !0);
900
- };
901
- if (!value || typeof value != "object" || Array.isArray(value)) return invalid4();
902
- let d = value;
903
- return Object.keys(d).sort().join(",") !== "mediaType,sha256,size" || !["application/octet-stream", "text/plain", "text/markdown", "application/json"].includes(d.mediaType) || !Number.isSafeInteger(d.size) || d.size < 0 || d.size > 16 * 1024 * 1024 || typeof d.sha256 != "string" || !/^[0-9a-f]{64}$/.test(d.sha256) ? invalid4() : d;
904
- }
905
- function safeEnrichmentName(value) {
906
- return safeFileName(value) && !/^original(?:\.|$)/i.test(value);
907
- }
908
- function parseFileEnrichMetadata(value, maxBytes) {
909
- let invalid4 = () => {
910
- throw new FileOperationError("invalid_input", 400);
911
- };
912
- if (!value || typeof value != "object" || Array.isArray(value)) return invalid4();
913
- let d = value;
914
- if (Object.keys(d).some((key) => !["name", "size", "mediaType", "replace"].includes(key)) || !safeEnrichmentName(d.name) || !Number.isSafeInteger(d.size) || d.size < 0 || d.mediaType !== void 0 && (typeof d.mediaType != "string" || d.mediaType.length > 255 || /[\u0000-\u001f\u007f]/.test(d.mediaType)) || d.replace !== void 0 && (typeof d.replace != "string" || !/^[0-9a-f]{64}$/.test(d.replace))) return invalid4();
915
- if (d.size > maxBytes) throw new FileOperationError("request_too_large", 413);
916
- return d;
917
- }
918
- function safeFileName(value) {
919
- return typeof value == "string" && value.length > 0 && value.length <= 255 && value === value.trim() && value !== "." && value !== ".." && !/[\u0000-\u001f\u007f-\u009f/\\:*?"<>|]/u.test(value) && !/[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/.test(value) && !/[. ]$/.test(value) && !/^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(value);
1316
+ let paths = distinct.map((name) => contentPath(id2, name));
1317
+ return timed(async (parent) => {
1318
+ let responses = {};
1319
+ try {
1320
+ for (let i = 0; i < paths.length; i++) {
1321
+ let response = await operation((context3, signal) => send(paths[i], { method: "GET" }, !1, context3, signal), context2, parent);
1322
+ Object.defineProperty(responses, distinct[i], { value: response, enumerable: !0 });
1323
+ }
1324
+ if (!context2.isCurrent()) throw new FileOperationError("unsupported_context", 400);
1325
+ return responses;
1326
+ } catch (error) {
1327
+ for (let response of Object.values(responses)) response.body?.cancel().catch(() => {
1328
+ });
1329
+ throw error;
1330
+ }
1331
+ });
920
1332
  }
921
- function safeFilePath(value) {
922
- return typeof value == "string" && value.length <= 1024 && value.split("/").every(safeFileName);
1333
+ var files = {
1334
+ delete(id2) {
1335
+ return operation((context2, signal) => send(idPath(id2), { method: "DELETE" }, 204, context2, signal));
1336
+ },
1337
+ async upload(id2) {
1338
+ return operation(async (context2, signal) => {
1339
+ let file2 = await send(`${idPath(id2)}/upload`, { method: "POST" }, !0, context2, signal);
1340
+ if (file2.id !== id2) throw unavailable();
1341
+ return file2;
1342
+ });
1343
+ },
1344
+ async create(input, options) {
1345
+ let context2 = await resolveFilesContext();
1346
+ if (!input || typeof input != "object" || Array.isArray(input) || Object.keys(input).some((key) => !["file", "size", "path", "name", "storage", "csv"].includes(key)) || options !== void 0 && (!options || typeof options != "object" || Array.isArray(options) || Object.keys(options).some((key) => key !== "requestKey")))
1347
+ throw invalid4();
1348
+ let blob = input.file instanceof Blob, array2 = input.file instanceof Uint8Array, stream = input.file instanceof ReadableStream;
1349
+ if (!blob && !array2 && !stream) throw invalid4();
1350
+ let intrinsic = blob ? input.file.size : array2 ? input.file.byteLength : void 0;
1351
+ if (intrinsic !== void 0 && input.size !== void 0 && input.size !== intrinsic) throw invalid4();
1352
+ let name = input.name;
1353
+ name === void 0 && blob && "name" in input.file && typeof input.file.name == "string" && (name = typeof globalThis.File < "u" && input.file instanceof globalThis.File ? input.file.name : input.file.name.split(/[\\/]/).at(-1));
1354
+ let metadata = parseFileCreateMetadata({
1355
+ name,
1356
+ size: intrinsic ?? input.size,
1357
+ path: input.path,
1358
+ storage: input.storage,
1359
+ csv: input.csv,
1360
+ ...blob && input.file.type === "application/pdf" ? { inputMediaType: "application/pdf" } : {},
1361
+ requestKey: options?.requestKey === void 0 ? crypto.randomUUID() : options.requestKey
1362
+ }, FILE_ORIGINAL_MAX_BYTES);
1363
+ if (!context2.local && metadata.storage === "local") throw new FileOperationError("unsupported_context", 400);
1364
+ let header = encodeURIComponent(JSON.stringify(metadata));
1365
+ if (new TextEncoder().encode(header).byteLength > METADATA_BYTES) throw invalid4();
1366
+ if (active >= MAX_ACTIVE) throw new FileOperationError("capacity", 429, !0);
1367
+ 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) => {
1368
+ try {
1369
+ let bytes = array2 ? input.file.slice() : await boundedBytes(
1370
+ blob ? input.file.stream() : input.file,
1371
+ metadata.size
1372
+ );
1373
+ if (bytes.byteLength !== metadata.size) throw invalid4();
1374
+ if (signal.aborted) throw unavailable();
1375
+ return await send("", {
1376
+ method: "POST",
1377
+ headers: { "content-type": "application/octet-stream", "x-oddington-file": header },
1378
+ body: bytes.buffer
1379
+ }, !0, context2, signal);
1380
+ } finally {
1381
+ active--;
1382
+ }
1383
+ }).catch((error) => {
1384
+ throw error instanceof FileOperationError ? error : unavailable();
1385
+ });
1386
+ },
1387
+ async enrich(input) {
1388
+ let context2 = await resolveFilesContext();
1389
+ if (!input || typeof input != "object" || Array.isArray(input) || Object.keys(input).some((key) => !["file", "name", "content", "mediaType", "replace"].includes(key)) || typeof input.content != "string" && !(input.content instanceof Uint8Array)) throw invalid4();
1390
+ let path = `${idPath(input.file)}/enrichments`;
1391
+ if (typeof input.content == "string" ? input.content.length > MAX_BYTES : input.content.byteLength > MAX_BYTES)
1392
+ throw new FileOperationError("request_too_large", 413);
1393
+ return operation(async (context3, signal) => {
1394
+ let bytes;
1395
+ if (typeof input.content == "string") {
1396
+ let buffer = new Uint8Array(Math.min(MAX_BYTES, input.content.length * 3)), encoded = new TextEncoder().encodeInto(input.content, buffer);
1397
+ if (encoded.read !== input.content.length) throw new FileOperationError("request_too_large", 413);
1398
+ bytes = buffer.slice(0, encoded.written);
1399
+ } else bytes = input.content.slice();
1400
+ let metadata = parseFileEnrichMetadata({
1401
+ name: input.name,
1402
+ size: bytes.byteLength,
1403
+ ...input.mediaType === void 0 ? {} : { mediaType: input.mediaType },
1404
+ ...input.replace === void 0 ? {} : { replace: input.replace }
1405
+ }, MAX_BYTES), header = encodeURIComponent(JSON.stringify(metadata));
1406
+ if (new TextEncoder().encode(header).byteLength > METADATA_BYTES) throw invalid4();
1407
+ let result2 = await send(path, { method: "POST", headers: {
1408
+ "content-type": "application/octet-stream",
1409
+ "x-oddington-enrichment": header
1410
+ }, body: bytes.buffer }, "enrichment", context3, signal);
1411
+ if (result2.name !== metadata.name) throw unavailable();
1412
+ return result2;
1413
+ }, context2);
1414
+ },
1415
+ async get(id2) {
1416
+ return operation((context2, signal) => send(idPath(id2), { method: "GET" }, !0, context2, signal));
1417
+ },
1418
+ read
1419
+ };
1420
+
1421
+ function validGoogleScopes(value) {
1422
+ return Array.isArray(value) && value.length > 0 && value.length <= 32 && value.every((scope) => typeof scope == "string" && scope.length <= 256 && /^(https:\/\/www\.googleapis\.com\/auth\/[A-Za-z0-9._-]+|https:\/\/mail\.google\.com\/|openid|email|profile)$/.test(scope)) && new Set(value).size === value.length;
923
1423
  }
924
- function parseFileCreateMetadata(value, maxBytes) {
925
- let invalid4 = () => {
926
- throw new FileOperationError("invalid_input", 400);
927
- };
928
- if (!value || typeof value != "object" || Array.isArray(value)) return invalid4();
929
- let input = value;
930
- if (Object.keys(input).some((key) => !["name", "path", "storage", "csv", "size", "requestKey"].includes(key))) return invalid4();
931
- let path = input.path === void 0 ? "Files" : input.path;
932
- if (!safeFileName(input.name) || !safeFilePath(path) || input.storage !== void 0 && input.storage !== "remote" && input.storage !== "local" || !Number.isSafeInteger(input.size) || input.size < 0) return invalid4();
933
- if (input.size > maxBytes) throw new FileOperationError("request_too_large", 413);
934
- if (typeof input.requestKey != "string" || input.requestKey.length < 1 || input.requestKey.length > 256 || input.requestKey !== input.requestKey.trim() || /[\u0000-\u001f\u007f]/.test(input.requestKey)) return invalid4();
935
- let csv;
936
- if (input.csv !== void 0) {
937
- if (!input.csv || typeof input.csv != "object" || Array.isArray(input.csv)) return invalid4();
938
- let hints = input.csv;
939
- if (Object.keys(hints).some((key) => !["encoding", "delimiter", "headerRow"].includes(key))) return invalid4();
940
- let encoding = hints.encoding === void 0 ? "utf-8" : hints.encoding, delimiter = hints.delimiter === void 0 ? "," : hints.delimiter, headerRow = hints.headerRow === void 0 ? 1 : hints.headerRow;
941
- if (encoding !== "utf-8" && encoding !== "windows-1252" || ![",", ";", " "].includes(delimiter) || !Number.isSafeInteger(headerRow) || headerRow < 1 || headerRow > 1e5) return invalid4();
942
- csv = { encoding, delimiter, headerRow };
1424
+
1425
+ var ROUTINE_CONNECTION_CREDENTIALS_ENV = "ODDINGTON_ROUTINE_CONNECTION_CREDENTIALS";
1426
+ 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);
1427
+ function parseConnectionLease(raw, now = Date.now()) {
1428
+ let v = raw;
1429
+ 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))
1430
+ throw new Error("Invalid connection credential");
1431
+ if (v.provider === "gmail") {
1432
+ if (Object.keys(v).sort().join(",") !== "accessToken,accountEmail,connectionId,expiresAt,name,provider,runId,scopes" || typeof v.accountEmail != "string" || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v.accountEmail) || !validGoogleScopes(v.scopes) || typeof v.expiresAt != "string" || !Number.isFinite(Date.parse(v.expiresAt)) || Date.parse(v.expiresAt) <= now + 6e4 || Date.parse(v.expiresAt) > now + 36e5)
1433
+ throw new Error("Invalid connection credential");
1434
+ return v;
943
1435
  }
944
- return {
945
- name: input.name,
946
- path,
947
- ...input.storage ? { storage: input.storage } : {},
948
- ...csv ? { csv } : {},
949
- size: input.size,
950
- requestKey: input.requestKey
951
- };
1436
+ if (v.provider === "airtable" && Object.keys(v).sort().join(",") === "accessToken,baseId,connectionId,name,provider,runExpiresAt,runId" && typeof v.baseId == "string" && /^app[A-Za-z0-9]+$/.test(v.baseId) && typeof v.runExpiresAt == "string" && Number.isFinite(Date.parse(v.runExpiresAt)) && Date.parse(v.runExpiresAt) > now + 6e4 && Date.parse(v.runExpiresAt) <= now + 15 * 6e4)
1437
+ return v;
1438
+ throw new Error("Invalid connection credential");
952
1439
  }
953
1440
 
954
- var DEFAULT_ORIGIN = "https://auth.oddington.ai";
955
- function oddingtonOrigin(environment = process.env) {
956
- let value = environment.ODDINGTON_OS_ORIGIN ?? DEFAULT_ORIGIN, url = new URL(value);
957
- if (url.protocol !== "https:" && url.hostname !== "127.0.0.1")
958
- throw new Error("ODDINGTON_OS_ORIGIN must use HTTPS");
959
- return url.origin;
960
- }
1441
+ var AirtableOperationError = class extends Error {
1442
+ constructor(message, effect = "unapplied", receipts = []) {
1443
+ super(message);
1444
+ this.effect = effect;
1445
+ this.receipts = receipts;
1446
+ this.name = "AirtableOperationError";
1447
+ }
1448
+ effect;
1449
+ receipts;
1450
+ };
961
1451
 
962
- var memberContext;
963
- function installMemberContext(resolve) {
964
- return memberContext = resolve, () => {
965
- memberContext === resolve && (memberContext = void 0);
966
- };
1452
+ var recordId = (v) => typeof v == "string" && /^rec[A-Za-z0-9]+$/.test(v), tableId = (v) => typeof v == "string" && /^tbl[A-Za-z0-9]+$/.test(v);
1453
+ function fail2(message) {
1454
+ throw new AirtableOperationError(message);
967
1455
  }
968
- async function resolveAppContext() {
969
- let host = memberContext;
970
- if (!host) throw new Error("App context unavailable.");
971
- let context2 = await host();
972
- if (host !== memberContext || !context2.isCurrent() || context2.expiresAt <= Date.now())
973
- throw new Error("App context unavailable.");
974
- if (context2.kind !== "local" && (typeof context2.accessToken != "string" || !/^[^\s]{1,8192}$/.test(context2.accessToken)))
975
- throw new Error("App context unavailable.");
976
- return context2;
1456
+ function object2(v) {
1457
+ return (!v || typeof v != "object" || Array.isArray(v) || ![null, Object.prototype].includes(Object.getPrototypeOf(v))) && fail2("Invalid Airtable data"), v;
977
1458
  }
978
- async function resolveDataContext() {
979
- try {
980
- let host = memberContext;
981
- if (host !== void 0) {
982
- let context2 = await host();
983
- if (host !== memberContext || !context2.isCurrent() || context2.expiresAt <= Date.now()) throw new Error();
984
- if (context2.kind === "local") return { local: context2.requestData };
985
- if (!context2 || !Number.isFinite(context2.expiresAt) || context2.expiresAt <= Date.now() || typeof context2.accessToken != "string" || !/^[^\s]{1,8192}$/.test(context2.accessToken)) throw new Error();
986
- return checked(context2.workspace, context2.endpoint, { authorization: `Bearer ${context2.accessToken}` });
987
- }
988
- throw new Error();
989
- } catch {
990
- throw new DataOperationError("context_unavailable");
991
- }
1459
+ function keys2(v, allowed, required = allowed) {
1460
+ (Object.keys(v).some((k) => !allowed.includes(k)) || required.some((k) => !Object.hasOwn(v, k))) && fail2("Invalid Airtable input");
992
1461
  }
993
- async function resolveFilesContext() {
1462
+ function text2(v) {
1463
+ return typeof v == "string" && v.length > 0 && v.length <= 1024;
1464
+ }
1465
+ function fields3(v, nonempty = !1) {
1466
+ let result2 = object2(v);
1467
+ (nonempty && !Object.keys(result2).length || Object.keys(result2).some((k) => !text2(k))) && fail2("Invalid Airtable fields");
994
1468
  try {
995
- let environment = {}, host = memberContext;
996
- if (host) {
997
- let context2 = await host();
998
- if (context2.kind === "local") throw new Error();
999
- if (!Number.isFinite(context2.expiresAt) || context2.expiresAt <= Date.now() || typeof context2.accessToken != "string" || !/^[^\s]{1,8192}$/.test(context2.accessToken)) throw new Error();
1000
- return {
1001
- ...filesContext(context2.workspace, context2.endpoint, { authorization: `Bearer ${context2.accessToken}` }),
1002
- isCurrent: context2.isCurrent,
1003
- local: void 0,
1004
- localFiles: void 0
1005
- };
1006
- }
1007
- throw new Error();
1469
+ return serializable(result2);
1008
1470
  } catch {
1009
- throw new FileOperationError("unsupported_context", 400);
1471
+ return fail2("Invalid Airtable fields");
1010
1472
  }
1011
1473
  }
1012
- function filesContext(workspace, endpoint, headers) {
1013
- if (!workspace || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(workspace) || workspace.length > 63 || ["auth", "www", "api", "os"].includes(workspace) || endpoint !== `https://${workspace}.oddington.ai`) throw new Error();
1014
- return { url: `${endpoint}/api/files`, headers: { ...headers, "x-oddington-workspace": workspace }, isCurrent: () => !0 };
1474
+ function record2(v) {
1475
+ let r = object2(v);
1476
+ return (!recordId(r.id) || r.createdTime !== void 0 && (typeof r.createdTime != "string" || !Number.isFinite(Date.parse(r.createdTime)))) && fail2("Malformed Airtable record"), { id: r.id, ...r.createdTime === void 0 ? {} : { createdTime: r.createdTime }, fields: fields3(r.fields) };
1015
1477
  }
1016
- function checked(workspace, endpoint, headers) {
1017
- if (!workspace || !/^[a-z0-9][a-z0-9-]{0,127}$/.test(workspace)) throw new Error();
1018
- let origin = new URL(endpoint);
1019
- if (origin.protocol !== "https:" || origin.username || origin.password || origin.search || origin.hash || origin.pathname !== "/")
1020
- throw new Error();
1021
- return { url: `${origin.origin}/api/data`, headers: { ...headers, "x-oddington-workspace": workspace } };
1478
+ function equal(a, b) {
1479
+ if (a === b) return !0;
1480
+ if (!a || !b || typeof a != "object" || typeof b != "object" || Array.isArray(a) !== Array.isArray(b)) return !1;
1481
+ let left = Object.keys(a), right = Object.keys(b);
1482
+ return left.length === right.length && left.every((k) => Object.hasOwn(b, k) && equal(
1483
+ a[k],
1484
+ b[k]
1485
+ ));
1022
1486
  }
1023
-
1024
- var data = {
1025
- repository(repository) {
1026
- async function send2(input) {
1027
- let context2 = await resolveDataContext();
1028
- try {
1029
- let envelope = { repository, ...input };
1030
- if (checkDataRequestSize(envelope), "local" in context2) return await context2.local("data/query", envelope);
1031
- let response = await fetch(context2.url, {
1032
- method: "POST",
1033
- redirect: "error",
1034
- cache: "no-store",
1035
- headers: { "content-type": "application/json", ...context2.headers },
1036
- body: JSON.stringify(envelope)
1037
- }), result2 = await response.json();
1038
- if (!response.ok)
1039
- throw dataTransportError(response.status, result2);
1040
- return result2;
1041
- } catch (error) {
1042
- throw error instanceof DataOperationError ? error : new DataOperationError("unavailable", !0);
1043
- }
1044
- }
1487
+ function createConnections(transport) {
1488
+ return { airtable(name) {
1489
+ (typeof name != "string" || !/^[a-z0-9][a-z0-9-]{0,62}$/.test(name)) && fail2("Invalid Airtable connection name");
1490
+ let call = (operation2, input) => transport(name, operation2, input);
1045
1491
  return {
1046
- collections: {
1047
- list: () => send2({ operation: "collections.list" }),
1048
- schema: (name) => send2({ operation: "collections.schema", name })
1049
- },
1492
+ tables: { list: async () => await call("tables.list", {}) },
1050
1493
  records: {
1051
- select: (query) => send2({ operation: "records.select", query }),
1052
- search: async (query, options) => send2({ operation: "records.search", ...captureRecordsSearchInput(query, options) })
1053
- },
1054
- history: {
1055
- list: async (options) => send2({ operation: "history.list", options: captureHistoryListOptions(options) }),
1056
- at: async (instant) => send2({ operation: "history.at", instant: captureHistoryInstant(instant) })
1057
- },
1058
- changes() {
1059
- let updates = [], creates = [], deletes = [], publicationId, pending, confirmed, snapshot = () => {
1060
- let captured = captureDataChanges(updates, creates, deletes), input = {
1061
- updates: captured.updates,
1062
- ...captured.creates.length ? { creates: captured.creates } : {},
1063
- ...captured.deletes.length ? { deletes: captured.deletes } : {}
1064
- };
1065
- return checkDataRequestSize({
1066
- repository,
1067
- operation: "changes.publish",
1068
- ...input,
1069
- publicationId: publicationId ?? "00000000-0000-4000-8000-000000000000"
1070
- }), input;
1071
- };
1072
- return {
1073
- create(record2) {
1074
- if (publicationId) throw new DataOperationError("invalid_change", !1, "unapplied");
1075
- let next = [...creates, captureDataCreate(record2)];
1076
- captureDataChanges(updates, next, deletes), checkDataRequestSize({
1077
- repository,
1078
- operation: "changes.publish",
1079
- updates,
1080
- creates: next,
1081
- ...deletes.length ? { deletes } : {},
1082
- publicationId: "00000000-0000-4000-8000-000000000000"
1083
- }), creates = next;
1084
- },
1085
- update(record2, set) {
1086
- if (publicationId) throw new DataOperationError("invalid_change", !1, "unapplied");
1087
- let next = [...updates, captureDataUpdate(record2, set)];
1088
- captureDataChanges(next, creates, deletes), checkDataRequestSize({
1089
- repository,
1090
- operation: "changes.publish",
1091
- updates: next,
1092
- ...creates.length ? { creates } : {},
1093
- ...deletes.length ? { deletes } : {},
1094
- publicationId: "00000000-0000-4000-8000-000000000000"
1095
- }), updates = next;
1096
- },
1097
- delete(record2) {
1098
- if (publicationId) throw new DataOperationError("invalid_change", !1, "unapplied");
1099
- let next = [...deletes, captureDataDelete(record2)];
1100
- captureDataChanges(updates, creates, next), checkDataRequestSize({
1101
- repository,
1102
- operation: "changes.publish",
1103
- updates,
1104
- deletes: next,
1105
- ...creates.length ? { creates } : {},
1106
- publicationId: "00000000-0000-4000-8000-000000000000"
1107
- }), deletes = next;
1108
- },
1109
- async validate() {
1110
- await send2({ operation: "changes.validate", ...snapshot() });
1111
- },
1112
- publish() {
1113
- if (confirmed) return Promise.resolve(structuredClone(confirmed));
1114
- if (!pending) {
1115
- let captured;
1116
- try {
1117
- captured = snapshot();
1118
- } catch (error) {
1119
- return Promise.reject(error);
1120
- }
1121
- publicationId ??= crypto.randomUUID(), pending = send2({ operation: "changes.publish", ...captured, publicationId }).then((result2) => (confirmed = structuredClone(result2), result2)).catch((error) => {
1122
- throw error instanceof DataOperationError ? new DataOperationError(
1123
- error.code,
1124
- error.retryable,
1125
- error.effect ?? (["unavailable", "publication_pending"].includes(error.code) ? "uncertain" : "unapplied"),
1126
- error.component
1127
- ) : new DataOperationError("unavailable", !0, "uncertain");
1128
- }).finally(() => {
1129
- pending = void 0;
1130
- });
1131
- }
1132
- return pending.then((result2) => structuredClone(result2));
1133
- }
1134
- };
1494
+ list: async (input) => await call("records.list", input),
1495
+ get: async (input) => await call("records.get", input),
1496
+ create: async (input) => await call("records.create", input),
1497
+ update: async (input) => await call("records.update", input),
1498
+ delete: async (input) => await call("records.delete", input)
1135
1499
  }
1136
1500
  };
1137
- }
1138
- };
1139
-
1140
- 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, invalid3 = () => new FileOperationError("invalid_input", 400), unavailable = () => new FileOperationError("unavailable", 503, !0), failures = {
1141
- 400: ["invalid_input", "unsupported_context"],
1142
- 401: ["unauthenticated"],
1143
- 403: ["access_denied"],
1144
- 405: ["method_not_allowed"],
1145
- 409: ["conflict", "local_only"],
1146
- 413: ["request_too_large"],
1147
- 429: ["capacity"],
1148
- 503: ["integrity", "unavailable"]
1149
- };
1150
- async function boundedBytes(stream, limit) {
1151
- if (!stream) return new Uint8Array();
1152
- let reader = stream.getReader(), bytes = new Uint8Array(limit), size = 0;
1501
+ } };
1502
+ }
1503
+ async function executeAirtableOperation(authority, operation2, raw, options = {}) {
1504
+ let { name, baseId, accessToken } = authority;
1505
+ (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(name) || !/^app[A-Za-z0-9]+$/.test(baseId) || !/^[A-Za-z0-9._~+/-]{1,8192}={0,2}$/.test(accessToken)) && fail2("Airtable connection unavailable");
1506
+ let input;
1153
1507
  try {
1154
- for (; ; ) {
1155
- let { done, value } = await reader.read();
1156
- if (done) break;
1157
- if (!(value instanceof Uint8Array)) throw invalid3();
1158
- if (size += value.byteLength, size > limit) throw new FileOperationError("request_too_large", 413);
1159
- bytes.set(value, size - value.byteLength);
1508
+ input = object2(serializable(raw));
1509
+ } catch {
1510
+ return fail2("Invalid Airtable input");
1511
+ }
1512
+ let mutation = ["records.create", "records.update", "records.delete"].includes(operation2), receipt = (ids2) => ({
1513
+ provider: "airtable",
1514
+ connection: `airtable/${name}`,
1515
+ baseId,
1516
+ table: input.table,
1517
+ operation: operation2.slice(8),
1518
+ records: ids2.map((id2) => ({ id: id2 }))
1519
+ });
1520
+ async function request(path2, init2 = {}, write = !1) {
1521
+ let response;
1522
+ try {
1523
+ response = await (options.fetch ?? fetch)(`https://api.airtable.com/v0/${path2}`, {
1524
+ ...init2,
1525
+ headers: { authorization: `Bearer ${accessToken}`, "content-type": "application/json" },
1526
+ redirect: "manual",
1527
+ signal: options.signal ? AbortSignal.any([options.signal, AbortSignal.timeout(15e3)]) : AbortSignal.timeout(15e3)
1528
+ });
1529
+ } catch {
1530
+ throw new AirtableOperationError("Airtable request acknowledgment unavailable", write ? "uncertain" : "unapplied");
1160
1531
  }
1161
- } finally {
1162
- reader.releaseLock();
1532
+ if (!response.ok)
1533
+ throw await response.body?.cancel().catch(() => {
1534
+ }), new AirtableOperationError("Airtable request rejected", write && (response.status >= 500 || response.status === 408 || response.status < 400) ? "uncertain" : "unapplied");
1535
+ try {
1536
+ if (!response.headers.get("content-type")?.toLowerCase().includes("application/json")) throw new Error();
1537
+ let value = object2(await response.json()), safe = serializable(value);
1538
+ if (Object.hasOwn(value, "error") || JSON.stringify(safe).includes(accessToken)) throw new Error();
1539
+ return value;
1540
+ } catch {
1541
+ throw new AirtableOperationError("Malformed Airtable acknowledgment", write ? "uncertain" : "unapplied");
1542
+ }
1543
+ }
1544
+ if (operation2 === "tables.list") {
1545
+ keys2(input, []);
1546
+ let tables = [], seen = /* @__PURE__ */ new Set(), offset;
1547
+ do {
1548
+ let query2 = new URLSearchParams(offset ? { offset } : {}), page = await request(`meta/bases/${baseId}/tables?${query2}`);
1549
+ Array.isArray(page.tables) || fail2("Malformed Airtable tables");
1550
+ for (let value of page.tables) {
1551
+ let t = object2(value);
1552
+ (!tableId(t.id) || !text2(t.name) || !text2(t.primaryFieldId) || !Array.isArray(t.fields) || tables.some((existing) => existing.id === t.id)) && fail2("Malformed Airtable table");
1553
+ let metadata = t.fields.map((value2) => {
1554
+ let f = object2(value2);
1555
+ return (typeof f.id != "string" || !/^fld[A-Za-z0-9]+$/.test(f.id) || !text2(f.name) || !text2(f.type)) && fail2("Malformed Airtable field"), { id: f.id, name: f.name, type: f.type, ...f.options === void 0 ? {} : { options: serializable(f.options) } };
1556
+ });
1557
+ (!metadata.some((f) => f.id === t.primaryFieldId) || new Set(metadata.map((f) => f.id)).size !== metadata.length) && fail2("Malformed Airtable fields"), tables.push({ id: t.id, name: t.name, primaryFieldId: t.primaryFieldId, fields: metadata });
1558
+ }
1559
+ offset = nextOffset(page.offset, seen);
1560
+ } while (offset);
1561
+ return tables;
1562
+ }
1563
+ (!text2(input.table) || input.table === "." || input.table === "..") && fail2("Invalid Airtable table"), mutation && !tableId(input.table) && fail2("Airtable writes require a stable table ID");
1564
+ let path = `${baseId}/${encodeURIComponent(input.table)}`;
1565
+ if (operation2 === "records.get") {
1566
+ keys2(input, ["table", "id"]), recordId(input.id) || fail2("Invalid Airtable record ID");
1567
+ let result3 = record2(await request(`${path}/${input.id}`));
1568
+ return result3.id !== input.id && fail2("Malformed Airtable record"), result3;
1569
+ }
1570
+ if (operation2 === "records.list") {
1571
+ keys2(input, ["table", "fields"], ["table"]), input.fields !== void 0 && (!Array.isArray(input.fields) || !input.fields.length || input.fields.some((f) => !text2(f)) || new Set(input.fields).size !== input.fields.length) && fail2("Invalid Airtable field selection");
1572
+ let records2 = [], ids2 = /* @__PURE__ */ new Set(), seen = /* @__PURE__ */ new Set(), offset;
1573
+ do {
1574
+ let query2 = new URLSearchParams();
1575
+ for (let field of input.fields ?? []) query2.append("fields[]", field);
1576
+ offset && query2.set("offset", offset);
1577
+ let page = await request(`${path}?${query2}`);
1578
+ Array.isArray(page.records) || fail2("Malformed Airtable records");
1579
+ for (let value of page.records) {
1580
+ let r = record2(value);
1581
+ ids2.has(r.id) && fail2("Malformed Airtable pagination"), ids2.add(r.id), records2.push(r);
1582
+ }
1583
+ offset = nextOffset(page.offset, seen);
1584
+ } while (offset);
1585
+ return records2;
1163
1586
  }
1164
- return bytes.slice(0, size);
1165
- }
1166
- async function timed(work, parent, timeout = TIMEOUT_MS) {
1167
- let controller = new AbortController(), timer, abort, expiry = new Promise((_, reject) => {
1168
- abort = () => {
1169
- controller.abort(), reject(unavailable());
1170
- }, timer = setTimeout(abort, timeout), parent?.addEventListener("abort", abort, { once: !0 }), parent?.aborted && abort();
1587
+ mutation || fail2("Unsupported Airtable operation"), keys2(input, ["table", "records"]), (!Array.isArray(input.records) || !input.records.length || input.records.length > 10) && fail2("Invalid Airtable batch");
1588
+ let kind = operation2.slice(8), ids = /* @__PURE__ */ new Set(), records = input.records.map((value) => {
1589
+ let r = object2(value);
1590
+ if (keys2(r, kind === "create" ? ["fields"] : kind === "update" ? ["id", "expected", "set"] : ["id", "expected"]), kind === "create") return { fields: fields3(r.fields, !0) };
1591
+ (!recordId(r.id) || ids.has(r.id)) && fail2("Invalid Airtable batch record ID"), ids.add(r.id);
1592
+ let set = kind === "update" ? fields3(r.set, !0) : void 0;
1593
+ return set && Object.values(set).some((value2) => value2 === null) && fail2("Airtable guard null cannot be an update value"), { id: r.id, expected: fields3(r.expected), ...set ? { set } : {} };
1171
1594
  });
1172
- try {
1173
- return await Promise.race([work(controller.signal), expiry]);
1174
- } finally {
1175
- clearTimeout(timer), parent?.removeEventListener("abort", abort);
1595
+ for (let r of records) {
1596
+ if (!("id" in r)) continue;
1597
+ let current2 = record2(await request(`${path}/${r.id}`));
1598
+ current2.id !== r.id && fail2("Malformed Airtable record");
1599
+ for (let [key, value] of Object.entries(r.expected))
1600
+ (value === null ? Object.hasOwn(current2.fields, key) : !Object.hasOwn(current2.fields, key) || !equal(current2.fields[key], value)) && fail2("Airtable reviewed values are stale");
1176
1601
  }
1177
- }
1178
- async function result(response, maximum = RESULT_BYTES) {
1602
+ await options.beforeMutation?.();
1603
+ let query = new URLSearchParams();
1604
+ if (kind === "delete") for (let id2 of ids) query.append("records[]", id2);
1605
+ let result2 = await request(`${path}${kind === "delete" ? `?${query}` : ""}`, {
1606
+ method: kind === "create" ? "POST" : kind === "update" ? "PATCH" : "DELETE",
1607
+ ...kind === "delete" ? {} : { body: JSON.stringify({ records: records.map((r) => "id" in r ? { id: r.id, fields: r.set } : r) }) }
1608
+ }, !0), acknowledged = [], returned = [];
1179
1609
  try {
1180
- let bytes = await boundedBytes(response.body, maximum);
1181
- return JSON.parse(new TextDecoder("utf-8", { fatal: !0 }).decode(bytes));
1610
+ if (!Array.isArray(result2.records)) throw new Error();
1611
+ for (let value of result2.records) {
1612
+ let r = object2(value);
1613
+ if (!recordId(r.id) || acknowledged.includes(r.id) || kind !== "create" && !ids.has(r.id) || kind === "delete" && r.deleted !== !0) throw new Error();
1614
+ acknowledged.push(r.id), kind !== "delete" && returned.push(record2(r));
1615
+ }
1616
+ if (acknowledged.length !== records.length) throw new Error();
1182
1617
  } catch {
1183
- throw unavailable();
1618
+ throw new AirtableOperationError(
1619
+ "Incomplete Airtable mutation acknowledgment",
1620
+ acknowledged.length ? "partial" : "uncertain",
1621
+ acknowledged.length ? [receipt(acknowledged)] : []
1622
+ );
1184
1623
  }
1624
+ return { receipt: receipt(acknowledged), ...kind === "delete" ? {} : { records: returned } };
1185
1625
  }
1186
- async function hostedContent(url, init2, parent, upstream = fetch) {
1187
- let delivery = new AbortController(), abort = () => delivery.abort(parent.reason);
1188
- parent.addEventListener("abort", abort, { once: !0 }), parent.aborted && abort();
1189
- let detach = () => parent.removeEventListener("abort", abort);
1190
- try {
1191
- let response = await upstream(url, { ...init2, signal: delivery.signal });
1192
- if (delivery.signal.aborted)
1193
- throw response.body?.cancel().catch(() => {
1194
- }), unavailable();
1195
- if (!response.body)
1196
- return detach(), response;
1197
- let source = response.body, reader, settled = !1, cancel = async (reason) => {
1198
- if (!settled) {
1199
- settled = !0, detach(), delivery.abort(reason);
1200
- try {
1201
- await (reader ? reader.cancel(reason) : source.cancel(reason));
1202
- } catch (error) {
1203
- if (error !== delivery.signal.reason && !(error instanceof Error && error.name === "AbortError")) throw error;
1204
- } finally {
1205
- reader?.releaseLock();
1206
- }
1207
- }
1208
- }, body2 = new ReadableStream({
1209
- async pull(controller) {
1210
- try {
1211
- reader ??= source.getReader();
1212
- let { done, value } = await reader.read();
1213
- if (settled) return;
1214
- done ? (settled = !0, detach(), reader.releaseLock(), controller.close()) : controller.enqueue(value);
1215
- } catch (error) {
1216
- if (settled) return;
1217
- controller.error(error), await cancel(error).catch(() => {
1218
- });
1219
- }
1220
- },
1221
- cancel
1222
- }, { highWaterMark: 0 }), wrapped = new Response(body2, { status: response.status, statusText: response.statusText, headers: response.headers });
1223
- for (let key of ["url", "redirected", "type"])
1224
- Object.defineProperty(wrapped, key, { value: response[key] });
1225
- return wrapped;
1226
- } catch (error) {
1227
- throw detach(), delivery.abort(error), error;
1228
- }
1626
+ function nextOffset(value, seen) {
1627
+ if (value !== void 0)
1628
+ return (!text2(value) || seen.has(value)) && fail2("Malformed Airtable pagination"), seen.add(value), value;
1229
1629
  }
1230
- async function send(suffix, init2, descriptor2, initial, parent) {
1231
- let original = initial, checkCurrent = () => {
1232
- if (parent?.aborted) throw unavailable();
1233
- if (!original.isCurrent()) throw new FileOperationError("unsupported_context", 400);
1234
- };
1235
- for (let attempt = 0; attempt < ATTEMPTS; attempt++) {
1236
- checkCurrent();
1237
- let context2 = await resolveFilesContext();
1238
- if (checkCurrent(), context2.url !== original.url) throw new FileOperationError("unsupported_context", 400);
1630
+
1631
+ var MANAGED_CONNECTION_CAPABILITY_ENV = "ODDINGTON_MANAGED_CONNECTION_CAPABILITY";
1632
+ function managedConnections(capability) {
1633
+ let endpoint = new URL(capability.endpoint);
1634
+ if (endpoint.protocol !== "http:" || endpoint.hostname !== "127.0.0.1" || !endpoint.port || endpoint.pathname !== "/" || endpoint.search || endpoint.hash || endpoint.username || endpoint.password || !/^[0-9a-f-]{36}$/.test(capability.token)) throw new AirtableOperationError("Airtable capability unavailable");
1635
+ return createConnections(async (name, operation2, input) => {
1636
+ let safeInput;
1239
1637
  try {
1240
- return await (async () => {
1241
- let response = context2.localFiles ? await context2.localFiles(suffix, init2, parent) : await (async () => {
1242
- let url = `${context2.url}${suffix}`, request = {
1243
- ...init2,
1244
- signal: parent,
1245
- headers: { ...context2.headers, ...init2.headers },
1246
- redirect: "error",
1247
- cache: "no-store"
1248
- };
1249
- return descriptor2 ? fetch(url, request) : hostedContent(url, request, parent);
1250
- })();
1251
- if (!response.ok) {
1252
- let value2 = await result(response), error = value2 && typeof value2 == "object" && "error" in value2 ? value2.error : void 0, received = error && typeof error == "object" && "code" in error ? error.code : void 0, code = failures[response.status]?.find((code2) => code2 === received);
1253
- throw code ? new FileOperationError(code, response.status, code === "capacity" || code === "unavailable") : unavailable();
1254
- }
1255
- if (response.status !== 200)
1256
- throw response.body?.cancel().catch(() => {
1257
- }), unavailable();
1258
- try {
1259
- checkCurrent();
1260
- } catch (error) {
1261
- throw response.body?.cancel().catch(() => {
1262
- }), error;
1263
- }
1264
- if (!descriptor2) return response;
1265
- let value = await result(response, descriptor2 === "control" ? CONTROL_RESULT_BYTES : RESULT_BYTES);
1266
- if (descriptor2 === "control")
1267
- return checkCurrent(), value;
1268
- let parsed;
1269
- if (descriptor2 === "enrichment") {
1270
- if (!value || typeof value != "object" || Array.isArray(value)) throw unavailable();
1271
- let { name, ...metadata } = value;
1272
- if (!safeEnrichmentName(name)) throw unavailable();
1273
- parsed = { name, ...parseEnrichmentDescriptor(metadata) };
1274
- } else parsed = parseFileDescriptor(value);
1275
- return checkCurrent(), parsed;
1276
- })();
1638
+ safeInput = serializable(input);
1639
+ } catch {
1640
+ safeInput = null;
1641
+ }
1642
+ try {
1643
+ let response = await fetch(new URL("airtable", endpoint), {
1644
+ method: "POST",
1645
+ redirect: "error",
1646
+ headers: { authorization: `Bearer ${capability.token}`, "content-type": "application/json" },
1647
+ body: JSON.stringify({ name, operation: operation2, input: safeInput }),
1648
+ signal: AbortSignal.timeout(125e3)
1649
+ }), value = await response.json();
1650
+ if (!response.ok) throw new AirtableOperationError(
1651
+ "Airtable operation failed; the execution retains its evidence",
1652
+ value.effect === "partial" || value.effect === "uncertain" ? value.effect : "unapplied",
1653
+ value.receipts ?? []
1654
+ );
1655
+ return value;
1277
1656
  } catch (error) {
1278
- let failure = error instanceof FileOperationError ? error : unavailable();
1279
- if (init2.body instanceof ReadableStream || !failure.retryable || attempt + 1 === ATTEMPTS) throw failure;
1657
+ throw error instanceof AirtableOperationError ? error : new AirtableOperationError(
1658
+ "Airtable parent acknowledgment unavailable",
1659
+ ["records.create", "records.update", "records.delete"].includes(operation2) ? "uncertain" : "unapplied"
1660
+ );
1280
1661
  }
1281
- }
1282
- throw unavailable();
1662
+ });
1283
1663
  }
1284
- function idPath(id2) {
1285
- if (typeof id2 != "string" || !FILE_ID_PATTERN.test(id2)) throw invalid3();
1286
- return `/${id2}`;
1664
+
1665
+ var observer;
1666
+ async function reportLocalRoutineEffect(value) {
1667
+ await observer?.(value);
1287
1668
  }
1288
- async function multipartCreate(input, metadata, exchange, release = () => {
1289
- active--;
1290
- }, upload) {
1291
- let route = upload ? `${idPath(upload.file.id)}/upload/multipart` : "/multipart", ticket, reader, chunk, offset = 0, receipts = [], pending = /* @__PURE__ */ new Set(), request = (work, timeout = TIMEOUT_MS) => timed((signal) => {
1292
- let task = work(signal);
1293
- return pending.add(task), task.then(() => pending.delete(task), () => pending.delete(task)), task;
1294
- }, void 0, timeout), control = (body2) => ({
1295
- method: "POST",
1296
- headers: { "content-type": "application/json", ...ticket ? { "x-oddington-transfer": ticket } : {} },
1297
- body: JSON.stringify(body2)
1298
- });
1669
+
1670
+ var connections = /* @__PURE__ */ createConnections(async (name, operation2, input) => {
1299
1671
  try {
1300
- let start = await request((signal) => exchange(`${route}/start`, control(upload?.file ?? metadata), "control", signal));
1301
- if (!start || typeof start != "object" || Array.isArray(start)) throw unavailable();
1302
- let session = start;
1303
- if (Object.keys(session).sort().join(",") !== "partBytes,ticket" || session.partBytes !== PART_BYTES || typeof session.ticket != "string" || !session.ticket || session.ticket.length > 64 * 1024) throw unavailable();
1304
- ticket = session.ticket, input.file instanceof ReadableStream && (reader = input.file.getReader());
1305
- let lastProgress = Date.now(), next = async () => {
1306
- try {
1307
- if (Date.now() - lastProgress >= TIMEOUT_MS) throw unavailable();
1308
- let value = await request(() => reader.read(), Math.max(1, TIMEOUT_MS - (Date.now() - lastProgress)));
1309
- return value.value?.byteLength && (lastProgress = Date.now()), value;
1310
- } catch (error) {
1311
- throw await reader.cancel(error).catch(() => {
1312
- }), error;
1313
- }
1314
- };
1315
- for (let sent = 0, partNumber = 1; sent < metadata.size || metadata.size === 0 && partNumber === 1; partNumber++) {
1316
- let length = Math.min(PART_BYTES, metadata.size - sent), part;
1317
- if (input.file instanceof Uint8Array) part = input.file.subarray(sent, sent + length).slice();
1318
- else if (input.file instanceof Blob) {
1319
- let slice = input.file.slice(sent, sent + length);
1320
- part = new Uint8Array(await request(() => slice.arrayBuffer()));
1321
- } else {
1322
- part = new Uint8Array(length);
1323
- let filled = 0;
1324
- for (; filled < length; ) {
1325
- if (!chunk || offset === chunk.byteLength) {
1326
- chunk = void 0, offset = 0;
1327
- let value2 = await next();
1328
- if (value2.done || !(value2.value instanceof Uint8Array) || (chunk = value2.value, chunk.byteLength > metadata.size - sent - filled)) throw invalid3();
1329
- }
1330
- let take = Math.min(length - filled, chunk.byteLength - offset);
1331
- part.set(chunk.subarray(offset, offset + take), filled), filled += take, offset += take, offset === chunk.byteLength && (chunk = void 0, offset = 0);
1332
- }
1333
- }
1334
- if (part.byteLength !== length) throw invalid3();
1335
- if (reader && sent + length === metadata.size)
1336
- for (lastProgress = Date.now(); ; ) {
1337
- let value2 = await next();
1338
- if (value2.done) break;
1339
- if (!(value2.value instanceof Uint8Array) || value2.value.byteLength) throw invalid3();
1340
- }
1341
- let value = await request((signal) => exchange(`${route}/part`, {
1342
- method: "POST",
1343
- headers: { "content-type": "application/octet-stream", "x-oddington-transfer": ticket, "x-oddington-part": String(partNumber) },
1344
- body: part.buffer
1345
- }, "control", signal));
1346
- part = void 0;
1347
- let receipt = value;
1348
- 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)) throw unavailable();
1349
- receipts.push({ partNumber, etag: receipt.etag }), sent += length, lastProgress = Date.now();
1672
+ let managed = process.env[MANAGED_CONNECTION_CAPABILITY_ENV];
1673
+ if (managed) {
1674
+ let client = managedConnections(JSON.parse(managed)).airtable(name);
1675
+ return operation2 === "tables.list" ? await client.tables.list() : await client.records[operation2.slice(8)](input);
1350
1676
  }
1351
- if (upload) for (let name of Object.keys(upload.file.enrichments)) {
1352
- let bytes = await request(() => upload.enrichment(name));
1353
- if (bytes.byteLength !== upload.file.enrichments[name].size || bytes.byteLength > MAX_BYTES) throw invalid3();
1354
- await request((signal) => exchange(`${route}/enrichments/${encodeURIComponent(name)}`, {
1355
- method: "POST",
1356
- headers: { "content-type": "application/octet-stream", "x-oddington-transfer": ticket },
1357
- body: bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)
1358
- }, "control", signal));
1677
+ let raw = JSON.parse(process.env[ROUTINE_CONNECTION_CREDENTIALS_ENV] ?? "[]");
1678
+ if (!Array.isArray(raw)) throw new Error();
1679
+ let leases = raw.map((value) => parseConnectionLease(value));
1680
+ if (leases.some((lease2) => lease2.provider !== "airtable") || new Set(leases.map((lease2) => lease2.name)).size !== leases.length) throw new Error();
1681
+ let lease = leases.find((lease2) => lease2.name === name);
1682
+ if (!lease || lease.provider !== "airtable") throw new Error();
1683
+ let writing = ["records.create", "records.update", "records.delete"].includes(operation2), captured = serializable(input), result2;
1684
+ try {
1685
+ result2 = await executeAirtableOperation(lease, operation2, captured);
1686
+ } catch (error) {
1687
+ throw writing && error instanceof AirtableOperationError && await reportLocalRoutineEffect({
1688
+ type: "effect",
1689
+ connection: `airtable/${name}`,
1690
+ operation: operation2,
1691
+ input: captured,
1692
+ effect: error.effect,
1693
+ receipts: error.receipts
1694
+ }), error;
1359
1695
  }
1360
- await upload?.beforeComplete();
1361
- let completion = control({ parts: receipts });
1362
- if (new TextEncoder().encode(completion.body).byteLength > 128 * 1024) throw unavailable();
1363
- return parseFileDescriptor(await request(
1364
- (signal) => exchange(`${route}/complete`, completion, !0, signal),
1365
- TIMEOUT_MS + Math.ceil(metadata.size / (1024 * 1024)) * 1e3
1366
- ));
1696
+ return writing && await reportLocalRoutineEffect({
1697
+ type: "effect",
1698
+ connection: `airtable/${name}`,
1699
+ operation: operation2,
1700
+ input: captured,
1701
+ effect: "confirmed",
1702
+ receipts: [result2.receipt]
1703
+ }), result2;
1367
1704
  } catch (error) {
1368
- throw reader && await reader.cancel(error).catch(() => {
1369
- }), ticket && await request((signal) => exchange(`${route}/abort`, control({}), "control", signal)).catch(() => {
1370
- }), error instanceof FileOperationError ? error : unavailable();
1371
- } finally {
1372
- chunk = void 0, reader?.releaseLock(), pending.size ? Promise.allSettled([...pending]).then(release) : release();
1705
+ throw error instanceof AirtableOperationError ? error : new AirtableOperationError("Airtable connection unavailable");
1706
+ }
1707
+ });
1708
+
1709
+ var LOCAL_ROUTINE_VALUE_BYTES = 256 * 1024;
1710
+
1711
+ var RECORD_WRITE_INPUT_LIMIT = 8 * 1024 * 1024, RoutineRecordWriteError = class extends Error {
1712
+ constructor(message, effect) {
1713
+ super(message);
1714
+ this.effect = effect;
1715
+ this.name = "RoutineRecordWriteError";
1716
+ }
1717
+ effect;
1718
+ };
1719
+ function validateRecordWrite(value) {
1720
+ let input = serializable(value);
1721
+ if (!input || Array.isArray(input) || typeof input != "object" || Object.keys(input).sort().join(",") !== "records,repository" || !validRepositoryAlias(input.repository) || !Array.isArray(input.records) || !input.records.length || input.records.length > 1e4 || new Set(input.records.map((edit) => edit?.ref)).size !== input.records.length)
1722
+ throw new InboxContractError("Invalid record update batch");
1723
+ for (let edit of input.records) {
1724
+ if (!edit || Array.isArray(edit) || typeof edit != "object" || !validRecordRef(edit.ref))
1725
+ throw new InboxContractError("Invalid record edit");
1726
+ if ("expectedVersion" in edit) {
1727
+ if (Object.keys(edit).sort().join(",") !== "expectedVersion,ref,set" || typeof edit.expectedVersion != "string" || !/^record-v1:[0-9a-f]{64}$/.test(edit.expectedVersion) || !edit.set || Array.isArray(edit.set) || typeof edit.set != "object" || !Object.keys(edit.set).length)
1728
+ throw new InboxContractError("Invalid record edit");
1729
+ continue;
1730
+ }
1731
+ if (Object.keys(edit).sort().join(",") !== "fields,path,ref" || typeof edit.path != "string" || !validCreatePath(edit.path) || !edit.fields || Array.isArray(edit.fields) || typeof edit.fields != "object" || !Object.keys(edit.fields).length)
1732
+ throw new InboxContractError("Invalid record creation");
1373
1733
  }
1734
+ return structuredClone(input);
1374
1735
  }
1375
- async function operation(work, initial, parent) {
1376
- let context2 = initial ?? await resolveFilesContext();
1377
- if (active >= MAX_ACTIVE) throw new FileOperationError("capacity", 429, !0);
1378
- if (active++, context2.localFiles)
1379
- try {
1380
- return await work(context2, parent ?? new AbortController().signal);
1381
- } finally {
1382
- active--;
1383
- }
1384
- return timed(async (signal) => {
1385
- try {
1386
- return await work(context2, signal);
1387
- } finally {
1388
- active--;
1389
- }
1390
- }, parent).catch((error) => {
1391
- throw error instanceof FileOperationError ? error : unavailable();
1392
- });
1736
+ function validateRecordUpdate(value) {
1737
+ let input = validateRecordWrite(value);
1738
+ if (input.records.some((record3) => !("expectedVersion" in record3)))
1739
+ throw new InboxContractError("Record creation is unavailable for this operation");
1740
+ return input;
1393
1741
  }
1394
- function contentPath(id2, name) {
1395
- let path = idPath(id2);
1396
- if (name === void 0) return `${path}/content`;
1397
- if (!safeEnrichmentName(name)) throw invalid3();
1398
- return `${path}/enrichments/${encodeURIComponent(name)}/content`;
1742
+ function validateRecordDelete(value) {
1743
+ let input = serializable(value);
1744
+ if (!input || Array.isArray(input) || typeof input != "object" || Object.keys(input).sort().join(",") !== "records,repository" || !validRepositoryAlias(input.repository) || !Array.isArray(input.records) || !input.records.length || input.records.length > 1e4 || new Set(input.records.map((record3) => record3?.ref)).size !== input.records.length)
1745
+ throw new InboxContractError("Invalid record deletion batch");
1746
+ for (let record3 of input.records)
1747
+ if (!record3 || Array.isArray(record3) || typeof record3 != "object" || Object.keys(record3).sort().join(",") !== "expectedVersion,ref" || !validRecordRef(record3.ref) || typeof record3.expectedVersion != "string" || !/^record-v1:[0-9a-f]{64}$/.test(record3.expectedVersion))
1748
+ throw new InboxContractError("Invalid record deletion");
1749
+ return structuredClone(input);
1399
1750
  }
1400
- async function read(id2, names) {
1401
- let context2 = await resolveFilesContext();
1402
- if (idPath(id2), names === void 0 || typeof names == "string") {
1403
- let path = contentPath(id2, names);
1404
- return operation((context3, signal) => send(path, { method: "GET" }, !1, context3, signal), context2);
1405
- }
1406
- if (!Array.isArray(names)) throw invalid3();
1407
- let distinct = [], seen = /* @__PURE__ */ new Set();
1408
- for (let name of names) {
1409
- if (typeof name != "string" || !safeEnrichmentName(name)) throw invalid3();
1410
- if (!seen.has(name)) {
1411
- if (seen.size === 16) throw invalid3();
1412
- seen.add(name), distinct.push(name);
1413
- }
1751
+ function validateRecordChange(value) {
1752
+ let input = serializable(value);
1753
+ if (!input || Array.isArray(input) || typeof input != "object" || Object.keys(input).sort().join(",") !== "records,repository" || !validRepositoryAlias(input.repository) || !Array.isArray(input.records) || !input.records.length || input.records.length > 1e4 || new Set(input.records.map((record3) => record3?.ref)).size !== input.records.length || new TextEncoder().encode(JSON.stringify(input)).byteLength > RECORD_WRITE_INPUT_LIMIT)
1754
+ throw new InboxContractError("Invalid record change batch");
1755
+ let writes = [], deletes = [];
1756
+ for (let record3 of input.records) {
1757
+ if (!record3 || typeof record3 != "object" || Array.isArray(record3))
1758
+ throw new InboxContractError("Invalid record change");
1759
+ "set" in record3 || "fields" in record3 ? writes.push(record3) : deletes.push(record3);
1414
1760
  }
1415
- let paths = distinct.map((name) => contentPath(id2, name));
1416
- return timed(async (parent) => {
1417
- let responses = {};
1418
- try {
1419
- for (let i = 0; i < paths.length; i++) {
1420
- let response = await operation((context3, signal) => send(paths[i], { method: "GET" }, !1, context3, signal), context2, parent);
1421
- Object.defineProperty(responses, distinct[i], { value: response, enumerable: !0 });
1422
- }
1423
- if (!context2.isCurrent()) throw new FileOperationError("unsupported_context", 400);
1424
- return responses;
1425
- } catch (error) {
1426
- for (let response of Object.values(responses)) response.body?.cancel().catch(() => {
1427
- });
1428
- throw error;
1429
- }
1430
- });
1761
+ return writes.length && validateRecordWrite({ repository: input.repository, records: writes }), deletes.length && validateRecordDelete({ repository: input.repository, records: deletes }), structuredClone(input);
1762
+ }
1763
+ function validateRoutineRecordWrite(value) {
1764
+ let body2 = serializable(value);
1765
+ if (!body2 || typeof body2 != "object" || Array.isArray(body2) || Object.keys(body2).sort().join(",") !== "input,requestKey" || typeof body2.requestKey != "string" || !body2.requestKey.trim() || body2.requestKey !== body2.requestKey.trim() || body2.requestKey.length > 512)
1766
+ throw new InboxContractError("A stable routine request key is required");
1767
+ return { input: validateRecordChange(body2.input), requestKey: body2.requestKey };
1768
+ }
1769
+ function validCreatePath(value) {
1770
+ return value === value.trim() && value.endsWith(".md") && !value.startsWith("/") && value.split("/").every((segment) => !!segment && ![".", ".."].includes(segment) && /^[a-zA-Z0-9_.-]+$/.test(segment));
1771
+ }
1772
+
1773
+ var sources = /* @__PURE__ */ new WeakMap();
1774
+ function registerBrowserAuth(handle2, source) {
1775
+ sources.set(handle2, source);
1776
+ }
1777
+ function browserAuthSource(handle2) {
1778
+ return sources.get(handle2);
1431
1779
  }
1432
- var files = {
1433
- async upload(id2) {
1434
- return operation(async (context2, signal) => {
1435
- let file = await send(`${idPath(id2)}/upload`, { method: "POST" }, !0, context2, signal);
1436
- if (file.id !== id2) throw unavailable();
1437
- return file;
1438
- });
1439
- },
1440
- async create(input, options) {
1441
- let context2 = await resolveFilesContext();
1442
- if (!input || typeof input != "object" || Array.isArray(input) || Object.keys(input).some((key) => !["file", "size", "path", "name", "storage", "csv"].includes(key)) || options !== void 0 && (!options || typeof options != "object" || Array.isArray(options) || Object.keys(options).some((key) => key !== "requestKey")))
1443
- throw invalid3();
1444
- let blob = input.file instanceof Blob, array2 = input.file instanceof Uint8Array, stream = input.file instanceof ReadableStream;
1445
- if (!blob && !array2 && !stream) throw invalid3();
1446
- let intrinsic = blob ? input.file.size : array2 ? input.file.byteLength : void 0;
1447
- if (intrinsic !== void 0 && input.size !== void 0 && input.size !== intrinsic) throw invalid3();
1448
- let name = input.name;
1449
- name === void 0 && blob && "name" in input.file && typeof input.file.name == "string" && (name = typeof globalThis.File < "u" && input.file instanceof globalThis.File ? input.file.name : input.file.name.split(/[\\/]/).at(-1));
1450
- let metadata = parseFileCreateMetadata({
1451
- name,
1452
- size: intrinsic ?? input.size,
1453
- path: input.path,
1454
- storage: input.storage,
1455
- csv: input.csv,
1456
- requestKey: options?.requestKey === void 0 ? crypto.randomUUID() : options.requestKey
1457
- }, FILE_ORIGINAL_MAX_BYTES);
1458
- if (!context2.local && metadata.storage === "local") throw new FileOperationError("unsupported_context", 400);
1459
- let header = encodeURIComponent(JSON.stringify(metadata));
1460
- if (new TextEncoder().encode(header).byteLength > METADATA_BYTES) throw invalid3();
1461
- if (active >= MAX_ACTIVE) throw new FileOperationError("capacity", 429, !0);
1462
- if (active++, context2.localFiles && metadata.size > MAX_BYTES) {
1463
- let controller = new AbortController(), file = input.file, offset = 0, body2 = file instanceof ReadableStream ? file : file instanceof Blob ? file.stream() : new ReadableStream({
1464
- pull(controller2) {
1465
- if (offset === file.byteLength) {
1466
- controller2.close();
1467
- return;
1468
- }
1469
- let end = Math.min(offset + 256 * 1024, file.byteLength);
1470
- controller2.enqueue(file.subarray(offset, end)), offset = end;
1471
- }
1472
- }, { highWaterMark: 0 });
1473
- try {
1474
- return await send("", { method: "POST", headers: {
1475
- "content-type": "application/octet-stream",
1476
- "x-oddington-file": header
1477
- }, body: body2 }, !0, context2, controller.signal);
1478
- } finally {
1479
- active--;
1480
- }
1481
- }
1482
- 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) => {
1483
- try {
1484
- let bytes = array2 ? input.file.slice() : await boundedBytes(
1485
- blob ? input.file.stream() : input.file,
1486
- metadata.size
1487
- );
1488
- if (bytes.byteLength !== metadata.size) throw invalid3();
1489
- if (signal.aborted) throw unavailable();
1490
- return await send("", {
1491
- method: "POST",
1492
- headers: { "content-type": "application/octet-stream", "x-oddington-file": header },
1493
- body: bytes.buffer
1494
- }, !0, context2, signal);
1495
- } finally {
1496
- active--;
1497
- }
1498
- }).catch((error) => {
1499
- throw error instanceof FileOperationError ? error : unavailable();
1500
- });
1501
- },
1502
- async enrich(input) {
1503
- let context2 = await resolveFilesContext();
1504
- if (!input || typeof input != "object" || Array.isArray(input) || Object.keys(input).some((key) => !["file", "name", "content", "mediaType", "replace"].includes(key)) || typeof input.content != "string" && !(input.content instanceof Uint8Array)) throw invalid3();
1505
- let path = `${idPath(input.file)}/enrichments`;
1506
- if (typeof input.content == "string" ? input.content.length > MAX_BYTES : input.content.byteLength > MAX_BYTES)
1507
- throw new FileOperationError("request_too_large", 413);
1508
- return operation(async (context3, signal) => {
1509
- let bytes;
1510
- if (typeof input.content == "string") {
1511
- let buffer = new Uint8Array(Math.min(MAX_BYTES, input.content.length * 3)), encoded = new TextEncoder().encodeInto(input.content, buffer);
1512
- if (encoded.read !== input.content.length) throw new FileOperationError("request_too_large", 413);
1513
- bytes = buffer.slice(0, encoded.written);
1514
- } else bytes = input.content.slice();
1515
- let metadata = parseFileEnrichMetadata({
1516
- name: input.name,
1517
- size: bytes.byteLength,
1518
- ...input.mediaType === void 0 ? {} : { mediaType: input.mediaType },
1519
- ...input.replace === void 0 ? {} : { replace: input.replace }
1520
- }, MAX_BYTES), header = encodeURIComponent(JSON.stringify(metadata));
1521
- if (new TextEncoder().encode(header).byteLength > METADATA_BYTES) throw invalid3();
1522
- let result2 = await send(path, { method: "POST", headers: {
1523
- "content-type": "application/octet-stream",
1524
- "x-oddington-enrichment": header
1525
- }, body: bytes.buffer }, "enrichment", context3, signal);
1526
- if (result2.name !== metadata.name) throw unavailable();
1527
- return result2;
1528
- }, context2);
1529
- },
1530
- async get(id2) {
1531
- return operation((context2, signal) => send(idPath(id2), { method: "GET" }, !0, context2, signal));
1532
- },
1533
- read
1534
- };
1535
1780
 
1536
1781
  var sessionKey = "_os.app.session", responseChecks = /* @__PURE__ */ new WeakMap(), AppResponseReadError = class extends Error {
1537
1782
  }, AppContextDenied = class extends Error {
@@ -1850,8 +2095,15 @@ function validBinding(value) {
1850
2095
  }
1851
2096
 
1852
2097
  var installedDispose;
1853
- function init({ auth }) {
2098
+ function init({ auth: input }) {
1854
2099
  installedDispose?.();
2100
+ let registered = browserAuthSource(input), auth2 = registered ?? input;
2101
+ if (!registered && input && "getState" in input || typeof auth2?.getSession != "function" || typeof auth2?.onAuthStateChange != "function") {
2102
+ installedDispose = installMemberContext(async () => {
2103
+ throw new Error("Hosted Data context unavailable.");
2104
+ });
2105
+ return;
2106
+ }
1855
2107
  let endpoint, workspace, localOrigin;
1856
2108
  try {
1857
2109
  let url = new URL(globalThis.location.origin);
@@ -1860,13 +2112,13 @@ function init({ auth }) {
1860
2112
  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);
1861
2113
  } catch {
1862
2114
  }
1863
- let active2 = !0, revision = 0, person, local = localOrigin ? appContext(localOrigin, () => active2) : void 0, { data: { subscription } } = auth.onAuthStateChange((event2, session) => {
2115
+ let active2 = !0, revision = 0, person, token, local = localOrigin ? appContext(localOrigin, () => active2) : void 0, { data: { subscription } } = auth2.onAuthStateChange((event2, session) => {
1864
2116
  let next = session?.user.id ?? null;
1865
- (event2 === "SIGNED_OUT" || person !== void 0 && next !== person) && (revision++, local?.invalidate()), person = next;
2117
+ (event2 === "SIGNED_OUT" || person !== void 0 && next !== person || registered && person !== void 0 && event2 === "SIGNED_IN" && token !== session?.access_token) && (revision++, local?.invalidate()), person = next, token = session?.access_token;
1866
2118
  }), uninstall = installMemberContext(async () => {
1867
2119
  if (local) return local.resolve();
1868
2120
  if (!active2 || !endpoint || !workspace || globalThis.location.origin !== endpoint) throw new Error("Hosted Data context unavailable.");
1869
- let started = revision, { data: { session }, error } = await auth.getSession();
2121
+ let started = revision, { data: { session }, error } = await auth2.getSession();
1870
2122
  if (!active2 || globalThis.location.origin !== endpoint || started !== revision || error || !session || person !== void 0 && person !== session.user.id)
1871
2123
  throw new Error("Hosted Data context unavailable.");
1872
2124
  let expiresAt = (session.expires_at ?? 0) * 1e3;
@@ -1886,6 +2138,403 @@ function init({ auth }) {
1886
2138
  }
1887
2139
  var os = { init };
1888
2140
 
2141
+ var CLI_CALLBACK = "http://127.0.0.1:53682/oauth/callback";
2142
+ function authorizationConfiguration(value) {
2143
+ let setup = value;
2144
+ if (!setup || typeof setup.clientId != "string" || !/^[A-Za-z0-9_-]{1,128}$/.test(setup.clientId) || setup.redirectUri !== CLI_CALLBACK || !Array.isArray(setup.scopes) || setup.scopes.length !== 1 || setup.scopes[0] !== "email") throw new Error("Unavailable");
2145
+ return { clientId: setup.clientId, redirectUri: CLI_CALLBACK, scopes: ["email"] };
2146
+ }
2147
+ function authorizationDetails(value, id2, setup, userId, account) {
2148
+ let details = value;
2149
+ return !details || details.authorization_id !== id2 || details.redirect_uri !== setup.redirectUri || details.client?.id !== setup.clientId || typeof details.client.name != "string" || !details.client.name.trim() || details.client.name.length > 128 || /[\x00-\x1f\x7f]/.test(details.client.name) || details.user?.id !== userId || details.user.email !== account.email || typeof details.scope != "string" || details.scope.trim() !== "email" ? Object.freeze({ status: "invalid" }) : Object.freeze({
2150
+ status: "consent",
2151
+ clientName: details.client.name.trim(),
2152
+ account,
2153
+ scopes: Object.freeze([...setup.scopes]),
2154
+ callbackAddress: new URL(setup.redirectUri).host
2155
+ });
2156
+ }
2157
+ function authorizationReturn(value, decision) {
2158
+ let returnUrl = value?.redirect_url;
2159
+ if (typeof returnUrl != "string" || returnUrl.length > 8192 || !returnUrl.startsWith(`${CLI_CALLBACK}?`) || /[\x00-\x20\x7f]/.test(returnUrl)) return Object.freeze({ status: "invalid" });
2160
+ try {
2161
+ let url = new URL(returnUrl);
2162
+ if (url.origin + url.pathname !== CLI_CALLBACK || url.username || url.password || url.hash) return Object.freeze({ status: "invalid" });
2163
+ let parameters = url.searchParams, allowed = ["code", "state", "error", "error_description", "error_uri"];
2164
+ for (let key of parameters.keys())
2165
+ if (!allowed.includes(key) || parameters.getAll(key).length !== 1) return Object.freeze({ status: "invalid" });
2166
+ let code = parameters.get("code"), error = parameters.get("error");
2167
+ return !parameters.get("state") || !!code == !!error || decision === "deny" && !error || decision === "approve" && !code || parameters.has("code") && !code || parameters.has("error") && !error || code && (parameters.has("error_description") || parameters.has("error_uri")) ? Object.freeze({ status: "invalid" }) : Object.freeze({ status: "return", returnUrl });
2168
+ } catch {
2169
+ return Object.freeze({ status: "invalid" });
2170
+ }
2171
+ }
2172
+ function authorizationFailure(error) {
2173
+ let status = error?.status;
2174
+ return Object.freeze({ status: status === 404 || status === 410 || status === 400 ? "expired" : status === 401 ? "signed_out" : status === 403 || status === 422 ? "invalid" : "unavailable" });
2175
+ }
2176
+
2177
+ function createBrowserAuth(runtime) {
2178
+ let coordinators = /* @__PURE__ */ new Map();
2179
+ function publish(c, state) {
2180
+ c.state = Object.freeze(state);
2181
+ for (let listener of c.listeners)
2182
+ try {
2183
+ listener(c.state);
2184
+ } catch {
2185
+ }
2186
+ }
2187
+ function notify(c, event2) {
2188
+ for (let listener of c.sessions)
2189
+ try {
2190
+ listener(event2, event2 === "SIGNED_OUT" ? null : c.session);
2191
+ } catch {
2192
+ }
2193
+ }
2194
+ function expire(c) {
2195
+ !c.active || !c.session && c.state.status === "expired" || (c.revision++, c.timer && clearTimeout(c.timer), c.session = null, publish(c, { status: "expired" }), notify(c, "SIGNED_OUT"));
2196
+ }
2197
+ function accept(c, event2, session) {
2198
+ if (!c.active || event2 === "INITIAL_SESSION" && c.state.status !== "initializing" || event2 === "TOKEN_REFRESHED" && !c.session && c.state.status !== "initializing") return;
2199
+ (c.session?.user?.id !== session?.user?.id || event2 === "SIGNED_OUT" || event2 === "SIGNED_IN" && c.session && session?.access_token !== c.session.access_token) && c.revision++, c.timer && clearTimeout(c.timer);
2200
+ let pending = c.state.pending;
2201
+ if (!session) {
2202
+ c.session = null, publish(c, { status: c.state.status === "expired" ? "expired" : "signed_out", ...pending ? { pending } : {} }), notify(c, "SIGNED_OUT");
2203
+ return;
2204
+ }
2205
+ if (!validSession(session)) {
2206
+ c.session = session, expire(c);
2207
+ return;
2208
+ }
2209
+ c.session = session;
2210
+ let email = typeof session.user.email == "string" ? session.user.email.trim() : "", metadata = session.user.user_metadata, displayName = ["name", "full_name", "first_name"].map((key) => metadata?.[key]).find((value) => typeof value == "string" && value.trim());
2211
+ publish(c, { status: "signed_in", account: Object.freeze({ email, displayName: displayName?.trim() || email }), ...pending ? { pending } : {} }), notify(c, event2);
2212
+ let checkExpiry = () => {
2213
+ if (!c.active || !c.session) return;
2214
+ let remaining = c.session.expires_at * 1e3 - Date.now();
2215
+ remaining <= 0 ? expire(c) : c.timer = setTimeout(checkExpiry, Math.min(remaining, 2147483647));
2216
+ };
2217
+ checkExpiry();
2218
+ }
2219
+ function validSession(session) {
2220
+ return typeof session.access_token == "string" && /^[^\s]{1,8192}$/.test(session.access_token) && typeof session.user?.id == "string" && !!session.user.id && Number.isFinite(session.expires_at) && session.expires_at * 1e3 > Date.now();
2221
+ }
2222
+ function createCoordinator(origin) {
2223
+ let c = {
2224
+ state: Object.freeze({ status: "initializing" }),
2225
+ session: null,
2226
+ revision: 0,
2227
+ active: !0,
2228
+ refs: 0,
2229
+ listeners: /* @__PURE__ */ new Set(),
2230
+ sessions: /* @__PURE__ */ new Set(),
2231
+ ready: Promise.resolve()
2232
+ };
2233
+ return c.ready = (async () => {
2234
+ let revision = c.revision;
2235
+ try {
2236
+ if (!allowedOrigin(origin)) throw runtime.failure("unavailable");
2237
+ let response = await fetch(`${origin}/api/auth/browser-configuration`, { method: "GET", credentials: "same-origin", redirect: "error", cache: "no-store" });
2238
+ if (!response.ok) throw runtime.failure("unavailable");
2239
+ let configuration = validateConfiguration(await response.json());
2240
+ if (configuration.authorization && origin !== "https://auth.oddington.ai" && origin !== "http://127.0.0.1:5176") throw runtime.failure("unavailable");
2241
+ if (c.authorization = configuration.authorization, !c.active || globalThis.location?.origin !== origin) return;
2242
+ if (c.provider = await runtime.provider(configuration, {
2243
+ revision: () => c.revision,
2244
+ current: (revision2) => current2(c, origin, revision2),
2245
+ canPersistSession: () => c.active && (c.state.status === "initializing" || !!c.session || c.state.pending === "verify_code"),
2246
+ canAuthorize: (accessToken) => current2(c, origin, c.revision) && !!c.session && c.session.access_token === accessToken && c.state.pending !== "sign_out"
2247
+ }), !c.active || globalThis.location?.origin !== origin) {
2248
+ c.provider.dispose();
2249
+ return;
2250
+ }
2251
+ let subscription = c.provider.onAuthStateChange((event2, session) => {
2252
+ c.state.status === "initializing" && c.state.pending === "sign_out" || accept(c, event2, session);
2253
+ }).data.subscription;
2254
+ c.unsubscribe = () => subscription.unsubscribe();
2255
+ let started = c.revision, { data: data3, error } = await c.provider.getSession();
2256
+ if (!current2(c, origin, started)) return;
2257
+ error ? terminalSessionFailure(error) ? expire(c) : (!c.session || !validSession(c.session)) && publish(c, { status: "unavailable", error: "unavailable" }) : c.state.pending !== "sign_out" && accept(c, "INITIAL_SESSION", data3.session);
2258
+ } catch {
2259
+ current2(c, origin, revision) && publish(c, { status: "unavailable", error: "unavailable" });
2260
+ }
2261
+ })(), c;
2262
+ }
2263
+ function current2(c, origin, revision) {
2264
+ return c.session && !validSession(c.session) && expire(c), c.active && c.revision === revision && globalThis.location?.origin === origin;
2265
+ }
2266
+ return { create() {
2267
+ let origin = globalThis.location?.origin ?? "", c = coordinators.get(origin);
2268
+ c || (c = createCoordinator(origin), coordinators.set(origin, c));
2269
+ let coordinator = c;
2270
+ coordinator.refs++;
2271
+ let active2 = !0, ownedAction, authorizationId, authorizationGeneration = 0, inspected, observers = /* @__PURE__ */ new Set(), sessionObservers = /* @__PURE__ */ new Set(), lastState = coordinator.state, observation = (state) => {
2272
+ lastState = state;
2273
+ for (let listener of observers)
2274
+ try {
2275
+ listener(state);
2276
+ } catch {
2277
+ }
2278
+ };
2279
+ coordinator.listeners.add(observation);
2280
+ let assertActive = () => {
2281
+ if (!active2) throw runtime.failure("disposed");
2282
+ };
2283
+ async function action(kind, work) {
2284
+ if (assertActive(), coordinator.action && kind !== "sign_out" || kind === "sign_out" && coordinator.state.pending === "sign_out") throw runtime.failure("busy");
2285
+ kind === "sign_out" && (coordinator.revision++, notify(coordinator, "SIGNED_OUT"));
2286
+ let marker = {};
2287
+ coordinator.action = marker, ownedAction = marker;
2288
+ let revision = coordinator.revision;
2289
+ publish(coordinator, { ...coordinator.state, error: void 0, pending: kind });
2290
+ try {
2291
+ if (await coordinator.ready, assertActive(), !coordinator.provider || !coordinator.active || globalThis.location?.origin !== origin) throw runtime.failure("unavailable");
2292
+ if (!current2(coordinator, origin, revision) || coordinator.action !== marker || (await work(coordinator.provider), assertActive(), coordinator.action !== marker) || !current2(coordinator, origin, revision) && kind !== "verify_code" && kind !== "sign_out") throw runtime.failure("stale");
2293
+ } catch (error) {
2294
+ let reason = active2 ? current2(coordinator, origin, revision) ? safeReason(error) ?? "unavailable" : "stale" : "disposed";
2295
+ throw active2 && current2(coordinator, origin, revision) && publish(coordinator, { ...coordinator.state, error: reason }), runtime.failure(reason);
2296
+ } finally {
2297
+ coordinator.action === marker && (coordinator.action = void 0, coordinator.active && (publish(coordinator, { ...coordinator.state, pending: void 0 }), kind === "sign_out" && coordinator.session && notify(coordinator, "TOKEN_REFRESHED"))), ownedAction === marker && (ownedAction = void 0);
2298
+ }
2299
+ }
2300
+ async function authorize(kind, id2) {
2301
+ if (!active2) return Object.freeze({ status: "disposed" });
2302
+ if (id2 !== authorizationId && (authorizationId = id2, authorizationGeneration++, inspected = void 0), !id2) return Object.freeze({ status: "missing" });
2303
+ if (typeof id2 != "string" || !/^[A-Za-z0-9_-]{1,128}$/.test(id2)) return Object.freeze({ status: "invalid" });
2304
+ let generation = authorizationGeneration;
2305
+ if (await coordinator.ready, !active2) return Object.freeze({ status: "disposed" });
2306
+ if (generation !== authorizationGeneration) return Object.freeze({ status: "stale" });
2307
+ let revision = coordinator.revision;
2308
+ if (!current2(coordinator, origin, revision)) return Object.freeze({ status: "stale" });
2309
+ if (!coordinator.session || coordinator.state.pending === "sign_out") return Object.freeze({ status: coordinator.state.status === "expired" ? "expired" : coordinator.state.status === "unavailable" ? "unavailable" : "signed_out" });
2310
+ if (!coordinator.authorization || !coordinator.provider) return Object.freeze({ status: "unavailable" });
2311
+ if (coordinator.action) return Object.freeze({ status: "busy" });
2312
+ if (kind !== "get" && (!inspected || inspected.id !== id2 || inspected.revision !== revision)) return Object.freeze({ status: "stale" });
2313
+ if (kind !== "get" && inspected?.result.status !== "consent") return inspected.result;
2314
+ let marker = {};
2315
+ coordinator.action = marker, ownedAction = marker;
2316
+ let requestCurrent = () => active2 && generation === authorizationGeneration && current2(coordinator, origin, revision) && coordinator.action === marker && !!coordinator.session && coordinator.state.pending !== "sign_out", stale = () => Object.freeze({ status: active2 ? "stale" : "disposed" });
2317
+ try {
2318
+ let provider2 = coordinator.provider, response = await provider2.authorization.get(id2, requestCurrent);
2319
+ if (!requestCurrent()) return stale();
2320
+ if (response.error) {
2321
+ inspected = void 0;
2322
+ let failure = authorizationFailure(response.error);
2323
+ return failure.status === "signed_out" && expire(coordinator), failure;
2324
+ }
2325
+ let account = coordinator.state.account, result2 = response.data && typeof response.data == "object" && "redirect_url" in response.data ? authorizationReturn(response.data) : authorizationDetails(response.data, id2, coordinator.authorization, coordinator.session.user.id, account);
2326
+ if (inspected = { id: id2, revision, result: result2 }, kind === "get" || result2.status !== "consent") return result2;
2327
+ if (!requestCurrent()) return stale();
2328
+ let decision = await provider2.authorization[kind](id2, requestCurrent);
2329
+ if (!requestCurrent()) return stale();
2330
+ let returned = decision.error ? authorizationFailure(decision.error) : authorizationReturn(decision.data, kind);
2331
+ return returned.status === "signed_out" && expire(coordinator), inspected = { id: id2, revision, result: returned }, returned;
2332
+ } catch (error) {
2333
+ return !requestCurrent() || (inspected = void 0, safeReason(error) === "stale") ? stale() : Object.freeze({ status: "unavailable" });
2334
+ } finally {
2335
+ coordinator.action === marker && (coordinator.action = void 0), ownedAction === marker && (ownedAction = void 0);
2336
+ }
2337
+ }
2338
+ let handle2 = Object.freeze({
2339
+ authorization: Object.freeze({
2340
+ get: (id2) => authorize("get", id2),
2341
+ approve: (id2) => authorize("approve", id2),
2342
+ deny: (id2) => authorize("deny", id2)
2343
+ }),
2344
+ getState() {
2345
+ return active2 && coordinator.session && !validSession(coordinator.session) && expire(coordinator), lastState;
2346
+ },
2347
+ subscribe(listener) {
2348
+ return assertActive(), observers.add(listener), () => {
2349
+ observers.delete(listener);
2350
+ };
2351
+ },
2352
+ async sendCode(email) {
2353
+ if (assertActive(), !validEmail(email)) throw runtime.failure("invalid_input");
2354
+ await action("send_code", async (provider2) => {
2355
+ let { error } = await provider2.signInWithOtp({ email: email.trim(), options: { shouldCreateUser: !1 } });
2356
+ if (error && !accountRefusal(error)) throw runtime.failure("unavailable");
2357
+ });
2358
+ },
2359
+ async verifyCode(email, code) {
2360
+ if (assertActive(), !validEmail(email) || typeof code != "string" || !/^\d{6}$/.test(code)) throw runtime.failure("invalid_input");
2361
+ await action("verify_code", async (provider2) => {
2362
+ let revision = coordinator.revision, { data: data3, error } = await provider2.verifyOtp({ email: email.trim(), token: code, type: "email" });
2363
+ if (error) throw runtime.failure(accountRefusal(error) ? "invalid_code" : "unavailable");
2364
+ if (!data3.session || !validSession(data3.session)) throw runtime.failure("invalid_code");
2365
+ if (!current2(coordinator, origin, revision) && (coordinator.session?.access_token !== data3.session.access_token || coordinator.revision !== revision + 1 || coordinator.state.pending !== "verify_code")) throw runtime.failure("stale");
2366
+ accept(coordinator, "SIGNED_IN", data3.session);
2367
+ });
2368
+ },
2369
+ async signOut() {
2370
+ assertActive(), await action("sign_out", async (provider2) => {
2371
+ let revision = coordinator.revision, { error } = await provider2.signOut({ scope: "local" });
2372
+ if (error) throw runtime.failure("unavailable");
2373
+ if (!current2(coordinator, origin, revision) && coordinator.session) throw runtime.failure("stale");
2374
+ accept(coordinator, "SIGNED_OUT", null);
2375
+ });
2376
+ },
2377
+ dispose() {
2378
+ if (active2) {
2379
+ active2 = !1, authorizationGeneration++, inspected = void 0, ownedAction && coordinator.action === ownedAction && coordinator.revision++, observers.clear(), coordinator.listeners.delete(observation);
2380
+ for (let listener of sessionObservers)
2381
+ coordinator.sessions.delete(listener), listener("SIGNED_OUT", null);
2382
+ sessionObservers.clear(), --coordinator.refs === 0 && (coordinator.active = !1, coordinator.revision++, coordinator.timer && clearTimeout(coordinator.timer), coordinator.unsubscribe?.(), coordinator.provider?.dispose(), coordinator.provider = void 0, coordinator.session = null, coordinator.listeners.clear(), coordinator.sessions.clear(), coordinators.delete(origin));
2383
+ }
2384
+ }
2385
+ });
2386
+ return registerBrowserAuth(handle2, {
2387
+ async getSession() {
2388
+ assertActive(), await coordinator.ready, assertActive();
2389
+ let revision = coordinator.revision;
2390
+ if (!current2(coordinator, origin, revision) || !coordinator.provider || !coordinator.session || coordinator.state.pending === "sign_out") return { data: { session: null }, error: null };
2391
+ let result2;
2392
+ try {
2393
+ result2 = await coordinator.provider.getSession();
2394
+ } catch {
2395
+ result2 = { data: { session: null }, error: runtime.failure("unavailable") };
2396
+ }
2397
+ return !active2 || !current2(coordinator, origin, revision) ? { data: { session: null }, error: runtime.failure("stale") } : result2.error ? !terminalSessionFailure(result2.error) && coordinator.session && validSession(coordinator.session) ? { data: { session: coordinator.session }, error: null } : (expire(coordinator), { data: { session: null }, error: runtime.failure("unavailable") }) : (accept(coordinator, "TOKEN_REFRESHED", result2.data.session), { data: { session: coordinator.session }, error: null });
2398
+ },
2399
+ onAuthStateChange(callback) {
2400
+ if (!active2)
2401
+ return callback("SIGNED_OUT", null), { data: { subscription: { unsubscribe() {
2402
+ } } } };
2403
+ let listener = (event2, session) => callback(event2, session);
2404
+ return coordinator.sessions.add(listener), sessionObservers.add(listener), callback("INITIAL_SESSION", coordinator.session), { data: { subscription: { unsubscribe() {
2405
+ coordinator.sessions.delete(listener), sessionObservers.delete(listener);
2406
+ } } } };
2407
+ }
2408
+ }), handle2;
2409
+ } };
2410
+ }
2411
+ function safeReason(error) {
2412
+ let reason = error?.reason;
2413
+ return ["invalid_input", "invalid_code", "unavailable", "busy", "stale", "disposed"].includes(reason) ? reason : void 0;
2414
+ }
2415
+ function accountRefusal(error) {
2416
+ return [400, 401, 403, 404, 422].includes(error?.status ?? 0);
2417
+ }
2418
+ function terminalSessionFailure(error) {
2419
+ return [400, 401, 403, 404, 422].includes(error?.status ?? 0);
2420
+ }
2421
+ function validEmail(email) {
2422
+ return typeof email == "string" && email.length <= 320 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim());
2423
+ }
2424
+ function allowedOrigin(origin) {
2425
+ if (origin === "http://127.0.0.1:5176" || origin === "https://auth.oddington.ai") return !0;
2426
+ try {
2427
+ let url = new URL(origin), slug = url.hostname.endsWith(".oddington.ai") ? url.hostname.slice(0, -13) : "";
2428
+ return slug.length <= 63 && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug) && !["auth", "api", "os", "www"].includes(slug) && origin === `https://${slug}.oddington.ai`;
2429
+ } catch {
2430
+ return !1;
2431
+ }
2432
+ }
2433
+ function validateConfiguration(value) {
2434
+ let configuration = value;
2435
+ if (!configuration || typeof configuration.providerUrl != "string" || typeof configuration.publishableKey != "string") throw new Error("Unavailable");
2436
+ let url = new URL(configuration.providerUrl);
2437
+ if (url.origin !== configuration.providerUrl || url.protocol !== "https:" || url.username || url.password || url.port || !configuration.publishableKey) throw new Error("Unavailable");
2438
+ return {
2439
+ providerUrl: url.origin,
2440
+ publishableKey: configuration.publishableKey,
2441
+ ...configuration.authorization === void 0 ? {} : { authorization: authorizationConfiguration(configuration.authorization) }
2442
+ };
2443
+ }
2444
+
2445
+ var BrowserAuthError = class extends Error {
2446
+ constructor(reason) {
2447
+ super(`Browser authentication failed: ${reason}`);
2448
+ this.reason = reason;
2449
+ this.name = "BrowserAuthError";
2450
+ }
2451
+ reason;
2452
+ };
2453
+ async function provider(configuration, guard) {
2454
+ let { AuthClient } = await import("./auth-provider.js");
2455
+ if (!guard.current(guard.revision())) throw new BrowserAuthError("stale");
2456
+ let provenance = /* @__PURE__ */ new Map(), removalRevision, authorizationRequest, authorizationQueue = Promise.resolve(), storage = globalThis.localStorage, client = new AuthClient({
2457
+ url: `${configuration.providerUrl}/auth/v1`,
2458
+ headers: { apikey: configuration.publishableKey, Authorization: `Bearer ${configuration.publishableKey}` },
2459
+ storageKey: `sb-${new URL(configuration.providerUrl).hostname.split(".")[0]}-auth-token`,
2460
+ persistSession: !0,
2461
+ autoRefreshToken: !0,
2462
+ detectSessionInUrl: !1,
2463
+ storage: {
2464
+ getItem: (key) => storage.getItem(key),
2465
+ removeItem(key) {
2466
+ if (!guard.current(removalRevision ?? guard.revision())) throw new BrowserAuthError("stale");
2467
+ storage.removeItem(key);
2468
+ },
2469
+ setItem(key, value) {
2470
+ let token = JSON.parse(value).access_token, revision = token ? provenance.get(token) : void 0;
2471
+ if (!guard.current(revision ?? guard.revision()) || token && !guard.canPersistSession()) throw new BrowserAuthError("stale");
2472
+ storage.setItem(key, value);
2473
+ }
2474
+ },
2475
+ fetch: Object.assign(async (input, init2) => {
2476
+ let revision = guard.revision(), request = String(input).startsWith(`${configuration.providerUrl}/auth/v1/oauth/authorizations/`) ? authorizationRequest : void 0, rejectStale = () => {
2477
+ throw request && (request.rejected = !0), new BrowserAuthError("stale");
2478
+ };
2479
+ request && (!request.current() || !guard.canAuthorize(new Headers(init2?.headers).get("authorization")?.replace(/^Bearer /, "") ?? "")) && rejectStale();
2480
+ let response = await fetch(input, { ...init2, redirect: "error" }), body2 = await response.arrayBuffer();
2481
+ (!guard.current(revision) || request && !request.current()) && rejectStale();
2482
+ let result2 = new Response(
2483
+ [204, 205, 304].includes(response.status) ? null : body2,
2484
+ { status: response.status, statusText: response.statusText, headers: response.headers }
2485
+ ), json2 = result2.json.bind(result2);
2486
+ return result2.json = async () => {
2487
+ let value = await json2();
2488
+ return (!guard.current(revision) || request && !request.current()) && rejectStale(), value && typeof value.access_token == "string" && provenance.set(value.access_token, revision), value;
2489
+ }, result2;
2490
+ }, { preconnect: fetch.preconnect })
2491
+ });
2492
+ async function authorization(kind, id2, current2) {
2493
+ let previous = authorizationQueue, release;
2494
+ authorizationQueue = new Promise((resolve) => {
2495
+ release = resolve;
2496
+ }), await previous;
2497
+ let request = { current: current2, rejected: !1 };
2498
+ authorizationRequest = request;
2499
+ try {
2500
+ if (!current2()) throw new BrowserAuthError("stale");
2501
+ let result2 = await (kind === "get" ? client.oauth.getAuthorizationDetails(id2) : kind === "approve" ? client.oauth.approveAuthorization(id2, { skipBrowserRedirect: !0 }) : client.oauth.denyAuthorization(id2, { skipBrowserRedirect: !0 }));
2502
+ if (request.rejected || !current2()) throw new BrowserAuthError("stale");
2503
+ return result2;
2504
+ } catch (error) {
2505
+ throw request.rejected || !current2() ? new BrowserAuthError("stale") : error;
2506
+ } finally {
2507
+ authorizationRequest = void 0, release();
2508
+ }
2509
+ }
2510
+ return {
2511
+ authorization: {
2512
+ get: (id2, current2) => authorization("get", id2, current2),
2513
+ approve: (id2, current2) => authorization("approve", id2, current2),
2514
+ deny: (id2, current2) => authorization("deny", id2, current2)
2515
+ },
2516
+ getSession: () => client.getSession(),
2517
+ onAuthStateChange: (callback) => client.onAuthStateChange((event2, session) => {
2518
+ let revision = session ? provenance.get(session.access_token) : void 0;
2519
+ revision !== void 0 && !guard.current(revision) || (callback(event2, session), session && provenance.delete(session.access_token));
2520
+ }),
2521
+ signInWithOtp: (input) => client.signInWithOtp(input),
2522
+ verifyOtp: (input) => client.verifyOtp(input),
2523
+ async signOut(input) {
2524
+ removalRevision = guard.revision();
2525
+ try {
2526
+ return await client.signOut(input);
2527
+ } finally {
2528
+ removalRevision = void 0;
2529
+ }
2530
+ },
2531
+ dispose() {
2532
+ provenance.clear(), client.dispose();
2533
+ }
2534
+ };
2535
+ }
2536
+ var auth = /* @__PURE__ */ createBrowserAuth({ provider, failure: (reason) => new BrowserAuthError(reason) });
2537
+
1889
2538
  var identifier = /^[a-z0-9]+(?:-[a-z0-9]+)*(?![\s\S])/, uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i, isReason = (value) => value === "authority_unavailable" || value === "execution_unavailable" || value === "invalid_input", AppRoutineError = class extends Error {
1890
2539
  constructor(reason) {
1891
2540
  super(reason);
@@ -2069,7 +2718,7 @@ var app = Object.freeze({ routines: new Proxy(/* @__PURE__ */ Object.create(null
2069
2718
  deleteProperty: () => !1
2070
2719
  }) });
2071
2720
 
2072
- var LIMIT = 16 * 1024, context;
2721
+ var LIMIT2 = 16 * 1024, context;
2073
2722
  var appRun = {
2074
2723
  current() {
2075
2724
  return context;
@@ -2090,7 +2739,7 @@ function validateCsvHints(value) {
2090
2739
  return v;
2091
2740
  }
2092
2741
 
2093
- function routinePublicationClient(options = {}) {
2742
+ function routinePublicationClient2(options = {}) {
2094
2743
  let environment = {}, local = void 0, workspace = options.workspace ?? environment.ODDINGTON_ROUTINE_WORKSPACE, capability = options.capability ?? environment.ODDINGTON_ROUTINE_RUN;
2095
2744
  if (!workspace || !/^[a-z0-9][a-z0-9-]*$/.test(workspace) || capability !== void 0 && !/^[0-9a-f]{64}$/.test(capability))
2096
2745
  throw new Error("A workspace and valid optional routine capability are required");
@@ -2158,6 +2807,7 @@ function routinePublicationClient(options = {}) {
2158
2807
  export {
2159
2808
  AirtableOperationError,
2160
2809
  AppRoutineError,
2810
+ BrowserAuthError,
2161
2811
  DataOperationError,
2162
2812
  FileOperationError,
2163
2813
  InboxContractError,
@@ -2165,10 +2815,13 @@ export {
2165
2815
  app,
2166
2816
  appRun,
2167
2817
  asksBlock,
2818
+ auth,
2168
2819
  connections,
2169
2820
  continueAsk,
2170
2821
  data,
2171
2822
  defineHandler,
2823
+ defineRoutine,
2824
+ file,
2172
2825
  fileBlock,
2173
2826
  files,
2174
2827
  os,
@@ -2178,6 +2831,7 @@ export {
2178
2831
  resolveAsk,
2179
2832
  respond,
2180
2833
  richTextBlock,
2181
- routinePublicationClient,
2834
+ routinePublicationClient2 as routinePublicationClient,
2835
+ schema,
2182
2836
  selection
2183
2837
  };