@every-app/sdk 0.2.0 → 0.3.0-rc.10

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.
Files changed (189) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +609 -3
  3. package/dist/cloudflare/catalogSettings.generated.d.ts +81 -0
  4. package/dist/cloudflare/catalogSettings.generated.d.ts.map +1 -0
  5. package/dist/cloudflare/catalogSettings.generated.js +174 -0
  6. package/dist/cloudflare/index.d.ts +23 -0
  7. package/dist/cloudflare/index.d.ts.map +1 -0
  8. package/dist/cloudflare/index.js +24 -0
  9. package/dist/cloudflare/sentinel.d.ts +127 -0
  10. package/dist/cloudflare/sentinel.d.ts.map +1 -0
  11. package/dist/cloudflare/sentinel.js +85 -0
  12. package/dist/cloudflare/worker.d.ts +805 -0
  13. package/dist/cloudflare/worker.d.ts.map +1 -0
  14. package/dist/cloudflare/worker.js +997 -0
  15. package/dist/dev/index.d.ts +8 -0
  16. package/dist/dev/index.d.ts.map +1 -0
  17. package/dist/dev/index.js +7 -0
  18. package/dist/dev/keypair.d.ts +26 -0
  19. package/dist/dev/keypair.d.ts.map +1 -0
  20. package/dist/dev/keypair.js +62 -0
  21. package/dist/index.d.ts +6 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +5 -0
  24. package/dist/internal/aiDefault.d.ts +204 -0
  25. package/dist/internal/aiDefault.d.ts.map +1 -0
  26. package/dist/internal/aiDefault.js +334 -0
  27. package/dist/internal/aiProviders.d.ts +72 -0
  28. package/dist/internal/aiProviders.d.ts.map +1 -0
  29. package/dist/internal/aiProviders.js +233 -0
  30. package/dist/internal/appSettings.d.ts +315 -0
  31. package/dist/internal/appSettings.d.ts.map +1 -0
  32. package/dist/internal/appSettings.js +549 -0
  33. package/dist/internal/audit.d.ts +108 -0
  34. package/dist/internal/audit.d.ts.map +1 -0
  35. package/dist/internal/audit.js +235 -0
  36. package/dist/internal/capabilities.d.ts +3174 -0
  37. package/dist/internal/capabilities.d.ts.map +1 -0
  38. package/dist/internal/capabilities.js +1110 -0
  39. package/dist/internal/catalog/commerce.d.ts +394 -0
  40. package/dist/internal/catalog/commerce.d.ts.map +1 -0
  41. package/dist/internal/catalog/commerce.js +477 -0
  42. package/dist/internal/catalog/comms.d.ts +375 -0
  43. package/dist/internal/catalog/comms.d.ts.map +1 -0
  44. package/dist/internal/catalog/comms.js +520 -0
  45. package/dist/internal/catalog/index.d.ts +1973 -0
  46. package/dist/internal/catalog/index.d.ts.map +1 -0
  47. package/dist/internal/catalog/index.js +19 -0
  48. package/dist/internal/catalog/inference.d.ts +393 -0
  49. package/dist/internal/catalog/inference.d.ts.map +1 -0
  50. package/dist/internal/catalog/inference.js +460 -0
  51. package/dist/internal/catalog/infra.d.ts +423 -0
  52. package/dist/internal/catalog/infra.d.ts.map +1 -0
  53. package/dist/internal/catalog/infra.js +543 -0
  54. package/dist/internal/catalog/search.d.ts +400 -0
  55. package/dist/internal/catalog/search.d.ts.map +1 -0
  56. package/dist/internal/catalog/search.js +463 -0
  57. package/dist/internal/devProviders.d.ts +49 -0
  58. package/dist/internal/devProviders.d.ts.map +1 -0
  59. package/dist/internal/devProviders.js +80 -0
  60. package/dist/internal/email.d.ts +280 -0
  61. package/dist/internal/email.d.ts.map +1 -0
  62. package/dist/internal/email.js +613 -0
  63. package/dist/internal/events.d.ts +193 -0
  64. package/dist/internal/events.d.ts.map +1 -0
  65. package/dist/internal/events.js +367 -0
  66. package/dist/internal/gatewaySettings.d.ts +83 -0
  67. package/dist/internal/gatewaySettings.d.ts.map +1 -0
  68. package/dist/internal/gatewaySettings.js +215 -0
  69. package/dist/internal/identity.d.ts +57 -5
  70. package/dist/internal/identity.d.ts.map +1 -1
  71. package/dist/internal/identity.js +99 -22
  72. package/dist/internal/index.d.ts +22 -1
  73. package/dist/internal/index.d.ts.map +1 -1
  74. package/dist/internal/index.js +22 -1
  75. package/dist/internal/members.d.ts +36 -0
  76. package/dist/internal/members.d.ts.map +1 -0
  77. package/dist/internal/members.js +7 -0
  78. package/dist/internal/oauthClient.d.ts +61 -0
  79. package/dist/internal/oauthClient.d.ts.map +1 -0
  80. package/dist/internal/oauthClient.js +133 -0
  81. package/dist/internal/oauthPresets.d.ts +33 -0
  82. package/dist/internal/oauthPresets.d.ts.map +1 -0
  83. package/dist/internal/oauthPresets.js +1064 -0
  84. package/dist/internal/oauthScopes.d.ts +172 -0
  85. package/dist/internal/oauthScopes.d.ts.map +1 -0
  86. package/dist/internal/oauthScopes.js +510 -0
  87. package/dist/internal/oauthSpec.d.ts +102 -0
  88. package/dist/internal/oauthSpec.d.ts.map +1 -0
  89. package/dist/internal/oauthSpec.js +789 -0
  90. package/dist/internal/organizationName.d.ts +18 -0
  91. package/dist/internal/organizationName.d.ts.map +1 -0
  92. package/dist/internal/organizationName.js +24 -0
  93. package/dist/internal/protocol.d.ts +150 -3
  94. package/dist/internal/protocol.d.ts.map +1 -1
  95. package/dist/internal/protocol.js +163 -2
  96. package/dist/internal/providerApps.d.ts +75 -0
  97. package/dist/internal/providerApps.d.ts.map +1 -0
  98. package/dist/internal/providerApps.js +59 -0
  99. package/dist/internal/providerRegistry.d.ts +218 -0
  100. package/dist/internal/providerRegistry.d.ts.map +1 -0
  101. package/dist/internal/providerRegistry.js +432 -0
  102. package/dist/internal/providerSpec.d.ts +66 -0
  103. package/dist/internal/providerSpec.d.ts.map +1 -0
  104. package/dist/internal/providerSpec.js +1154 -0
  105. package/dist/internal/remoteMcp.d.ts +188 -0
  106. package/dist/internal/remoteMcp.d.ts.map +1 -0
  107. package/dist/internal/remoteMcp.js +543 -0
  108. package/dist/internal/signInProviders.d.ts +104 -0
  109. package/dist/internal/signInProviders.d.ts.map +1 -0
  110. package/dist/internal/signInProviders.js +245 -0
  111. package/dist/internal/tools.d.ts +115 -0
  112. package/dist/internal/tools.d.ts.map +1 -0
  113. package/dist/internal/tools.js +80 -0
  114. package/dist/manifest.d.ts +238 -0
  115. package/dist/manifest.d.ts.map +1 -0
  116. package/dist/manifest.js +30 -0
  117. package/dist/server/ai.d.ts +111 -0
  118. package/dist/server/ai.d.ts.map +1 -0
  119. package/dist/server/ai.js +145 -0
  120. package/dist/server/aiBinding.d.ts +68 -0
  121. package/dist/server/aiBinding.d.ts.map +1 -0
  122. package/dist/server/aiBinding.js +294 -0
  123. package/dist/server/attribution.d.ts +43 -0
  124. package/dist/server/attribution.d.ts.map +1 -0
  125. package/dist/server/attribution.js +75 -0
  126. package/dist/server/authorizeUrl.d.ts +43 -0
  127. package/dist/server/authorizeUrl.d.ts.map +1 -0
  128. package/dist/server/authorizeUrl.js +106 -0
  129. package/dist/server/connectUrl.d.ts +21 -0
  130. package/dist/server/connectUrl.d.ts.map +1 -0
  131. package/dist/server/connectUrl.js +37 -0
  132. package/dist/server/context.d.ts +68 -0
  133. package/dist/server/context.d.ts.map +1 -0
  134. package/dist/server/context.js +117 -0
  135. package/dist/server/emailBinding.d.ts +72 -0
  136. package/dist/server/emailBinding.d.ts.map +1 -0
  137. package/dist/server/emailBinding.js +124 -0
  138. package/dist/server/emit.d.ts +63 -0
  139. package/dist/server/emit.d.ts.map +1 -0
  140. package/dist/server/emit.js +101 -0
  141. package/dist/server/env.d.ts +44 -0
  142. package/dist/server/env.d.ts.map +1 -0
  143. package/dist/server/env.js +1 -0
  144. package/dist/server/everyApp.d.ts +65 -19
  145. package/dist/server/everyApp.d.ts.map +1 -1
  146. package/dist/server/everyApp.js +195 -30
  147. package/dist/server/fromMcpTools.d.ts +89 -0
  148. package/dist/server/fromMcpTools.d.ts.map +1 -0
  149. package/dist/server/fromMcpTools.js +192 -0
  150. package/dist/server/gatewayFetch.d.ts +54 -14
  151. package/dist/server/gatewayFetch.d.ts.map +1 -1
  152. package/dist/server/gatewayFetch.js +201 -37
  153. package/dist/server/gatewayUrl.d.ts +13 -0
  154. package/dist/server/gatewayUrl.d.ts.map +1 -0
  155. package/dist/server/gatewayUrl.js +34 -0
  156. package/dist/server/index.d.ts +23 -3
  157. package/dist/server/index.d.ts.map +1 -1
  158. package/dist/server/index.js +19 -2
  159. package/dist/server/integrationFetch.d.ts +162 -0
  160. package/dist/server/integrationFetch.d.ts.map +1 -0
  161. package/dist/server/integrationFetch.js +688 -0
  162. package/dist/server/listMembers.d.ts +32 -0
  163. package/dist/server/listMembers.d.ts.map +1 -0
  164. package/dist/server/listMembers.js +71 -0
  165. package/dist/server/nodeServer.d.ts +48 -0
  166. package/dist/server/nodeServer.d.ts.map +1 -0
  167. package/dist/server/nodeServer.js +45 -0
  168. package/dist/server/refusal.d.ts +55 -0
  169. package/dist/server/refusal.d.ts.map +1 -0
  170. package/dist/server/refusal.js +193 -0
  171. package/dist/server/sendEmail.d.ts +39 -0
  172. package/dist/server/sendEmail.d.ts.map +1 -0
  173. package/dist/server/sendEmail.js +114 -0
  174. package/dist/server/tools.d.ts +184 -0
  175. package/dist/server/tools.d.ts.map +1 -0
  176. package/dist/server/tools.js +347 -0
  177. package/dist/server/toolsEndpoint.d.ts +32 -0
  178. package/dist/server/toolsEndpoint.d.ts.map +1 -0
  179. package/dist/server/toolsEndpoint.js +237 -0
  180. package/dist/tanstack-start/index.d.ts +9 -0
  181. package/dist/tanstack-start/index.d.ts.map +1 -0
  182. package/dist/tanstack-start/index.js +29 -0
  183. package/dist/testing/index.d.ts +93 -0
  184. package/dist/testing/index.d.ts.map +1 -0
  185. package/dist/testing/index.js +156 -0
  186. package/package.json +42 -8
  187. package/dist/server/mcp.d.ts +0 -66
  188. package/dist/server/mcp.d.ts.map +0 -1
  189. package/dist/server/mcp.js +0 -147
@@ -0,0 +1,997 @@
1
+ import { createHash, createHmac } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { parseEnv } from "node:util";
5
+ import { DEV_ISSUER, DEV_ORG_ID, DEV_PUBLIC_KEY_PEM } from "../dev/keypair.js";
6
+ // The catalog's settings, generated from ../internal/capabilities.ts, which
7
+ // this file must not import (see ./index.ts: the Vite plugin hides whatever
8
+ // cloudflare.config.ts imports from the Worker).
9
+ import { DEV_GATEWAY_KEYS } from "./catalogSettings.generated.js";
10
+ import { helperWorkerName, sentinelDeployTarget, workflowNameFor, } from "./sentinel.js";
11
+ export { helperWorkerName, workflowNameFor } from "./sentinel.js";
12
+ /** The names of the manifest's `secrets`, whichever form each took. */
13
+ function manifestSecretNames(manifest) {
14
+ return (manifest.secrets ?? []).map((entry) => typeof entry === "string" ? entry : entry.name);
15
+ }
16
+ /** The ids of the manifest's `providers`, whichever form each took. */
17
+ function manifestProviderIds(manifest) {
18
+ return (manifest.providers ?? []).map((entry) => typeof entry === "string" ? entry : entry.id);
19
+ }
20
+ /**
21
+ * Observability every Every App Worker ships with: logs (with invocation
22
+ * logs), sampled traces and Workers Issues, with query strings redacted so
23
+ * tokens and invitation ids in URLs stay out of logs.
24
+ */
25
+ export const EVERYAPP_OBSERVABILITY = Object.freeze({
26
+ enabled: true,
27
+ headSamplingRate: 1,
28
+ redactQueryString: true,
29
+ logs: Object.freeze({
30
+ enabled: true,
31
+ invocationLogs: true,
32
+ persist: true,
33
+ headSamplingRate: 1,
34
+ }),
35
+ traces: Object.freeze({
36
+ enabled: true,
37
+ persist: true,
38
+ headSamplingRate: 0.1,
39
+ }),
40
+ issues: Object.freeze({ enabled: true }),
41
+ });
42
+ const DEFAULT_COMPATIBILITY_DATE = "2026-06-01";
43
+ /** Set by `everyapp deploy` around the production build it installs. */
44
+ const BUILD_ENV = "EVERYAPP_BUILD";
45
+ /** Names the deploy sets itself (as `everyapp secret set` refuses). */
46
+ const RESERVED_SECRET_NAME = /^(GATEWAY_URL|CF_VERSION_METADATA|EVERYAPP_\w*|EVERY_APP_\w*|APP__\w*)$/i;
47
+ const GATEWAY_WORKER_NAME = "every-app-gateway";
48
+ const GATEWAY_ENTRYPOINT = "AppGateway";
49
+ /**
50
+ * Where `everyapp dev` serves its launcher, which it passes to the app's dev
51
+ * server in EVERYAPP_DEV_GATEWAY_URL; its default address otherwise. The
52
+ * deployed Worker gets the Gateway's URL from the deploy instead.
53
+ */
54
+ const DEV_GATEWAY_URL_ENV = "EVERYAPP_DEV_GATEWAY_URL";
55
+ const DEFAULT_DEV_GATEWAY_URL = "http://localhost:8787";
56
+ function devGatewayUrl() {
57
+ return {
58
+ type: "text",
59
+ value: process.env[DEV_GATEWAY_URL_ENV] || DEFAULT_DEV_GATEWAY_URL,
60
+ };
61
+ }
62
+ /**
63
+ * The manifest's `capabilities` as the SDK's local-dev twin of the gateway
64
+ * reads them: with no gateway to hold an OAuth client's calls to the scope
65
+ * sets the app declares, `integrationFetch()` holds them to these.
66
+ */
67
+ function devCapabilities(manifest) {
68
+ return { type: "text", value: JSON.stringify(manifest.capabilities ?? {}) };
69
+ }
70
+ /**
71
+ * The binding the dev Worker gets the specs of providers registered on a
72
+ * gateway in (`DEV_PROVIDERS_ENV` in ../internal/devProviders.ts; a copy,
73
+ * kept in step by a test, since this file may not import ../internal).
74
+ */
75
+ const DEV_PROVIDERS_ENV = "EVERYAPP_DEV_PROVIDERS";
76
+ /** Linked dev's env names (`LINKED_DEV_URL_ENV`, `LINKED_DEV_NONCE_ENV` in ../internal/protocol.ts; copies, kept in step by the same test). */
77
+ const LINKED_DEV_URL_ENV = "EVERYAPP_LINKED_DEV_URL";
78
+ const LINKED_DEV_NONCE_ENV = "EVERYAPP_LINKED_DEV_NONCE";
79
+ /**
80
+ * The specs `everyapp provider add` (in an app directory) and `everyapp
81
+ * provider show <id> --save` wrote under the app, in name order; none
82
+ * without the directory. integrationFetch()'s dev fallback reaches a
83
+ * provider outside the catalog from them, with the developer's own key.
84
+ */
85
+ function devProviderFiles(root) {
86
+ const dir = path.join(root ?? process.cwd(), ".everyapp", "providers");
87
+ let names;
88
+ try {
89
+ names = fs
90
+ .readdirSync(dir)
91
+ .filter((name) => name.endsWith(".json"))
92
+ .sort();
93
+ }
94
+ catch {
95
+ return [];
96
+ }
97
+ return names.map((name) => {
98
+ const text = fs.readFileSync(path.join(dir, name), "utf8");
99
+ let spec;
100
+ try {
101
+ spec = JSON.parse(text);
102
+ }
103
+ catch {
104
+ spec = text;
105
+ }
106
+ return { file: `.everyapp/providers/${name}`, spec };
107
+ });
108
+ }
109
+ /**
110
+ * Linked dev (`everyapp dev --gateway`, docs/design/linked-dev.md): the
111
+ * gateway-lite starts the app's dev server with the forwarder's loopback
112
+ * URL and the run's nonce in the process environment; the dev Worker gets
113
+ * both as text, and the SDK's `gatewayBinding()` builds the shim from
114
+ * them in place of the missing service binding. Absent in every other
115
+ * run, and never in a deploy.
116
+ */
117
+ function addLinkedDev(env) {
118
+ const url = process.env[LINKED_DEV_URL_ENV]?.trim();
119
+ const nonce = process.env[LINKED_DEV_NONCE_ENV];
120
+ if (!url || !nonce)
121
+ return;
122
+ env[LINKED_DEV_URL_ENV] = { type: "text", value: url };
123
+ env[LINKED_DEV_NONCE_ENV] = { type: "text", value: nonce };
124
+ }
125
+ /** EVERYAPP_DEV_PROVIDERS, when the app has any spec files. */
126
+ function addDevProviders(env, root) {
127
+ const files = devProviderFiles(root);
128
+ if (files.length === 0)
129
+ return;
130
+ env[DEV_PROVIDERS_ENV] = { type: "text", value: JSON.stringify(files) };
131
+ }
132
+ /**
133
+ * The Gateway settings the spec files name for the capabilities the
134
+ * manifest lists outside {@link DEV_GATEWAY_KEYS} (a registered provider's
135
+ * key, a Basic username held as a setting, its host setting), read
136
+ * loosely: the SDK parses the spec when the app calls the provider.
137
+ */
138
+ function devProviderKeys(manifest, root) {
139
+ const listed = Object.keys(manifest.capabilities ?? {}).filter((id) => !Object.hasOwn(DEV_GATEWAY_KEYS.capabilities, id));
140
+ if (listed.length === 0)
141
+ return [];
142
+ const record = (value) => value && typeof value === "object" && !Array.isArray(value)
143
+ ? value
144
+ : undefined;
145
+ const names = [];
146
+ for (const { file, spec } of devProviderFiles(root)) {
147
+ const fields = record(spec);
148
+ const id = typeof fields?.["id"] === "string"
149
+ ? fields["id"]
150
+ : file
151
+ .split("/")
152
+ .at(-1)
153
+ .replace(/\.json$/, "");
154
+ if (!fields || !listed.includes(id))
155
+ continue;
156
+ const auth = record(fields["auth"]);
157
+ for (const name of [
158
+ auth?.["secret"],
159
+ record(auth?.["user"])?.["secret"],
160
+ fields["hostSetting"],
161
+ ]) {
162
+ if (typeof name === "string" && /^[A-Z][A-Z0-9_]*$/.test(name)) {
163
+ names.push(name);
164
+ }
165
+ }
166
+ }
167
+ return names;
168
+ }
169
+ /** Cloudflare's limit on a Workflow's name. */
170
+ const MAX_WORKFLOW_NAME_LENGTH = 64;
171
+ /**
172
+ * The store development binds Secrets Store secrets in: Miniflare keeps
173
+ * one per id under `.cloudflare/state`, and any id works locally.
174
+ */
175
+ const LOCAL_SECRETS_STORE_ID = "local";
176
+ /** Cloudflare's limit on a Vectorize index's name. */
177
+ const MAX_VECTORIZE_NAME_LENGTH = 64;
178
+ /** Cloudflare's limit on a Worker's name. */
179
+ /**
180
+ * Where a developer puts a local Postgres URL for a Hyperdrive binding
181
+ * (`.dev.vars` or `.env`), the variable wrangler reads for the same.
182
+ */
183
+ const LOCAL_HYPERDRIVE_PREFIX = "CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_";
184
+ /**
185
+ * Build the Worker definition for an Every App app from its manifest.
186
+ *
187
+ * - `development` (cf dev) and no mode (cf API commands, `cf workers types`):
188
+ * EVERYAPP_DEV is on, the identity keys and issuer are the well-known dev
189
+ * ones (`@every-app/sdk/dev`) as plain text, so nothing needs writing to
190
+ * `.dev.vars`, and each D1 database gets a fixed local id (see
191
+ * {@link localD1Id}) that `cf d1 … --local` can address.
192
+ * - `production`: tenant-neutral. Everything install-specific (the Worker
193
+ * name, organization, gateway URL, identity keys, resource names and ids)
194
+ * is a placeholder from {@link sentinelDeployTarget}, which the installer
195
+ * rewrites (`everyapp deploy`, or a hosted gateway's deployer), so every
196
+ * build of an app is the same. Only with EVERYAPP_BUILD=1, which
197
+ * `everyapp deploy` sets: a bare `cf deploy` would upload the
198
+ * placeholders.
199
+ */
200
+ export function everyAppWorker(options) {
201
+ const { manifest } = options;
202
+ const { deploy, name } = appWorkerName(options, "everyAppWorker");
203
+ const env = {
204
+ EVERYAPP_APP_ID: { type: "text", value: manifest.id },
205
+ // The manifest itself, for a page that shows what the app asked for:
206
+ // the Worker can't import everyapp.config.ts (see ./index.ts).
207
+ EVERYAPP_MANIFEST: {
208
+ type: "json",
209
+ value: manifest,
210
+ },
211
+ CF_VERSION_METADATA: { type: "version-metadata" },
212
+ };
213
+ if (deploy) {
214
+ // The gateway mints every identity for the Worker's name (`aud`).
215
+ env["EVERYAPP_AUDIENCE"] = { type: "text", value: name };
216
+ env["EVERYAPP_IDENTITY_PUBLIC_KEYS"] = {
217
+ type: "text",
218
+ value: JSON.stringify(deploy.identityPublicKeys),
219
+ };
220
+ env["EVERYAPP_IDENTITY_ISSUER"] = {
221
+ type: "text",
222
+ value: deploy.gatewayUrl,
223
+ };
224
+ env["EVERY_APP_GATEWAY"] = gatewayBinding(deploy, manifest.id, name);
225
+ // Sent by the deploy pipeline through `cf deploy --secrets-file`.
226
+ env["GATEWAY_URL"] = { type: "secret" };
227
+ env["EVERY_APP_ORG_ID"] = { type: "secret" };
228
+ }
229
+ else {
230
+ env["EVERYAPP_DEV"] = { type: "text", value: "1" };
231
+ env["EVERYAPP_DEV_CAPABILITIES"] = devCapabilities(manifest);
232
+ env["EVERYAPP_IDENTITY_PUBLIC_KEYS"] = {
233
+ type: "text",
234
+ value: JSON.stringify([DEV_PUBLIC_KEY_PEM]),
235
+ };
236
+ env["EVERYAPP_IDENTITY_ISSUER"] = { type: "text", value: DEV_ISSUER };
237
+ env["GATEWAY_URL"] = devGatewayUrl();
238
+ // The stub gateway's organization, as the deploy's secret would be, so
239
+ // background work's withOrganization(env.EVERY_APP_ORG_ID, …) runs
240
+ // here too. Not a pin: under the dev opt-in everyApp() enforces none,
241
+ // so `everyapp dev --mode mirror`'s real organizations still open.
242
+ env["EVERY_APP_ORG_ID"] = { type: "text", value: DEV_ORG_ID };
243
+ addDevProviders(env, options.root);
244
+ addLinkedDev(env);
245
+ }
246
+ Object.assign(env, storageBindings(manifest, name, deploy));
247
+ addQueueBindings(env, manifest, name, deploy);
248
+ addAiBindings(env, manifest, deploy);
249
+ addBrowserBindings(env, manifest);
250
+ addImagesBindings(env, manifest);
251
+ addAiGatewayBindings(env, manifest, name, deploy);
252
+ addSendEmailBindings(env, manifest);
253
+ addSecretsStoreBindings(env, manifest, name, deploy);
254
+ addAnalyticsEngineBindings(env, manifest, name);
255
+ addDatabaseBindings(env, options, name, undefined, deploy);
256
+ // The app binds every class, a helper Worker's in that Worker, and
257
+ // exports only its own.
258
+ const { bindings, exports } = classBindings(manifest, name, undefined, env);
259
+ Object.assign(env, bindings);
260
+ for (const helper of manifest.workers ?? []) {
261
+ if (!helper.binding)
262
+ continue;
263
+ if (env[helper.binding]) {
264
+ throw new Error(`workers: ${helper.name}'s binding ${helper.binding} reuses a binding name the Worker already has.`);
265
+ }
266
+ env[helper.binding] = {
267
+ type: "worker",
268
+ worker: helperWorkerName(name, helper.name),
269
+ };
270
+ }
271
+ addSecrets(env, options, deploy);
272
+ const triggers = [
273
+ ...cronTriggers(options.crons),
274
+ ...queueTriggers(manifest, name, undefined, deploy),
275
+ ];
276
+ const compatibilityDate = manifest.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE;
277
+ // cf types a Durable Object namespace with its class only when the binding
278
+ // names the defining Worker by a definition whose module it can read, so
279
+ // the classes this Worker defines name it that way; cf resolves the
280
+ // reference to the Worker's name. A path entrypoint gives cf no module.
281
+ // Only here: `EveryAppBinding` names Workers by name, as the Vite
282
+ // plugin's `auxiliaryWorkers` require.
283
+ const { entrypoint } = options;
284
+ const ownClasses = {};
285
+ if (typeof entrypoint === "object") {
286
+ const self = {
287
+ name,
288
+ compatibilityDate,
289
+ entrypoint,
290
+ };
291
+ for (const durableObject of manifest.resources?.durableObjects ?? []) {
292
+ if (durableObject.worker !== undefined)
293
+ continue;
294
+ ownClasses[durableObject.name] = {
295
+ type: "durable-object",
296
+ worker: self,
297
+ exportName: durableObject.className,
298
+ };
299
+ }
300
+ }
301
+ return {
302
+ name,
303
+ entrypoint,
304
+ compatibilityDate,
305
+ compatibilityFlags: compatibilityFlags(options.compatibilityFlags),
306
+ workersDev: false,
307
+ previewUrls: false,
308
+ ...(triggers.length ? { triggers } : {}),
309
+ ...runtimeSettings(manifest, manifest.limits, deploy),
310
+ observability: observability(),
311
+ // Each class keeps its key's place in `env`: classBindings bound it by name.
312
+ env: { ...env, ...ownClasses },
313
+ exports: exports,
314
+ };
315
+ }
316
+ /**
317
+ * Build the definition of one of the app's helper Workers (the manifest's
318
+ * `workers`), e.g. one that runs a memory-hungry job away from the app's
319
+ * Worker. It is named `<app worker>--<worker>`, is private like the app, and
320
+ * binds the app's and Analytics Engine datasets, the classes it defines, and
321
+ * binds the app's D1 databases, KV namespaces, R2 buckets, Workers AI,
322
+ * Browser Rendering, Images, Vectorize indexes, AI Gateways, `send_email`
323
+ * bindings and Secrets Store secrets, the classes it defines, and
324
+ * the gateway (with the app's identity, for capabilities). Only the app's
325
+ * Worker reaches it. Pass it to the Vite plugin:
326
+ *
327
+ * cloudflare({
328
+ * auxiliaryWorkers: [
329
+ * { config: everyAppAuxiliaryWorker({ manifest, worker: "audit", entrypoint: "./src/audit.ts", ctx: { mode } }) },
330
+ * ],
331
+ * })
332
+ */
333
+ export function everyAppAuxiliaryWorker(options) {
334
+ const { manifest, worker } = options;
335
+ const helper = manifest.workers?.find((entry) => entry.name === worker);
336
+ if (!helper) {
337
+ throw new Error(`everyapp.config.ts declares no helper Worker "${worker}" in workers.`);
338
+ }
339
+ const { deploy, name: appWorker } = appWorkerName(options, "everyAppAuxiliaryWorker");
340
+ const name = helperWorkerName(appWorker, worker);
341
+ const env = {
342
+ EVERYAPP_APP_ID: { type: "text", value: manifest.id },
343
+ ...storageBindings(manifest, appWorker, deploy),
344
+ };
345
+ addQueueBindings(env, manifest, appWorker, deploy);
346
+ addSendEmailBindings(env, manifest);
347
+ if (deploy) {
348
+ // The app's identity: the gateway treats its helper Workers as the app.
349
+ env["EVERY_APP_GATEWAY"] = gatewayBinding(deploy, manifest.id, appWorker);
350
+ // Sent by the deploy pipeline through `cf deploy --secrets-file`, like
351
+ // the app's Worker: a helper's gateway calls name the organization too.
352
+ env["GATEWAY_URL"] = { type: "secret" };
353
+ env["EVERY_APP_ORG_ID"] = { type: "secret" };
354
+ }
355
+ else {
356
+ env["EVERYAPP_DEV"] = { type: "text", value: "1" };
357
+ env["EVERYAPP_DEV_CAPABILITIES"] = devCapabilities(manifest);
358
+ env["GATEWAY_URL"] = devGatewayUrl();
359
+ env["EVERY_APP_ORG_ID"] = { type: "text", value: DEV_ORG_ID };
360
+ addDevProviders(env, options.root);
361
+ addLinkedDev(env);
362
+ }
363
+ addAiBindings(env, manifest, deploy);
364
+ addBrowserBindings(env, manifest);
365
+ addImagesBindings(env, manifest);
366
+ addAiGatewayBindings(env, manifest, appWorker, deploy);
367
+ addSecretsStoreBindings(env, manifest, appWorker, deploy);
368
+ addAnalyticsEngineBindings(env, manifest, appWorker);
369
+ addDatabaseBindings(env, options, appWorker, worker, deploy);
370
+ const { bindings, exports } = classBindings(manifest, appWorker, worker, env);
371
+ Object.assign(env, bindings);
372
+ addSecrets(env, options, deploy);
373
+ const triggers = [
374
+ ...cronTriggers(options.crons),
375
+ ...queueTriggers(manifest, appWorker, worker, deploy),
376
+ ];
377
+ return {
378
+ name,
379
+ entrypoint: options.entrypoint,
380
+ compatibilityDate: manifest.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE,
381
+ compatibilityFlags: compatibilityFlags(options.compatibilityFlags),
382
+ workersDev: false,
383
+ previewUrls: false,
384
+ ...(triggers.length ? { triggers } : {}),
385
+ ...runtimeSettings(manifest, helper.limits, deploy),
386
+ observability: observability(),
387
+ env,
388
+ exports,
389
+ };
390
+ }
391
+ /** The placeholder target (in production) and the app Worker's name. */
392
+ function appWorkerName(options, builder) {
393
+ const { manifest, ctx } = options;
394
+ assertAppId(manifest.id);
395
+ let deploy;
396
+ if (ctx.mode === "production") {
397
+ if (process.env[BUILD_ENV] !== "1") {
398
+ throw new Error(`EveryApp Workers are built for production only by \`everyapp deploy\` (\`pnpm run deploy\`), which sets ${BUILD_ENV}=1 and installs the build: ` +
399
+ "a bare `cf build` or `cf deploy` would ship a Worker whose name, identity keys, gateway binding and resources are placeholders. " +
400
+ "For a local build, run Vite in development mode (`vite build --mode development`).");
401
+ }
402
+ deploy = sentinelDeployTarget(manifest);
403
+ }
404
+ else if (ctx.mode !== undefined && ctx.mode !== "development") {
405
+ throw new Error(`${builder}() builds in the "development" or "production" mode, not "${ctx.mode}".`);
406
+ }
407
+ const name = deploy?.workerName ?? workerNameFor(manifest.id);
408
+ if (name === GATEWAY_WORKER_NAME) {
409
+ throw new Error(`App id "${manifest.id}" is reserved: its Worker would be the gateway's own (${GATEWAY_WORKER_NAME}).`);
410
+ }
411
+ return { deploy, name };
412
+ }
413
+ function gatewayBinding(deploy, appId, appWorker) {
414
+ return {
415
+ type: "worker",
416
+ worker: GATEWAY_WORKER_NAME,
417
+ exportName: GATEWAY_ENTRYPOINT,
418
+ props: {
419
+ organizationId: deploy.organizationId,
420
+ appId,
421
+ workerName: appWorker,
422
+ },
423
+ };
424
+ }
425
+ /**
426
+ * The app's D1 databases, KV namespaces, R2 buckets and Vectorize indexes,
427
+ * which all its Workers share.
428
+ */
429
+ function storageBindings(manifest, appWorker, deploy) {
430
+ const env = {};
431
+ for (const binding of manifest.resources?.d1 ?? []) {
432
+ if (deploy) {
433
+ const d1 = provisioned(deploy.resources.d1, binding);
434
+ env[binding] = { type: "d1", name: d1.name, id: d1.id };
435
+ }
436
+ else {
437
+ env[binding] = {
438
+ type: "d1",
439
+ name: resourceNameFor(appWorker, binding),
440
+ id: localD1Id(appWorker, binding),
441
+ };
442
+ }
443
+ }
444
+ for (const binding of manifest.resources?.kv ?? []) {
445
+ if (deploy) {
446
+ env[binding] = {
447
+ type: "kv",
448
+ id: provisioned(deploy.resources.kv, binding).id,
449
+ };
450
+ }
451
+ else if (manifest.workers?.length) {
452
+ // The Vite plugin's own default for the app's Worker, named so that
453
+ // its helpers share the namespace in dev, as deployed ones do.
454
+ env[binding] = { type: "kv", id: `${binding}-${appWorker}` };
455
+ }
456
+ else {
457
+ env[binding] = { type: "kv" };
458
+ }
459
+ }
460
+ // Deployed, the names the gateway assigns at registration (placeholders
461
+ // until installed); in dev its rule, so a local bucket is named like a
462
+ // deployed one.
463
+ for (const binding of manifest.resources?.r2 ?? []) {
464
+ if (env[binding]) {
465
+ throw new Error(`r2: ${binding} reuses a binding name the Worker already has.`);
466
+ }
467
+ if (deploy) {
468
+ env[binding] = {
469
+ type: "r2",
470
+ name: provisioned(deploy.resources.r2, binding).name,
471
+ };
472
+ }
473
+ else {
474
+ env[binding] = { type: "r2", name: bucketNameFor(appWorker, binding) };
475
+ }
476
+ }
477
+ // Deployed, the index the deploy provisioned (a placeholder until
478
+ // installed); in development the account's index of the dev Worker's
479
+ // name, reached remotely: Cloudflare has no local Vectorize, so calls
480
+ // throw until the Vite plugin's remoteBindings is on.
481
+ for (const { name: binding } of manifest.resources?.vectorize ?? []) {
482
+ if (env[binding]) {
483
+ throw new Error(`vectorize: ${binding} reuses a binding name the Worker already has.`);
484
+ }
485
+ if (deploy) {
486
+ env[binding] = {
487
+ type: "vectorize",
488
+ name: provisioned(deploy.resources.vectorize, binding).name,
489
+ };
490
+ continue;
491
+ }
492
+ const index = suffixedName(appWorker, binding);
493
+ if (index.length > MAX_VECTORIZE_NAME_LENGTH) {
494
+ throw new Error(`vectorize: ${binding} would be the Vectorize index "${index}", longer than Cloudflare's ${MAX_VECTORIZE_NAME_LENGTH} characters; shorten the binding name.`);
495
+ }
496
+ env[binding] = { type: "vectorize", name: index, dev: { remote: true } };
497
+ }
498
+ return env;
499
+ }
500
+ /**
501
+ * A producer binding to each of the app's queues, which every one of its
502
+ * Workers gets. Deployed, each names the queue the deploy provisioned (a
503
+ * placeholder until installed); in development, the queue its rule gives
504
+ * (Miniflare makes it, shared by the Workers of one `cf dev`).
505
+ */
506
+ function addQueueBindings(env, manifest, appWorker, deploy) {
507
+ for (const { name: binding } of manifest.resources?.queues ?? []) {
508
+ if (env[binding]) {
509
+ throw new Error(`queues: ${binding} reuses a binding name the Worker already has.`);
510
+ }
511
+ env[binding] = {
512
+ type: "queue",
513
+ name: queueName(appWorker, binding, deploy),
514
+ };
515
+ }
516
+ }
517
+ /**
518
+ * The manifest's Workers AI bindings, which every one of the app's Workers
519
+ * gets: `{ type: "ai" }` deployed (account-wide, nothing to provision), and
520
+ * marked remote in development, where Miniflare has no Workers AI. The
521
+ * remote mark does nothing until the app's Vite config sets
522
+ * `remoteBindings: true`, so turning that on is the one switch.
523
+ */
524
+ function addAiBindings(env, manifest, deploy) {
525
+ for (const binding of manifest.resources?.ai ?? []) {
526
+ if (env[binding]) {
527
+ throw new Error(`ai: ${binding} reuses a binding name the Worker already has.`);
528
+ }
529
+ env[binding] = deploy
530
+ ? { type: "ai" }
531
+ : { type: "ai", dev: { remote: true } };
532
+ }
533
+ }
534
+ /**
535
+ * The consumer triggers of one of the app's Workers: each queue whose
536
+ * `consumer` is this Worker (`true`, or no `worker`, for the app's own;
537
+ * `forWorker` for a helper), with its settings and its dead-letter queue,
538
+ * another of the app's queues, by name.
539
+ */
540
+ function queueTriggers(manifest, appWorker, forWorker, deploy) {
541
+ const triggers = [];
542
+ for (const queue of manifest.resources?.queues ?? []) {
543
+ if (queue.consumer === undefined)
544
+ continue;
545
+ const settings = queue.consumer === true ? {} : queue.consumer;
546
+ if (settings.worker !== forWorker)
547
+ continue;
548
+ const { deadLetterQueue } = settings;
549
+ if (deadLetterQueue !== undefined &&
550
+ !manifest.resources?.queues?.some((other) => other.name === deadLetterQueue)) {
551
+ throw new Error(`queues: ${queue.name}'s deadLetterQueue "${deadLetterQueue}" isn't one of resources.queues.`);
552
+ }
553
+ triggers.push({
554
+ type: "queue",
555
+ name: queueName(appWorker, queue.name, deploy),
556
+ ...(deadLetterQueue !== undefined
557
+ ? { deadLetterQueue: queueName(appWorker, deadLetterQueue, deploy) }
558
+ : {}),
559
+ ...Object.fromEntries(QUEUE_CONSUMER_SETTINGS.flatMap((key) => settings[key] === undefined ? [] : [[key, settings[key]]])),
560
+ });
561
+ }
562
+ return triggers;
563
+ }
564
+ /** The consumer settings a queue trigger carries, in the order it lists them. */
565
+ const QUEUE_CONSUMER_SETTINGS = [
566
+ "maxBatchSize",
567
+ "maxBatchTimeout",
568
+ "maxRetries",
569
+ "maxConcurrency",
570
+ "retryDelay",
571
+ "visibilityTimeoutMs",
572
+ ];
573
+ /** A queue binding's queue: the deploy's placeholder, or in development its rule. */
574
+ function queueName(appWorker, binding, deploy) {
575
+ return deploy
576
+ ? provisioned(deploy.resources.queues, binding).name
577
+ : queueNameFor(appWorker, binding);
578
+ }
579
+ /**
580
+ * The manifest's Browser Rendering bindings, which every one of the app's
581
+ * Workers gets: `{ type: "browser" }` in both modes (account-wide,
582
+ * nothing to provision; Miniflare serves a local browser in development).
583
+ */
584
+ function addBrowserBindings(env, manifest) {
585
+ for (const binding of manifest.resources?.browser ?? []) {
586
+ if (env[binding]) {
587
+ throw new Error(`browser: ${binding} reuses a binding name the Worker already has.`);
588
+ }
589
+ env[binding] = { type: "browser" };
590
+ }
591
+ }
592
+ /**
593
+ * The manifest's Images bindings, which every one of the app's Workers
594
+ * gets: `{ type: "images" }` in both modes (account-wide, nothing to
595
+ * provision; Miniflare answers it locally in development).
596
+ */
597
+ function addImagesBindings(env, manifest) {
598
+ for (const binding of manifest.resources?.images ?? []) {
599
+ if (env[binding]) {
600
+ throw new Error(`images: ${binding} reuses a binding name the Worker already has.`);
601
+ }
602
+ env[binding] = { type: "images" };
603
+ }
604
+ }
605
+ /**
606
+ * The app's AI Gateways, which all its Workers share, each a `json`
607
+ * binding of `{ id, accountId, url }`. Deployed, the gateway the deploy
608
+ * provisioned (a placeholder until installed); in development the id the
609
+ * deploy's rule gives, with an empty account and URL: there is no gateway,
610
+ * so the app calls the provider directly.
611
+ */
612
+ function addAiGatewayBindings(env, manifest, appWorker, deploy) {
613
+ for (const { name: binding } of manifest.resources?.aiGateway ?? []) {
614
+ if (env[binding]) {
615
+ throw new Error(`aiGateway: ${binding} reuses a binding name the Worker already has.`);
616
+ }
617
+ const value = deploy
618
+ ? { ...provisioned(deploy.resources.aiGateway, binding) }
619
+ : { id: aiGatewayIdFor(appWorker, binding), accountId: "", url: "" };
620
+ env[binding] = { type: "json", value };
621
+ }
622
+ }
623
+ /**
624
+ * The manifest's `send_email` bindings, the same on every one of the
625
+ * app's Workers in both modes: Cloudflare's binding with the addresses
626
+ * the manifest lists (the deploy's policy holds the built lists to them).
627
+ * Deployed, Cloudflare checks a message against them and the account's
628
+ * Email Sending domains when the Worker sends; in development Miniflare
629
+ * logs the message and checks the senders.
630
+ */
631
+ function addSendEmailBindings(env, manifest) {
632
+ for (const entry of manifest.resources?.sendEmail ?? []) {
633
+ if (env[entry.name]) {
634
+ throw new Error(`sendEmail: ${entry.name} reuses a binding name the Worker already has.`);
635
+ }
636
+ const senders = entry.allowedSenderAddresses
637
+ ? { allowedSenderAddresses: [...entry.allowedSenderAddresses] }
638
+ : {};
639
+ if (entry.destinationAddress !== undefined &&
640
+ entry.allowedDestinationAddresses !== undefined) {
641
+ throw new Error(`sendEmail: ${entry.name} sets both destinationAddress and allowedDestinationAddresses; set one.`);
642
+ }
643
+ env[entry.name] =
644
+ entry.destinationAddress !== undefined
645
+ ? {
646
+ type: "send-email",
647
+ ...senders,
648
+ destinationAddress: entry.destinationAddress,
649
+ }
650
+ : entry.allowedDestinationAddresses !== undefined
651
+ ? {
652
+ type: "send-email",
653
+ ...senders,
654
+ allowedDestinationAddresses: [
655
+ ...entry.allowedDestinationAddresses,
656
+ ],
657
+ }
658
+ : { type: "send-email", ...senders };
659
+ }
660
+ }
661
+ /**
662
+ * The manifest's Secrets Store secrets, which every one of the app's
663
+ * Workers binds. Deployed, the store and secret the deploy provisioned
664
+ * (placeholders until installed); in development the local store `local`,
665
+ * under the name the deploy would give the secret, so a seeded local
666
+ * secret is named like a deployed one.
667
+ */
668
+ function addSecretsStoreBindings(env, manifest, appWorker, deploy) {
669
+ for (const { name: binding } of manifest.resources?.secretsStore ?? []) {
670
+ if (env[binding]) {
671
+ throw new Error(`secretsStore: ${binding} reuses a binding name the Worker already has.`);
672
+ }
673
+ if (deploy) {
674
+ const secret = provisioned(deploy.resources.secretsStore, binding);
675
+ env[binding] = {
676
+ type: "secrets-store-secret",
677
+ storeId: secret.storeId,
678
+ secretName: secret.secretName,
679
+ };
680
+ }
681
+ else {
682
+ env[binding] = {
683
+ type: "secrets-store-secret",
684
+ storeId: LOCAL_SECRETS_STORE_ID,
685
+ secretName: suffixedName(appWorker, binding),
686
+ };
687
+ }
688
+ }
689
+ }
690
+ /**
691
+ * The manifest's Analytics Engine datasets, which every one of the app's
692
+ * Workers writes to: the `dataset` an entry names, or
693
+ * `<app worker>--<binding>`. Deployed, the app's Worker name is the build's
694
+ * placeholder, which the install rewrites like every other; a named
695
+ * dataset holds none and stays as written.
696
+ */
697
+ function addAnalyticsEngineBindings(env, manifest, appWorker) {
698
+ for (const entry of manifest.resources?.analyticsEngine ?? []) {
699
+ if (env[entry.name]) {
700
+ throw new Error(`analyticsEngine: ${entry.name} reuses a binding name the Worker already has.`);
701
+ }
702
+ env[entry.name] = {
703
+ type: "analytics-engine-dataset",
704
+ name: entry.dataset ?? suffixedName(appWorker, entry.name),
705
+ };
706
+ }
707
+ }
708
+ /**
709
+ * DATABASE_PROVIDER and the Hyperdrive configs one of the app's Workers
710
+ * binds: each config every Worker shares, and those a helper (`forWorker`)
711
+ * alone takes. Deployed, each is the config the deploy provisioned; in
712
+ * development, a local Postgres URL set as
713
+ * CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<binding>, or nothing.
714
+ */
715
+ function addDatabaseBindings(env, options, appWorker, forWorker, deploy) {
716
+ const { manifest } = options;
717
+ if (manifest.database) {
718
+ env["DATABASE_PROVIDER"] = {
719
+ type: "text",
720
+ value: manifest.database.provider,
721
+ };
722
+ }
723
+ const configs = (manifest.resources?.hyperdrive ?? []).filter((config) => config.worker === undefined || config.worker === forWorker);
724
+ if (configs.length === 0)
725
+ return;
726
+ const local = deploy
727
+ ? {}
728
+ : localEnv(options.root ?? process.cwd(), options.ctx.mode ?? "development");
729
+ for (const config of configs) {
730
+ if (env[config.name]) {
731
+ throw new Error(`hyperdrive: ${config.name} reuses a binding name the Worker already has.`);
732
+ }
733
+ if (deploy) {
734
+ env[config.name] = {
735
+ type: "hyperdrive",
736
+ id: provisioned(deploy.resources.hyperdrive, config.name).id,
737
+ };
738
+ continue;
739
+ }
740
+ const connectionString = local[`${LOCAL_HYPERDRIVE_PREFIX}${config.name}`];
741
+ if (connectionString) {
742
+ env[config.name] = {
743
+ type: "hyperdrive",
744
+ id: localD1Id(appWorker, config.name),
745
+ dev: { connectionString },
746
+ };
747
+ }
748
+ }
749
+ }
750
+ /**
751
+ * The placement and limits of one of the app's Workers: deployed, it runs
752
+ * near the database's region; its CPU limit is the manifest's.
753
+ */
754
+ function runtimeSettings(manifest, limits, deploy) {
755
+ const region = manifest.database?.region;
756
+ return {
757
+ ...(deploy && region ? { placement: { region } } : {}),
758
+ ...(limits?.cpuMs !== undefined ? { limits: { cpuMs: limits.cpuMs } } : {}),
759
+ };
760
+ }
761
+ /**
762
+ * Durable Object and Workflow bindings, and the classes to export, for one
763
+ * of the app's Workers: the app's own (`forWorker` undefined) binds every
764
+ * class, each in the Worker that defines it, and a helper Worker binds only
765
+ * its own. Each Worker exports the classes it defines. Workflow names are
766
+ * account-wide, so all of them are named after the app's Worker.
767
+ */
768
+ function classBindings(manifest, appWorker, forWorker,
769
+ /** The bindings the Worker already has, which no class may reuse. */
770
+ taken) {
771
+ const bindings = {};
772
+ const exports = {};
773
+ const definedHere = (resource) => resource.worker === forWorker;
774
+ const definingWorker = (resource) => resource.worker ? helperWorkerName(appWorker, resource.worker) : appWorker;
775
+ for (const durableObject of manifest.resources?.durableObjects ?? []) {
776
+ if (forWorker !== undefined && !definedHere(durableObject))
777
+ continue;
778
+ bindings[durableObject.name] = {
779
+ type: "durable-object",
780
+ worker: definingWorker(durableObject),
781
+ exportName: durableObject.className,
782
+ };
783
+ if (definedHere(durableObject)) {
784
+ exports[durableObject.className] = {
785
+ type: "durable-object",
786
+ storage: "sqlite",
787
+ };
788
+ }
789
+ }
790
+ // The defining Worker exports each Workflow, and every Worker that binds
791
+ // one can start it and look up its instances.
792
+ for (const workflow of manifest.resources?.workflows ?? []) {
793
+ if (forWorker !== undefined && !definedHere(workflow))
794
+ continue;
795
+ if (taken[workflow.name] ||
796
+ bindings[workflow.name] ||
797
+ exports[workflow.className]) {
798
+ throw new Error(`workflows: ${workflow.name} (${workflow.className}) reuses a binding or class name the Worker already has.`);
799
+ }
800
+ const workflowName = workflowNameFor(appWorker, workflow.name);
801
+ if (workflowName.length > MAX_WORKFLOW_NAME_LENGTH) {
802
+ throw new Error(`workflows: ${workflow.name} would be the Workflow "${workflowName}", longer than Cloudflare's ${MAX_WORKFLOW_NAME_LENGTH} characters; shorten the binding name.`);
803
+ }
804
+ bindings[workflow.name] = {
805
+ type: "workflow",
806
+ name: workflowName,
807
+ worker: definingWorker(workflow),
808
+ exportName: workflow.className,
809
+ };
810
+ if (definedHere(workflow)) {
811
+ exports[workflow.className] = { type: "workflow", name: workflowName };
812
+ }
813
+ }
814
+ return { bindings, exports };
815
+ }
816
+ /**
817
+ * Check the names of the manifest's `secrets` and `settings`, and in dev
818
+ * declare the ones set locally (a setting that isn't, with its default),
819
+ * with the developer's own keys for the manifest's providers and
820
+ * capabilities.
821
+ */
822
+ function addSecrets(env, options, deploy) {
823
+ const settings = options.manifest.settings ?? [];
824
+ const secrets = [
825
+ ...manifestSecretNames(options.manifest),
826
+ ...settings.map((setting) => setting.name),
827
+ ];
828
+ const settingNames = new Set(settings.map((setting) => setting.name));
829
+ for (const secret of secrets) {
830
+ const field = settingNames.has(secret) ? "settings" : "secrets";
831
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(secret)) {
832
+ throw new Error(`${field}: "${secret}" isn't a valid binding name.`);
833
+ }
834
+ if (env[secret] || RESERVED_SECRET_NAME.test(secret)) {
835
+ throw new Error(`${field}: ${secret} is a name the deploy sets or a binding it builds.`);
836
+ }
837
+ }
838
+ if (!deploy) {
839
+ // With `.dev.vars` setting it, EVERYAPP_EMAIL_SENDERS stands in for the
840
+ // senders a gateway holds for an installation (`everyapp email set`).
841
+ const names = new Set([
842
+ ...secrets,
843
+ ...devGatewayKeys(options.manifest),
844
+ ...devProviderKeys(options.manifest, options.root),
845
+ ...(options.manifest.capabilities?.["email"]
846
+ ? ["EVERYAPP_EMAIL_SENDERS"]
847
+ : []),
848
+ ]);
849
+ for (const secret of locallySetSecrets(options.root ?? process.cwd(), options.ctx.mode ?? "development", [...names].filter((name) => !env[name]))) {
850
+ env[secret] = { type: "secret" };
851
+ }
852
+ // A setting nobody set locally reads its default, as a deployed one does.
853
+ for (const setting of settings) {
854
+ if (!env[setting.name] && setting.default !== undefined) {
855
+ env[setting.name] = { type: "text", value: setting.default };
856
+ }
857
+ }
858
+ }
859
+ }
860
+ /** The {@link DEV_GATEWAY_KEYS} of the manifest's providers and capabilities. */
861
+ function devGatewayKeys(manifest) {
862
+ const keysOf = (table, id) => (Object.hasOwn(table, id) ? table[id] : []);
863
+ return [
864
+ ...manifestProviderIds(manifest).flatMap((provider) => keysOf(DEV_GATEWAY_KEYS.providers, provider)),
865
+ ...Object.keys(manifest.capabilities ?? {}).flatMap((id) => keysOf(DEV_GATEWAY_KEYS.capabilities, id)),
866
+ ];
867
+ }
868
+ /** `nodejs_compat`, then the app's own flags, each once. */
869
+ function compatibilityFlags(flags) {
870
+ return [...new Set(["nodejs_compat", ...(flags ?? [])])];
871
+ }
872
+ function cronTriggers(crons) {
873
+ return (crons ?? []).map((schedule) => {
874
+ if (!/^\S+( \S+){4}$/.test(schedule)) {
875
+ throw new Error(`crons: "${schedule}" isn't a five-field cron schedule, e.g. "0 6 * * *".`);
876
+ }
877
+ return { type: "scheduled", schedule };
878
+ });
879
+ }
880
+ function observability() {
881
+ return {
882
+ ...EVERYAPP_OBSERVABILITY,
883
+ logs: { ...EVERYAPP_OBSERVABILITY.logs },
884
+ traces: { ...EVERYAPP_OBSERVABILITY.traces },
885
+ issues: { ...EVERYAPP_OBSERVABILITY.issues },
886
+ };
887
+ }
888
+ /**
889
+ * The local D1 database id for a binding: a 32-hex string, because
890
+ * `cf d1 … --local` rejects names. The everyapp CLI computes the same id to
891
+ * apply local migrations to the database `cf dev` serves.
892
+ */
893
+ export function localD1Id(workerName, binding) {
894
+ return createHash("sha256")
895
+ .update(`${workerName}:${binding}`)
896
+ .digest("hex")
897
+ .slice(0, 32);
898
+ }
899
+ /** Where `cf dev` keeps D1 databases, under the app's state directory. */
900
+ const LOCAL_D1_OBJECT = "miniflare-D1DatabaseObject";
901
+ /**
902
+ * The sqlite file `cf dev` (and `cf d1 … --local`) keeps the local D1
903
+ * database `databaseId` in, under the app at `root`, for tools such as
904
+ * drizzle-kit studio: `.cloudflare/state/v3/d1/miniflare-D1DatabaseObject/
905
+ * <hash>.sqlite`. The database is a Durable Object named after its id, and
906
+ * the file after that object's id. `databaseId` is the {@link localD1Id}.
907
+ */
908
+ export function localD1Path(root, databaseId) {
909
+ return path.join(root, ".cloudflare", "state", "v3", "d1", LOCAL_D1_OBJECT, `${durableObjectIdFromName(LOCAL_D1_OBJECT, databaseId)}.sqlite`);
910
+ }
911
+ /** The id workerd's `idFromName(name)` gives in the namespace `uniqueKey`. */
912
+ function durableObjectIdFromName(uniqueKey, name) {
913
+ const key = createHash("sha256").update(uniqueKey).digest();
914
+ const nameHmac = createHmac("sha256", key)
915
+ .update(name)
916
+ .digest()
917
+ .subarray(0, 16);
918
+ const hmac = createHmac("sha256", key)
919
+ .update(nameHmac)
920
+ .digest()
921
+ .subarray(0, 16);
922
+ return Buffer.concat([nameHmac, hmac]).toString("hex");
923
+ }
924
+ /**
925
+ * The `secrets` set for local dev, read the way the Vite plugin reads them:
926
+ * `.dev.vars[.<mode>]` alone when present, otherwise `.env` files and the
927
+ * process environment.
928
+ */
929
+ function locallySetSecrets(root, mode, names) {
930
+ if (names.length === 0)
931
+ return [];
932
+ const values = localEnv(root, mode);
933
+ return names.filter((name) => values[name]);
934
+ }
935
+ /** The values local dev sees, read as the Vite plugin reads them. */
936
+ function localEnv(root, mode) {
937
+ const read = (name) => {
938
+ const file = path.join(root, name);
939
+ return fs.existsSync(file) ? parseEnv(fs.readFileSync(file, "utf8")) : {};
940
+ };
941
+ const devVars = [`.dev.vars.${mode}`, ".dev.vars"].find((name) => fs.existsSync(path.join(root, name)));
942
+ return devVars
943
+ ? read(devVars)
944
+ : {
945
+ ...read(".env"),
946
+ ...read(".env.local"),
947
+ ...read(`.env.${mode}`),
948
+ ...read(`.env.${mode}.local`),
949
+ ...process.env,
950
+ };
951
+ }
952
+ /** The dev Worker name, e.g. "todo" -> "every-todo". */
953
+ function workerNameFor(appId) {
954
+ return appId.startsWith("every-") ? appId : `every-${appId}`;
955
+ }
956
+ /**
957
+ * Copies of the gateway's naming rules (@every-app/perimeter/manifest's
958
+ * resourceNameFor, bucketNameFor, vectorizeNameFor and storeSecretNameFor):
959
+ * `DB` and `KV` (`R2` for a bucket) take the Worker's name, any other
960
+ * binding (and every Vectorize index and Secrets Store secret)
961
+ * `<worker>--<binding>`.
962
+ */
963
+ function resourceNameFor(workerName, binding) {
964
+ return binding === "DB" || binding === "KV"
965
+ ? workerName
966
+ : suffixedName(workerName, binding);
967
+ }
968
+ function bucketNameFor(workerName, binding) {
969
+ return binding === "R2" ? workerName : suffixedName(workerName, binding);
970
+ }
971
+ /** A copy of @every-app/perimeter/manifest's queueNameFor: `<worker>--<binding>`. */
972
+ function queueNameFor(workerName, binding) {
973
+ return suffixedName(workerName, binding);
974
+ }
975
+ function suffixedName(workerName, binding) {
976
+ return `${workerName}--${binding.toLowerCase().replaceAll("_", "-")}`;
977
+ }
978
+ /**
979
+ * A copy of the gateway's aiGatewayIdFor: one hyphen, since an AI
980
+ * Gateway's id may not hold `--`.
981
+ */
982
+ function aiGatewayIdFor(workerName, binding) {
983
+ return `${workerName}-${binding.toLowerCase().replaceAll("_", "-")}`;
984
+ }
985
+ function assertAppId(id) {
986
+ if (typeof id !== "string" || !/^[a-z]([a-z0-9-]*[a-z0-9])?$/.test(id)) {
987
+ throw new Error(`everyapp.config.ts id must be kebab-case (lowercase letters, digits, hyphens); got ${JSON.stringify(id)}.`);
988
+ }
989
+ }
990
+ /** The target's resource for a manifest binding, which it always has. */
991
+ function provisioned(resources, binding) {
992
+ const resource = resources?.[binding];
993
+ if (resource === undefined) {
994
+ throw new Error(`The build has no placeholder for binding "${binding}".`);
995
+ }
996
+ return resource;
997
+ }