@blotout/qinto-cli 0.1.6 → 0.1.8

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.
@@ -14584,7 +14584,7 @@ var envelopeRequestSchema = external_exports.object({
14584
14584
  globalId: external_exports.string().min(1).max(64).nullable(),
14585
14585
  properties: external_exports.record(external_exports.string(), external_exports.unknown()),
14586
14586
  geo: external_exports.record(external_exports.string(), external_exports.string()).optional()
14587
- }).strict(),
14587
+ }).strict().optional(),
14588
14588
  consent: external_exports.object({ enabled: external_exports.boolean(), categories: external_exports.record(external_exports.string(), external_exports.boolean()), apps: external_exports.record(external_exports.string(), external_exports.boolean()) }).strict().optional(),
14589
14589
  geo: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
14590
14590
  // Only what this app captured or wrote for this visitor; `updatedAt` is when the current value
@@ -14608,7 +14608,11 @@ var envelopeRequestSchema = external_exports.object({
14608
14608
  browser: external_exports.record(external_exports.string().min(1).max(64), external_exports.unknown()).optional(),
14609
14609
  origin: external_exports.object({ app: external_exports.string().min(1).max(30), install: external_exports.number().int().min(1), depth: external_exports.number().int().min(0) }).strict().optional()
14610
14610
  }).strict().optional()
14611
- }).strict();
14611
+ }).strict().superRefine((envelope, ctx) => {
14612
+ if (envelope.kind !== "rpc" && envelope.user === void 0) {
14613
+ ctx.addIssue({ code: "custom", path: ["user"], message: `A ${envelope.kind} envelope needs a user` });
14614
+ }
14615
+ });
14612
14616
  var envelopeResponseSchema = external_exports.object({
14613
14617
  v: external_exports.literal(ENVELOPE_VERSION),
14614
14618
  // Transform invocations only: pass the action through, drop it, or replace its payload (§5.4).
@@ -14688,7 +14692,14 @@ var scheduleSchema = external_exports.object({
14688
14692
  id: external_exports.string().min(1).max(32),
14689
14693
  cron: external_exports.string().min(1).max(MAX_CRON_LENGTH),
14690
14694
  scheduledTime: external_exports.number().int().nonnegative().safe(),
14691
- 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)
14692
14703
  }).strict();
14693
14704
  var controlEnvelopeSchema = external_exports.object({
14694
14705
  v: external_exports.literal(CONTROL_ENVELOPE_VERSION),
@@ -14756,6 +14767,9 @@ var MAX_UI_BODY_BYTES = 1024 * 1024;
14756
14767
  var appUiWritePath = (domainId, appSlug2) => {
14757
14768
  return `/app-ui/sites/${domainId}/apps/${appSlug2}/install-variables`;
14758
14769
  };
14770
+ var appUiCatalogQueryPath = (domainId, appSlug2, resourceIdentifier) => {
14771
+ return `/app-ui/sites/${domainId}/apps/${appSlug2}/catalogs/${resourceIdentifier}/query`;
14772
+ };
14759
14773
  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();
14760
14774
  var uiContextSchema = external_exports.object({
14761
14775
  version: external_exports.string().min(1).max(32),
@@ -15361,6 +15375,7 @@ var brokerActive = async (env, input) => {
15361
15375
  };
15362
15376
  var buildAppsClient = (env, envelope) => {
15363
15377
  const caller = callerSlug(env);
15378
+ const subject = envelope.user?.anonId;
15364
15379
  return {
15365
15380
  call: async (callee, fn, args, options) => {
15366
15381
  const answer = await askBroker(env, `apps.call ${callee}.${fn}`, {
@@ -15368,7 +15383,9 @@ var buildAppsClient = (env, envelope) => {
15368
15383
  kind: "rpc",
15369
15384
  slug: caller,
15370
15385
  key: env.QINTO_INTERNAL_KEY,
15371
- userId: envelope.user.anonId,
15386
+ // An endpoint, schedule or panel calls with no visitor; the broker then reaches the callee
15387
+ // with no user block and its ctx.user is null.
15388
+ ...subject === void 0 ? {} : { userId: subject },
15372
15389
  callee,
15373
15390
  fn,
15374
15391
  args,
@@ -15430,6 +15447,14 @@ var buildObservability = () => {
15430
15447
  };
15431
15448
  };
15432
15449
 
15450
+ // src/subject.ts
15451
+ var requireSubject = (anonId, what) => {
15452
+ if (anonId === null) {
15453
+ throw new Error(`${what} needs a visitor, and this invocation has none.`);
15454
+ }
15455
+ return anonId;
15456
+ };
15457
+
15433
15458
  // src/variables.ts
15434
15459
  var parseVariableMaps = (env) => {
15435
15460
  const maps = /* @__PURE__ */ new Map();
@@ -15565,9 +15590,9 @@ var readOnce = (env, subject) => {
15565
15590
  return pending;
15566
15591
  };
15567
15592
  };
15568
- var fetchedContext = (env, anonId, collector) => {
15593
+ var fetchedContext = (env, subject, collector) => {
15569
15594
  const read = readOnce(env, () => {
15570
- return anonId;
15595
+ return subject("context.load");
15571
15596
  });
15572
15597
  return {
15573
15598
  private: {
@@ -15575,6 +15600,7 @@ var fetchedContext = (env, anonId, collector) => {
15575
15600
  return (await read()).private;
15576
15601
  },
15577
15602
  set: (patch) => {
15603
+ subject("context.set");
15578
15604
  collector.set("private", patch);
15579
15605
  }
15580
15606
  },
@@ -15583,6 +15609,7 @@ var fetchedContext = (env, anonId, collector) => {
15583
15609
  return (await read()).shared;
15584
15610
  },
15585
15611
  set: (patch) => {
15612
+ subject("context.set");
15586
15613
  collector.set("shared", patch);
15587
15614
  }
15588
15615
  }
@@ -15654,20 +15681,15 @@ var readStamp = (payload) => {
15654
15681
  }
15655
15682
  return { q: stamp, payload: params?.payload };
15656
15683
  };
15657
- var requireSubject = (anonId, what) => {
15658
- if (anonId === null) {
15659
- throw new Error(`${what} needs a visitor, and this workflow was started without one.`);
15660
- }
15661
- return anonId;
15662
- };
15663
15684
  var buildWorkflowContext = (env, stamp) => {
15664
15685
  const { variableKinds, appVariableKinds } = contextKindsOf(env);
15665
15686
  const { variables, secrets } = projectInstallValues(parseVariableMaps(env), variableKinds, stamp.ord);
15666
15687
  const { appVariables, appSecrets } = projectAppValues(env, appVariableKinds);
15667
15688
  const install = { id: stamp.installId, label: stamp.installLabel };
15668
- const client = (anonId) => {
15669
- return buildAppsClient(env, { user: { anonId, globalId: null, properties: {} }, depth: 0 });
15670
- };
15689
+ const apps = buildAppsClient(env, {
15690
+ ...stamp.anonId === null ? {} : { user: { anonId: stamp.anonId, globalId: null, properties: {} } },
15691
+ depth: 0
15692
+ });
15671
15693
  return {
15672
15694
  env,
15673
15695
  install,
@@ -15690,15 +15712,7 @@ var buildWorkflowContext = (env, stamp) => {
15690
15712
  broadcast: message.broadcast
15691
15713
  });
15692
15714
  },
15693
- apps: {
15694
- call: async (slug, fn, args, options) => {
15695
- return client(requireSubject(stamp.anonId, "apps.call")).call(slug, fn, args, options);
15696
- },
15697
- // The broker's installs body carries no subject, so this keeps working without one.
15698
- installs: async (slug) => {
15699
- return client(stamp.anonId ?? "").installs(slug);
15700
- }
15701
- }
15715
+ apps
15702
15716
  };
15703
15717
  };
15704
15718
  var handlerFor = (bundle, className) => {
@@ -15747,6 +15761,7 @@ var buildContext = ({
15747
15761
  context
15748
15762
  }) => {
15749
15763
  const { variables, secrets } = projectInstallValues(maps, kinds, install.ord);
15764
+ const subject = envelope.user?.anonId ?? null;
15750
15765
  return {
15751
15766
  env,
15752
15767
  site: envelope.site,
@@ -15755,18 +15770,19 @@ var buildContext = ({
15755
15770
  ord: install.ord,
15756
15771
  installId: install.id,
15757
15772
  installLabel: install.label,
15758
- anonId: envelope.user.anonId
15773
+ anonId: subject
15759
15774
  }),
15760
15775
  variables,
15761
15776
  secrets,
15762
15777
  appVariables: appScope.appVariables,
15763
15778
  appSecrets: appScope.appSecrets,
15764
- user: envelope.user,
15779
+ user: envelope.user ?? null,
15765
15780
  consent: envelope.consent,
15766
15781
  geo: envelope.geo,
15767
15782
  // Collect one entry past the per-install cap: the platform rejects an over-limit group whole,
15768
15783
  // and the extra entry keeps it visibly over while bounding memory.
15769
15784
  emit: (event) => {
15785
+ requireSubject(subject, "emit");
15770
15786
  if (event.type !== "track") {
15771
15787
  throw new Error("only track events may be emitted");
15772
15788
  }
@@ -15779,6 +15795,7 @@ var buildContext = ({
15779
15795
  }
15780
15796
  },
15781
15797
  push: (message) => {
15798
+ requireSubject(subject, "push");
15782
15799
  if (pushes.length <= MAX_PUSHES_PER_INSTALL) {
15783
15800
  pushes.push(message);
15784
15801
  }
@@ -15786,6 +15803,7 @@ var buildContext = ({
15786
15803
  attribution: {
15787
15804
  values: envelope.attribution ?? {},
15788
15805
  set: (key, value) => {
15806
+ requireSubject(subject, "attribution.set");
15789
15807
  if (attribution.length <= MAX_ATTRIBUTION_WRITES_PER_INSTALL) {
15790
15808
  attribution.push({ key, value });
15791
15809
  }
@@ -15793,19 +15811,21 @@ var buildContext = ({
15793
15811
  },
15794
15812
  facts: {
15795
15813
  list: (input) => {
15796
- return brokerFactsRead(env, envelope.user.anonId, input);
15814
+ return brokerFactsRead(env, requireSubject(subject, "facts.list"), input);
15797
15815
  }
15798
15816
  },
15799
15817
  context: {
15800
15818
  private: {
15801
15819
  entry: envelope.context?.private ?? null,
15802
15820
  set: (patch) => {
15821
+ requireSubject(subject, "context.set");
15803
15822
  context.set("private", patch);
15804
15823
  }
15805
15824
  },
15806
15825
  shared: {
15807
15826
  byApp: envelope.context?.shared ?? {},
15808
15827
  set: (patch) => {
15828
+ requireSubject(subject, "context.set");
15809
15829
  context.set("shared", patch);
15810
15830
  }
15811
15831
  }
@@ -15837,6 +15857,48 @@ var failureMessage = (error51) => {
15837
15857
  return message.slice(0, MAX_ERROR_MESSAGE_LENGTH);
15838
15858
  };
15839
15859
 
15860
+ // src/catalog-query.ts
15861
+ var MAX_CATALOG_RESPONSE_BYTES = 5 * 1024 * 1024;
15862
+ var parseCatalogResponse = async (response) => {
15863
+ const raw = await readBody(response, MAX_CATALOG_RESPONSE_BYTES);
15864
+ if (raw === null) {
15865
+ throw new Error("The catalog query response exceeded the app runtime limit.");
15866
+ }
15867
+ let body;
15868
+ try {
15869
+ body = JSON.parse(raw);
15870
+ } catch {
15871
+ throw new Error(`The catalog query returned an invalid response (${response.status}).`);
15872
+ }
15873
+ if (!response.ok) {
15874
+ const error51 = body;
15875
+ const reason = typeof error51.message === "string" ? error51.message : typeof error51.error === "string" ? error51.error : `request failed with status ${response.status}`;
15876
+ throw new Error(`Catalog query failed: ${reason}`);
15877
+ }
15878
+ return body;
15879
+ };
15880
+ var buildCatalogQuery = ({ env, ticket, installId }) => {
15881
+ return {
15882
+ catalog: async (resourceIdentifier, sql) => {
15883
+ const platform = env;
15884
+ const context = JSON.parse(platform.APP_CONTEXT);
15885
+ const path = appUiCatalogQueryPath(context.domainId, context.slug, resourceIdentifier);
15886
+ return traced(
15887
+ "qinto.catalog.query",
15888
+ { attrs: { "app.install_id": installId, "app.catalog.resource": resourceIdentifier } },
15889
+ async () => {
15890
+ const response = await fetch(new URL(path, platform.QINTO_PLATFORM_API), {
15891
+ method: "POST",
15892
+ headers: { authorization: `Bearer ${ticket}`, "content-type": "application/json" },
15893
+ body: JSON.stringify({ sql })
15894
+ });
15895
+ return parseCatalogResponse(response);
15896
+ }
15897
+ );
15898
+ }
15899
+ };
15900
+ };
15901
+
15840
15902
  // src/schedule.ts
15841
15903
  var dispatchSchedule = async (envelope, bundle, env) => {
15842
15904
  const schedule = envelope.schedule;
@@ -15870,6 +15932,8 @@ var dispatchSchedule = async (envelope, bundle, env) => {
15870
15932
  appVariables: appScope.appVariables,
15871
15933
  appSecrets: appScope.appSecrets,
15872
15934
  observability: buildObservability(),
15935
+ apps: buildAppsClient(env, { depth: 0 }),
15936
+ query: buildCatalogQuery({ env, ticket: install.catalogQueryTicket, installId: install.id }),
15873
15937
  push: async (userId, message) => {
15874
15938
  await brokerPush(env, {
15875
15939
  userId,
@@ -16073,14 +16137,15 @@ var buildContext2 = ({ env, maps, kinds, appScope, install }) => {
16073
16137
  return {
16074
16138
  env,
16075
16139
  install: { id: install.id, label: install.label },
16076
- // An endpoint has no visitor, so a workflow it starts cannot push or call other apps. `push`
16077
- // below is the exception: it names its subject rather than inheriting one.
16140
+ // An endpoint has no visitor, so a workflow it starts cannot push. `push` below is the
16141
+ // exception: it names its subject rather than inheriting one.
16078
16142
  startWorkflow: buildStartWorkflow({ ord: install.ord, installId: install.id, installLabel: install.label, anonId: null }),
16079
16143
  variables,
16080
16144
  secrets,
16081
16145
  appVariables: appScope.appVariables,
16082
16146
  appSecrets: appScope.appSecrets,
16083
16147
  observability: buildObservability(),
16148
+ apps: buildAppsClient(env, { depth: 0 }),
16084
16149
  push: async (userId, message) => {
16085
16150
  await brokerPush(env, {
16086
16151
  userId,
@@ -16361,7 +16426,8 @@ var dispatchUi = async ({ request, env, versions }) => {
16361
16426
  appVariables,
16362
16427
  appSecrets,
16363
16428
  viewer: context.viewer,
16364
- observability: buildObservability()
16429
+ observability: buildObservability(),
16430
+ apps: buildAppsClient(env, { depth: 0 })
16365
16431
  };
16366
16432
  let response;
16367
16433
  try {
@@ -16382,6 +16448,29 @@ var dispatchUi = async ({ request, env, versions }) => {
16382
16448
  return response;
16383
16449
  };
16384
16450
 
16451
+ // src/durable-object.ts
16452
+ var VERSION_KEY = "qintoSchemaVersion";
16453
+ var applyDurableObjectMigrations = (storage, migrations) => {
16454
+ const applied = storage.kv.get(VERSION_KEY) ?? 0;
16455
+ for (const [index, migration] of migrations.entries()) {
16456
+ if (index < applied) {
16457
+ continue;
16458
+ }
16459
+ for (const statement of migration.statements) {
16460
+ storage.sql.exec(statement);
16461
+ }
16462
+ storage.kv.put(VERSION_KEY, index + 1);
16463
+ }
16464
+ };
16465
+ var withAppDurableObject = (Base, migrations) => {
16466
+ return class extends Base {
16467
+ constructor(ctx, env) {
16468
+ applyDurableObjectMigrations(ctx.storage, migrations);
16469
+ super(ctx, env);
16470
+ }
16471
+ };
16472
+ };
16473
+
16385
16474
  // src/index.ts
16386
16475
  installTracedFetch();
16387
16476
  var toHandler = (candidate) => {
@@ -16528,9 +16617,16 @@ var dispatchRpc = async (envelope, bundle, env) => {
16528
16617
  const maps = parseVariableMaps(env);
16529
16618
  const { variableKinds: kinds, appVariableKinds } = contextKindsOf(env);
16530
16619
  const appScope = projectAppValues(env, appVariableKinds);
16620
+ const subject = envelope.user?.anonId ?? null;
16531
16621
  const ctx = {
16532
16622
  ...buildContext({ env, envelope, install, maps, kinds, appScope, emits, pushes, attribution, context }),
16533
- context: fetchedContext(env, envelope.user.anonId, context)
16623
+ context: fetchedContext(
16624
+ env,
16625
+ (what) => {
16626
+ return requireSubject(subject, what);
16627
+ },
16628
+ context
16629
+ )
16534
16630
  };
16535
16631
  let value;
16536
16632
  try {
@@ -16639,5 +16735,6 @@ var createRuntime = (input) => {
16639
16735
  };
16640
16736
  export {
16641
16737
  createRuntime,
16642
- createWorkflow
16738
+ createWorkflow,
16739
+ withAppDurableObject
16643
16740
  };
@@ -8,8 +8,10 @@ var withSecurityHeaders = /* @__PURE__ */ __name((response, portalOrigin) => {
8
8
  response.headers.set("content-security-policy", `frame-ancestors ${portalOrigin}`);
9
9
  response.headers.set("x-content-type-options", "nosniff");
10
10
  response.headers.set("referrer-policy", "no-referrer");
11
+ response.headers.set("vary", "sec-fetch-dest");
11
12
  return response;
12
13
  }, "withSecurityHeaders");
14
+ var NAVIGATION_DESTINATIONS = /* @__PURE__ */ new Set(["document", "object", "embed"]);
13
15
  var assetKeys = /* @__PURE__ */ __name((hostname, pathname) => {
14
16
  const dot = hostname.indexOf(".");
15
17
  const slug = dot === -1 ? hostname : hostname.slice(0, dot);
@@ -27,6 +29,10 @@ var serve = /* @__PURE__ */ __name(async (request, env) => {
27
29
  if (request.method !== "GET" && request.method !== "HEAD") {
28
30
  return new Response("method not allowed", { status: 405 });
29
31
  }
32
+ const destination = request.headers.get("sec-fetch-dest");
33
+ if (destination !== null && NAVIGATION_DESTINATIONS.has(destination)) {
34
+ return new Response("unauthorized", { status: 401 });
35
+ }
30
36
  const url = new URL(request.url);
31
37
  const keys = assetKeys(url.hostname, url.pathname);
32
38
  if (keys === null) {