@blotout/qinto-cli 0.1.5 → 0.1.7

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).
@@ -15361,6 +15365,7 @@ var brokerActive = async (env, input) => {
15361
15365
  };
15362
15366
  var buildAppsClient = (env, envelope) => {
15363
15367
  const caller = callerSlug(env);
15368
+ const subject = envelope.user?.anonId;
15364
15369
  return {
15365
15370
  call: async (callee, fn, args, options) => {
15366
15371
  const answer = await askBroker(env, `apps.call ${callee}.${fn}`, {
@@ -15368,7 +15373,9 @@ var buildAppsClient = (env, envelope) => {
15368
15373
  kind: "rpc",
15369
15374
  slug: caller,
15370
15375
  key: env.QINTO_INTERNAL_KEY,
15371
- userId: envelope.user.anonId,
15376
+ // An endpoint, schedule or panel calls with no visitor; the broker then reaches the callee
15377
+ // with no user block and its ctx.user is null.
15378
+ ...subject === void 0 ? {} : { userId: subject },
15372
15379
  callee,
15373
15380
  fn,
15374
15381
  args,
@@ -15430,6 +15437,14 @@ var buildObservability = () => {
15430
15437
  };
15431
15438
  };
15432
15439
 
15440
+ // src/subject.ts
15441
+ var requireSubject = (anonId, what) => {
15442
+ if (anonId === null) {
15443
+ throw new Error(`${what} needs a visitor, and this invocation has none.`);
15444
+ }
15445
+ return anonId;
15446
+ };
15447
+
15433
15448
  // src/variables.ts
15434
15449
  var parseVariableMaps = (env) => {
15435
15450
  const maps = /* @__PURE__ */ new Map();
@@ -15565,9 +15580,9 @@ var readOnce = (env, subject) => {
15565
15580
  return pending;
15566
15581
  };
15567
15582
  };
15568
- var fetchedContext = (env, anonId, collector) => {
15583
+ var fetchedContext = (env, subject, collector) => {
15569
15584
  const read = readOnce(env, () => {
15570
- return anonId;
15585
+ return subject("context.load");
15571
15586
  });
15572
15587
  return {
15573
15588
  private: {
@@ -15575,6 +15590,7 @@ var fetchedContext = (env, anonId, collector) => {
15575
15590
  return (await read()).private;
15576
15591
  },
15577
15592
  set: (patch) => {
15593
+ subject("context.set");
15578
15594
  collector.set("private", patch);
15579
15595
  }
15580
15596
  },
@@ -15583,6 +15599,7 @@ var fetchedContext = (env, anonId, collector) => {
15583
15599
  return (await read()).shared;
15584
15600
  },
15585
15601
  set: (patch) => {
15602
+ subject("context.set");
15586
15603
  collector.set("shared", patch);
15587
15604
  }
15588
15605
  }
@@ -15654,20 +15671,15 @@ var readStamp = (payload) => {
15654
15671
  }
15655
15672
  return { q: stamp, payload: params?.payload };
15656
15673
  };
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
15674
  var buildWorkflowContext = (env, stamp) => {
15664
15675
  const { variableKinds, appVariableKinds } = contextKindsOf(env);
15665
15676
  const { variables, secrets } = projectInstallValues(parseVariableMaps(env), variableKinds, stamp.ord);
15666
15677
  const { appVariables, appSecrets } = projectAppValues(env, appVariableKinds);
15667
15678
  const install = { id: stamp.installId, label: stamp.installLabel };
15668
- const client = (anonId) => {
15669
- return buildAppsClient(env, { user: { anonId, globalId: null, properties: {} }, depth: 0 });
15670
- };
15679
+ const apps = buildAppsClient(env, {
15680
+ ...stamp.anonId === null ? {} : { user: { anonId: stamp.anonId, globalId: null, properties: {} } },
15681
+ depth: 0
15682
+ });
15671
15683
  return {
15672
15684
  env,
15673
15685
  install,
@@ -15690,15 +15702,7 @@ var buildWorkflowContext = (env, stamp) => {
15690
15702
  broadcast: message.broadcast
15691
15703
  });
15692
15704
  },
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
- }
15705
+ apps
15702
15706
  };
15703
15707
  };
15704
15708
  var handlerFor = (bundle, className) => {
@@ -15747,6 +15751,7 @@ var buildContext = ({
15747
15751
  context
15748
15752
  }) => {
15749
15753
  const { variables, secrets } = projectInstallValues(maps, kinds, install.ord);
15754
+ const subject = envelope.user?.anonId ?? null;
15750
15755
  return {
15751
15756
  env,
15752
15757
  site: envelope.site,
@@ -15755,18 +15760,19 @@ var buildContext = ({
15755
15760
  ord: install.ord,
15756
15761
  installId: install.id,
15757
15762
  installLabel: install.label,
15758
- anonId: envelope.user.anonId
15763
+ anonId: subject
15759
15764
  }),
15760
15765
  variables,
15761
15766
  secrets,
15762
15767
  appVariables: appScope.appVariables,
15763
15768
  appSecrets: appScope.appSecrets,
15764
- user: envelope.user,
15769
+ user: envelope.user ?? null,
15765
15770
  consent: envelope.consent,
15766
15771
  geo: envelope.geo,
15767
15772
  // Collect one entry past the per-install cap: the platform rejects an over-limit group whole,
15768
15773
  // and the extra entry keeps it visibly over while bounding memory.
15769
15774
  emit: (event) => {
15775
+ requireSubject(subject, "emit");
15770
15776
  if (event.type !== "track") {
15771
15777
  throw new Error("only track events may be emitted");
15772
15778
  }
@@ -15779,6 +15785,7 @@ var buildContext = ({
15779
15785
  }
15780
15786
  },
15781
15787
  push: (message) => {
15788
+ requireSubject(subject, "push");
15782
15789
  if (pushes.length <= MAX_PUSHES_PER_INSTALL) {
15783
15790
  pushes.push(message);
15784
15791
  }
@@ -15786,6 +15793,7 @@ var buildContext = ({
15786
15793
  attribution: {
15787
15794
  values: envelope.attribution ?? {},
15788
15795
  set: (key, value) => {
15796
+ requireSubject(subject, "attribution.set");
15789
15797
  if (attribution.length <= MAX_ATTRIBUTION_WRITES_PER_INSTALL) {
15790
15798
  attribution.push({ key, value });
15791
15799
  }
@@ -15793,19 +15801,21 @@ var buildContext = ({
15793
15801
  },
15794
15802
  facts: {
15795
15803
  list: (input) => {
15796
- return brokerFactsRead(env, envelope.user.anonId, input);
15804
+ return brokerFactsRead(env, requireSubject(subject, "facts.list"), input);
15797
15805
  }
15798
15806
  },
15799
15807
  context: {
15800
15808
  private: {
15801
15809
  entry: envelope.context?.private ?? null,
15802
15810
  set: (patch) => {
15811
+ requireSubject(subject, "context.set");
15803
15812
  context.set("private", patch);
15804
15813
  }
15805
15814
  },
15806
15815
  shared: {
15807
15816
  byApp: envelope.context?.shared ?? {},
15808
15817
  set: (patch) => {
15818
+ requireSubject(subject, "context.set");
15809
15819
  context.set("shared", patch);
15810
15820
  }
15811
15821
  }
@@ -15870,6 +15880,7 @@ var dispatchSchedule = async (envelope, bundle, env) => {
15870
15880
  appVariables: appScope.appVariables,
15871
15881
  appSecrets: appScope.appSecrets,
15872
15882
  observability: buildObservability(),
15883
+ apps: buildAppsClient(env, { depth: 0 }),
15873
15884
  push: async (userId, message) => {
15874
15885
  await brokerPush(env, {
15875
15886
  userId,
@@ -16073,14 +16084,15 @@ var buildContext2 = ({ env, maps, kinds, appScope, install }) => {
16073
16084
  return {
16074
16085
  env,
16075
16086
  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.
16087
+ // An endpoint has no visitor, so a workflow it starts cannot push. `push` below is the
16088
+ // exception: it names its subject rather than inheriting one.
16078
16089
  startWorkflow: buildStartWorkflow({ ord: install.ord, installId: install.id, installLabel: install.label, anonId: null }),
16079
16090
  variables,
16080
16091
  secrets,
16081
16092
  appVariables: appScope.appVariables,
16082
16093
  appSecrets: appScope.appSecrets,
16083
16094
  observability: buildObservability(),
16095
+ apps: buildAppsClient(env, { depth: 0 }),
16084
16096
  push: async (userId, message) => {
16085
16097
  await brokerPush(env, {
16086
16098
  userId,
@@ -16361,7 +16373,8 @@ var dispatchUi = async ({ request, env, versions }) => {
16361
16373
  appVariables,
16362
16374
  appSecrets,
16363
16375
  viewer: context.viewer,
16364
- observability: buildObservability()
16376
+ observability: buildObservability(),
16377
+ apps: buildAppsClient(env, { depth: 0 })
16365
16378
  };
16366
16379
  let response;
16367
16380
  try {
@@ -16382,6 +16395,29 @@ var dispatchUi = async ({ request, env, versions }) => {
16382
16395
  return response;
16383
16396
  };
16384
16397
 
16398
+ // src/durable-object.ts
16399
+ var VERSION_KEY = "qintoSchemaVersion";
16400
+ var applyDurableObjectMigrations = (storage, migrations) => {
16401
+ const applied = storage.kv.get(VERSION_KEY) ?? 0;
16402
+ for (const [index, migration] of migrations.entries()) {
16403
+ if (index < applied) {
16404
+ continue;
16405
+ }
16406
+ for (const statement of migration.statements) {
16407
+ storage.sql.exec(statement);
16408
+ }
16409
+ storage.kv.put(VERSION_KEY, index + 1);
16410
+ }
16411
+ };
16412
+ var withAppDurableObject = (Base, migrations) => {
16413
+ return class extends Base {
16414
+ constructor(ctx, env) {
16415
+ applyDurableObjectMigrations(ctx.storage, migrations);
16416
+ super(ctx, env);
16417
+ }
16418
+ };
16419
+ };
16420
+
16385
16421
  // src/index.ts
16386
16422
  installTracedFetch();
16387
16423
  var toHandler = (candidate) => {
@@ -16528,9 +16564,16 @@ var dispatchRpc = async (envelope, bundle, env) => {
16528
16564
  const maps = parseVariableMaps(env);
16529
16565
  const { variableKinds: kinds, appVariableKinds } = contextKindsOf(env);
16530
16566
  const appScope = projectAppValues(env, appVariableKinds);
16567
+ const subject = envelope.user?.anonId ?? null;
16531
16568
  const ctx = {
16532
16569
  ...buildContext({ env, envelope, install, maps, kinds, appScope, emits, pushes, attribution, context }),
16533
- context: fetchedContext(env, envelope.user.anonId, context)
16570
+ context: fetchedContext(
16571
+ env,
16572
+ (what) => {
16573
+ return requireSubject(subject, what);
16574
+ },
16575
+ context
16576
+ )
16534
16577
  };
16535
16578
  let value;
16536
16579
  try {
@@ -16639,5 +16682,6 @@ var createRuntime = (input) => {
16639
16682
  };
16640
16683
  export {
16641
16684
  createRuntime,
16642
- createWorkflow
16685
+ createWorkflow,
16686
+ withAppDurableObject
16643
16687
  };
@@ -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) {