@blotout/qinto-cli 0.1.21 → 0.1.23

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.
@@ -4,6 +4,10 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
+ // ../../../libs/app-manifest/src/app-roles.ts
8
+ var DECLARABLE_APP_ROLES = ["owner", "admin", "manager", "editor", "member", "viewer"];
9
+ var APP_ROLES = ["full", ...DECLARABLE_APP_ROLES];
10
+
7
11
  // ../../../libs/app-manifest/src/cron.ts
8
12
  var MAX_CRON_LENGTH = 128;
9
13
  var MINUTE_LITERAL = /^([0-9]|[1-5][0-9])$/;
@@ -18947,6 +18951,10 @@ var VARIABLE_NAME = /^[A-Z][A-Z0-9_]*$/;
18947
18951
  var APP_SLUG_PATTERN = /^[a-z][a-z0-9-]{2,29}$/;
18948
18952
  var FUNCTION_NAME = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
18949
18953
  var identifierField = external_exports.string().min(1).max(64).regex(IDENTIFIER);
18954
+ var RENDERABLE_TEXT = /^[^\u0000-\u0009\u000B-\u001F\u007F\u200B-\u200F\u202A-\u202E\u2066-\u2069\uFEFF]*$/;
18955
+ var renderableText = (min, max) => {
18956
+ return external_exports.string().min(min).max(max).regex(RENDERABLE_TEXT, "must not carry control or bidirectional-override characters");
18957
+ };
18950
18958
  var httpsUrl = (field2) => {
18951
18959
  return external_exports.url().max(2048).refine((value) => value.startsWith("https://"), `${field2} must be an HTTPS URL`);
18952
18960
  };
@@ -19082,6 +19090,35 @@ var validateOauth = (input2, ctx) => {
19082
19090
  }
19083
19091
  };
19084
19092
 
19093
+ // ../../../libs/app-manifest/src/roles.ts
19094
+ var MAX_APP_ROLE_DESCRIPTION = 200;
19095
+ var roleSchema = external_exports.object({
19096
+ key: external_exports.enum(DECLARABLE_APP_ROLES, {
19097
+ error: (issue2) => {
19098
+ return issue2.input === "full" ? "full access is always available and is not declared" : void 0;
19099
+ }
19100
+ }),
19101
+ description: renderableText(1, MAX_APP_ROLE_DESCRIPTION).optional()
19102
+ }).strict();
19103
+ var rolesSchema = external_exports.array(roleSchema).max(DECLARABLE_APP_ROLES.length).superRefine((roles, ctx) => {
19104
+ uniqueOrIssue(
19105
+ ctx,
19106
+ roles.map((role) => role.key),
19107
+ [],
19108
+ "role"
19109
+ );
19110
+ }).transform((roles) => {
19111
+ return [...roles].sort((a, b) => APP_ROLES.indexOf(a.key) - APP_ROLES.indexOf(b.key));
19112
+ });
19113
+ var validateRoles = (manifest, ctx) => {
19114
+ const declared = /* @__PURE__ */ new Set(["full", ...(manifest.roles ?? []).map((role) => role.key)]);
19115
+ for (const [index, tool] of manifest.tools.entries()) {
19116
+ if (tool.role !== void 0 && !declared.has(tool.role)) {
19117
+ ctx.addIssue({ code: "custom", path: ["tools", index, "role"], message: `Tool role ${tool.role} is not declared in roles` });
19118
+ }
19119
+ }
19120
+ };
19121
+
19085
19122
  // ../../../libs/app-manifest/src/route.ts
19086
19123
  var ENDPOINT_PATH_PATTERN = /^\/(?:(?::[a-zA-Z][a-zA-Z0-9_]*|[a-zA-Z0-9_-]+)(?:\/(?::[a-zA-Z][a-zA-Z0-9_]*|[a-zA-Z0-9_-]+))*)?$/;
19087
19124
  var isParam = (segment) => {
@@ -19115,10 +19152,6 @@ var MAX_MANIFEST_TOOLS_BYTES = 150 * 1024;
19115
19152
  var TOOL_PROPERTY_NAME = /^[a-zA-Z][a-zA-Z0-9_]*$/;
19116
19153
  var RESERVED_TOOL_ARGUMENTS = ["teamId", "siteId", "installId"];
19117
19154
  var TOOL_STRING_FORMATS = ["uuid", "date", "date-time", "email", "uri"];
19118
- var RENDERABLE_TEXT = /^[^\u0000-\u0009\u000B-\u001F\u007F\u200B-\u200F\u202A-\u202E\u2066-\u2069\uFEFF]*$/;
19119
- var renderableText = (min, max) => {
19120
- return external_exports.string().min(min).max(max).regex(RENDERABLE_TEXT, "must not carry control or bidirectional-override characters");
19121
- };
19122
19155
  var propertyName = external_exports.string().min(1).max(48).regex(TOOL_PROPERTY_NAME);
19123
19156
  var FORMAT_SCHEMAS = {
19124
19157
  uuid: external_exports.uuid(),
@@ -19214,7 +19247,8 @@ var toolSchema = external_exports.object({
19214
19247
  title: renderableText(1, MAX_APP_TOOL_TITLE_LENGTH),
19215
19248
  description: renderableText(MIN_APP_TOOL_DESCRIPTION, MAX_APP_TOOL_DESCRIPTION),
19216
19249
  inputSchema: toolInputSchema,
19217
- annotations: toolAnnotationsSchema
19250
+ annotations: toolAnnotationsSchema,
19251
+ role: external_exports.enum(APP_ROLES).optional()
19218
19252
  }).strict();
19219
19253
  var nodeChildren = (value) => {
19220
19254
  const properties = value["properties"];
@@ -19618,6 +19652,9 @@ var baseSchema = external_exports.object({
19618
19652
  description: "The push topics this app broadcasts to every app on the site. Requires the `sharedContext` data capability; a topic left out reaches only this app\u2019s own browser code."
19619
19653
  }),
19620
19654
  endpoints: external_exports.array(endpointSchema).max(16).default([]).meta({ description: "The HTTP routes served at https://<site>/app/<slug><path>." }),
19655
+ roles: rolesSchema.optional().meta({
19656
+ description: "The app roles this app uses, from the fixed list owner, admin, manager, editor, member, viewer. Full access is always available and is not declared. A team member is given one app role per app."
19657
+ }),
19621
19658
  tools: toolsSchema.meta({
19622
19659
  description: "The MCP tools this app serves to AI assistants, published as `<slug>_<name>`. Each one a site manager accepts becomes callable on their behalf by an assistant."
19623
19660
  }),
@@ -19974,6 +20011,7 @@ var appManifestSchema = baseSchema.superRefine((manifest, ctx) => {
19974
20011
  validateOauth(manifest, ctx);
19975
20012
  validateServer(manifest, ctx);
19976
20013
  validateTools(manifest.tools, ctx);
20014
+ validateRoles(manifest, ctx);
19977
20015
  });
19978
20016
 
19979
20017
  // ../../../libs/app-manifest/src/oauth-flow.ts
@@ -20146,6 +20184,14 @@ var lifecycleEventSchema = external_exports.discriminatedUnion("type", [
20146
20184
  }).strict(),
20147
20185
  external_exports.object({ ...lifecycleBase, type: external_exports.literal("uninstalled"), scope: external_exports.enum(["app", "install"]) }).strict()
20148
20186
  ]);
20187
+ var deleteRequestSchema = external_exports.object({
20188
+ eventId: external_exports.string().min(1).max(64),
20189
+ occurredAt: external_exports.number().int().nonnegative().safe(),
20190
+ install: installSchema2,
20191
+ personId: external_exports.string().min(1).nullable(),
20192
+ anonIds: external_exports.array(external_exports.string().min(1)),
20193
+ identifiers: external_exports.array(external_exports.object({ type: external_exports.enum(["email", "phone", "custom"]), value: external_exports.string().min(1) }).strict()).optional()
20194
+ }).strict();
20149
20195
  var migrationSchema = external_exports.discriminatedUnion("phase", [
20150
20196
  external_exports.object({
20151
20197
  resource: external_exports.string().min(1).max(64),
@@ -20188,7 +20234,12 @@ var toolSchema2 = external_exports.object({
20188
20234
  name: external_exports.string().min(1).max(MAX_APP_TOOL_NAME_LENGTH).regex(APP_TOOL_NAME_PATTERN),
20189
20235
  // Who asked, and what they may do on this site. Derived from the bearer by the portal, never
20190
20236
  // from the model's arguments.
20191
- viewer: external_exports.object({ userId: external_exports.string().min(1).max(64), access: external_exports.enum(["read", "write"]) }).strict(),
20237
+ viewer: external_exports.object({
20238
+ userId: external_exports.string().min(1).max(64),
20239
+ access: external_exports.enum(["read", "write"]),
20240
+ // Optional only while a site worker or app runtime that predates it can still receive this envelope; absent means `full`.
20241
+ appRole: external_exports.enum(APP_ROLES).optional()
20242
+ }).strict(),
20192
20243
  // Already validated against the tool's declared inputSchema, with the reserved routing names
20193
20244
  // stripped. Every declared input schema has an object root, so this is an object too — and a
20194
20245
  // call that names no arguments normalizes to `{}` rather than reaching the handler as undefined.
@@ -20203,10 +20254,11 @@ var toolSchema2 = external_exports.object({
20203
20254
  }, "Tool arguments exceed the size cap");
20204
20255
  var controlEnvelopeSchema = external_exports.object({
20205
20256
  v: external_exports.literal(CONTROL_ENVELOPE_VERSION),
20206
- kind: external_exports.enum(["noop", "lifecycle", "migration", "schedule", "tool"]),
20257
+ kind: external_exports.enum(["noop", "lifecycle", "deleteRequest", "migration", "schedule", "tool"]),
20207
20258
  app: external_exports.object({ slug: external_exports.string().min(1).max(30), version: external_exports.string().min(1).max(32) }).strict(),
20208
20259
  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(),
20209
20260
  lifecycle: lifecycleEventSchema.optional(),
20261
+ deleteRequest: deleteRequestSchema.optional(),
20210
20262
  migration: migrationSchema.optional(),
20211
20263
  schedule: scheduleSchema2.optional(),
20212
20264
  tool: toolSchema2.optional()
@@ -20214,6 +20266,9 @@ var controlEnvelopeSchema = external_exports.object({
20214
20266
  if (envelope.kind === "lifecycle" !== (envelope.lifecycle !== void 0)) {
20215
20267
  ctx.addIssue({ code: "custom", path: ["lifecycle"], message: "Lifecycle payload must match the control kind" });
20216
20268
  }
20269
+ if (envelope.kind === "deleteRequest" !== (envelope.deleteRequest !== void 0)) {
20270
+ ctx.addIssue({ code: "custom", path: ["deleteRequest"], message: "Delete request payload must match the control kind" });
20271
+ }
20217
20272
  if (envelope.kind === "migration" !== (envelope.migration !== void 0)) {
20218
20273
  ctx.addIssue({ code: "custom", path: ["migration"], message: "Migration payload must match the control kind" });
20219
20274
  }
@@ -20223,8 +20278,8 @@ var controlEnvelopeSchema = external_exports.object({
20223
20278
  if (envelope.kind === "tool" !== (envelope.tool !== void 0)) {
20224
20279
  ctx.addIssue({ code: "custom", path: ["tool"], message: "Tool payload must match the control kind" });
20225
20280
  }
20226
- if (envelope.kind === "lifecycle" && envelope.install === void 0) {
20227
- ctx.addIssue({ code: "custom", path: ["install"], message: "Lifecycle control needs an install" });
20281
+ if ((envelope.kind === "lifecycle" || envelope.kind === "deleteRequest") && envelope.install === void 0) {
20282
+ ctx.addIssue({ code: "custom", path: ["install"], message: `${envelope.kind} control needs an install` });
20228
20283
  }
20229
20284
  if (envelope.kind === "tool" && envelope.install === void 0) {
20230
20285
  ctx.addIssue({ code: "custom", path: ["install"], message: "Tool control needs an install" });
@@ -20263,6 +20318,8 @@ var toolResultSchema = external_exports.object({
20263
20318
  var controlResponseSchema = external_exports.object({
20264
20319
  v: external_exports.literal(CONTROL_ENVELOPE_VERSION),
20265
20320
  ok: external_exports.boolean(),
20321
+ // The bundle has no delete request handler, so a retry cannot succeed.
20322
+ skipped: external_exports.literal(true).optional(),
20266
20323
  tool: toolResultSchema.optional(),
20267
20324
  migration: external_exports.union([
20268
20325
  external_exports.object({ done: external_exports.literal(true) }).strict(),
@@ -20416,7 +20473,12 @@ var appUiAnalyticsQueryPath = (domainId, appSlug2, resourceIdentifier) => {
20416
20473
  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();
20417
20474
  var uiContextSchema = external_exports.object({
20418
20475
  version: external_exports.string().min(1).max(32),
20419
- viewer: external_exports.object({ userId: external_exports.string().min(1).max(64), access: external_exports.enum(["read", "write"]) }).strict(),
20476
+ viewer: external_exports.object({
20477
+ userId: external_exports.string().min(1).max(64),
20478
+ access: external_exports.enum(["read", "write"]),
20479
+ // Optional only while a site worker that predates it can still send this header; absent means `full`.
20480
+ appRole: external_exports.enum(APP_ROLES).optional()
20481
+ }).strict(),
20420
20482
  install: uiInstallSchema,
20421
20483
  installs: external_exports.array(uiInstallSchema).min(1).max(MAX_INSTALLS_PER_APP),
20422
20484
  writeTicket: external_exports.string().min(1).max(2048).optional()
@@ -20435,6 +20497,21 @@ var parseUiContext = (raw) => {
20435
20497
  return parsed.success ? parsed.data : null;
20436
20498
  };
20437
20499
 
20500
+ // src/app-env.ts
20501
+ var PLATFORM_BINDINGS = new Set(
20502
+ PLATFORM_APP_BINDINGS.filter((name) => {
20503
+ return name !== PLATFORM_APP_BINDING_NAMES.installToken;
20504
+ })
20505
+ );
20506
+ var PLATFORM_PREFIXES = ["VAR_", "APPVAR_", PLATFORM_APP_OAUTH_BINDING_PREFIX];
20507
+ var appEnv = (env) => {
20508
+ return Object.fromEntries(
20509
+ Object.entries(env).filter(([name]) => {
20510
+ return !PLATFORM_BINDINGS.has(name) && !PLATFORM_PREFIXES.some((prefix) => name.startsWith(prefix));
20511
+ })
20512
+ );
20513
+ };
20514
+
20438
20515
  // ../../../libs/tail-trace/src/validate.ts
20439
20516
  var TRACE_ID = /^[0-9a-f]{32}$/;
20440
20517
  var SPAN_ID = /^[0-9a-f]{16}$/;
@@ -21023,6 +21100,15 @@ var brokerIdentityRead = async (env, identifier, options = {}) => {
21023
21100
  });
21024
21101
  return { globalId: answer.globalId ?? null, anonIds: answer.anonIds ?? [], nextCursor: answer.nextCursor ?? null };
21025
21102
  };
21103
+ var brokerCurrencyRates = async (env) => {
21104
+ const answer = await askBroker(env, "apps.currency rates", {
21105
+ v: ENVELOPE_VERSION,
21106
+ kind: "currencyRates",
21107
+ slug: callerSlug(env),
21108
+ key: env.QINTO_INTERNAL_KEY
21109
+ });
21110
+ return { rates: answer.rates, fetchedAt: answer.fetchedAt };
21111
+ };
21026
21112
  var buildIdentity = (env) => {
21027
21113
  return {
21028
21114
  resolve: (identifier, options) => {
@@ -21561,7 +21647,7 @@ var buildWorkflowContext = (env, stamp) => {
21561
21647
  depth: 0
21562
21648
  });
21563
21649
  return {
21564
- env,
21650
+ env: appEnv(env),
21565
21651
  install,
21566
21652
  startWorkflow: buildStartWorkflow(stamp),
21567
21653
  oauth: buildOauth(env, stamp.ord),
@@ -21634,12 +21720,13 @@ var buildContext = ({
21634
21720
  emits,
21635
21721
  pushes,
21636
21722
  captures,
21637
- context
21723
+ context,
21724
+ currency
21638
21725
  }) => {
21639
21726
  const { variables, secrets } = projectInstallValues(maps, kinds, install.ord);
21640
21727
  const subject = envelope.user?.anonId ?? null;
21641
21728
  return {
21642
- env,
21729
+ env: appEnv(env),
21643
21730
  site,
21644
21731
  install: { id: install.id, label: install.label },
21645
21732
  startWorkflow: buildStartWorkflow({
@@ -21703,10 +21790,45 @@ var buildContext = ({
21703
21790
  },
21704
21791
  observability: buildObservability(install.ord),
21705
21792
  identity: buildIdentity(env),
21793
+ currency,
21706
21794
  apps: buildAppsClient(env, envelope)
21707
21795
  };
21708
21796
  };
21709
21797
 
21798
+ // src/currency.ts
21799
+ var rateBetween = (rates, from, to) => {
21800
+ const source = from.trim().toUpperCase();
21801
+ const target = to.trim().toUpperCase();
21802
+ if (!Object.hasOwn(rates, source) || !Object.hasOwn(rates, target)) {
21803
+ return null;
21804
+ }
21805
+ return rates[source] / rates[target];
21806
+ };
21807
+ var buildCurrency = (env) => {
21808
+ let snapshot;
21809
+ const load = () => {
21810
+ snapshot ??= brokerCurrencyRates(env).catch((error61) => {
21811
+ throw new Error("the platform currency rates are not available", { cause: error61 });
21812
+ });
21813
+ return snapshot;
21814
+ };
21815
+ return {
21816
+ convert: async (amount, from, to) => {
21817
+ const rate = rateBetween((await load()).rates, from, to);
21818
+ return rate === null ? null : amount * rate;
21819
+ },
21820
+ rate: async (from, to) => {
21821
+ return rateBetween((await load()).rates, from, to);
21822
+ },
21823
+ list: async () => {
21824
+ return Object.keys((await load()).rates).sort();
21825
+ },
21826
+ updatedAt: async () => {
21827
+ return new Date((await load()).fetchedAt);
21828
+ }
21829
+ };
21830
+ };
21831
+
21710
21832
  // src/install-span.ts
21711
21833
  var installSpan = (install, run) => {
21712
21834
  return traced(
@@ -21803,10 +21925,11 @@ var dispatchSchedule = async (envelope, bundle, env) => {
21803
21925
  const appScope = projectAppValues(env, appVariableKinds);
21804
21926
  const event = { id: schedule.id, cron: schedule.cron, scheduledTime: new Date(schedule.scheduledTime) };
21805
21927
  const failed = [];
21928
+ const currency = buildCurrency(env);
21806
21929
  for (const install of schedule.installs) {
21807
21930
  const { variables, secrets } = projectInstallValues(maps, variableKinds, install.ord);
21808
21931
  const context = {
21809
- env,
21932
+ env: appEnv(env),
21810
21933
  site,
21811
21934
  install: { id: install.id, label: install.label },
21812
21935
  startWorkflow: buildStartWorkflow({
@@ -21822,6 +21945,7 @@ var dispatchSchedule = async (envelope, bundle, env) => {
21822
21945
  appSecrets: appScope.appSecrets,
21823
21946
  observability: buildObservability(install.ord),
21824
21947
  identity: buildIdentity(env),
21948
+ currency,
21825
21949
  apps: buildAppsClient(env, { depth: 0 }),
21826
21950
  query: buildQuery({ env, ticket: install.queryTicket, installId: install.id }),
21827
21951
  push: async (userId, message) => {
@@ -21938,10 +22062,11 @@ var buildContext2 = (envelope, env) => {
21938
22062
  const appScope = projectAppValues(env, appVariableKinds);
21939
22063
  const { variables, secrets } = projectInstallValues(maps, variableKinds, install.ord);
21940
22064
  return {
21941
- env,
22065
+ env: appEnv(env),
21942
22066
  site,
21943
22067
  install: { id: install.id, label: install.label },
21944
- viewer: envelope.tool.viewer,
22068
+ // An envelope from a portal that predates the role, or one carrying `full`, has no `appRole`.
22069
+ viewer: { ...envelope.tool.viewer, appRole: envelope.tool.viewer.appRole ?? "full" },
21945
22070
  callId: envelope.tool.callId,
21946
22071
  startWorkflow: buildStartWorkflow({ ord: install.ord, installId: install.id, installLabel: install.label, anonId: null }),
21947
22072
  oauth: buildOauth(env, install.ord),
@@ -21951,6 +22076,7 @@ var buildContext2 = (envelope, env) => {
21951
22076
  appSecrets: appScope.appSecrets,
21952
22077
  observability: buildObservability(install.ord),
21953
22078
  identity: buildIdentity(env),
22079
+ currency: buildCurrency(env),
21954
22080
  apps: buildAppsClient(env, { depth: 0 }),
21955
22081
  query: buildQuery({ env, ticket: envelope.tool.queryTicket, installId: install.id }),
21956
22082
  track: async (userId, event) => {
@@ -22014,12 +22140,11 @@ var controlContextValues = (env) => {
22014
22140
  appScope: projectAppValues(env, appVariableKinds)
22015
22141
  };
22016
22142
  };
22017
- var lifecycleContext = (env, envelope) => {
22018
- const install = envelope.install;
22143
+ var lifecycleContext = (env, install) => {
22019
22144
  const { maps, variableKinds, site, appScope } = controlContextValues(env);
22020
22145
  const { variables, secrets } = projectInstallValues(maps, variableKinds, install.ord);
22021
22146
  return {
22022
- env,
22147
+ env: appEnv(env),
22023
22148
  site,
22024
22149
  install: { id: install.id, label: install.label },
22025
22150
  variables,
@@ -22032,19 +22157,21 @@ var lifecycleContext = (env, envelope) => {
22032
22157
  installLabel: install.label,
22033
22158
  anonId: null
22034
22159
  }),
22035
- observability: buildObservability(install.ord)
22160
+ observability: buildObservability(install.ord),
22161
+ currency: buildCurrency(env)
22036
22162
  };
22037
22163
  };
22038
22164
  var migrationContext = (env) => {
22039
22165
  const { site, appScope } = controlContextValues(env);
22040
22166
  return {
22041
- env,
22167
+ env: appEnv(env),
22042
22168
  site,
22043
22169
  appVariables: appScope.appVariables,
22044
22170
  appSecrets: appScope.appSecrets,
22045
22171
  // A migration runs once for the site, not once per install, so it has no install to attribute
22046
22172
  // an event or a saved error to.
22047
- observability: buildObservability(null)
22173
+ observability: buildObservability(null),
22174
+ currency: buildCurrency(env)
22048
22175
  };
22049
22176
  };
22050
22177
  var runStatementMigration = async (env, migration) => {
@@ -22065,22 +22192,43 @@ var runStatementMigration = async (env, migration) => {
22065
22192
  const ledger = database.prepare("INSERT INTO _qinto_migrations (id, checksum, phase, applied_at) VALUES (?, ?, ?, ?)").bind(migration.id, migration.checksum, migration.phase, (/* @__PURE__ */ new Date()).toISOString());
22066
22193
  await database.batch([...migration.statements.map((statement) => database.prepare(statement)), ledger]);
22067
22194
  };
22068
- var dispatchLifecycle = async (envelope, bundle, env) => {
22069
- const event = envelope.lifecycle;
22070
- const handler = bundle.onLifecycle?.[event.type];
22071
- if (typeof handler !== "function") {
22072
- return json2(200, { v: CONTROL_ENVELOPE_VERSION, ok: true });
22073
- }
22074
- const install = envelope.install;
22195
+ var runInstallHook = async (install, run, env) => {
22075
22196
  try {
22076
22197
  await installSpan(install, () => {
22077
- return handler(event, lifecycleContext(env, envelope));
22198
+ return run(lifecycleContext(env, install));
22078
22199
  });
22079
22200
  return json2(200, { v: CONTROL_ENVELOPE_VERSION, ok: true });
22080
22201
  } catch (error61) {
22081
22202
  return json2(200, { v: CONTROL_ENVELOPE_VERSION, ok: false, error: { message: failureMessage(error61) } });
22082
22203
  }
22083
22204
  };
22205
+ var dispatchLifecycle = async (envelope, bundle, env) => {
22206
+ const event = envelope.lifecycle;
22207
+ const handler = bundle.onLifecycle?.[event.type];
22208
+ if (typeof handler !== "function") {
22209
+ return json2(200, { v: CONTROL_ENVELOPE_VERSION, ok: true });
22210
+ }
22211
+ return runInstallHook(
22212
+ envelope.install,
22213
+ (context) => {
22214
+ return handler(event, context);
22215
+ },
22216
+ env
22217
+ );
22218
+ };
22219
+ var dispatchDeleteRequest = async (envelope, bundle, env) => {
22220
+ const handler = bundle.onDeleteRequest;
22221
+ if (typeof handler !== "function") {
22222
+ return json2(200, { v: CONTROL_ENVELOPE_VERSION, ok: false, skipped: true });
22223
+ }
22224
+ return runInstallHook(
22225
+ envelope.install,
22226
+ (context) => {
22227
+ return handler(envelope.deleteRequest, context);
22228
+ },
22229
+ env
22230
+ );
22231
+ };
22084
22232
  var dispatchMigration = async (envelope, bundle, env) => {
22085
22233
  const migration = envelope.migration;
22086
22234
  if (linkedDatabasesOf(env).includes(migration.resource)) {
@@ -22135,6 +22283,9 @@ var dispatchControl = async ({ request, env, versions }) => {
22135
22283
  case "lifecycle": {
22136
22284
  return dispatchLifecycle(envelope, bundle, env);
22137
22285
  }
22286
+ case "deleteRequest": {
22287
+ return dispatchDeleteRequest(envelope, bundle, env);
22288
+ }
22138
22289
  case "migration": {
22139
22290
  return dispatchMigration(envelope, bundle, env);
22140
22291
  }
@@ -22185,11 +22336,12 @@ var buildContext3 = ({
22185
22336
  site,
22186
22337
  geo,
22187
22338
  params,
22188
- user
22339
+ user,
22340
+ currency
22189
22341
  }) => {
22190
22342
  const { variables, secrets } = projectInstallValues(maps, kinds, install.ord);
22191
22343
  return {
22192
- env,
22344
+ env: appEnv(env),
22193
22345
  site,
22194
22346
  geo,
22195
22347
  params,
@@ -22208,6 +22360,7 @@ var buildContext3 = ({
22208
22360
  appSecrets: appScope.appSecrets,
22209
22361
  observability: buildObservability(install.ord),
22210
22362
  identity: buildIdentity(env),
22363
+ currency,
22211
22364
  apps: buildAppsClient(env, { depth: 0 }),
22212
22365
  push: async (userId, message) => {
22213
22366
  await brokerPush(env, {
@@ -22300,6 +22453,7 @@ var dispatchEndpoint = async ({ request, env, versions }) => {
22300
22453
  const maps = parseVariableMaps(env);
22301
22454
  const { variableKinds, appVariableKinds, site } = appContextOf(env);
22302
22455
  const appScope = projectAppValues(env, appVariableKinds);
22456
+ const currency = buildCurrency(env);
22303
22457
  const invoke = async (install) => {
22304
22458
  return installSpan(install, () => {
22305
22459
  return handler(
@@ -22313,7 +22467,8 @@ var dispatchEndpoint = async ({ request, env, versions }) => {
22313
22467
  site,
22314
22468
  geo: context.geo,
22315
22469
  params: context.params,
22316
- user: context.user
22470
+ user: context.user,
22471
+ currency
22317
22472
  })
22318
22473
  );
22319
22474
  });
@@ -22456,6 +22611,69 @@ var checkAgainst = async (schema, value) => {
22456
22611
  return { ok: false, message };
22457
22612
  };
22458
22613
 
22614
+ // src/rpc.ts
22615
+ var dispatchRpc = async (envelope, bundle, env) => {
22616
+ if (envelope.installs.length > 1) {
22617
+ return jsonError(400, "rpc envelope carries more than one install");
22618
+ }
22619
+ const rpc = envelope.rpc;
22620
+ if (rpc === void 0) {
22621
+ return jsonError(400, "rpc envelope carries no rpc call");
22622
+ }
22623
+ const definition = rpcDefinitionOf(bundle?.rpc?.[rpc.fn]);
22624
+ if (definition === void 0) {
22625
+ return jsonError(200, `missing handler: rpc.${rpc.fn}`);
22626
+ }
22627
+ const args = await checkAgainst(definition.input, rpc.args);
22628
+ if (!args.ok) {
22629
+ return json2(200, { v: ENVELOPE_VERSION, error: { code: "invalid_arguments", message: failureMessage(args.message) } });
22630
+ }
22631
+ const install = envelope.installs[0];
22632
+ const emits = [];
22633
+ const pushes = [];
22634
+ const captures = [];
22635
+ const context = contextCollector();
22636
+ const maps = parseVariableMaps(env);
22637
+ const { variableKinds: kinds, appVariableKinds, site } = appContextOf(env);
22638
+ const appScope = projectAppValues(env, appVariableKinds);
22639
+ const currency = buildCurrency(env);
22640
+ const subject = envelope.user?.anonId ?? null;
22641
+ const ctx = {
22642
+ ...buildContext({ env, envelope, site, install, maps, kinds, appScope, emits, pushes, captures, context, currency }),
22643
+ context: fetchedContext(
22644
+ env,
22645
+ (what) => {
22646
+ return requireSubject(subject, what);
22647
+ },
22648
+ context
22649
+ )
22650
+ };
22651
+ let value;
22652
+ try {
22653
+ value = await installSpan(install, () => {
22654
+ return definition.handler(args.value, ctx);
22655
+ });
22656
+ } catch (error61) {
22657
+ const message = failureMessage(error61);
22658
+ console.warn(`rpc failed for install ${install.id}: ${message}`);
22659
+ return json2(200, { v: ENVELOPE_VERSION, error: { message } });
22660
+ }
22661
+ const checked = await checkAgainst(definition.output, value);
22662
+ if (!checked.ok) {
22663
+ return jsonError(200, failureMessage(`rpc result does not match the output schema: ${checked.message}`));
22664
+ }
22665
+ const result = checked.value === void 0 ? null : checked.value;
22666
+ if (new TextEncoder().encode(JSON.stringify(result)).length > MAX_RPC_BYTES) {
22667
+ return jsonError(200, "rpc result exceeds the size cap");
22668
+ }
22669
+ const body = { v: ENVELOPE_VERSION, rpc: { result } };
22670
+ const contextWrites = context.writes();
22671
+ if (emits.length > 0 || pushes.length > 0 || captures.length > 0 || contextWrites.length > 0) {
22672
+ body.effects = [{ ord: install.ord, emits, pushes, captures, context: contextWrites }];
22673
+ }
22674
+ return json2(200, body);
22675
+ };
22676
+
22459
22677
  // src/traced-fetch.ts
22460
22678
  var MAX_BODY_CHARS = 65536;
22461
22679
  var TOO_LARGE = `[not recorded: the body is over ${MAX_BODY_CHARS} characters]`;
@@ -22607,7 +22825,7 @@ var dispatchUi = async ({ request, env, versions }) => {
22607
22825
  const { variables, secrets } = projectInstallValues(maps, variableKinds, context.install.ord);
22608
22826
  const { appVariables, appSecrets } = projectAppValues(env, appVariableKinds);
22609
22827
  const ctx = {
22610
- env,
22828
+ env: appEnv(env),
22611
22829
  site,
22612
22830
  install: { id: context.install.id, label: context.install.label },
22613
22831
  installs: projectAllInstallValues(maps, variableKinds, context.installs),
@@ -22624,7 +22842,8 @@ var dispatchUi = async ({ request, env, versions }) => {
22624
22842
  secrets,
22625
22843
  appVariables,
22626
22844
  appSecrets,
22627
- viewer: context.viewer,
22845
+ // A site worker that predates app roles sends none, and every member had full access then.
22846
+ viewer: { ...context.viewer, appRole: context.viewer.appRole ?? "full" },
22628
22847
  observability: buildObservability(context.install.ord),
22629
22848
  identity: buildIdentity(env),
22630
22849
  apps: buildAppsClient(env, { depth: 0 })
@@ -22666,7 +22885,7 @@ var withAppDurableObject = (Base, migrations) => {
22666
22885
  return class extends Base {
22667
22886
  constructor(ctx, env) {
22668
22887
  applyDurableObjectMigrations(ctx.storage, migrations);
22669
- super(ctx, env);
22888
+ super(ctx, appEnv(env));
22670
22889
  }
22671
22890
  };
22672
22891
  };
@@ -22692,12 +22911,13 @@ var runInstalls = async (envelope, env, invoke) => {
22692
22911
  const appScope = projectAppValues(env, appVariableKinds);
22693
22912
  const groups = [];
22694
22913
  let failure2;
22914
+ const currency = buildCurrency(env);
22695
22915
  for (const install of envelope.installs) {
22696
22916
  const emits = [];
22697
22917
  const pushes = [];
22698
22918
  const captures = [];
22699
22919
  const context = contextCollector();
22700
- const ctx = buildContext({ env, envelope, site, install, maps, kinds, appScope, emits, pushes, captures, context });
22920
+ const ctx = buildContext({ env, envelope, site, install, maps, kinds, appScope, emits, pushes, captures, context, currency });
22701
22921
  try {
22702
22922
  await installSpan(install, () => {
22703
22923
  return invoke(ctx, install);
@@ -22758,7 +22978,8 @@ var runTransform = async (envelope, env, action, handler) => {
22758
22978
  emits,
22759
22979
  pushes,
22760
22980
  captures,
22761
- context
22981
+ context,
22982
+ currency: buildCurrency(env)
22762
22983
  });
22763
22984
  const ctx = {
22764
22985
  ...withoutApps,
@@ -22810,66 +23031,6 @@ var dispatchTransform = async (envelope, bundle, env) => {
22810
23031
  }
22811
23032
  return runTransform(envelope, env, action, handler);
22812
23033
  };
22813
- var dispatchRpc = async (envelope, bundle, env) => {
22814
- if (envelope.installs.length > 1) {
22815
- return jsonError(400, "rpc envelope carries more than one install");
22816
- }
22817
- const rpc = envelope.rpc;
22818
- if (rpc === void 0) {
22819
- return jsonError(400, "rpc envelope carries no rpc call");
22820
- }
22821
- const definition = rpcDefinitionOf(bundle?.rpc?.[rpc.fn]);
22822
- if (definition === void 0) {
22823
- return jsonError(200, `missing handler: rpc.${rpc.fn}`);
22824
- }
22825
- const args = await checkAgainst(definition.input, rpc.args);
22826
- if (!args.ok) {
22827
- return json2(200, { v: ENVELOPE_VERSION, error: { code: "invalid_arguments", message: failureMessage(args.message) } });
22828
- }
22829
- const install = envelope.installs[0];
22830
- const emits = [];
22831
- const pushes = [];
22832
- const captures = [];
22833
- const context = contextCollector();
22834
- const maps = parseVariableMaps(env);
22835
- const { variableKinds: kinds, appVariableKinds, site } = appContextOf(env);
22836
- const appScope = projectAppValues(env, appVariableKinds);
22837
- const subject = envelope.user?.anonId ?? null;
22838
- const ctx = {
22839
- ...buildContext({ env, envelope, site, install, maps, kinds, appScope, emits, pushes, captures, context }),
22840
- context: fetchedContext(
22841
- env,
22842
- (what) => {
22843
- return requireSubject(subject, what);
22844
- },
22845
- context
22846
- )
22847
- };
22848
- let value;
22849
- try {
22850
- value = await installSpan(install, () => {
22851
- return definition.handler(args.value, ctx);
22852
- });
22853
- } catch (error61) {
22854
- const message = failureMessage(error61);
22855
- console.warn(`rpc failed for install ${install.id}: ${message}`);
22856
- return json2(200, { v: ENVELOPE_VERSION, error: { message } });
22857
- }
22858
- const checked = await checkAgainst(definition.output, value);
22859
- if (!checked.ok) {
22860
- return jsonError(200, failureMessage(`rpc result does not match the output schema: ${checked.message}`));
22861
- }
22862
- const result = checked.value === void 0 ? null : checked.value;
22863
- if (new TextEncoder().encode(JSON.stringify(result)).length > MAX_RPC_BYTES) {
22864
- return jsonError(200, "rpc result exceeds the size cap");
22865
- }
22866
- const body = { v: ENVELOPE_VERSION, rpc: { result } };
22867
- const contextWrites = context.writes();
22868
- if (emits.length > 0 || pushes.length > 0 || captures.length > 0 || contextWrites.length > 0) {
22869
- body.effects = [{ ord: install.ord, emits, pushes, captures, context: contextWrites }];
22870
- }
22871
- return json2(200, body);
22872
- };
22873
23034
  var dispatch = async (envelope, versions, env) => {
22874
23035
  if (!(envelope.app.version in versions)) {
22875
23036
  return jsonError(200, "unknown version");