@blotout/qinto-cli 0.1.16 → 0.1.18

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.
@@ -18115,10 +18115,10 @@ var stringbool = (...args) => _stringbool({
18115
18115
  String: ZodString
18116
18116
  }, ...args);
18117
18117
  function json(params) {
18118
- const jsonSchema = lazy(() => {
18119
- return union([string2(params), number2(), boolean2(), _null3(), array(jsonSchema), record(string2(), jsonSchema)]);
18118
+ const jsonSchema2 = lazy(() => {
18119
+ return union([string2(params), number2(), boolean2(), _null3(), array(jsonSchema2), record(string2(), jsonSchema2)]);
18120
18120
  });
18121
- return jsonSchema;
18121
+ return jsonSchema2;
18122
18122
  }
18123
18123
  function preprocess(fn, schema) {
18124
18124
  return new ZodPreprocess({
@@ -18945,6 +18945,7 @@ function date4(params) {
18945
18945
  var IDENTIFIER = /^[a-z][a-z0-9-]*$/;
18946
18946
  var VARIABLE_NAME = /^[A-Z][A-Z0-9_]*$/;
18947
18947
  var APP_SLUG_PATTERN = /^[a-z][a-z0-9-]{2,29}$/;
18948
+ var FUNCTION_NAME = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
18948
18949
  var identifierField = external_exports.string().min(1).max(64).regex(IDENTIFIER);
18949
18950
  var httpsUrl = (field2) => {
18950
18951
  return external_exports.url().max(2048).refine((value) => value.startsWith("https://"), `${field2} must be an HTTPS URL`);
@@ -18958,6 +18959,10 @@ var uniqueOrIssue = (ctx, values, path, label) => {
18958
18959
  seen.add(value);
18959
18960
  }
18960
18961
  };
18962
+ var PLATFORM_ZONES = ["blotout.io", "qinto.tech"];
18963
+ var isPlatformHost = (host) => {
18964
+ return PLATFORM_ZONES.some((zone) => host === zone || host.endsWith(`.${zone}`));
18965
+ };
18961
18966
 
18962
18967
  // ../../../libs/app-manifest/src/oauth.ts
18963
18968
  var OAUTH_TOKEN_METHODS = ["POST", "GET"];
@@ -18987,8 +18992,7 @@ var RESERVED_AUTHORIZE_PARAMS = /* @__PURE__ */ new Set([
18987
18992
  var PRIVATE_HOST = /^(localhost|\[?::1\]?|.+\.local|.+\.internal|10\..+|127\..+|169\.254\..+|192\.168\..+|172\.(1[6-9]|2\d|3[01])\..+|\d+\.\d+\.\d+\.\d+)$/;
18988
18993
  var vendorUrl = (field2) => {
18989
18994
  return httpsUrl(field2).refine((value) => {
18990
- const host = new URL(value).hostname;
18991
- return host !== "qinto.io" && !host.endsWith(".qinto.io") && host !== "qinto.tech" && !host.endsWith(".qinto.tech");
18995
+ return !isPlatformHost(new URL(value).hostname);
18992
18996
  }, `${field2} must be the vendor's own endpoint, not a Qinto platform URL`).refine((value) => {
18993
18997
  return !PRIVATE_HOST.test(new URL(value).hostname);
18994
18998
  }, `${field2} must be a public host`).refine((value) => {
@@ -19345,7 +19349,6 @@ var MAX_MANIFEST_MIGRATION_BYTES = 1024 * 1024;
19345
19349
  var BINDING_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
19346
19350
  var CLASS_NAME = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
19347
19351
  var MIGRATION_ID = /^[a-z0-9][a-z0-9-]*$/;
19348
- var FUNCTION_NAME = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
19349
19352
  var EVENT_TOPIC = /^[a-z][a-z0-9:_-]*$/;
19350
19353
  var QUERY_PARAM_NAME = /^[A-Za-z0-9_.-]+$/;
19351
19354
  var COOKIE_NAME = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
@@ -19383,10 +19386,10 @@ var PLATFORM_APP_RESOURCE_IDENTIFIERS = {
19383
19386
  };
19384
19387
  var PLATFORM_APP_VARIABLE_IDENTIFIER_PREFIX = "var-";
19385
19388
  var PLATFORM_APP_SCOPE_VARIABLE_IDENTIFIER_PREFIX = "appvar-";
19389
+ var STORAGE_LINK_BINDING_PREFIX = "QINTO_LINK_";
19386
19390
  var legalUrl = (field2) => {
19387
19391
  return httpsUrl(field2).refine((value) => {
19388
- const host = new URL(value).hostname;
19389
- return host !== "qinto.io" && !host.endsWith(".qinto.io");
19392
+ return !isPlatformHost(new URL(value).hostname);
19390
19393
  }, `${field2} must be the developer's own document, not a Qinto platform URL`);
19391
19394
  };
19392
19395
  var bindingField = external_exports.string().min(1).max(255).regex(BINDING_NAME);
@@ -19559,7 +19562,7 @@ var captureSchema = external_exports.object({
19559
19562
  var endpointSchema = external_exports.object({
19560
19563
  path: external_exports.string().min(1).max(255).regex(ENDPOINT_PATH_PATTERN, "Endpoint paths are absolute; each segment is a literal or a :name parameter"),
19561
19564
  methods: external_exports.array(external_exports.enum(["GET", "POST", "PUT", "PATCH", "DELETE"])).min(1).max(5),
19562
- auth: external_exports.enum(["public", "token"]),
19565
+ auth: external_exports.enum(["public", "token", "visitor"]),
19563
19566
  mode: external_exports.enum(["webhook", "api"])
19564
19567
  }).strict();
19565
19568
  var eventsSchema = external_exports.object({
@@ -19653,7 +19656,7 @@ var validateBindings = (manifest, ctx) => {
19653
19656
  "variable name"
19654
19657
  );
19655
19658
  for (const [index, resource] of manifest.resources.entries()) {
19656
- if (reserved.has(resource.binding) || resource.binding.startsWith("VAR_") || resource.binding.startsWith("APPVAR_") || resource.binding.startsWith(PLATFORM_APP_OAUTH_BINDING_PREFIX)) {
19659
+ if (reserved.has(resource.binding) || resource.binding.startsWith("VAR_") || resource.binding.startsWith("APPVAR_") || resource.binding.startsWith(PLATFORM_APP_OAUTH_BINDING_PREFIX) || resource.binding.startsWith(STORAGE_LINK_BINDING_PREFIX)) {
19657
19660
  ctx.addIssue({ code: "custom", path: ["resources", index, "binding"], message: `Binding ${resource.binding} is reserved` });
19658
19661
  }
19659
19662
  if (reservedIdentifiers.has(resource.identifier) || resource.identifier.startsWith(PLATFORM_APP_VARIABLE_IDENTIFIER_PREFIX) || resource.identifier.startsWith(PLATFORM_APP_SCOPE_VARIABLE_IDENTIFIER_PREFIX)) {
@@ -19785,6 +19788,22 @@ var validatePipelines = (manifest, ctx) => {
19785
19788
  }
19786
19789
  }
19787
19790
  };
19791
+ var validateVisitorEndpoint = (manifest, ctx, endpoint, index) => {
19792
+ if (endpoint.mode !== "api") {
19793
+ ctx.addIssue({
19794
+ code: "custom",
19795
+ path: ["endpoints", index, "mode"],
19796
+ message: "A visitor endpoint answers one visitor, so its mode must be api"
19797
+ });
19798
+ }
19799
+ if (manifest.browser?.entry === void 0) {
19800
+ ctx.addIssue({
19801
+ code: "custom",
19802
+ path: ["endpoints", index, "auth"],
19803
+ message: "A visitor endpoint is called by the app's own browser code, so it needs a browser entry"
19804
+ });
19805
+ }
19806
+ };
19788
19807
  var validateEndpoints = (manifest, ctx) => {
19789
19808
  const shapes = /* @__PURE__ */ new Map();
19790
19809
  for (const [index, endpoint] of manifest.endpoints.entries()) {
@@ -19810,7 +19829,9 @@ var validateEndpoints = (manifest, ctx) => {
19810
19829
  message: `Endpoint path ${endpoint.path} names the parameter ${repeated} twice`
19811
19830
  });
19812
19831
  }
19813
- if (endpoint.mode === "api" && manifest.install.multiple) {
19832
+ if (endpoint.auth === "visitor") {
19833
+ validateVisitorEndpoint(manifest, ctx, endpoint, index);
19834
+ } else if (endpoint.mode === "api" && manifest.install.multiple) {
19814
19835
  ctx.addIssue({
19815
19836
  code: "custom",
19816
19837
  path: ["endpoints", index, "mode"],
@@ -19956,8 +19977,17 @@ var appManifestSchema = baseSchema.superRefine((manifest, ctx) => {
19956
19977
  var MAX_VENDOR_BODY_BYTES = 64 * 1024;
19957
19978
  var MAX_EXPIRES_IN_SECONDS = 365 * 24 * 60 * 60;
19958
19979
 
19980
+ // ../../../libs/app-manifest/src/rpc-contract.ts
19981
+ var MAX_RPC_CONTRACT_BYTES = 256 * 1024;
19982
+ var jsonSchema = external_exports.record(external_exports.string(), external_exports.unknown());
19983
+ var rpcFunctionContractSchema = external_exports.object({ visibility: external_exports.enum(["public", "private"]), input: jsonSchema, output: jsonSchema }).strict();
19984
+ var appRpcContractSchema = external_exports.record(external_exports.string().regex(FUNCTION_NAME).max(64), rpcFunctionContractSchema).refine((contract) => Object.keys(contract).length <= 32, "An RPC contract carries at most 32 functions").refine((contract) => {
19985
+ return new TextEncoder().encode(JSON.stringify(contract)).length <= MAX_RPC_CONTRACT_BYTES;
19986
+ }, "The RPC contract exceeds the size cap");
19987
+
19959
19988
  // ../../../libs/app-envelope/src/envelope.ts
19960
- var ENVELOPE_VERSION = 2;
19989
+ var ENVELOPE_VERSION = 3;
19990
+ var ENVELOPE_ERROR_CODES = ["invalid_arguments"];
19961
19991
  var MAX_INSTALLS_PER_APP = 15;
19962
19992
  var MAX_EMITS_PER_INSTALL = 10;
19963
19993
  var MAX_PUSHES_PER_INSTALL = 10;
@@ -20011,12 +20041,12 @@ var actionContextSchema = external_exports.discriminatedUnion("source", [
20011
20041
  var envelopeRequestSchema = external_exports.object({
20012
20042
  v: external_exports.literal(ENVELOPE_VERSION),
20013
20043
  kind: external_exports.enum(["event", "connect", "transform", "rpc"]),
20014
- site: external_exports.object({ origin: external_exports.string().min(1).max(255) }).strict(),
20015
20044
  app: external_exports.object({ slug: external_exports.string().min(1).max(30), version: external_exports.string().min(1).max(32) }).strict(),
20016
20045
  installs: external_exports.array(installSchema).min(1).max(MAX_INSTALLS_PER_APP),
20017
20046
  user: external_exports.object({
20018
20047
  anonId: external_exports.string().min(1).max(64),
20019
20048
  globalId: external_exports.string().min(1).max(64).nullable(),
20049
+ createdAt: external_exports.number().int().optional(),
20020
20050
  properties: external_exports.record(external_exports.string(), external_exports.unknown()),
20021
20051
  geo: external_exports.record(external_exports.string(), external_exports.string()).optional()
20022
20052
  }).strict().optional(),
@@ -20056,13 +20086,14 @@ var envelopeResponseSchema = external_exports.object({
20056
20086
  rpc: external_exports.object({ result: external_exports.unknown() }).strict().refine((rpc) => {
20057
20087
  return new TextEncoder().encode(JSON.stringify(rpc.result ?? null)).length <= MAX_RPC_BYTES;
20058
20088
  }, "RPC result exceeds the size cap").optional(),
20059
- error: external_exports.object({ message: external_exports.string().max(2048) }).strict().optional()
20089
+ error: external_exports.object({ message: external_exports.string().max(2048), code: external_exports.enum(ENVELOPE_ERROR_CODES).optional() }).strict().optional()
20060
20090
  }).strict();
20061
20091
 
20062
20092
  // ../../../libs/app-envelope/src/control.ts
20063
- var CONTROL_ENVELOPE_VERSION = 1;
20093
+ var CONTROL_ENVELOPE_VERSION = 2;
20064
20094
  var MAX_CONTROL_BYTES = 256 * 1024;
20065
20095
  var MAX_MIGRATION_CHECKPOINT_BYTES = 16 * 1024;
20096
+ var MIGRATION_DATABASE_LINK_CHANGED = "migration_database_link_changed";
20066
20097
  var MAX_TOOL_ARGUMENTS_BYTES = 32 * 1024;
20067
20098
  var MAX_TOOL_RESULT_BYTES = 64 * 1024;
20068
20099
  var MAX_TOOL_DATA_DEPTH = 8;
@@ -20163,7 +20194,6 @@ var toolSchema2 = external_exports.object({
20163
20194
  var controlEnvelopeSchema = external_exports.object({
20164
20195
  v: external_exports.literal(CONTROL_ENVELOPE_VERSION),
20165
20196
  kind: external_exports.enum(["noop", "lifecycle", "migration", "schedule", "tool"]),
20166
- site: external_exports.object({ origin: external_exports.string().min(1).max(255) }).strict(),
20167
20197
  app: external_exports.object({ slug: external_exports.string().min(1).max(30), version: external_exports.string().min(1).max(32) }).strict(),
20168
20198
  install: 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().optional(),
20169
20199
  lifecycle: lifecycleEventSchema.optional(),
@@ -20237,6 +20267,7 @@ var controlResponseSchema = external_exports.object({
20237
20267
  var ENDPOINT_CONTEXT_HEADER = "x-qinto-endpoint-context";
20238
20268
  var ENDPOINT_OUTCOMES_HEADER = "x-qinto-endpoint-outcomes";
20239
20269
  var MAX_ENDPOINT_BODY_BYTES = 1024 * 1024;
20270
+ var MAX_VISITOR_ENDPOINT_BODY_BYTES = 2 * 1024 * 1024;
20240
20271
  var MAX_ENDPOINT_OUTCOMES_BYTES = 64 * 1024;
20241
20272
  var MAX_ENDPOINT_PATH_LENGTH = 16 * 1024;
20242
20273
  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();
@@ -20252,12 +20283,14 @@ var endpointContextSchema = external_exports.object({
20252
20283
  method: external_exports.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
20253
20284
  mode: external_exports.enum(["webhook", "api"]),
20254
20285
  version: external_exports.string().min(1).max(32),
20255
- site: external_exports.object({ origin: external_exports.string().min(1).max(255) }).strict(),
20256
20286
  geo: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
20257
- installs: external_exports.array(installSchema3).min(1).max(15)
20287
+ installs: external_exports.array(installSchema3).min(1).max(15),
20288
+ user: external_exports.object({ anonId: external_exports.string().min(1).max(64) }).strict().optional()
20258
20289
  }).strict().refine((context) => {
20259
20290
  return context.mode === "webhook" || context.installs.length === 1;
20260
- }, "An api endpoint carries exactly one install");
20291
+ }, "An api endpoint carries exactly one install").refine((context) => {
20292
+ return context.user === void 0 || context.mode === "api";
20293
+ }, "Only an api endpoint carries a visitor");
20261
20294
  var endpointOutcomesSchema = external_exports.object({
20262
20295
  outcomes: external_exports.array(
20263
20296
  external_exports.object({
@@ -20868,6 +20901,10 @@ var siteChannel = (env) => {
20868
20901
  var channelFetch = (env, init) => {
20869
20902
  return siteChannel(env).fetch(BROKER_URL, init);
20870
20903
  };
20904
+ var brokerFailure = (answer, status) => {
20905
+ const error61 = answer.error ?? `the broker answered ${status}`;
20906
+ return answer.message === void 0 ? error61 : `${error61}: ${answer.message}`;
20907
+ };
20871
20908
  var askBroker = async (env, name, body) => {
20872
20909
  return traced(name, {}, async () => {
20873
20910
  const headers = { "content-type": "application/json" };
@@ -20891,7 +20928,7 @@ var askBroker = async (env, name, body) => {
20891
20928
  }
20892
20929
  const answer = parsed;
20893
20930
  if (answer.error !== void 0 || !response.ok) {
20894
- throw new Error(answer.error ?? `the broker answered ${response.status}`);
20931
+ throw new Error(brokerFailure(answer, response.status));
20895
20932
  }
20896
20933
  return answer;
20897
20934
  });
@@ -21269,9 +21306,13 @@ var parseVariableMaps = (env) => {
21269
21306
  }
21270
21307
  return maps;
21271
21308
  };
21272
- var contextKindsOf = (env) => {
21309
+ var appContextOf = (env) => {
21310
+ const context = JSON.parse(env.APP_CONTEXT);
21311
+ return { variableKinds: context.variableKinds ?? {}, appVariableKinds: context.appVariableKinds ?? {}, site: context.site };
21312
+ };
21313
+ var linkedDatabasesOf = (env) => {
21273
21314
  const context = JSON.parse(env.APP_CONTEXT);
21274
- return { variableKinds: context.variableKinds ?? {}, appVariableKinds: context.appVariableKinds ?? {} };
21315
+ return context.linkedDatabases ?? [];
21275
21316
  };
21276
21317
  var projectAppValues = (env, kinds) => {
21277
21318
  const appVariables = {};
@@ -21290,7 +21331,7 @@ var projectAppValues = (env, kinds) => {
21290
21331
  return { appVariables, appSecrets };
21291
21332
  };
21292
21333
  var secretValues = (env) => {
21293
- const { variableKinds, appVariableKinds } = contextKindsOf(env);
21334
+ const { variableKinds, appVariableKinds } = appContextOf(env);
21294
21335
  const values = /* @__PURE__ */ new Set();
21295
21336
  for (const [name, byOrd] of parseVariableMaps(env)) {
21296
21337
  if (variableKinds[name] !== "secret") {
@@ -21477,7 +21518,7 @@ var readStamp = (payload) => {
21477
21518
  return { q: stamp, payload: params?.payload };
21478
21519
  };
21479
21520
  var buildWorkflowContext = (env, stamp) => {
21480
- const { variableKinds, appVariableKinds } = contextKindsOf(env);
21521
+ const { variableKinds, appVariableKinds } = appContextOf(env);
21481
21522
  const { variables, secrets } = projectInstallValues(parseVariableMaps(env), variableKinds, stamp.ord);
21482
21523
  const { appVariables, appSecrets } = projectAppValues(env, appVariableKinds);
21483
21524
  const install = { id: stamp.installId, label: stamp.installLabel };
@@ -21548,6 +21589,7 @@ var createWorkflow = (className, bundle) => {
21548
21589
  var buildContext = ({
21549
21590
  env,
21550
21591
  envelope,
21592
+ site,
21551
21593
  install,
21552
21594
  maps,
21553
21595
  kinds,
@@ -21561,7 +21603,7 @@ var buildContext = ({
21561
21603
  const subject = envelope.user?.anonId ?? null;
21562
21604
  return {
21563
21605
  env,
21564
- site: envelope.site,
21606
+ site,
21565
21607
  install: { id: install.id, label: install.label },
21566
21608
  startWorkflow: buildStartWorkflow({
21567
21609
  ord: install.ord,
@@ -21725,7 +21767,7 @@ var dispatchSchedule = async (envelope, bundle, env) => {
21725
21767
  error: { message: `missing handler: schedules.${schedule.id}` }
21726
21768
  });
21727
21769
  }
21728
- const { variableKinds, appVariableKinds } = contextKindsOf(env);
21770
+ const { variableKinds, appVariableKinds, site } = appContextOf(env);
21729
21771
  const maps = parseVariableMaps(env);
21730
21772
  const appScope = projectAppValues(env, appVariableKinds);
21731
21773
  const event = { id: schedule.id, cron: schedule.cron, scheduledTime: new Date(schedule.scheduledTime) };
@@ -21734,7 +21776,7 @@ var dispatchSchedule = async (envelope, bundle, env) => {
21734
21776
  const { variables, secrets } = projectInstallValues(maps, variableKinds, install.ord);
21735
21777
  const context = {
21736
21778
  env,
21737
- site: envelope.site,
21779
+ site,
21738
21780
  install: { id: install.id, label: install.label },
21739
21781
  startWorkflow: buildStartWorkflow({
21740
21782
  ord: install.ord,
@@ -21759,6 +21801,11 @@ var dispatchSchedule = async (envelope, bundle, env) => {
21759
21801
  data: message.data,
21760
21802
  broadcast: message.broadcast
21761
21803
  });
21804
+ },
21805
+ context: (userId) => {
21806
+ return asyncContext(env, () => {
21807
+ return userId;
21808
+ });
21762
21809
  }
21763
21810
  };
21764
21811
  try {
@@ -21852,13 +21899,13 @@ var checkResult = (result) => {
21852
21899
  };
21853
21900
  var buildContext2 = (envelope, env) => {
21854
21901
  const install = envelope.install;
21855
- const { variableKinds, appVariableKinds } = contextKindsOf(env);
21902
+ const { variableKinds, appVariableKinds, site } = appContextOf(env);
21856
21903
  const maps = parseVariableMaps(env);
21857
21904
  const appScope = projectAppValues(env, appVariableKinds);
21858
21905
  const { variables, secrets } = projectInstallValues(maps, variableKinds, install.ord);
21859
21906
  return {
21860
21907
  env,
21861
- site: envelope.site,
21908
+ site,
21862
21909
  install: { id: install.id, label: install.label },
21863
21910
  viewer: envelope.tool.viewer,
21864
21911
  callId: envelope.tool.callId,
@@ -21922,20 +21969,21 @@ var controlError = (status, message) => {
21922
21969
  return json2(status, { v: CONTROL_ENVELOPE_VERSION, ok: false, error: { message } });
21923
21970
  };
21924
21971
  var controlContextValues = (env) => {
21925
- const { variableKinds, appVariableKinds } = contextKindsOf(env);
21972
+ const { variableKinds, appVariableKinds, site } = appContextOf(env);
21926
21973
  return {
21927
21974
  maps: parseVariableMaps(env),
21928
21975
  variableKinds,
21976
+ site,
21929
21977
  appScope: projectAppValues(env, appVariableKinds)
21930
21978
  };
21931
21979
  };
21932
21980
  var lifecycleContext = (env, envelope) => {
21933
21981
  const install = envelope.install;
21934
- const { maps, variableKinds, appScope } = controlContextValues(env);
21982
+ const { maps, variableKinds, site, appScope } = controlContextValues(env);
21935
21983
  const { variables, secrets } = projectInstallValues(maps, variableKinds, install.ord);
21936
21984
  return {
21937
21985
  env,
21938
- site: envelope.site,
21986
+ site,
21939
21987
  install: { id: install.id, label: install.label },
21940
21988
  variables,
21941
21989
  secrets,
@@ -21950,11 +21998,11 @@ var lifecycleContext = (env, envelope) => {
21950
21998
  observability: buildObservability(install.ord)
21951
21999
  };
21952
22000
  };
21953
- var migrationContext = (env, envelope) => {
21954
- const { appScope } = controlContextValues(env);
22001
+ var migrationContext = (env) => {
22002
+ const { site, appScope } = controlContextValues(env);
21955
22003
  return {
21956
22004
  env,
21957
- site: envelope.site,
22005
+ site,
21958
22006
  appVariables: appScope.appVariables,
21959
22007
  appSecrets: appScope.appSecrets,
21960
22008
  // A migration runs once for the site, not once per install, so it has no install to attribute
@@ -21998,6 +22046,9 @@ var dispatchLifecycle = async (envelope, bundle, env) => {
21998
22046
  };
21999
22047
  var dispatchMigration = async (envelope, bundle, env) => {
22000
22048
  const migration = envelope.migration;
22049
+ if (linkedDatabasesOf(env).includes(migration.resource)) {
22050
+ return json2(200, { v: CONTROL_ENVELOPE_VERSION, ok: false, error: { message: MIGRATION_DATABASE_LINK_CHANGED } });
22051
+ }
22001
22052
  try {
22002
22053
  if (migration.phase !== "backfill") {
22003
22054
  await runStatementMigration(env, migration);
@@ -22007,10 +22058,7 @@ var dispatchMigration = async (envelope, bundle, env) => {
22007
22058
  if (typeof handler !== "function") {
22008
22059
  throw new Error(`missing migration handler: ${migration.handler}`);
22009
22060
  }
22010
- const result = await handler(
22011
- { id: migration.id, checkpoint: migration.checkpoint },
22012
- migrationContext(env, envelope)
22013
- );
22061
+ const result = await handler({ id: migration.id, checkpoint: migration.checkpoint }, migrationContext(env));
22014
22062
  if (result.done !== true && result.done !== false) {
22015
22063
  throw new Error("migration handler returned no completion result");
22016
22064
  }
@@ -22099,7 +22147,8 @@ var buildContext3 = ({
22099
22147
  install,
22100
22148
  site,
22101
22149
  geo,
22102
- params
22150
+ params,
22151
+ user
22103
22152
  }) => {
22104
22153
  const { variables, secrets } = projectInstallValues(maps, kinds, install.ord);
22105
22154
  return {
@@ -22108,9 +22157,13 @@ var buildContext3 = ({
22108
22157
  geo,
22109
22158
  params,
22110
22159
  install: { id: install.id, label: install.label },
22111
- // An endpoint has no visitor, so a workflow it starts cannot push. `push` below is the
22112
- // exception: it names its subject rather than inheriting one.
22113
- startWorkflow: buildStartWorkflow({ ord: install.ord, installId: install.id, installLabel: install.label, anonId: null }),
22160
+ ...user === void 0 ? {} : { user: { anonId: user.anonId, globalId: null, properties: {} } },
22161
+ startWorkflow: buildStartWorkflow({
22162
+ ord: install.ord,
22163
+ installId: install.id,
22164
+ installLabel: install.label,
22165
+ anonId: user?.anonId ?? null
22166
+ }),
22114
22167
  oauth: buildOauth(env, install.ord),
22115
22168
  variables,
22116
22169
  secrets,
@@ -22128,6 +22181,11 @@ var buildContext3 = ({
22128
22181
  broadcast: message.broadcast
22129
22182
  });
22130
22183
  },
22184
+ context: (userId) => {
22185
+ return asyncContext(env, () => {
22186
+ return userId;
22187
+ });
22188
+ },
22131
22189
  skip: () => {
22132
22190
  return { kind: "skip" };
22133
22191
  }
@@ -22171,6 +22229,21 @@ var runWebhook = async (invoke, installs) => {
22171
22229
  }
22172
22230
  return Response.json({ outcomes }, { headers: { [ENDPOINT_OUTCOMES_HEADER]: "1" } });
22173
22231
  };
22232
+ var endpointHandler = (bundle, context) => {
22233
+ const endpoints = bundle?.endpoints;
22234
+ const candidate = endpoints !== void 0 && Object.hasOwn(endpoints, context.route) ? endpoints[context.route] : void 0;
22235
+ if (typeof candidate === "function") {
22236
+ return candidate;
22237
+ }
22238
+ const visitor = typeof candidate === "object" && candidate !== null ? candidate.__qintoVisitorEndpoint : void 0;
22239
+ if (typeof visitor !== "function") {
22240
+ return Response.json({ error: "not_found" }, { status: 404 });
22241
+ }
22242
+ if (context.user === void 0) {
22243
+ return Response.json({ error: "invalid_context" }, { status: 400 });
22244
+ }
22245
+ return visitor;
22246
+ };
22174
22247
  var dispatchEndpoint = async ({ request, env, versions }) => {
22175
22248
  const context = parseContext(request.headers.get(ENDPOINT_CONTEXT_HEADER));
22176
22249
  if (context === null) {
@@ -22179,22 +22252,29 @@ var dispatchEndpoint = async ({ request, env, versions }) => {
22179
22252
  if (!(context.version in versions)) {
22180
22253
  return Response.json({ error: "unknown version" }, { status: 404 });
22181
22254
  }
22182
- const bundle = versions[context.version];
22183
- const endpoints = bundle?.endpoints;
22184
- const candidate = endpoints !== void 0 && Object.hasOwn(endpoints, context.route) ? endpoints[context.route] : void 0;
22185
- if (typeof candidate !== "function") {
22186
- return Response.json({ error: "not_found" }, { status: 404 });
22255
+ const handler = endpointHandler(versions[context.version], context);
22256
+ if (handler instanceof Response) {
22257
+ return handler;
22187
22258
  }
22188
- const handler = candidate;
22189
22259
  const body = await request.arrayBuffer();
22190
22260
  const maps = parseVariableMaps(env);
22191
- const { variableKinds, appVariableKinds } = contextKindsOf(env);
22261
+ const { variableKinds, appVariableKinds, site } = appContextOf(env);
22192
22262
  const appScope = projectAppValues(env, appVariableKinds);
22193
22263
  const invoke = async (install) => {
22194
22264
  return installSpan(install, () => {
22195
22265
  return handler(
22196
22266
  handlerRequest(request, context, body),
22197
- buildContext3({ env, maps, kinds: variableKinds, appScope, install, site: context.site, geo: context.geo, params: context.params })
22267
+ buildContext3({
22268
+ env,
22269
+ maps,
22270
+ kinds: variableKinds,
22271
+ appScope,
22272
+ install,
22273
+ site,
22274
+ geo: context.geo,
22275
+ params: context.params,
22276
+ user: context.user
22277
+ })
22198
22278
  );
22199
22279
  });
22200
22280
  };
@@ -22300,6 +22380,42 @@ var withPipelineRetry = (env, bindings) => {
22300
22380
  return wrapped;
22301
22381
  };
22302
22382
 
22383
+ // src/rpc-definition.ts
22384
+ var isSchema = (candidate) => {
22385
+ return typeof candidate === "object" && candidate !== null && "~standard" in candidate;
22386
+ };
22387
+ var rpcDefinitionOf = (candidate) => {
22388
+ if (typeof candidate !== "object" || candidate === null || !("__qintoRpc" in candidate)) {
22389
+ return void 0;
22390
+ }
22391
+ const marker = candidate.__qintoRpc;
22392
+ if (typeof marker !== "object" || marker === null || !("handler" in marker) || typeof marker.handler !== "function") {
22393
+ return void 0;
22394
+ }
22395
+ const definition = { handler: marker.handler };
22396
+ if ("input" in marker && isSchema(marker.input)) {
22397
+ definition.input = marker.input;
22398
+ }
22399
+ if ("output" in marker && isSchema(marker.output)) {
22400
+ definition.output = marker.output;
22401
+ }
22402
+ return definition;
22403
+ };
22404
+ var checkAgainst = async (schema, value) => {
22405
+ if (schema === void 0) {
22406
+ return { ok: true, value };
22407
+ }
22408
+ const result = await schema["~standard"].validate(value);
22409
+ if (result.issues === void 0) {
22410
+ return { ok: true, value: result.value };
22411
+ }
22412
+ const message = result.issues.map((issue2) => {
22413
+ const path = (issue2.path ?? []).map((segment) => String(typeof segment === "object" ? segment.key : segment)).join(".");
22414
+ return path === "" ? issue2.message : `${path}: ${issue2.message}`;
22415
+ }).join("; ");
22416
+ return { ok: false, message };
22417
+ };
22418
+
22303
22419
  // src/traced-fetch.ts
22304
22420
  var MAX_BODY_CHARS = 65536;
22305
22421
  var TOO_LARGE = `[not recorded: the body is over ${MAX_BODY_CHARS} characters]`;
@@ -22446,12 +22562,13 @@ var dispatchUi = async ({ request, env, versions }) => {
22446
22562
  if (handler === void 0) {
22447
22563
  return Response.json({ error: "not_found" }, { status: 404 });
22448
22564
  }
22449
- const { variableKinds, appVariableKinds } = contextKindsOf(env);
22565
+ const { variableKinds, appVariableKinds, site } = appContextOf(env);
22450
22566
  const maps = parseVariableMaps(env);
22451
22567
  const { variables, secrets } = projectInstallValues(maps, variableKinds, context.install.ord);
22452
22568
  const { appVariables, appSecrets } = projectAppValues(env, appVariableKinds);
22453
22569
  const ctx = {
22454
22570
  env,
22571
+ site,
22455
22572
  install: { id: context.install.id, label: context.install.label },
22456
22573
  installs: projectAllInstallValues(maps, variableKinds, context.installs),
22457
22574
  // The install is named rather than inherited: one panel serves every install, and reading the
@@ -22531,7 +22648,7 @@ var projectAction = (action, installId) => {
22531
22648
  };
22532
22649
  var runInstalls = async (envelope, env, invoke) => {
22533
22650
  const maps = parseVariableMaps(env);
22534
- const { variableKinds: kinds, appVariableKinds } = contextKindsOf(env);
22651
+ const { variableKinds: kinds, appVariableKinds, site } = appContextOf(env);
22535
22652
  const appScope = projectAppValues(env, appVariableKinds);
22536
22653
  const groups = [];
22537
22654
  let failure2;
@@ -22540,7 +22657,7 @@ var runInstalls = async (envelope, env, invoke) => {
22540
22657
  const pushes = [];
22541
22658
  const captures = [];
22542
22659
  const context = contextCollector();
22543
- const ctx = buildContext({ env, envelope, install, maps, kinds, appScope, emits, pushes, captures, context });
22660
+ const ctx = buildContext({ env, envelope, site, install, maps, kinds, appScope, emits, pushes, captures, context });
22544
22661
  try {
22545
22662
  await installSpan(install, () => {
22546
22663
  return invoke(ctx, install);
@@ -22588,11 +22705,12 @@ var runTransform = async (envelope, env, action, handler) => {
22588
22705
  const captures = [];
22589
22706
  const context = contextCollector();
22590
22707
  const maps = parseVariableMaps(env);
22591
- const { variableKinds: kinds, appVariableKinds } = contextKindsOf(env);
22708
+ const { variableKinds: kinds, appVariableKinds, site } = appContextOf(env);
22592
22709
  const appScope = projectAppValues(env, appVariableKinds);
22593
22710
  const { apps: _apps, ...withoutApps } = buildContext({
22594
22711
  env,
22595
22712
  envelope,
22713
+ site,
22596
22714
  install,
22597
22715
  maps,
22598
22716
  kinds,
@@ -22660,21 +22778,25 @@ var dispatchRpc = async (envelope, bundle, env) => {
22660
22778
  if (rpc === void 0) {
22661
22779
  return jsonError(400, "rpc envelope carries no rpc call");
22662
22780
  }
22663
- const handler = toHandler(bundle?.rpc?.[rpc.fn]);
22664
- if (handler === void 0) {
22781
+ const definition = rpcDefinitionOf(bundle?.rpc?.[rpc.fn]);
22782
+ if (definition === void 0) {
22665
22783
  return jsonError(200, `missing handler: rpc.${rpc.fn}`);
22666
22784
  }
22785
+ const args = await checkAgainst(definition.input, rpc.args);
22786
+ if (!args.ok) {
22787
+ return json2(200, { v: ENVELOPE_VERSION, error: { code: "invalid_arguments", message: failureMessage(args.message) } });
22788
+ }
22667
22789
  const install = envelope.installs[0];
22668
22790
  const emits = [];
22669
22791
  const pushes = [];
22670
22792
  const captures = [];
22671
22793
  const context = contextCollector();
22672
22794
  const maps = parseVariableMaps(env);
22673
- const { variableKinds: kinds, appVariableKinds } = contextKindsOf(env);
22795
+ const { variableKinds: kinds, appVariableKinds, site } = appContextOf(env);
22674
22796
  const appScope = projectAppValues(env, appVariableKinds);
22675
22797
  const subject = envelope.user?.anonId ?? null;
22676
22798
  const ctx = {
22677
- ...buildContext({ env, envelope, install, maps, kinds, appScope, emits, pushes, captures, context }),
22799
+ ...buildContext({ env, envelope, site, install, maps, kinds, appScope, emits, pushes, captures, context }),
22678
22800
  context: fetchedContext(
22679
22801
  env,
22680
22802
  (what) => {
@@ -22686,14 +22808,18 @@ var dispatchRpc = async (envelope, bundle, env) => {
22686
22808
  let value;
22687
22809
  try {
22688
22810
  value = await installSpan(install, () => {
22689
- return handler(rpc.args, ctx);
22811
+ return definition.handler(args.value, ctx);
22690
22812
  });
22691
22813
  } catch (error61) {
22692
22814
  const message = failureMessage(error61);
22693
22815
  console.warn(`rpc failed for install ${install.id}: ${message}`);
22694
22816
  return json2(200, { v: ENVELOPE_VERSION, error: { message } });
22695
22817
  }
22696
- const result = value === void 0 ? null : value;
22818
+ const checked = await checkAgainst(definition.output, value);
22819
+ if (!checked.ok) {
22820
+ return jsonError(200, failureMessage(`rpc result does not match the output schema: ${checked.message}`));
22821
+ }
22822
+ const result = checked.value === void 0 ? null : checked.value;
22697
22823
  if (new TextEncoder().encode(JSON.stringify(result)).length > MAX_RPC_BYTES) {
22698
22824
  return jsonError(200, "rpc result exceeds the size cap");
22699
22825
  }