@oddington/sdk 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -21709,44 +21709,91 @@ class DataOperationError extends Error {
21709
21709
  }
21710
21710
  }
21711
21711
 
21712
- // ../../shared/data-search-input.ts
21712
+ // ../../shared/data-history-input.ts
21713
21713
  var invalid = () => {
21714
21714
  throw new DataOperationError("invalid_query");
21715
21715
  };
21716
- var scalarText = (value) => typeof value === "string" && !/[\u0000\uD800-\uDFFF]/u.test(value);
21717
- function captureRecordsSearchInput(query, options) {
21718
- if (!scalarText(query))
21716
+ var historyText = (value) => typeof value === "string" && !/[\u0000\uD800-\uDFFF]/u.test(value);
21717
+ function historyVersion(value) {
21718
+ 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);
21719
+ }
21720
+ function historyProperties(value) {
21721
+ if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value)))
21722
+ return invalid();
21723
+ const descriptors = Object.getOwnPropertyDescriptors(value);
21724
+ if (Reflect.ownKeys(value).some((key) => typeof key !== "string" || !descriptors[key]?.enumerable || !Object.hasOwn(descriptors[key], "value")))
21725
+ return invalid();
21726
+ return descriptors;
21727
+ }
21728
+ function captureHistoryListOptions(value) {
21729
+ if (value === undefined)
21730
+ return {};
21731
+ const descriptors = historyProperties(value), result = {};
21732
+ if (Object.keys(descriptors).some((key) => !["limit", "before"].includes(key)))
21733
+ return invalid();
21734
+ if (Object.hasOwn(descriptors, "limit")) {
21735
+ const limit = descriptors.limit.value;
21736
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100)
21737
+ return invalid();
21738
+ result.limit = limit;
21739
+ }
21740
+ if (Object.hasOwn(descriptors, "before")) {
21741
+ const before = descriptors.before.value;
21742
+ if (!historyVersion(before))
21743
+ return invalid();
21744
+ result.before = before;
21745
+ }
21746
+ return result;
21747
+ }
21748
+ function captureHistoryInstant(value) {
21749
+ if (!historyText(value) || value.length > 1024)
21750
+ return invalid();
21751
+ const match = /^(\d{4})-(\d{2})-(\d{2})[Tt](\d{2}):(\d{2}):(\d{2})(?:\.\d+)?([Zz]|([+-])(\d{2}):(\d{2}))$/.exec(value);
21752
+ if (!match)
21719
21753
  return invalid();
21754
+ const [year, month, day, hour, minute, second] = match.slice(1, 7).map(Number);
21755
+ const days = new Date(Date.UTC(year, month, 0));
21756
+ days.setUTCFullYear(year);
21757
+ if (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)
21758
+ return invalid();
21759
+ return value;
21760
+ }
21761
+
21762
+ // ../../shared/data-search-input.ts
21763
+ var invalid2 = () => {
21764
+ throw new DataOperationError("invalid_query");
21765
+ };
21766
+ function captureRecordsSearchInput(query, options) {
21767
+ if (!historyText(query))
21768
+ return invalid2();
21720
21769
  const length = [...query.trim()].length;
21721
21770
  if (length < 1 || length > 256)
21722
- return invalid();
21771
+ return invalid2();
21723
21772
  if (options === undefined)
21724
21773
  return { query };
21725
- if (!options || typeof options !== "object" || Array.isArray(options) || ![Object.prototype, null].includes(Object.getPrototypeOf(options)))
21726
- return invalid();
21727
- const descriptors = Object.getOwnPropertyDescriptors(options);
21728
- if (Reflect.ownKeys(options).some((key) => typeof key !== "string" || !["collections", "limit"].includes(key) || !descriptors[key]?.enumerable || !Object.hasOwn(descriptors[key], "value")))
21729
- return invalid();
21774
+ const descriptors = historyProperties(options);
21775
+ if (Object.keys(descriptors).some((key) => !["collections", "limit"].includes(key)))
21776
+ return invalid2();
21730
21777
  const captured = {};
21731
21778
  if (Object.hasOwn(descriptors, "collections")) {
21732
21779
  const supplied = descriptors.collections.value;
21733
21780
  if (!Array.isArray(supplied) || supplied.length < 1 || supplied.length > 16 || Reflect.ownKeys(supplied).length !== supplied.length + 1)
21734
- return invalid();
21781
+ return invalid2();
21735
21782
  const collections = [];
21736
21783
  for (let index = 0;index < supplied.length; index++) {
21737
21784
  const descriptor = Object.getOwnPropertyDescriptor(supplied, String(index));
21738
- if (!descriptor?.enumerable || !Object.hasOwn(descriptor, "value") || !scalarText(descriptor.value))
21739
- return invalid();
21785
+ if (!descriptor?.enumerable || !Object.hasOwn(descriptor, "value") || !historyText(descriptor.value))
21786
+ return invalid2();
21740
21787
  collections.push(descriptor.value);
21741
21788
  }
21742
21789
  if (new Set(collections).size !== collections.length)
21743
- return invalid();
21790
+ return invalid2();
21744
21791
  captured.collections = collections;
21745
21792
  }
21746
21793
  if (Object.hasOwn(descriptors, "limit")) {
21747
21794
  const supplied = descriptors.limit.value;
21748
21795
  if (!Number.isInteger(supplied) || supplied < 1 || supplied > 100)
21749
- return invalid();
21796
+ return invalid2();
21750
21797
  captured.limit = supplied;
21751
21798
  }
21752
21799
  return { query, options: captured };
@@ -21886,55 +21933,55 @@ class FileOperationError extends Error {
21886
21933
  var 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}$/;
21887
21934
  var FILE_ORIGINAL_MAX_BYTES = 5 * 1024 ** 3;
21888
21935
  function parseFileDescriptor(value) {
21889
- const invalid2 = () => {
21936
+ const invalid3 = () => {
21890
21937
  throw new FileOperationError("unavailable", 503, true);
21891
21938
  };
21892
21939
  if (!value || typeof value !== "object" || Array.isArray(value))
21893
- return invalid2();
21940
+ return invalid3();
21894
21941
  const f = value;
21895
21942
  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))
21896
- return invalid2();
21943
+ return invalid3();
21897
21944
  const d = f.details;
21898
21945
  if (d.kind === "other") {
21899
21946
  if (Object.keys(d).length !== 1 || f.mediaType !== "application/octet-stream")
21900
- return invalid2();
21947
+ return invalid3();
21901
21948
  } else if (d.kind === "csv") {
21902
21949
  if (Object.keys(d).sort().join(",") !== "dataRows,delimiter,encoding,headerRow,kind" || f.mediaType !== "text/csv" || !["utf-8", "windows-1252"].includes(d.encoding) || ![",", ";", "\t"].includes(d.delimiter) || !Number.isSafeInteger(d.headerRow) || d.headerRow < 1 || !Number.isSafeInteger(d.dataRows) || d.dataRows < 0)
21903
- return invalid2();
21950
+ return invalid3();
21904
21951
  } else
21905
- return invalid2();
21952
+ return invalid3();
21906
21953
  for (const [name, descriptor] of Object.entries(f.enrichments)) {
21907
21954
  if (!safeEnrichmentName(name))
21908
- return invalid2();
21955
+ return invalid3();
21909
21956
  parseEnrichmentDescriptor(descriptor);
21910
21957
  }
21911
21958
  if (new TextEncoder().encode(JSON.stringify(f)).byteLength > 16 * 1024)
21912
- return invalid2();
21959
+ return invalid3();
21913
21960
  return f;
21914
21961
  }
21915
21962
  function parseEnrichmentDescriptor(value) {
21916
- const invalid2 = () => {
21963
+ const invalid3 = () => {
21917
21964
  throw new FileOperationError("unavailable", 503, true);
21918
21965
  };
21919
21966
  if (!value || typeof value !== "object" || Array.isArray(value))
21920
- return invalid2();
21967
+ return invalid3();
21921
21968
  const d = value;
21922
21969
  if (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))
21923
- return invalid2();
21970
+ return invalid3();
21924
21971
  return d;
21925
21972
  }
21926
21973
  function safeEnrichmentName(value) {
21927
21974
  return safeFileName(value) && !/^original(?:\.|$)/i.test(value);
21928
21975
  }
21929
21976
  function parseFileEnrichMetadata(value, maxBytes) {
21930
- const invalid2 = () => {
21977
+ const invalid3 = () => {
21931
21978
  throw new FileOperationError("invalid_input", 400);
21932
21979
  };
21933
21980
  if (!value || typeof value !== "object" || Array.isArray(value))
21934
- return invalid2();
21981
+ return invalid3();
21935
21982
  const d = value;
21936
21983
  if (Object.keys(d).some((key) => !["name", "size", "mediaType", "replace"].includes(key)) || !safeEnrichmentName(d.name) || !Number.isSafeInteger(d.size) || d.size < 0 || d.mediaType !== undefined && (typeof d.mediaType !== "string" || d.mediaType.length > 255 || /[\u0000-\u001f\u007f]/.test(d.mediaType)) || d.replace !== undefined && (typeof d.replace !== "string" || !/^[0-9a-f]{64}$/.test(d.replace)))
21937
- return invalid2();
21984
+ return invalid3();
21938
21985
  if (d.size > maxBytes)
21939
21986
  throw new FileOperationError("request_too_large", 413);
21940
21987
  return d;
@@ -21946,37 +21993,37 @@ function safeFilePath(value) {
21946
21993
  return typeof value === "string" && value.length <= 1024 && value.split("/").every(safeFileName);
21947
21994
  }
21948
21995
  function parseFileCreateMetadata(value, maxBytes) {
21949
- const invalid2 = () => {
21996
+ const invalid3 = () => {
21950
21997
  throw new FileOperationError("invalid_input", 400);
21951
21998
  };
21952
21999
  if (!value || typeof value !== "object" || Array.isArray(value))
21953
- return invalid2();
22000
+ return invalid3();
21954
22001
  const input = value;
21955
22002
  if (Object.keys(input).some((key) => !["name", "path", "storage", "csv", "size", "requestKey"].includes(key)))
21956
- return invalid2();
22003
+ return invalid3();
21957
22004
  const path = input.path === undefined ? "Files" : input.path;
21958
22005
  if (!safeFileName(input.name) || !safeFilePath(path))
21959
- return invalid2();
22006
+ return invalid3();
21960
22007
  if (input.storage !== undefined && input.storage !== "remote" && input.storage !== "local")
21961
- return invalid2();
22008
+ return invalid3();
21962
22009
  if (!Number.isSafeInteger(input.size) || input.size < 0)
21963
- return invalid2();
22010
+ return invalid3();
21964
22011
  if (input.size > maxBytes)
21965
22012
  throw new FileOperationError("request_too_large", 413);
21966
22013
  if (typeof input.requestKey !== "string" || input.requestKey.length < 1 || input.requestKey.length > 256 || input.requestKey !== input.requestKey.trim() || /[\u0000-\u001f\u007f]/.test(input.requestKey))
21967
- return invalid2();
22014
+ return invalid3();
21968
22015
  let csv;
21969
22016
  if (input.csv !== undefined) {
21970
22017
  if (!input.csv || typeof input.csv !== "object" || Array.isArray(input.csv))
21971
- return invalid2();
22018
+ return invalid3();
21972
22019
  const hints = input.csv;
21973
22020
  if (Object.keys(hints).some((key) => !["encoding", "delimiter", "headerRow"].includes(key)))
21974
- return invalid2();
22021
+ return invalid3();
21975
22022
  const encoding = hints.encoding === undefined ? "utf-8" : hints.encoding;
21976
22023
  const delimiter = hints.delimiter === undefined ? "," : hints.delimiter;
21977
22024
  const headerRow = hints.headerRow === undefined ? 1 : hints.headerRow;
21978
22025
  if (encoding !== "utf-8" && encoding !== "windows-1252" || ![",", ";", "\t"].includes(delimiter) || !Number.isSafeInteger(headerRow) || headerRow < 1 || headerRow > 1e5)
21979
- return invalid2();
22026
+ return invalid3();
21980
22027
  csv = { encoding, delimiter, headerRow };
21981
22028
  }
21982
22029
  return {
@@ -22030,7 +22077,7 @@ function privateRoutineRequest(environment = typeof __ODDINGTON_BROWSER__ !== "u
22030
22077
  const tcp = /^tcp:\/\/127\.0\.0\.1:([1-9][0-9]{0,4})$/.exec(socket);
22031
22078
  const endpoint = tcp ? { hostname: "127.0.0.1", port: Number(tcp[1]) } : { socketPath: socket };
22032
22079
  const id2 = crypto.randomUUID();
22033
- if (init.body instanceof ReadableStream) {
22080
+ if (init.body instanceof ReadableStream || suffix === "" && init.method === "POST") {
22034
22081
  await new Promise((resolve, reject) => {
22035
22082
  const admission = httpRequest({
22036
22083
  ...endpoint,
@@ -22432,15 +22479,16 @@ var data = {
22432
22479
  async function send(input) {
22433
22480
  const context = await resolveDataContext();
22434
22481
  try {
22435
- checkDataRequestSize({ repository, ...input });
22482
+ const envelope = { repository, ...input };
22483
+ checkDataRequestSize(envelope);
22436
22484
  if ("local" in context)
22437
- return await context.local("data/query", { repository, ...input });
22485
+ return await context.local("data/query", envelope);
22438
22486
  const response = await fetch(context.url, {
22439
22487
  method: "POST",
22440
22488
  redirect: "error",
22441
22489
  cache: "no-store",
22442
22490
  headers: { "content-type": "application/json", ...context.headers },
22443
- body: JSON.stringify({ repository, ...input })
22491
+ body: JSON.stringify(envelope)
22444
22492
  });
22445
22493
  const result = await response.json();
22446
22494
  if (!response.ok) {
@@ -22460,13 +22508,11 @@ var data = {
22460
22508
  },
22461
22509
  records: {
22462
22510
  select: (query) => send({ operation: "records.select", query }),
22463
- search: (query, options) => {
22464
- try {
22465
- return send({ operation: "records.search", ...captureRecordsSearchInput(query, options) });
22466
- } catch (error) {
22467
- return Promise.reject(error);
22468
- }
22469
- }
22511
+ search: async (query, options) => send({ operation: "records.search", ...captureRecordsSearchInput(query, options) })
22512
+ },
22513
+ history: {
22514
+ list: async (options) => send({ operation: "history.list", options: captureHistoryListOptions(options) }),
22515
+ at: async (instant) => send({ operation: "history.at", instant: captureHistoryInstant(instant) })
22470
22516
  },
22471
22517
  changes() {
22472
22518
  let updates = [], creates = [], deletes = [], publicationId;
@@ -22574,7 +22620,7 @@ var TIMEOUT_MS = 30000;
22574
22620
  var MAX_ACTIVE = 4;
22575
22621
  var ATTEMPTS = 2;
22576
22622
  var active = 0;
22577
- var invalid2 = () => new FileOperationError("invalid_input", 400);
22623
+ var invalid3 = () => new FileOperationError("invalid_input", 400);
22578
22624
  var unavailable = () => new FileOperationError("unavailable", 503, true);
22579
22625
  var failures = {
22580
22626
  400: ["invalid_input", "unsupported_context"],
@@ -22598,7 +22644,7 @@ async function boundedBytes(stream, limit) {
22598
22644
  if (done)
22599
22645
  break;
22600
22646
  if (!(value instanceof Uint8Array))
22601
- throw invalid2();
22647
+ throw invalid3();
22602
22648
  size += value.byteLength;
22603
22649
  if (size > limit)
22604
22650
  throw new FileOperationError("request_too_large", 413);
@@ -22780,7 +22826,7 @@ async function send(suffix, init, descriptor, initial, parent) {
22780
22826
  }
22781
22827
  function idPath(id2) {
22782
22828
  if (typeof id2 !== "string" || !FILE_ID_PATTERN.test(id2))
22783
- throw invalid2();
22829
+ throw invalid3();
22784
22830
  return `/${id2}`;
22785
22831
  }
22786
22832
  async function multipartCreate(input, metadata, exchange, release = () => {
@@ -22844,12 +22890,12 @@ async function multipartCreate(input, metadata, exchange, release = () => {
22844
22890
  offset = 0;
22845
22891
  const value2 = await next();
22846
22892
  if (value2.done)
22847
- throw invalid2();
22893
+ throw invalid3();
22848
22894
  if (!(value2.value instanceof Uint8Array))
22849
- throw invalid2();
22895
+ throw invalid3();
22850
22896
  chunk = value2.value;
22851
22897
  if (chunk.byteLength > metadata.size - sent - filled)
22852
- throw invalid2();
22898
+ throw invalid3();
22853
22899
  }
22854
22900
  const take = Math.min(length - filled, chunk.byteLength - offset);
22855
22901
  part.set(chunk.subarray(offset, offset + take), filled);
@@ -22862,7 +22908,7 @@ async function multipartCreate(input, metadata, exchange, release = () => {
22862
22908
  }
22863
22909
  }
22864
22910
  if (part.byteLength !== length)
22865
- throw invalid2();
22911
+ throw invalid3();
22866
22912
  if (reader && sent + length === metadata.size) {
22867
22913
  lastProgress = Date.now();
22868
22914
  for (;; ) {
@@ -22870,7 +22916,7 @@ async function multipartCreate(input, metadata, exchange, release = () => {
22870
22916
  if (value2.done)
22871
22917
  break;
22872
22918
  if (!(value2.value instanceof Uint8Array) || value2.value.byteLength)
22873
- throw invalid2();
22919
+ throw invalid3();
22874
22920
  }
22875
22921
  }
22876
22922
  const value = await request((signal) => exchange(`${route}/part`, {
@@ -22890,7 +22936,7 @@ async function multipartCreate(input, metadata, exchange, release = () => {
22890
22936
  for (const name of Object.keys(upload.file.enrichments)) {
22891
22937
  const bytes = await request(() => upload.enrichment(name));
22892
22938
  if (bytes.byteLength !== upload.file.enrichments[name].size || bytes.byteLength > MAX_BYTES)
22893
- throw invalid2();
22939
+ throw invalid3();
22894
22940
  await request((signal) => exchange(`${route}/enrichments/${encodeURIComponent(name)}`, {
22895
22941
  method: "POST",
22896
22942
  headers: { "content-type": "application/octet-stream", "x-oddington-transfer": ticket },
@@ -22944,7 +22990,7 @@ function contentPath(id2, name) {
22944
22990
  if (name === undefined)
22945
22991
  return `${path}/content`;
22946
22992
  if (!safeEnrichmentName(name))
22947
- throw invalid2();
22993
+ throw invalid3();
22948
22994
  return `${path}/enrichments/${encodeURIComponent(name)}/content`;
22949
22995
  }
22950
22996
  async function read(id2, names) {
@@ -22955,16 +23001,16 @@ async function read(id2, names) {
22955
23001
  return operation((context2, signal) => send(path, { method: "GET" }, false, context2, signal), context);
22956
23002
  }
22957
23003
  if (!Array.isArray(names))
22958
- throw invalid2();
23004
+ throw invalid3();
22959
23005
  const distinct = [];
22960
23006
  const seen = new Set;
22961
23007
  for (const name of names) {
22962
23008
  if (typeof name !== "string" || !safeEnrichmentName(name))
22963
- throw invalid2();
23009
+ throw invalid3();
22964
23010
  if (seen.has(name))
22965
23011
  continue;
22966
23012
  if (seen.size === 16)
22967
- throw invalid2();
23013
+ throw invalid3();
22968
23014
  seen.add(name);
22969
23015
  distinct.push(name);
22970
23016
  }
@@ -22998,15 +23044,15 @@ var files = {
22998
23044
  async create(input, options) {
22999
23045
  const context = await resolveFilesContext();
23000
23046
  if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key) => !["file", "size", "path", "name", "storage", "csv"].includes(key)) || options !== undefined && (!options || typeof options !== "object" || Array.isArray(options) || Object.keys(options).some((key) => key !== "requestKey")))
23001
- throw invalid2();
23047
+ throw invalid3();
23002
23048
  const blob = input.file instanceof Blob;
23003
23049
  const array2 = input.file instanceof Uint8Array;
23004
23050
  const stream = input.file instanceof ReadableStream;
23005
23051
  if (!blob && !array2 && !stream)
23006
- throw invalid2();
23052
+ throw invalid3();
23007
23053
  const intrinsic = blob ? input.file.size : array2 ? input.file.byteLength : undefined;
23008
23054
  if (intrinsic !== undefined && input.size !== undefined && input.size !== intrinsic)
23009
- throw invalid2();
23055
+ throw invalid3();
23010
23056
  let name = input.name;
23011
23057
  if (name === undefined && blob && "name" in input.file && typeof input.file.name === "string") {
23012
23058
  name = typeof globalThis.File !== "undefined" && input.file instanceof globalThis.File ? input.file.name : input.file.name.split(/[\\/]/).at(-1);
@@ -23023,7 +23069,7 @@ var files = {
23023
23069
  throw new FileOperationError("unsupported_context", 400);
23024
23070
  const header = encodeURIComponent(JSON.stringify(metadata));
23025
23071
  if (new TextEncoder().encode(header).byteLength > METADATA_BYTES)
23026
- throw invalid2();
23072
+ throw invalid3();
23027
23073
  if (active >= MAX_ACTIVE)
23028
23074
  throw new FileOperationError("capacity", 429, true);
23029
23075
  active++;
@@ -23057,7 +23103,7 @@ var files = {
23057
23103
  try {
23058
23104
  const bytes = array2 ? input.file.slice() : await boundedBytes(blob ? input.file.stream() : input.file, metadata.size);
23059
23105
  if (bytes.byteLength !== metadata.size)
23060
- throw invalid2();
23106
+ throw invalid3();
23061
23107
  if (signal.aborted)
23062
23108
  throw unavailable();
23063
23109
  return await send("", {
@@ -23075,7 +23121,7 @@ var files = {
23075
23121
  async enrich(input) {
23076
23122
  const context = await resolveFilesContext();
23077
23123
  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))
23078
- throw invalid2();
23124
+ throw invalid3();
23079
23125
  const path = `${idPath(input.file)}/enrichments`;
23080
23126
  if (typeof input.content === "string" ? input.content.length > MAX_BYTES : input.content.byteLength > MAX_BYTES)
23081
23127
  throw new FileOperationError("request_too_large", 413);
@@ -23097,7 +23143,7 @@ var files = {
23097
23143
  }, MAX_BYTES);
23098
23144
  const header = encodeURIComponent(JSON.stringify(metadata));
23099
23145
  if (new TextEncoder().encode(header).byteLength > METADATA_BYTES)
23100
- throw invalid2();
23146
+ throw invalid3();
23101
23147
  const result2 = await send(path, { method: "POST", headers: {
23102
23148
  "content-type": "application/octet-stream",
23103
23149
  "x-oddington-enrichment": header
@@ -23114,9 +23160,16 @@ var files = {
23114
23160
  };
23115
23161
  // src/app-context.ts
23116
23162
  var sessionKey = "_os.app.session";
23163
+ var responseChecks = new WeakMap;
23117
23164
 
23118
23165
  class AppResponseReadError extends Error {
23119
23166
  }
23167
+
23168
+ class AppContextDenied extends Error {
23169
+ }
23170
+
23171
+ class AppRenewalUnavailable extends Error {
23172
+ }
23120
23173
  async function appResponse(response, maximum, signal) {
23121
23174
  if (signal.aborted || !response.body || response.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase() !== "application/json")
23122
23175
  throw new Error;
@@ -23137,8 +23190,13 @@ async function appResponse(response, maximum, signal) {
23137
23190
  }
23138
23191
  if (signal.aborted)
23139
23192
  throw new Error;
23140
- if (chunk.done)
23141
- return JSON.parse(text3 + decoder.decode());
23193
+ if (chunk.done) {
23194
+ const value = JSON.parse(text3 + decoder.decode());
23195
+ await responseChecks.get(response)?.(value);
23196
+ if (signal.aborted)
23197
+ throw new Error;
23198
+ return value;
23199
+ }
23142
23200
  bytes += chunk.value.byteLength;
23143
23201
  if (bytes > maximum) {
23144
23202
  abort();
@@ -23165,38 +23223,239 @@ function appContext(origin, active2) {
23165
23223
  let denied = false;
23166
23224
  const controller = new AbortController;
23167
23225
  let expiry;
23168
- const invalidate = () => {
23169
- denied = true;
23226
+ let startup;
23227
+ let renewalTimer;
23228
+ let renewal;
23229
+ let observation;
23230
+ let resolution2;
23231
+ let disposed = false;
23232
+ const stored = () => globalThis.localStorage.getItem(sessionKey);
23233
+ const alive = () => {
23234
+ try {
23235
+ return !denied && !disposed && active2() && globalThis.location.origin === origin && (!binding || binding.expiresAt > Date.now());
23236
+ } catch {
23237
+ return false;
23238
+ }
23239
+ };
23240
+ const cancel = () => {
23170
23241
  controller.abort();
23171
23242
  clearTimeout(expiry);
23243
+ clearTimeout(startup);
23244
+ clearTimeout(renewalTimer);
23172
23245
  clearInterval(monitor);
23246
+ globalThis.removeEventListener?.("storage", storageChanged);
23247
+ };
23248
+ const invalidate = () => {
23249
+ denied = true;
23250
+ cancel();
23173
23251
  try {
23174
- if (active2() && globalThis.location.origin === origin && (!credential || globalThis.localStorage.getItem(sessionKey) === credential)) {
23252
+ if (!disposed && active2() && globalThis.location.origin === origin && (!credential || globalThis.localStorage.getItem(sessionKey) === credential)) {
23175
23253
  globalThis.localStorage.removeItem(sessionKey);
23176
23254
  }
23177
23255
  } catch {}
23178
23256
  };
23179
23257
  const current = () => {
23180
23258
  try {
23181
- return !denied && active2() && globalThis.location.origin === origin && globalThis.localStorage.getItem(sessionKey) === credential && (!binding || binding.expiresAt > Date.now());
23259
+ return alive() && stored() === credential;
23182
23260
  } catch {
23183
23261
  return false;
23184
23262
  }
23185
23263
  };
23186
- const monitor = setInterval(() => {
23187
- if (credential && !current())
23264
+ const sameSession = (value) => !binding || value.appId === binding.appId && value.memberId === binding.memberId && value.workspace === binding.workspace && value.sessionId === binding.sessionId;
23265
+ const bounded = (value) => validBinding(value) && value.expiresAt > Date.now() && value.expiresAt <= Date.now() + 600000 && sameSession(value);
23266
+ async function proof(candidate, signal = controller.signal) {
23267
+ if (!alive() || !/^[A-Za-z0-9_-]{43}$/.test(candidate))
23268
+ throw new Error;
23269
+ const url = `${origin}/_os/app/context`;
23270
+ const response = await fetch(url, {
23271
+ credentials: "omit",
23272
+ redirect: "error",
23273
+ cache: "no-store",
23274
+ referrerPolicy: "no-referrer",
23275
+ headers: { authorization: `Bearer ${candidate}` },
23276
+ signal
23277
+ });
23278
+ if (response.redirected || response.url && response.url !== url)
23279
+ throw new Error;
23280
+ if (response.status === 401 || response.status === 403)
23281
+ throw new AppContextDenied;
23282
+ if (!response.ok)
23283
+ throw new Error;
23284
+ const value = await appResponse(response, 4096, signal);
23285
+ if (!alive() || !bounded(value))
23286
+ throw new Error;
23287
+ return value;
23288
+ }
23289
+ function install(value, candidate) {
23290
+ binding = { ...value };
23291
+ credential = candidate;
23292
+ clearTimeout(expiry);
23293
+ clearTimeout(renewalTimer);
23294
+ const remaining = binding.expiresAt - Date.now();
23295
+ expiry = setTimeout(invalidate, remaining);
23296
+ renewalTimer = setTimeout(() => {
23297
+ renew();
23298
+ }, remaining - Math.min(60000, remaining / 5));
23299
+ }
23300
+ async function synchronize() {
23301
+ if (!alive())
23302
+ throw new Error;
23303
+ if (!credential)
23304
+ return;
23305
+ if (stored() === credential)
23306
+ return;
23307
+ if (!binding)
23308
+ throw new Error;
23309
+ if (observation)
23310
+ return observation;
23311
+ observation = (async () => {
23312
+ while (alive()) {
23313
+ const candidate = stored();
23314
+ if (!candidate)
23315
+ throw new Error;
23316
+ let value;
23317
+ try {
23318
+ value = await proof(candidate);
23319
+ } catch {
23320
+ if (stored() !== candidate)
23321
+ continue;
23322
+ throw new Error;
23323
+ }
23324
+ if (stored() !== candidate)
23325
+ continue;
23326
+ install(value, candidate);
23327
+ return;
23328
+ }
23329
+ throw new Error;
23330
+ })();
23331
+ try {
23332
+ await observation;
23333
+ } catch {
23334
+ invalidate();
23335
+ throw new Error("Local app context unavailable.");
23336
+ } finally {
23337
+ observation = undefined;
23338
+ }
23339
+ }
23340
+ async function renew() {
23341
+ if (renewal || !binding || !alive())
23342
+ return;
23343
+ renewal = (async () => {
23344
+ const deadline = binding.expiresAt;
23345
+ const requestController = new AbortController;
23346
+ const abort = () => requestController.abort();
23347
+ controller.signal.addEventListener("abort", abort, { once: true });
23348
+ const timeout = setTimeout(abort, Math.min(20000, deadline - Date.now()));
23349
+ const previous = credential;
23350
+ try {
23351
+ await synchronize();
23352
+ if (credential !== previous)
23353
+ return;
23354
+ const url = `${origin}/_os/app/renew`;
23355
+ let response;
23356
+ try {
23357
+ response = await fetch(url, {
23358
+ method: "POST",
23359
+ credentials: "omit",
23360
+ redirect: "error",
23361
+ cache: "no-store",
23362
+ referrerPolicy: "no-referrer",
23363
+ signal: requestController.signal,
23364
+ headers: { authorization: `Bearer ${previous}` }
23365
+ });
23366
+ } catch {
23367
+ throw new AppRenewalUnavailable;
23368
+ }
23369
+ if (response.redirected || response.url && response.url !== url)
23370
+ throw new Error;
23371
+ const value = await appResponse(response, 4096, requestController.signal);
23372
+ if (response.status === 503 && value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).join(",") === "error" && "error" in value && value.error === "renewal_unavailable")
23373
+ throw new AppRenewalUnavailable;
23374
+ if (response.status === 403 && value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).join(",") === "error" && "error" in value && value.error === "renewal_conflict") {
23375
+ while (alive() && stored() === previous && !requestController.signal.aborted) {
23376
+ await new Promise((resolve2, reject) => {
23377
+ const abortWait = () => {
23378
+ clearTimeout(timer);
23379
+ reject(new Error);
23380
+ };
23381
+ const timer = setTimeout(() => {
23382
+ requestController.signal.removeEventListener("abort", abortWait);
23383
+ resolve2();
23384
+ }, 100);
23385
+ requestController.signal.addEventListener("abort", abortWait, { once: true });
23386
+ });
23387
+ }
23388
+ if (requestController.signal.aborted)
23389
+ throw new Error;
23390
+ await synchronize();
23391
+ return;
23392
+ }
23393
+ if (!response.ok)
23394
+ throw new Error;
23395
+ if (!alive() || Date.now() >= deadline || !value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).sort().join(",") !== "context,credential")
23396
+ throw new Error;
23397
+ const result2 = value;
23398
+ if (!bounded(result2.context) || typeof result2.credential !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(result2.credential) || result2.credential === previous)
23399
+ throw new Error;
23400
+ if (stored() !== previous) {
23401
+ await synchronize();
23402
+ return;
23403
+ }
23404
+ globalThis.localStorage.setItem(sessionKey, result2.credential);
23405
+ install(result2.context, result2.credential);
23406
+ } catch (error) {
23407
+ try {
23408
+ if (alive() && stored() && stored() !== previous) {
23409
+ await synchronize();
23410
+ return;
23411
+ }
23412
+ } catch {}
23413
+ try {
23414
+ if (alive() && stored() && stored() !== previous) {
23415
+ await synchronize();
23416
+ return;
23417
+ }
23418
+ } catch {}
23419
+ if ((error instanceof AppRenewalUnavailable || error instanceof AppResponseReadError || requestController.signal.aborted) && current() && Date.now() < deadline) {
23420
+ renewalTimer = setTimeout(() => {
23421
+ renew();
23422
+ }, Math.min(5000, (deadline - Date.now()) / 5));
23423
+ } else
23424
+ invalidate();
23425
+ } finally {
23426
+ clearTimeout(timeout);
23427
+ controller.signal.removeEventListener("abort", abort);
23428
+ }
23429
+ })();
23430
+ try {
23431
+ await renewal;
23432
+ } finally {
23433
+ renewal = undefined;
23434
+ }
23435
+ }
23436
+ const storageChanged = () => {
23437
+ if (!credential)
23438
+ return;
23439
+ if (!alive()) {
23188
23440
  invalidate();
23441
+ return;
23442
+ }
23443
+ synchronize().catch(invalidate);
23444
+ };
23445
+ const monitor = setInterval(() => {
23446
+ storageChanged();
23189
23447
  }, 100);
23448
+ globalThis.addEventListener?.("storage", storageChanged);
23190
23449
  const dispose = () => {
23191
- controller.abort();
23192
- clearTimeout(expiry);
23193
- clearInterval(monitor);
23450
+ disposed = true;
23451
+ cancel();
23194
23452
  };
23195
23453
  async function requestData(operation2, input) {
23196
23454
  try {
23197
23455
  if (operation2 !== "data/query" || !current())
23198
23456
  throw new Error;
23199
23457
  const url = `${origin}/_os/app/data`;
23458
+ const admitted = credential;
23200
23459
  const response = await fetch(url, {
23201
23460
  method: "POST",
23202
23461
  credentials: "omit",
@@ -23207,13 +23466,20 @@ function appContext(origin, active2) {
23207
23466
  headers: { "content-type": "application/json", authorization: `Bearer ${credential}` },
23208
23467
  body: JSON.stringify(input)
23209
23468
  });
23469
+ await synchronize();
23470
+ while (alive() && stored() !== credential)
23471
+ await synchronize();
23210
23472
  if (!current() || response.redirected || response.url && response.url !== url)
23211
23473
  throw new Error;
23212
23474
  if (response.status === 401 || response.status === 403) {
23213
- invalidate();
23475
+ if (admitted === credential)
23476
+ invalidate();
23214
23477
  throw new DataOperationError("access_denied");
23215
23478
  }
23216
23479
  const result2 = await appResponse(response, Number.MAX_SAFE_INTEGER, controller.signal);
23480
+ await synchronize();
23481
+ while (alive() && stored() !== credential)
23482
+ await synchronize();
23217
23483
  if (!current())
23218
23484
  throw new Error;
23219
23485
  if (!response.ok)
@@ -23233,6 +23499,7 @@ function appContext(origin, active2) {
23233
23499
  throw new Error("Local app context unavailable.");
23234
23500
  }
23235
23501
  const url = `${origin}/_os/app/routines/${encodeURIComponent(key)}/${suffix}`;
23502
+ const admitted = credential;
23236
23503
  const response = await fetch(url, {
23237
23504
  ...init,
23238
23505
  credentials: "omit",
@@ -23242,61 +23509,107 @@ function appContext(origin, active2) {
23242
23509
  signal: controller.signal,
23243
23510
  headers: { ...init.body === undefined ? {} : { "content-type": "application/json" }, authorization: `Bearer ${credential}` }
23244
23511
  });
23512
+ await synchronize();
23513
+ while (alive() && stored() !== credential)
23514
+ await synchronize();
23245
23515
  if (!current() || response.redirected || response.url && response.url !== url) {
23246
23516
  invalidate();
23247
23517
  throw new Error("Local app context unavailable.");
23248
23518
  }
23249
- if (response.status === 401 || response.status === 403)
23519
+ if ((response.status === 401 || response.status === 403) && admitted === credential)
23250
23520
  invalidate();
23521
+ responseChecks.set(response, async (value) => {
23522
+ await synchronize();
23523
+ while (alive() && stored() !== credential)
23524
+ await synchronize();
23525
+ if (admitted === credential && value && typeof value === "object" && "error" in value && value.error === "authority_unavailable")
23526
+ invalidate();
23527
+ });
23251
23528
  return response;
23252
23529
  }
23253
- const resolve = async () => {
23530
+ function context() {
23531
+ const issued = credential;
23532
+ return {
23533
+ kind: "local",
23534
+ ...binding,
23535
+ get expiresAt() {
23536
+ return binding.expiresAt;
23537
+ },
23538
+ isCurrent: current,
23539
+ signal: controller.signal,
23540
+ invalidate: () => {
23541
+ if (issued === credential)
23542
+ invalidate();
23543
+ },
23544
+ requestRoutine,
23545
+ requestData,
23546
+ requestAvailability: (key) => requestRoutine(key, "availability", { method: "GET" })
23547
+ };
23548
+ }
23549
+ const resolveOnce = async () => {
23550
+ let attempted;
23254
23551
  try {
23255
23552
  if (denied || !active2() || globalThis.location.origin !== origin)
23256
23553
  throw new Error;
23257
- const stored = globalThis.localStorage.getItem(sessionKey);
23258
- if (!stored || !/^[A-Za-z0-9_-]{43}$/.test(stored) || credential && stored !== credential)
23554
+ if (credential)
23555
+ await synchronize();
23556
+ const candidate = stored();
23557
+ if (!candidate || !/^[A-Za-z0-9_-]{43}$/.test(candidate))
23259
23558
  throw new Error;
23260
- credential = stored;
23559
+ credential = candidate;
23560
+ attempted = candidate;
23261
23561
  if (!current())
23262
23562
  throw new Error;
23263
- const response = await fetch(`${origin}/_os/app/context`, {
23264
- credentials: "omit",
23265
- redirect: "error",
23266
- referrerPolicy: "no-referrer",
23267
- headers: { authorization: `Bearer ${credential}` },
23268
- signal: controller.signal
23269
- });
23270
- if (!response.ok || response.redirected || response.url && response.url !== `${origin}/_os/app/context` || !response.headers.get("content-type")?.startsWith("application/json"))
23563
+ const value = await proof(candidate);
23564
+ if (stored() !== candidate || credential !== candidate) {
23565
+ if (!binding)
23566
+ binding = { ...value };
23567
+ await synchronize();
23568
+ return context();
23569
+ }
23570
+ if (!current())
23271
23571
  throw new Error;
23272
- const value = await appResponse(response, 4096, controller.signal);
23273
- if (!validBinding(value) || !current() || value.expiresAt <= Date.now() || value.expiresAt > Date.now() + 600000 || binding && (value.appId !== binding.appId || value.memberId !== binding.memberId || value.workspace !== binding.workspace || value.expiresAt > binding.expiresAt))
23572
+ if (binding && value.expiresAt > binding.expiresAt)
23274
23573
  throw new Error;
23275
- binding = { ...value };
23276
- clearTimeout(expiry);
23277
- expiry = setTimeout(invalidate, binding.expiresAt - Date.now());
23278
- return {
23279
- kind: "local",
23280
- ...binding,
23281
- isCurrent: current,
23282
- signal: controller.signal,
23283
- invalidate,
23284
- requestRoutine,
23285
- requestData,
23286
- requestAvailability: (key) => requestRoutine(key, "availability", { method: "GET" })
23287
- };
23288
- } catch {
23574
+ if (!binding || value.expiresAt !== binding.expiresAt)
23575
+ install(value, candidate);
23576
+ return context();
23577
+ } catch (error) {
23578
+ try {
23579
+ if (binding && current() && attempted && attempted !== credential)
23580
+ return context();
23581
+ if (binding && alive() && stored() && stored() !== credential) {
23582
+ await synchronize();
23583
+ return context();
23584
+ }
23585
+ } catch {}
23586
+ if (error instanceof AppContextDenied && binding && alive() && renewal)
23587
+ throw new Error("Local app context unavailable.");
23289
23588
  invalidate();
23290
23589
  throw new Error("Local app context unavailable.");
23291
23590
  }
23292
23591
  };
23293
- return { resolve, invalidate, dispose };
23592
+ const resolve = () => {
23593
+ if (resolution2)
23594
+ return resolution2;
23595
+ resolution2 = resolveOnce().finally(() => {
23596
+ resolution2 = undefined;
23597
+ });
23598
+ return resolution2;
23599
+ };
23600
+ const start = () => {
23601
+ startup = setTimeout(() => {
23602
+ if (alive() && !resolution2 && !binding)
23603
+ resolve().catch(() => {});
23604
+ }, 0);
23605
+ };
23606
+ return { resolve, start, invalidate, dispose };
23294
23607
  }
23295
23608
  function validBinding(value) {
23296
- if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).sort().join(",") !== "appId,expiresAt,memberId,workspace")
23609
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).sort().join(",") !== "appId,expiresAt,memberId,sessionId,workspace")
23297
23610
  return false;
23298
23611
  const binding = value;
23299
- return typeof binding.appId === "string" && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(binding.appId) && typeof binding.workspace === "string" && /^[a-z0-9][a-z0-9-]{0,62}$/.test(binding.workspace) && typeof binding.memberId === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(binding.memberId) && typeof binding.expiresAt === "number" && Number.isFinite(binding.expiresAt);
23612
+ return typeof binding.appId === "string" && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(binding.appId) && typeof binding.workspace === "string" && /^[a-z0-9][a-z0-9-]{0,62}$/.test(binding.workspace) && typeof binding.memberId === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(binding.memberId) && typeof binding.sessionId === "string" && /^[A-Za-z0-9_-]{43}$/.test(binding.sessionId) && typeof binding.expiresAt === "number" && Number.isFinite(binding.expiresAt);
23300
23613
  }
23301
23614
 
23302
23615
  // src/os.ts
@@ -23364,6 +23677,7 @@ function init({ auth }) {
23364
23677
  installedDispose = undefined;
23365
23678
  };
23366
23679
  installedDispose = dispose;
23680
+ local?.start();
23367
23681
  }
23368
23682
  var os = { init };
23369
23683
  // src/app-routine-browser.ts