@blotout/qinto-cli 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3036,10 +3036,10 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
3036
3036
  const shape = def.shape;
3037
3037
  const propValues = {};
3038
3038
  for (const key in shape) {
3039
- const field = shape[key]._zod;
3040
- if (field.values) {
3039
+ const field2 = shape[key]._zod;
3040
+ if (field2.values) {
3041
3041
  propValues[key] ?? (propValues[key] = /* @__PURE__ */ new Set());
3042
- for (const v of field.values)
3042
+ for (const v of field2.values)
3043
3043
  propValues[key].add(v);
3044
3044
  }
3045
3045
  }
@@ -14692,7 +14692,14 @@ var scheduleSchema = external_exports.object({
14692
14692
  id: external_exports.string().min(1).max(32),
14693
14693
  cron: external_exports.string().min(1).max(MAX_CRON_LENGTH),
14694
14694
  scheduledTime: external_exports.number().int().nonnegative().safe(),
14695
- installs: external_exports.array(external_exports.object({ id: external_exports.string().min(1).max(64), ord: external_exports.number().int().min(1), label: external_exports.string().max(100) }).strict()).min(1).max(MAX_INSTALLS_PER_APP)
14695
+ installs: external_exports.array(
14696
+ external_exports.object({
14697
+ id: external_exports.string().min(1).max(64),
14698
+ ord: external_exports.number().int().min(1),
14699
+ label: external_exports.string().max(100),
14700
+ catalogQueryTicket: external_exports.string().min(1).max(2048)
14701
+ }).strict()
14702
+ ).min(1).max(MAX_INSTALLS_PER_APP)
14696
14703
  }).strict();
14697
14704
  var controlEnvelopeSchema = external_exports.object({
14698
14705
  v: external_exports.literal(CONTROL_ENVELOPE_VERSION),
@@ -14734,12 +14741,21 @@ var ENDPOINT_CONTEXT_HEADER = "x-qinto-endpoint-context";
14734
14741
  var ENDPOINT_OUTCOMES_HEADER = "x-qinto-endpoint-outcomes";
14735
14742
  var MAX_ENDPOINT_BODY_BYTES = 1024 * 1024;
14736
14743
  var MAX_ENDPOINT_OUTCOMES_BYTES = 64 * 1024;
14744
+ var MAX_ENDPOINT_PATH_LENGTH = 16 * 1024;
14737
14745
  var installSchema3 = external_exports.object({ id: external_exports.string().min(1).max(64), ord: external_exports.number().int().min(1), label: external_exports.string().max(100) }).strict();
14738
14746
  var endpointContextSchema = external_exports.object({
14739
- path: external_exports.string().min(1).max(255),
14747
+ // The declared template, which keys the bundle's handler map; `path` is what the caller asked
14748
+ // for. Only `route` may key the rate limiter — a concrete path gives every parameter value its
14749
+ // own bucket, which is no limit at all.
14750
+ route: external_exports.string().min(1).max(255),
14751
+ path: external_exports.string().min(1).max(MAX_ENDPOINT_PATH_LENGTH),
14752
+ // A name is cut from `route`, so the route's own cap bounds it; anything tighter would reject a
14753
+ // parameter the manifest grammar accepts and the worker already matched.
14754
+ params: external_exports.record(external_exports.string().min(1).max(255), external_exports.string().max(MAX_ENDPOINT_PATH_LENGTH)).default({}),
14740
14755
  method: external_exports.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
14741
14756
  mode: external_exports.enum(["webhook", "api"]),
14742
14757
  version: external_exports.string().min(1).max(32),
14758
+ site: external_exports.object({ origin: external_exports.string().min(1).max(255) }).strict(),
14743
14759
  installs: external_exports.array(installSchema3).min(1).max(15)
14744
14760
  }).strict().refine((context) => {
14745
14761
  return context.mode === "webhook" || context.installs.length === 1;
@@ -14754,12 +14770,105 @@ var endpointOutcomesSchema = external_exports.object({
14754
14770
  ).max(15)
14755
14771
  }).strict();
14756
14772
 
14773
+ // ../../../libs/app-envelope/src/oauth.ts
14774
+ var APP_OAUTH_BINDING_PREFIX = "OAUTH_";
14775
+ var appOauthBindingName = (providerId, ordinal) => {
14776
+ return `${APP_OAUTH_BINDING_PREFIX}${providerId.toUpperCase().replaceAll("-", "_")}_${ordinal}`;
14777
+ };
14778
+ var parseAppOauthBinding = (raw) => {
14779
+ if (raw === void 0 || raw === "") {
14780
+ return null;
14781
+ }
14782
+ let parsed;
14783
+ try {
14784
+ parsed = JSON.parse(raw);
14785
+ } catch {
14786
+ return null;
14787
+ }
14788
+ if (typeof parsed !== "object" || parsed === null) {
14789
+ return null;
14790
+ }
14791
+ const value = parsed;
14792
+ if (value["st"] !== "connected" && value["st"] !== "reconnect_required") {
14793
+ return null;
14794
+ }
14795
+ const expiresAt = value["e"];
14796
+ const token = value["t"];
14797
+ return {
14798
+ ...typeof token === "string" && token !== "" ? { t: token } : {},
14799
+ e: typeof expiresAt === "number" ? expiresAt : null,
14800
+ st: value["st"]
14801
+ };
14802
+ };
14803
+
14804
+ // ../../../libs/app-envelope/src/observability.ts
14805
+ var APP_OBSERVABILITY_HEADER = "x-qinto-app-observability";
14806
+ var APP_OBSERVABILITY_VERSION = 1;
14807
+ var MAX_APP_OBSERVABILITY_EVENTS = 20;
14808
+ var MAX_APP_SAVED_ERRORS = 5;
14809
+ var MAX_APP_EVENT_NAME_BYTES = 128;
14810
+ var MAX_APP_ACTION_NAME_BYTES = 256;
14811
+ var MAX_APP_ERROR_DESCRIPTION_BYTES = 2048;
14812
+ var MAX_APP_ERROR_PAYLOAD_BYTES = 4096;
14813
+ var MAX_APP_DATA_POINT_BLOB_BYTES = 16 * 1024;
14814
+ var MAX_APP_OBSERVABILITY_BYTES = 32 * 1024;
14815
+ var APP_OBSERVABILITY_JSON_BUDGET = MAX_APP_OBSERVABILITY_BYTES / 4 * 3;
14816
+ var APP_EVENT_STATUSES = ["success", "error"];
14817
+ var APP_ERROR_CATEGORIES = ["authorization", "configuration", "validation", "rate_limit", "upstream", "internal"];
14818
+ var encoder = new TextEncoder();
14819
+ var decoder = new TextDecoder();
14820
+ var observabilityEventSchema = external_exports.object({ ord: external_exports.number().int().min(1), name: external_exports.string().min(1).max(MAX_APP_EVENT_NAME_BYTES), status: external_exports.enum(APP_EVENT_STATUSES) }).strict();
14821
+ var savedErrorSchema = external_exports.object({
14822
+ ord: external_exports.number().int().min(1),
14823
+ category: external_exports.enum(APP_ERROR_CATEGORIES),
14824
+ action: external_exports.string().min(1).max(MAX_APP_ACTION_NAME_BYTES),
14825
+ description: external_exports.string().min(1).max(MAX_APP_ERROR_DESCRIPTION_BYTES),
14826
+ payload: external_exports.string().max(MAX_APP_ERROR_PAYLOAD_BYTES).optional(),
14827
+ error: external_exports.string().max(MAX_APP_ERROR_PAYLOAD_BYTES).optional()
14828
+ }).strict();
14829
+ var appObservabilityRecordSchema = external_exports.object({
14830
+ v: external_exports.literal(APP_OBSERVABILITY_VERSION),
14831
+ truncated: external_exports.boolean().optional(),
14832
+ events: external_exports.array(observabilityEventSchema).max(MAX_APP_OBSERVABILITY_EVENTS),
14833
+ errors: external_exports.array(savedErrorSchema).max(MAX_APP_SAVED_ERRORS)
14834
+ }).strict();
14835
+ var utf8Length = (value) => {
14836
+ return encoder.encode(value).length;
14837
+ };
14838
+ var truncateUtf8 = (value, maxBytes) => {
14839
+ if (utf8Length(value) <= maxBytes) {
14840
+ return value;
14841
+ }
14842
+ let kept = "";
14843
+ let used = 0;
14844
+ for (const character of value) {
14845
+ const size = utf8Length(character);
14846
+ if (used + size > maxBytes) {
14847
+ break;
14848
+ }
14849
+ kept += character;
14850
+ used += size;
14851
+ }
14852
+ return kept;
14853
+ };
14854
+ var serializeAppObservability = (record2) => {
14855
+ const bytes = encoder.encode(JSON.stringify(record2));
14856
+ let binary = "";
14857
+ for (const byte of bytes) {
14858
+ binary += String.fromCharCode(byte);
14859
+ }
14860
+ return btoa(binary);
14861
+ };
14862
+
14757
14863
  // ../../../libs/app-envelope/src/ui.ts
14758
14864
  var UI_CONTEXT_HEADER = "x-qinto-ui-context";
14759
14865
  var MAX_UI_BODY_BYTES = 1024 * 1024;
14760
14866
  var appUiWritePath = (domainId, appSlug2) => {
14761
14867
  return `/app-ui/sites/${domainId}/apps/${appSlug2}/install-variables`;
14762
14868
  };
14869
+ var appUiCatalogQueryPath = (domainId, appSlug2, resourceIdentifier) => {
14870
+ return `/app-ui/sites/${domainId}/apps/${appSlug2}/catalogs/${resourceIdentifier}/query`;
14871
+ };
14763
14872
  var uiInstallSchema = external_exports.object({ id: external_exports.string().min(1).max(64), ord: external_exports.number().int().min(1), label: external_exports.string().max(100), enabled: external_exports.boolean() }).strict();
14764
14873
  var uiContextSchema = external_exports.object({
14765
14874
  version: external_exports.string().min(1).max(32),
@@ -14796,10 +14905,10 @@ var formatSpansRecord = (record2) => {
14796
14905
  };
14797
14906
 
14798
14907
  // ../../../libs/tail-trace/src/app-spans.ts
14799
- var encoder = new TextEncoder();
14800
- var decoder = new TextDecoder();
14908
+ var encoder2 = new TextEncoder();
14909
+ var decoder2 = new TextDecoder();
14801
14910
  var serializeAppSpans = (record2) => {
14802
- const bytes = encoder.encode(JSON.stringify(record2));
14911
+ const bytes = encoder2.encode(JSON.stringify(record2));
14803
14912
  let binary = "";
14804
14913
  for (const byte of bytes) {
14805
14914
  binary += String.fromCharCode(byte);
@@ -14808,12 +14917,12 @@ var serializeAppSpans = (record2) => {
14808
14917
  };
14809
14918
 
14810
14919
  // ../../../libs/tail-trace/src/budget.ts
14811
- var encoder2 = new TextEncoder();
14920
+ var encoder3 = new TextEncoder();
14812
14921
  var byteLength = (value) => {
14813
- return encoder2.encode(JSON.stringify(value)).length;
14922
+ return encoder3.encode(JSON.stringify(value)).length;
14814
14923
  };
14815
14924
  var fitsBudget = (text, max) => {
14816
- return text.length * 3 <= max || encoder2.encode(text).length <= max;
14925
+ return text.length * 3 <= max || encoder3.encode(text).length <= max;
14817
14926
  };
14818
14927
  var MIN_KEEP = 64;
14819
14928
  var halveLongestString = (holders) => {
@@ -15397,6 +15506,162 @@ var buildAppsClient = (env, envelope) => {
15397
15506
  };
15398
15507
  };
15399
15508
 
15509
+ // src/oauth.ts
15510
+ var EXPIRY_FLOOR_MS = 1e3;
15511
+ var read = (env, id, ord, now) => {
15512
+ const raw = env[appOauthBindingName(id, ord)];
15513
+ const parsed = parseAppOauthBinding(typeof raw === "string" ? raw : void 0);
15514
+ if (parsed === null) {
15515
+ return null;
15516
+ }
15517
+ const expired = parsed.e !== null && parsed.e - now < EXPIRY_FLOOR_MS;
15518
+ return {
15519
+ id,
15520
+ status: parsed.st === "connected" && !expired ? "connected" : "reconnect_required",
15521
+ expiresAt: parsed.e
15522
+ };
15523
+ };
15524
+ var buildOauth = (env, ord, now = Date.now) => {
15525
+ return {
15526
+ token: (id) => {
15527
+ const raw = env[appOauthBindingName(id, ord)];
15528
+ const parsed = parseAppOauthBinding(typeof raw === "string" ? raw : void 0);
15529
+ if (parsed === null) {
15530
+ throw new Error(`not_connected: this install has no ${id} connection`);
15531
+ }
15532
+ const state = read(env, id, ord, now());
15533
+ if (parsed.t === void 0 || state?.status !== "connected") {
15534
+ throw new Error(`reconnect_required: the ${id} connection has ended and a site manager must reconnect it`);
15535
+ }
15536
+ return parsed.t;
15537
+ },
15538
+ connection: (id) => {
15539
+ return read(env, id, ord, now());
15540
+ }
15541
+ };
15542
+ };
15543
+ var oauthTokenValues = (env) => {
15544
+ const values = /* @__PURE__ */ new Set();
15545
+ for (const [name, raw] of Object.entries(env)) {
15546
+ if (!name.startsWith("OAUTH_") || typeof raw !== "string") {
15547
+ continue;
15548
+ }
15549
+ const token = parseAppOauthBinding(raw)?.t;
15550
+ if (token !== void 0 && token !== "") {
15551
+ values.add(token);
15552
+ }
15553
+ }
15554
+ return [...values];
15555
+ };
15556
+ var buildUiOauth = (env, installs, now = Date.now) => {
15557
+ const ordinalOf = (installId) => {
15558
+ return installs.find((install) => install.id === installId)?.ord ?? null;
15559
+ };
15560
+ return {
15561
+ token: (id, options) => {
15562
+ const ord = ordinalOf(options.installId);
15563
+ if (ord === null) {
15564
+ throw new Error(`not_connected: ${options.installId} is not an install of this app`);
15565
+ }
15566
+ return buildOauth(env, ord, now).token(id);
15567
+ },
15568
+ connection: (id, options) => {
15569
+ const ord = ordinalOf(options.installId);
15570
+ return ord === null ? null : buildOauth(env, ord, now).connection(id);
15571
+ }
15572
+ };
15573
+ };
15574
+
15575
+ // src/observability-collector.ts
15576
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
15577
+ var ENVELOPE_OVERHEAD = 64;
15578
+ var collectors = new AsyncLocalStorage2();
15579
+ var serialized = (value) => {
15580
+ if (value instanceof Error) {
15581
+ const stack = value.stack;
15582
+ return JSON.stringify({ name: value.name, message: value.message, ...stack === void 0 ? {} : { stack } });
15583
+ }
15584
+ let text;
15585
+ try {
15586
+ text = JSON.stringify(value);
15587
+ } catch {
15588
+ return String(value);
15589
+ }
15590
+ return typeof text === "string" ? text : String(value);
15591
+ };
15592
+ var field = (value, redact, maxBytes) => {
15593
+ return truncateUtf8(redact(value), maxBytes);
15594
+ };
15595
+ var optionalField = (value, redact) => {
15596
+ if (value === void 0) {
15597
+ return void 0;
15598
+ }
15599
+ return field(serialized(value), redact, MAX_APP_ERROR_PAYLOAD_BYTES);
15600
+ };
15601
+ var createObservabilityCollector = (redact) => {
15602
+ const events = [];
15603
+ const errors = [];
15604
+ let used = ENVELOPE_OVERHEAD;
15605
+ let truncated = false;
15606
+ const size = (entry) => {
15607
+ return utf8Length(JSON.stringify(entry)) + 1;
15608
+ };
15609
+ return {
15610
+ addEvent: (ord, name, status) => {
15611
+ if (name === "" || !APP_EVENT_STATUSES.includes(status)) {
15612
+ truncated = true;
15613
+ return;
15614
+ }
15615
+ if (events.length >= MAX_APP_OBSERVABILITY_EVENTS) {
15616
+ truncated = true;
15617
+ return;
15618
+ }
15619
+ const entry = { ord, name: field(name, redact, MAX_APP_EVENT_NAME_BYTES), status };
15620
+ used += size(entry);
15621
+ events.push(entry);
15622
+ },
15623
+ addError: (ord, input) => {
15624
+ if (!APP_ERROR_CATEGORIES.includes(input.category) || input.action === "" || input.description === "") {
15625
+ truncated = true;
15626
+ return;
15627
+ }
15628
+ if (errors.length >= MAX_APP_SAVED_ERRORS) {
15629
+ truncated = true;
15630
+ return;
15631
+ }
15632
+ const payload = optionalField(input.payload, redact);
15633
+ const error51 = optionalField(input.error, redact);
15634
+ const entry = {
15635
+ ord,
15636
+ category: input.category,
15637
+ action: field(input.action, redact, MAX_APP_ACTION_NAME_BYTES),
15638
+ description: field(input.description, redact, MAX_APP_ERROR_DESCRIPTION_BYTES),
15639
+ ...payload === void 0 ? {} : { payload },
15640
+ ...error51 === void 0 ? {} : { error: error51 }
15641
+ };
15642
+ const entrySize = size(entry);
15643
+ if (used + entrySize > APP_OBSERVABILITY_JSON_BUDGET) {
15644
+ truncated = true;
15645
+ return;
15646
+ }
15647
+ used += entrySize;
15648
+ errors.push(entry);
15649
+ },
15650
+ record: () => {
15651
+ if (events.length === 0 && errors.length === 0) {
15652
+ return null;
15653
+ }
15654
+ return { v: APP_OBSERVABILITY_VERSION, ...truncated ? { truncated } : {}, events, errors };
15655
+ }
15656
+ };
15657
+ };
15658
+ var runWithObservability = (collector, run) => {
15659
+ return collectors.run(collector, run);
15660
+ };
15661
+ var currentObservability = () => {
15662
+ return collectors.getStore();
15663
+ };
15664
+
15400
15665
  // src/observability.ts
15401
15666
  var exceptionAttrs = (error51) => {
15402
15667
  const attrs = {
@@ -15419,7 +15684,7 @@ var consoleText = (error51) => {
15419
15684
  }
15420
15685
  return redactUrlParams(error51 instanceof Error ? error51.stack ?? error51.message : String(error51));
15421
15686
  };
15422
- var buildObservability = () => {
15687
+ var buildObservability = (ord) => {
15423
15688
  return {
15424
15689
  log: (message, attrs) => {
15425
15690
  addSpanEvent(message, attrs === void 0 ? {} : { attrs });
@@ -15429,6 +15694,21 @@ var buildObservability = () => {
15429
15694
  addSpanEvent(message, { level: "error", attrs: { ...attrs, ...error51 === void 0 ? {} : exceptionAttrs(error51) } });
15430
15695
  console.error(redactUrlParams(message), consoleText(error51), redactAttrs(attrs ?? {}));
15431
15696
  },
15697
+ event: (name, status) => {
15698
+ addSpanEvent(name, { attrs: { "app.event.status": status } });
15699
+ if (ord !== null) {
15700
+ currentObservability()?.addEvent(ord, name, status);
15701
+ }
15702
+ },
15703
+ saveError: (input) => {
15704
+ addSpanEvent(input.description, {
15705
+ level: "error",
15706
+ attrs: { "app.error.category": input.category, "app.error.action": input.action }
15707
+ });
15708
+ if (ord !== null) {
15709
+ currentObservability()?.addError(ord, input);
15710
+ }
15711
+ },
15432
15712
  span: ((...args) => {
15433
15713
  const [name] = args;
15434
15714
  const { attrs, run } = spanParts(args);
@@ -15505,6 +15785,9 @@ var secretValues = (env) => {
15505
15785
  values.add(value);
15506
15786
  }
15507
15787
  }
15788
+ for (const value of oauthTokenValues(env)) {
15789
+ values.add(value);
15790
+ }
15508
15791
  return [...values];
15509
15792
  };
15510
15793
  var projectInstallValues = (maps, kinds, ord) => {
@@ -15581,13 +15864,13 @@ var readOnce = (env, subject) => {
15581
15864
  };
15582
15865
  };
15583
15866
  var fetchedContext = (env, subject, collector) => {
15584
- const read = readOnce(env, () => {
15867
+ const read2 = readOnce(env, () => {
15585
15868
  return subject("context.load");
15586
15869
  });
15587
15870
  return {
15588
15871
  private: {
15589
15872
  load: async () => {
15590
- return (await read()).private;
15873
+ return (await read2()).private;
15591
15874
  },
15592
15875
  set: (patch) => {
15593
15876
  subject("context.set");
@@ -15596,7 +15879,7 @@ var fetchedContext = (env, subject, collector) => {
15596
15879
  },
15597
15880
  shared: {
15598
15881
  load: async () => {
15599
- return (await read()).shared;
15882
+ return (await read2()).shared;
15600
15883
  },
15601
15884
  set: (patch) => {
15602
15885
  subject("context.set");
@@ -15606,13 +15889,13 @@ var fetchedContext = (env, subject, collector) => {
15606
15889
  };
15607
15890
  };
15608
15891
  var asyncContext = (env, subject) => {
15609
- const read = readOnce(env, () => {
15892
+ const read2 = readOnce(env, () => {
15610
15893
  return subject("context.load");
15611
15894
  });
15612
15895
  return {
15613
15896
  private: {
15614
15897
  load: async () => {
15615
- return (await read()).private;
15898
+ return (await read2()).private;
15616
15899
  },
15617
15900
  set: async (patch) => {
15618
15901
  assertValidPatch(patch);
@@ -15624,7 +15907,7 @@ var asyncContext = (env, subject) => {
15624
15907
  },
15625
15908
  shared: {
15626
15909
  load: async () => {
15627
- return (await read()).shared;
15910
+ return (await read2()).shared;
15628
15911
  },
15629
15912
  set: async (patch) => {
15630
15913
  assertValidPatch(patch);
@@ -15684,12 +15967,13 @@ var buildWorkflowContext = (env, stamp) => {
15684
15967
  env,
15685
15968
  install,
15686
15969
  startWorkflow: buildStartWorkflow(stamp),
15970
+ oauth: buildOauth(env, stamp.ord),
15687
15971
  variables,
15688
15972
  secrets,
15689
15973
  appVariables,
15690
15974
  appSecrets,
15691
15975
  userId: stamp.anonId,
15692
- observability: buildObservability(),
15976
+ observability: buildObservability(stamp.ord),
15693
15977
  context: asyncContext(env, (what) => {
15694
15978
  return requireSubject(stamp.anonId, what);
15695
15979
  }),
@@ -15766,6 +16050,7 @@ var buildContext = ({
15766
16050
  secrets,
15767
16051
  appVariables: appScope.appVariables,
15768
16052
  appSecrets: appScope.appSecrets,
16053
+ oauth: buildOauth(env, install.ord),
15769
16054
  user: envelope.user ?? null,
15770
16055
  consent: envelope.consent,
15771
16056
  geo: envelope.geo,
@@ -15820,7 +16105,7 @@ var buildContext = ({
15820
16105
  }
15821
16106
  }
15822
16107
  },
15823
- observability: buildObservability(),
16108
+ observability: buildObservability(install.ord),
15824
16109
  apps: buildAppsClient(env, envelope)
15825
16110
  };
15826
16111
  };
@@ -15847,6 +16132,48 @@ var failureMessage = (error51) => {
15847
16132
  return message.slice(0, MAX_ERROR_MESSAGE_LENGTH);
15848
16133
  };
15849
16134
 
16135
+ // src/catalog-query.ts
16136
+ var MAX_CATALOG_RESPONSE_BYTES = 5 * 1024 * 1024;
16137
+ var parseCatalogResponse = async (response) => {
16138
+ const raw = await readBody(response, MAX_CATALOG_RESPONSE_BYTES);
16139
+ if (raw === null) {
16140
+ throw new Error("The catalog query response exceeded the app runtime limit.");
16141
+ }
16142
+ let body;
16143
+ try {
16144
+ body = JSON.parse(raw);
16145
+ } catch {
16146
+ throw new Error(`The catalog query returned an invalid response (${response.status}).`);
16147
+ }
16148
+ if (!response.ok) {
16149
+ const error51 = body;
16150
+ const reason = typeof error51.message === "string" ? error51.message : typeof error51.error === "string" ? error51.error : `request failed with status ${response.status}`;
16151
+ throw new Error(`Catalog query failed: ${reason}`);
16152
+ }
16153
+ return body;
16154
+ };
16155
+ var buildCatalogQuery = ({ env, ticket, installId }) => {
16156
+ return {
16157
+ catalog: async (resourceIdentifier, sql) => {
16158
+ const platform = env;
16159
+ const context = JSON.parse(platform.APP_CONTEXT);
16160
+ const path = appUiCatalogQueryPath(context.domainId, context.slug, resourceIdentifier);
16161
+ return traced(
16162
+ "qinto.catalog.query",
16163
+ { attrs: { "app.install_id": installId, "app.catalog.resource": resourceIdentifier } },
16164
+ async () => {
16165
+ const response = await fetch(new URL(path, platform.QINTO_PLATFORM_API), {
16166
+ method: "POST",
16167
+ headers: { authorization: `Bearer ${ticket}`, "content-type": "application/json" },
16168
+ body: JSON.stringify({ sql })
16169
+ });
16170
+ return parseCatalogResponse(response);
16171
+ }
16172
+ );
16173
+ }
16174
+ };
16175
+ };
16176
+
15850
16177
  // src/schedule.ts
15851
16178
  var dispatchSchedule = async (envelope, bundle, env) => {
15852
16179
  const schedule = envelope.schedule;
@@ -15875,12 +16202,14 @@ var dispatchSchedule = async (envelope, bundle, env) => {
15875
16202
  installLabel: install.label,
15876
16203
  anonId: null
15877
16204
  }),
16205
+ oauth: buildOauth(env, install.ord),
15878
16206
  variables,
15879
16207
  secrets,
15880
16208
  appVariables: appScope.appVariables,
15881
16209
  appSecrets: appScope.appSecrets,
15882
- observability: buildObservability(),
16210
+ observability: buildObservability(install.ord),
15883
16211
  apps: buildAppsClient(env, { depth: 0 }),
16212
+ query: buildCatalogQuery({ env, ticket: install.catalogQueryTicket, installId: install.id }),
15884
16213
  push: async (userId, message) => {
15885
16214
  await brokerPush(env, {
15886
16215
  userId,
@@ -15940,7 +16269,7 @@ var lifecycleContext = (env, envelope) => {
15940
16269
  installLabel: install.label,
15941
16270
  anonId: null
15942
16271
  }),
15943
- observability: buildObservability()
16272
+ observability: buildObservability(install.ord)
15944
16273
  };
15945
16274
  };
15946
16275
  var migrationContext = (env, envelope) => {
@@ -15950,7 +16279,9 @@ var migrationContext = (env, envelope) => {
15950
16279
  site: envelope.site,
15951
16280
  appVariables: appScope.appVariables,
15952
16281
  appSecrets: appScope.appSecrets,
15953
- observability: buildObservability()
16282
+ // A migration runs once for the site, not once per install, so it has no install to attribute
16283
+ // an event or a saved error to.
16284
+ observability: buildObservability(null)
15954
16285
  };
15955
16286
  };
15956
16287
  var runStatementMigration = async (env, migration) => {
@@ -16079,19 +16410,22 @@ var handlerRequest = (request, context, body) => {
16079
16410
  body: body.byteLength === 0 ? void 0 : body
16080
16411
  });
16081
16412
  };
16082
- var buildContext2 = ({ env, maps, kinds, appScope, install }) => {
16413
+ var buildContext2 = ({ env, maps, kinds, appScope, install, site, params }) => {
16083
16414
  const { variables, secrets } = projectInstallValues(maps, kinds, install.ord);
16084
16415
  return {
16085
16416
  env,
16417
+ site,
16418
+ params,
16086
16419
  install: { id: install.id, label: install.label },
16087
16420
  // An endpoint has no visitor, so a workflow it starts cannot push. `push` below is the
16088
16421
  // exception: it names its subject rather than inheriting one.
16089
16422
  startWorkflow: buildStartWorkflow({ ord: install.ord, installId: install.id, installLabel: install.label, anonId: null }),
16423
+ oauth: buildOauth(env, install.ord),
16090
16424
  variables,
16091
16425
  secrets,
16092
16426
  appVariables: appScope.appVariables,
16093
16427
  appSecrets: appScope.appSecrets,
16094
- observability: buildObservability(),
16428
+ observability: buildObservability(install.ord),
16095
16429
  apps: buildAppsClient(env, { depth: 0 }),
16096
16430
  push: async (userId, message) => {
16097
16431
  await brokerPush(env, {
@@ -16154,7 +16488,8 @@ var dispatchEndpoint = async ({ request, env, versions }) => {
16154
16488
  return Response.json({ error: "unknown version" }, { status: 404 });
16155
16489
  }
16156
16490
  const bundle = versions[context.version];
16157
- const candidate = bundle?.endpoints?.[context.path];
16491
+ const endpoints = bundle?.endpoints;
16492
+ const candidate = endpoints !== void 0 && Object.hasOwn(endpoints, context.route) ? endpoints[context.route] : void 0;
16158
16493
  if (typeof candidate !== "function") {
16159
16494
  return Response.json({ error: "not_found" }, { status: 404 });
16160
16495
  }
@@ -16165,7 +16500,10 @@ var dispatchEndpoint = async ({ request, env, versions }) => {
16165
16500
  const appScope = projectAppValues(env, appVariableKinds);
16166
16501
  const invoke = async (install) => {
16167
16502
  return installSpan(install, () => {
16168
- return handler(handlerRequest(request, context, body), buildContext2({ env, maps, kinds: variableKinds, appScope, install }));
16503
+ return handler(
16504
+ handlerRequest(request, context, body),
16505
+ buildContext2({ env, maps, kinds: variableKinds, appScope, install, site: context.site, params: context.params })
16506
+ );
16169
16507
  });
16170
16508
  };
16171
16509
  const installs = [...context.installs].sort((left, right) => {
@@ -16188,29 +16526,38 @@ var appSlug = (context) => {
16188
16526
  return "app";
16189
16527
  }
16190
16528
  };
16191
- var withSpans = (response, record2) => {
16529
+ var withSpans = (response, record2, collector) => {
16192
16530
  const headers = new Headers(response.headers);
16193
16531
  headers.set(APP_SPANS_HEADER, serializeAppSpans(record2));
16532
+ headers.delete(APP_OBSERVABILITY_HEADER);
16533
+ const observability = collector.record();
16534
+ if (observability !== null) {
16535
+ headers.set(APP_OBSERVABILITY_HEADER, serializeAppObservability(observability));
16536
+ }
16194
16537
  return new Response(response.body, { status: response.status, statusText: response.statusText, headers });
16195
16538
  };
16196
16539
  var runAppInvocation = async (input, handle) => {
16197
16540
  let record2 = { spans: [] };
16541
+ const redact = spanRedactor(input.secrets);
16542
+ const collector = createObservabilityCollector(redact);
16198
16543
  try {
16199
16544
  const response = await runInvocation(
16200
16545
  {
16201
16546
  name: appSlug(input.context),
16202
16547
  trace: parseTraceContext(input.request.headers.get(TRACE_HEADER)),
16203
16548
  budget: APP_SPANS_JSON_BUDGET,
16204
- redact: spanRedactor(input.secrets),
16549
+ redact,
16205
16550
  emit: (emitted) => {
16206
16551
  record2 = emitted;
16207
16552
  }
16208
16553
  },
16209
- handle
16554
+ () => {
16555
+ return runWithObservability(collector, handle);
16556
+ }
16210
16557
  );
16211
- return withSpans(response, record2);
16558
+ return withSpans(response, record2, collector);
16212
16559
  } catch {
16213
- return withSpans(input.failure(), record2);
16560
+ return withSpans(input.failure(), record2, collector);
16214
16561
  }
16215
16562
  };
16216
16563
 
@@ -16236,21 +16583,21 @@ var capturedBody = async (open) => {
16236
16583
  return void 0;
16237
16584
  }
16238
16585
  const reader = body.getReader();
16239
- const decoder2 = new TextDecoder();
16586
+ const decoder3 = new TextDecoder();
16240
16587
  const expired = new Promise((resolve) => {
16241
16588
  timer = setTimeout(() => resolve(null), READ_BUDGET_MS);
16242
16589
  });
16243
16590
  let text = "";
16244
16591
  for (; ; ) {
16245
- const read = await Promise.race([reader.read(), expired]);
16246
- if (read === null) {
16592
+ const read2 = await Promise.race([reader.read(), expired]);
16593
+ if (read2 === null) {
16247
16594
  abandon(reader);
16248
16595
  return INCOMPLETE;
16249
16596
  }
16250
- if (read.done) {
16597
+ if (read2.done) {
16251
16598
  break;
16252
16599
  }
16253
- text += decoder2.decode(read.value, { stream: true });
16600
+ text += decoder3.decode(read2.value, { stream: true });
16254
16601
  if (text.length > MAX_BODY_CHARS) {
16255
16602
  abandon(reader);
16256
16603
  return TOO_LARGE;
@@ -16362,6 +16709,9 @@ var dispatchUi = async ({ request, env, versions }) => {
16362
16709
  env,
16363
16710
  install: { id: context.install.id, label: context.install.label },
16364
16711
  installs: projectAllInstallValues(maps, variableKinds, context.installs),
16712
+ // The install is named rather than inherited: one panel serves every install, and reading the
16713
+ // subject install's token while showing another account's UI would be silently wrong.
16714
+ oauth: buildUiOauth(env, context.installs),
16365
16715
  settings: buildSettings({ env, ticket: context.writeTicket }),
16366
16716
  // The panel's viewer is a portal user, not a visitor, so a workflow it starts carries no subject.
16367
16717
  startWorkflow: buildUiStartWorkflow(
@@ -16373,7 +16723,7 @@ var dispatchUi = async ({ request, env, versions }) => {
16373
16723
  appVariables,
16374
16724
  appSecrets,
16375
16725
  viewer: context.viewer,
16376
- observability: buildObservability(),
16726
+ observability: buildObservability(context.install.ord),
16377
16727
  apps: buildAppsClient(env, { depth: 0 })
16378
16728
  };
16379
16729
  let response;