@every-app/sdk 0.1.14 → 0.3.0-rc.1

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 (167) hide show
  1. package/README.md +608 -2
  2. package/dist/cloudflare/index.d.ts +21 -1
  3. package/dist/cloudflare/index.d.ts.map +1 -1
  4. package/dist/cloudflare/index.js +21 -1
  5. package/dist/cloudflare/sentinel.d.ts +69 -0
  6. package/dist/cloudflare/sentinel.d.ts.map +1 -0
  7. package/dist/cloudflare/sentinel.js +50 -0
  8. package/dist/cloudflare/worker.d.ts +511 -0
  9. package/dist/cloudflare/worker.d.ts.map +1 -0
  10. package/dist/cloudflare/worker.js +738 -0
  11. package/dist/dev/index.d.ts +8 -0
  12. package/dist/dev/index.d.ts.map +1 -0
  13. package/dist/dev/index.js +7 -0
  14. package/dist/dev/keypair.d.ts +24 -0
  15. package/dist/dev/keypair.d.ts.map +1 -0
  16. package/dist/dev/keypair.js +60 -0
  17. package/dist/internal/aiProviders.d.ts +67 -0
  18. package/dist/internal/aiProviders.d.ts.map +1 -0
  19. package/dist/internal/aiProviders.js +220 -0
  20. package/dist/internal/appSettings.d.ts +237 -0
  21. package/dist/internal/appSettings.d.ts.map +1 -0
  22. package/dist/internal/appSettings.js +374 -0
  23. package/dist/internal/capabilities.d.ts +1344 -0
  24. package/dist/internal/capabilities.d.ts.map +1 -0
  25. package/dist/internal/capabilities.js +1521 -0
  26. package/dist/internal/devProviders.d.ts +49 -0
  27. package/dist/internal/devProviders.d.ts.map +1 -0
  28. package/dist/internal/devProviders.js +62 -0
  29. package/dist/internal/email.d.ts +280 -0
  30. package/dist/internal/email.d.ts.map +1 -0
  31. package/dist/internal/email.js +613 -0
  32. package/dist/internal/events.d.ts +182 -0
  33. package/dist/internal/events.d.ts.map +1 -0
  34. package/dist/internal/events.js +324 -0
  35. package/dist/internal/gatewaySettings.d.ts +82 -0
  36. package/dist/internal/gatewaySettings.d.ts.map +1 -0
  37. package/dist/internal/gatewaySettings.js +209 -0
  38. package/dist/internal/identity.d.ts +88 -0
  39. package/dist/internal/identity.d.ts.map +1 -0
  40. package/dist/internal/identity.js +231 -0
  41. package/dist/internal/index.d.ts +19 -0
  42. package/dist/internal/index.d.ts.map +1 -0
  43. package/dist/internal/index.js +18 -0
  44. package/dist/internal/members.d.ts +35 -0
  45. package/dist/internal/members.d.ts.map +1 -0
  46. package/dist/internal/members.js +7 -0
  47. package/dist/internal/oauthClient.d.ts +63 -0
  48. package/dist/internal/oauthClient.d.ts.map +1 -0
  49. package/dist/internal/oauthClient.js +194 -0
  50. package/dist/internal/oauthScopes.d.ts +117 -0
  51. package/dist/internal/oauthScopes.d.ts.map +1 -0
  52. package/dist/internal/oauthScopes.js +416 -0
  53. package/dist/internal/organizationName.d.ts +18 -0
  54. package/dist/internal/organizationName.d.ts.map +1 -0
  55. package/dist/internal/organizationName.js +24 -0
  56. package/dist/internal/protocol.d.ts +195 -0
  57. package/dist/internal/protocol.d.ts.map +1 -0
  58. package/dist/internal/protocol.js +250 -0
  59. package/dist/internal/providerApps.d.ts +75 -0
  60. package/dist/internal/providerApps.d.ts.map +1 -0
  61. package/dist/internal/providerApps.js +59 -0
  62. package/dist/internal/providerRegistry.d.ts +147 -0
  63. package/dist/internal/providerRegistry.d.ts.map +1 -0
  64. package/dist/internal/providerRegistry.js +262 -0
  65. package/dist/internal/providerSpec.d.ts +55 -0
  66. package/dist/internal/providerSpec.d.ts.map +1 -0
  67. package/dist/internal/providerSpec.js +746 -0
  68. package/dist/internal/remoteMcp.d.ts +188 -0
  69. package/dist/internal/remoteMcp.d.ts.map +1 -0
  70. package/dist/internal/remoteMcp.js +543 -0
  71. package/dist/internal/tools.d.ts +115 -0
  72. package/dist/internal/tools.d.ts.map +1 -0
  73. package/dist/internal/tools.js +80 -0
  74. package/dist/server/attribution.d.ts +39 -0
  75. package/dist/server/attribution.d.ts.map +1 -0
  76. package/dist/server/attribution.js +67 -0
  77. package/dist/server/authorizeUrl.d.ts +43 -0
  78. package/dist/server/authorizeUrl.d.ts.map +1 -0
  79. package/dist/server/authorizeUrl.js +110 -0
  80. package/dist/server/emit.d.ts +63 -0
  81. package/dist/server/emit.d.ts.map +1 -0
  82. package/dist/server/emit.js +101 -0
  83. package/dist/server/everyApp.d.ts +81 -0
  84. package/dist/server/everyApp.d.ts.map +1 -0
  85. package/dist/server/everyApp.js +185 -0
  86. package/dist/server/gatewayFetch.d.ts +37 -0
  87. package/dist/server/gatewayFetch.d.ts.map +1 -0
  88. package/dist/server/gatewayFetch.js +115 -0
  89. package/dist/server/index.d.ts +18 -0
  90. package/dist/server/index.d.ts.map +1 -0
  91. package/dist/server/index.js +15 -0
  92. package/dist/server/integrationFetch.d.ts +131 -0
  93. package/dist/server/integrationFetch.d.ts.map +1 -0
  94. package/dist/server/integrationFetch.js +526 -0
  95. package/dist/server/listMembers.d.ts +32 -0
  96. package/dist/server/listMembers.d.ts.map +1 -0
  97. package/dist/server/listMembers.js +74 -0
  98. package/dist/server/nodeServer.d.ts +48 -0
  99. package/dist/server/nodeServer.d.ts.map +1 -0
  100. package/dist/server/nodeServer.js +45 -0
  101. package/dist/server/sendEmail.d.ts +39 -0
  102. package/dist/server/sendEmail.d.ts.map +1 -0
  103. package/dist/server/sendEmail.js +114 -0
  104. package/dist/server/tools.d.ts +184 -0
  105. package/dist/server/tools.d.ts.map +1 -0
  106. package/dist/server/tools.js +347 -0
  107. package/dist/server/toolsEndpoint.d.ts +32 -0
  108. package/dist/server/toolsEndpoint.d.ts.map +1 -0
  109. package/dist/server/toolsEndpoint.js +231 -0
  110. package/package.json +21 -31
  111. package/dist/cloudflare/getLocalD1Url.d.ts +0 -2
  112. package/dist/cloudflare/getLocalD1Url.d.ts.map +0 -1
  113. package/dist/cloudflare/getLocalD1Url.js +0 -40
  114. package/dist/cloudflare/lazyInit.d.ts +0 -22
  115. package/dist/cloudflare/lazyInit.d.ts.map +0 -1
  116. package/dist/cloudflare/lazyInit.js +0 -65
  117. package/dist/cloudflare/server/gateway.d.ts +0 -29
  118. package/dist/cloudflare/server/gateway.d.ts.map +0 -1
  119. package/dist/cloudflare/server/gateway.js +0 -69
  120. package/dist/cloudflare/server/index.d.ts +0 -3
  121. package/dist/cloudflare/server/index.d.ts.map +0 -1
  122. package/dist/cloudflare/server/index.js +0 -2
  123. package/dist/core/authenticatedFetch.d.ts +0 -9
  124. package/dist/core/authenticatedFetch.d.ts.map +0 -1
  125. package/dist/core/authenticatedFetch.js +0 -31
  126. package/dist/core/index.d.ts +0 -4
  127. package/dist/core/index.d.ts.map +0 -1
  128. package/dist/core/index.js +0 -2
  129. package/dist/core/sessionManager.d.ts +0 -58
  130. package/dist/core/sessionManager.d.ts.map +0 -1
  131. package/dist/core/sessionManager.js +0 -357
  132. package/dist/shared/bypassGatewayLocalOnly.d.ts +0 -15
  133. package/dist/shared/bypassGatewayLocalOnly.d.ts.map +0 -1
  134. package/dist/shared/bypassGatewayLocalOnly.js +0 -45
  135. package/dist/shared/parseMessagePayload.d.ts +0 -3
  136. package/dist/shared/parseMessagePayload.d.ts.map +0 -1
  137. package/dist/shared/parseMessagePayload.js +0 -18
  138. package/dist/tanstack/EmbeddedAppProvider.d.ts +0 -30
  139. package/dist/tanstack/EmbeddedAppProvider.d.ts.map +0 -1
  140. package/dist/tanstack/EmbeddedAppProvider.js +0 -57
  141. package/dist/tanstack/GatewayRequiredError.d.ts +0 -20
  142. package/dist/tanstack/GatewayRequiredError.d.ts.map +0 -1
  143. package/dist/tanstack/GatewayRequiredError.js +0 -91
  144. package/dist/tanstack/_internal/useEveryAppSession.d.ts +0 -18
  145. package/dist/tanstack/_internal/useEveryAppSession.d.ts.map +0 -1
  146. package/dist/tanstack/_internal/useEveryAppSession.js +0 -47
  147. package/dist/tanstack/index.d.ts +0 -4
  148. package/dist/tanstack/index.d.ts.map +0 -1
  149. package/dist/tanstack/index.js +0 -3
  150. package/dist/tanstack/server/authConfig.d.ts +0 -3
  151. package/dist/tanstack/server/authConfig.d.ts.map +0 -1
  152. package/dist/tanstack/server/authConfig.js +0 -13
  153. package/dist/tanstack/server/authenticateRequest.d.ts +0 -31
  154. package/dist/tanstack/server/authenticateRequest.d.ts.map +0 -1
  155. package/dist/tanstack/server/authenticateRequest.js +0 -154
  156. package/dist/tanstack/server/index.d.ts +0 -4
  157. package/dist/tanstack/server/index.d.ts.map +0 -1
  158. package/dist/tanstack/server/index.js +0 -2
  159. package/dist/tanstack/server/types.d.ts +0 -5
  160. package/dist/tanstack/server/types.d.ts.map +0 -1
  161. package/dist/tanstack/server/types.js +0 -1
  162. package/dist/tanstack/useEveryAppRouter.d.ts +0 -7
  163. package/dist/tanstack/useEveryAppRouter.d.ts.map +0 -1
  164. package/dist/tanstack/useEveryAppRouter.js +0 -66
  165. package/dist/tanstack/useSessionTokenClientMiddleware.d.ts +0 -2
  166. package/dist/tanstack/useSessionTokenClientMiddleware.d.ts.map +0 -1
  167. package/dist/tanstack/useSessionTokenClientMiddleware.js +0 -29
@@ -0,0 +1,738 @@
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_PUBLIC_KEY_PEM } from "../dev/keypair.js";
6
+ import { sentinelDeployTarget } from "./sentinel.js";
7
+ /** The names of the manifest's `secrets`, whichever form each took. */
8
+ function manifestSecretNames(manifest) {
9
+ return (manifest.secrets ?? []).map((entry) => typeof entry === "string" ? entry : entry.name);
10
+ }
11
+ /** The ids of the manifest's `providers`, whichever form each took. */
12
+ function manifestProviderIds(manifest) {
13
+ return (manifest.providers ?? []).map((entry) => typeof entry === "string" ? entry : entry.id);
14
+ }
15
+ /**
16
+ * The gateway secrets holding the keys of the AI providers and capabilities
17
+ * an app may ask for, with the settings a provider needs besides its key
18
+ * (Workers AI's account). With no gateway in development, the SDK reaches
19
+ * them with the developer's own values under the same names. A copy of the
20
+ * tables in ../internal, which the Worker loads: the Vite plugin hides
21
+ * whatever cloudflare.config.ts imports from the Worker. A test keeps them
22
+ * in step.
23
+ */
24
+ const DEV_GATEWAY_KEYS = {
25
+ providers: {
26
+ openai: ["OPENAI_API_KEY"],
27
+ openrouter: ["OPENROUTER_API_KEY"],
28
+ "workers-ai": ["WORKERS_AI_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID"],
29
+ },
30
+ capabilities: {
31
+ dataforseo: ["DATAFORSEO_API_KEY"],
32
+ context: ["CONTEXT_API_KEY"],
33
+ // The sender, and the developer's own Email Sending account: set all
34
+ // but the name and sendEmail() really sends; otherwise it prints.
35
+ // Resend's key and EMAIL_PROVIDER pick the provider integrationStatus()
36
+ // names, as the gateway would.
37
+ // TODO(integration): the last two added by the email-providers lane
38
+ // (this file is outside its ownership list).
39
+ email: [
40
+ "EMAIL_FROM",
41
+ "EMAIL_FROM_NAME",
42
+ "EMAIL_REST_API_TOKEN",
43
+ "CLOUDFLARE_ACCOUNT_ID",
44
+ "RESEND_API_KEY",
45
+ "EMAIL_PROVIDER",
46
+ ],
47
+ // The organization's members: nothing to set; listMembers() answers
48
+ // from the dev identity.
49
+ members: [],
50
+ // OAuth clients: the id, the secret, and the instance a provider with
51
+ // no fixed host is on.
52
+ linkedin: ["LINKEDIN_CLIENT_ID", "LINKEDIN_CLIENT_SECRET"],
53
+ facebook: ["FACEBOOK_APP_ID", "FACEBOOK_APP_SECRET"],
54
+ instagram: ["INSTAGRAM_APP_ID", "INSTAGRAM_APP_SECRET"],
55
+ threads: ["THREADS_APP_ID", "THREADS_APP_SECRET"],
56
+ youtube: ["YOUTUBE_CLIENT_ID", "YOUTUBE_CLIENT_SECRET"],
57
+ tiktok: ["TIKTOK_CLIENT_ID", "TIKTOK_CLIENT_SECRET"],
58
+ "tiktok-business": [
59
+ "TIKTOK_BUSINESS_CLIENT_ID",
60
+ "TIKTOK_BUSINESS_CLIENT_SECRET",
61
+ ],
62
+ reddit: ["REDDIT_CLIENT_ID", "REDDIT_CLIENT_SECRET"],
63
+ pinterest: ["PINTEREST_CLIENT_ID", "PINTEREST_CLIENT_SECRET"],
64
+ discord: ["DISCORD_CLIENT_ID", "DISCORD_CLIENT_SECRET"],
65
+ slack: ["SLACK_CLIENT_ID", "SLACK_CLIENT_SECRET"],
66
+ mastodon: ["MASTODON_CLIENT_ID", "MASTODON_CLIENT_SECRET", "MASTODON_URL"],
67
+ kick: ["KICK_CLIENT_ID", "KICK_CLIENT_SECRET"],
68
+ twitch: ["TWITCH_CLIENT_ID", "TWITCH_CLIENT_SECRET"],
69
+ tumblr: ["TUMBLR_CLIENT_ID", "TUMBLR_CLIENT_SECRET"],
70
+ dribbble: ["DRIBBBLE_CLIENT_ID", "DRIBBBLE_CLIENT_SECRET"],
71
+ "google-workspace": [
72
+ "GOOGLE_WORKSPACE_CLIENT_ID",
73
+ "GOOGLE_WORKSPACE_CLIENT_SECRET",
74
+ ],
75
+ "microsoft-365": ["MICROSOFT_365_CLIENT_ID", "MICROSOFT_365_CLIENT_SECRET"],
76
+ },
77
+ };
78
+ /**
79
+ * Observability every Every App Worker ships with: logs (with invocation
80
+ * logs), sampled traces and Workers Issues, with query strings redacted so
81
+ * tokens and invitation ids in URLs stay out of logs.
82
+ */
83
+ export const EVERYAPP_OBSERVABILITY = Object.freeze({
84
+ enabled: true,
85
+ headSamplingRate: 1,
86
+ redactQueryString: true,
87
+ logs: Object.freeze({
88
+ enabled: true,
89
+ invocationLogs: true,
90
+ persist: true,
91
+ headSamplingRate: 1,
92
+ }),
93
+ traces: Object.freeze({
94
+ enabled: true,
95
+ persist: true,
96
+ headSamplingRate: 0.1,
97
+ }),
98
+ issues: Object.freeze({ enabled: true }),
99
+ });
100
+ const DEFAULT_COMPATIBILITY_DATE = "2026-06-01";
101
+ /** Set by `everyapp deploy` around the production build it installs. */
102
+ const BUILD_ENV = "EVERYAPP_BUILD";
103
+ /** Names the deploy sets itself (as `everyapp secret set` refuses). */
104
+ const RESERVED_SECRET_NAME = /^(GATEWAY_URL|CF_VERSION_METADATA|EVERYAPP_\w*|EVERY_APP_\w*|APP__\w*)$/i;
105
+ const GATEWAY_WORKER_NAME = "every-app-gateway";
106
+ const GATEWAY_ENTRYPOINT = "AppGateway";
107
+ /**
108
+ * The manifest's `capabilities` as the SDK's local-dev twin of the gateway
109
+ * reads them: with no gateway to hold an OAuth client's calls to the scope
110
+ * sets the app declares, `integrationFetch()` holds them to these.
111
+ */
112
+ function devCapabilities(manifest) {
113
+ return { type: "text", value: JSON.stringify(manifest.capabilities ?? {}) };
114
+ }
115
+ /**
116
+ * The binding the dev Worker gets the specs of providers registered on a
117
+ * gateway in (`DEV_PROVIDERS_ENV` in ../internal/devProviders.ts; a copy,
118
+ * kept in step by a test, for the reason {@link DEV_GATEWAY_KEYS} is one).
119
+ */
120
+ const DEV_PROVIDERS_ENV = "EVERYAPP_DEV_PROVIDERS";
121
+ /**
122
+ * The specs `everyapp provider add` (in an app directory) and `everyapp
123
+ * provider show <id> --save` wrote under the app, in name order; none
124
+ * without the directory. integrationFetch()'s dev fallback reaches a
125
+ * provider outside the catalog from them, with the developer's own key.
126
+ */
127
+ function devProviderFiles(root) {
128
+ const dir = path.join(root ?? process.cwd(), ".everyapp", "providers");
129
+ let names;
130
+ try {
131
+ names = fs
132
+ .readdirSync(dir)
133
+ .filter((name) => name.endsWith(".json"))
134
+ .sort();
135
+ }
136
+ catch {
137
+ return [];
138
+ }
139
+ return names.map((name) => {
140
+ const text = fs.readFileSync(path.join(dir, name), "utf8");
141
+ let spec;
142
+ try {
143
+ spec = JSON.parse(text);
144
+ }
145
+ catch {
146
+ spec = text;
147
+ }
148
+ return { file: `.everyapp/providers/${name}`, spec };
149
+ });
150
+ }
151
+ /** EVERYAPP_DEV_PROVIDERS, when the app has any spec files. */
152
+ function addDevProviders(env, root) {
153
+ const files = devProviderFiles(root);
154
+ if (files.length === 0)
155
+ return;
156
+ env[DEV_PROVIDERS_ENV] = { type: "text", value: JSON.stringify(files) };
157
+ }
158
+ /**
159
+ * The Gateway settings the spec files name for the capabilities the
160
+ * manifest lists outside {@link DEV_GATEWAY_KEYS} (a registered provider's
161
+ * key, a Basic username held as a setting, its host setting), read
162
+ * loosely: the SDK parses the spec when the app calls the provider.
163
+ */
164
+ function devProviderKeys(manifest, root) {
165
+ const listed = Object.keys(manifest.capabilities ?? {}).filter((id) => !Object.hasOwn(DEV_GATEWAY_KEYS.capabilities, id));
166
+ if (listed.length === 0)
167
+ return [];
168
+ const record = (value) => value && typeof value === "object" && !Array.isArray(value)
169
+ ? value
170
+ : undefined;
171
+ const names = [];
172
+ for (const { file, spec } of devProviderFiles(root)) {
173
+ const fields = record(spec);
174
+ const id = typeof fields?.["id"] === "string"
175
+ ? fields["id"]
176
+ : file
177
+ .split("/")
178
+ .at(-1)
179
+ .replace(/\.json$/, "");
180
+ if (!fields || !listed.includes(id))
181
+ continue;
182
+ const auth = record(fields["auth"]);
183
+ for (const name of [
184
+ auth?.["secret"],
185
+ record(auth?.["user"])?.["secret"],
186
+ fields["hostSetting"],
187
+ ]) {
188
+ if (typeof name === "string" && /^[A-Z][A-Z0-9_]*$/.test(name)) {
189
+ names.push(name);
190
+ }
191
+ }
192
+ }
193
+ return names;
194
+ }
195
+ /** Cloudflare's limit on a Workflow's name. */
196
+ const MAX_WORKFLOW_NAME_LENGTH = 64;
197
+ /** Cloudflare's limit on a Worker's name. */
198
+ const MAX_WORKER_NAME_LENGTH = 63;
199
+ /**
200
+ * Where a developer puts a local Postgres URL for a Hyperdrive binding
201
+ * (`.dev.vars` or `.env`), the variable wrangler reads for the same.
202
+ */
203
+ const LOCAL_HYPERDRIVE_PREFIX = "CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_";
204
+ /**
205
+ * Build the Worker definition for an Every App app from its manifest.
206
+ *
207
+ * - `development` (cf dev) and no mode (cf API commands, `cf workers types`):
208
+ * EVERYAPP_DEV is on, the identity keys and issuer are the well-known dev
209
+ * ones (`@every-app/sdk/dev`) as plain text, so nothing needs writing to
210
+ * `.dev.vars`, and each D1 database gets a fixed local id (see
211
+ * {@link localD1Id}) that `cf d1 … --local` can address.
212
+ * - `production`: tenant-neutral. Everything install-specific (the Worker
213
+ * name, organization, gateway URL, identity keys, resource names and ids)
214
+ * is a placeholder from {@link sentinelDeployTarget}, which the installer
215
+ * rewrites (`everyapp deploy`, or a hosted gateway's deployer), so every
216
+ * build of an app is the same. Only with EVERYAPP_BUILD=1, which
217
+ * `everyapp deploy` sets: a bare `cf deploy` would upload the
218
+ * placeholders.
219
+ */
220
+ export function everyAppWorker(options) {
221
+ const { manifest } = options;
222
+ const { deploy, name } = appWorkerName(options, "everyAppWorker");
223
+ const env = {
224
+ EVERYAPP_APP_ID: { type: "text", value: manifest.id },
225
+ CF_VERSION_METADATA: { type: "version-metadata" },
226
+ };
227
+ if (deploy) {
228
+ // The gateway mints every identity for the Worker's name (`aud`).
229
+ env["EVERYAPP_AUDIENCE"] = { type: "text", value: name };
230
+ env["EVERYAPP_IDENTITY_PUBLIC_KEYS"] = {
231
+ type: "text",
232
+ value: JSON.stringify(deploy.identityPublicKeys),
233
+ };
234
+ env["EVERYAPP_IDENTITY_ISSUER"] = {
235
+ type: "text",
236
+ value: deploy.gatewayUrl,
237
+ };
238
+ env["EVERY_APP_GATEWAY"] = gatewayBinding(deploy, manifest.id, name);
239
+ // Sent by the deploy pipeline through `cf deploy --secrets-file`.
240
+ env["GATEWAY_URL"] = { type: "secret" };
241
+ env["EVERY_APP_ORG_ID"] = { type: "secret" };
242
+ }
243
+ else {
244
+ env["EVERYAPP_DEV"] = { type: "text", value: "1" };
245
+ env["EVERYAPP_DEV_CAPABILITIES"] = devCapabilities(manifest);
246
+ env["EVERYAPP_IDENTITY_PUBLIC_KEYS"] = {
247
+ type: "text",
248
+ value: JSON.stringify([DEV_PUBLIC_KEY_PEM]),
249
+ };
250
+ env["EVERYAPP_IDENTITY_ISSUER"] = { type: "text", value: DEV_ISSUER };
251
+ addDevProviders(env, options.root);
252
+ }
253
+ Object.assign(env, storageBindings(manifest, name, deploy));
254
+ addDatabaseBindings(env, options, name, undefined, deploy);
255
+ // The app binds every class, a helper Worker's in that Worker, and
256
+ // exports only its own.
257
+ const { bindings, exports } = classBindings(manifest, name, undefined, env);
258
+ Object.assign(env, bindings);
259
+ for (const helper of manifest.workers ?? []) {
260
+ if (!helper.binding)
261
+ continue;
262
+ if (env[helper.binding]) {
263
+ throw new Error(`workers: ${helper.name}'s binding ${helper.binding} reuses a binding name the Worker already has.`);
264
+ }
265
+ env[helper.binding] = {
266
+ type: "worker",
267
+ worker: helperWorkerName(name, helper.name),
268
+ };
269
+ }
270
+ addSecrets(env, options, deploy);
271
+ const triggers = cronTriggers(options.crons);
272
+ const compatibilityDate = manifest.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE;
273
+ // cf types a Durable Object namespace with its class only when the binding
274
+ // names the defining Worker by a definition whose module it can read, so
275
+ // the classes this Worker defines name it that way; cf resolves the
276
+ // reference to the Worker's name. A path entrypoint gives cf no module.
277
+ const { entrypoint } = options;
278
+ if (typeof entrypoint === "object") {
279
+ const self = {
280
+ name,
281
+ compatibilityDate,
282
+ entrypoint,
283
+ };
284
+ for (const durableObject of manifest.resources?.durableObjects ?? []) {
285
+ if (durableObject.worker !== undefined)
286
+ continue;
287
+ env[durableObject.name] = {
288
+ type: "durable-object",
289
+ worker: self,
290
+ exportName: durableObject.className,
291
+ };
292
+ }
293
+ }
294
+ return {
295
+ name,
296
+ entrypoint,
297
+ compatibilityDate,
298
+ compatibilityFlags: compatibilityFlags(options.compatibilityFlags),
299
+ workersDev: false,
300
+ previewUrls: false,
301
+ ...(triggers.length ? { triggers } : {}),
302
+ ...runtimeSettings(manifest, manifest.limits, deploy),
303
+ observability: observability(),
304
+ env: env,
305
+ exports: exports,
306
+ };
307
+ }
308
+ /**
309
+ * Build the definition of one of the app's helper Workers (the manifest's
310
+ * `workers`), e.g. one that runs a memory-hungry job away from the app's
311
+ * Worker. It is named `<app worker>--<worker>`, is private like the app, and
312
+ * binds the app's D1 databases, KV namespaces and R2 buckets, the classes it defines, and
313
+ * the gateway (with the app's identity, for capabilities). Only the app's
314
+ * Worker reaches it. Pass it to the Vite plugin:
315
+ *
316
+ * cloudflare({
317
+ * auxiliaryWorkers: [
318
+ * { config: everyAppAuxiliaryWorker({ manifest, worker: "audit", entrypoint: "./src/audit.ts", ctx: { mode } }) },
319
+ * ],
320
+ * })
321
+ */
322
+ export function everyAppAuxiliaryWorker(options) {
323
+ const { manifest, worker } = options;
324
+ const helper = manifest.workers?.find((entry) => entry.name === worker);
325
+ if (!helper) {
326
+ throw new Error(`everyapp.config.ts declares no helper Worker "${worker}" in workers.`);
327
+ }
328
+ const { deploy, name: appWorker } = appWorkerName(options, "everyAppAuxiliaryWorker");
329
+ const name = helperWorkerName(appWorker, worker);
330
+ const env = {
331
+ EVERYAPP_APP_ID: { type: "text", value: manifest.id },
332
+ ...storageBindings(manifest, appWorker, deploy),
333
+ };
334
+ if (deploy) {
335
+ // The app's identity: the gateway treats its helper Workers as the app.
336
+ env["EVERY_APP_GATEWAY"] = gatewayBinding(deploy, manifest.id, appWorker);
337
+ // Sent by the deploy pipeline through `cf deploy --secrets-file`, like
338
+ // the app's Worker: a helper's gateway calls name the organization too.
339
+ env["GATEWAY_URL"] = { type: "secret" };
340
+ env["EVERY_APP_ORG_ID"] = { type: "secret" };
341
+ }
342
+ else {
343
+ env["EVERYAPP_DEV"] = { type: "text", value: "1" };
344
+ env["EVERYAPP_DEV_CAPABILITIES"] = devCapabilities(manifest);
345
+ addDevProviders(env, options.root);
346
+ }
347
+ addDatabaseBindings(env, options, appWorker, worker, deploy);
348
+ const { bindings, exports } = classBindings(manifest, appWorker, worker, env);
349
+ Object.assign(env, bindings);
350
+ addSecrets(env, options, deploy);
351
+ const triggers = cronTriggers(options.crons);
352
+ return {
353
+ name,
354
+ entrypoint: options.entrypoint,
355
+ compatibilityDate: manifest.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE,
356
+ compatibilityFlags: compatibilityFlags(options.compatibilityFlags),
357
+ workersDev: false,
358
+ previewUrls: false,
359
+ ...(triggers.length ? { triggers } : {}),
360
+ ...runtimeSettings(manifest, helper.limits, deploy),
361
+ observability: observability(),
362
+ env,
363
+ exports,
364
+ };
365
+ }
366
+ /**
367
+ * A helper Worker's name: `<app worker>--<name>`, within Cloudflare's
368
+ * limit. New app ids can't contain `--`, so it is never another app's
369
+ * Worker name.
370
+ */
371
+ export function helperWorkerName(appWorker, helper) {
372
+ const name = `${appWorker}--${helper}`;
373
+ if (name.length > MAX_WORKER_NAME_LENGTH) {
374
+ throw new Error(`Helper Worker ${name} is longer than Cloudflare's ${MAX_WORKER_NAME_LENGTH} characters; shorten its name in everyapp.config.ts.`);
375
+ }
376
+ return name;
377
+ }
378
+ /** The placeholder target (in production) and the app Worker's name. */
379
+ function appWorkerName(options, builder) {
380
+ const { manifest, ctx } = options;
381
+ assertAppId(manifest.id);
382
+ let deploy;
383
+ if (ctx.mode === "production") {
384
+ if (process.env[BUILD_ENV] !== "1") {
385
+ throw new Error(`Every App Workers are built for production only by \`everyapp deploy\` (\`pnpm run deploy\`), which sets ${BUILD_ENV}=1 and installs the build: ` +
386
+ "a bare `cf build` or `cf deploy` would ship a Worker whose name, identity keys, gateway binding and resources are placeholders. " +
387
+ "For a local build, run Vite in development mode (`vite build --mode development`).");
388
+ }
389
+ deploy = sentinelDeployTarget(manifest);
390
+ }
391
+ else if (ctx.mode !== undefined && ctx.mode !== "development") {
392
+ throw new Error(`${builder}() builds in the "development" or "production" mode, not "${ctx.mode}".`);
393
+ }
394
+ const name = deploy?.workerName ?? workerNameFor(manifest.id);
395
+ if (name === GATEWAY_WORKER_NAME) {
396
+ throw new Error(`App id "${manifest.id}" is reserved: its Worker would be the gateway's own (${GATEWAY_WORKER_NAME}).`);
397
+ }
398
+ return { deploy, name };
399
+ }
400
+ function gatewayBinding(deploy, appId, appWorker) {
401
+ return {
402
+ type: "worker",
403
+ worker: GATEWAY_WORKER_NAME,
404
+ exportName: GATEWAY_ENTRYPOINT,
405
+ props: {
406
+ organizationId: deploy.organizationId,
407
+ appId,
408
+ workerName: appWorker,
409
+ },
410
+ };
411
+ }
412
+ /**
413
+ * The app's D1 databases, KV namespaces and R2 buckets, which all its
414
+ * Workers share.
415
+ */
416
+ function storageBindings(manifest, appWorker, deploy) {
417
+ const env = {};
418
+ for (const binding of manifest.resources?.d1 ?? []) {
419
+ if (deploy) {
420
+ const d1 = provisioned(deploy.resources.d1, binding);
421
+ env[binding] = { type: "d1", name: d1.name, id: d1.id };
422
+ }
423
+ else {
424
+ env[binding] = {
425
+ type: "d1",
426
+ name: resourceNameFor(appWorker, binding),
427
+ id: localD1Id(appWorker, binding),
428
+ };
429
+ }
430
+ }
431
+ for (const binding of manifest.resources?.kv ?? []) {
432
+ if (deploy) {
433
+ env[binding] = {
434
+ type: "kv",
435
+ id: provisioned(deploy.resources.kv, binding).id,
436
+ };
437
+ }
438
+ else if (manifest.workers?.length) {
439
+ // The Vite plugin's own default for the app's Worker, named so that
440
+ // its helpers share the namespace in dev, as deployed ones do.
441
+ env[binding] = { type: "kv", id: `${binding}-${appWorker}` };
442
+ }
443
+ else {
444
+ env[binding] = { type: "kv" };
445
+ }
446
+ }
447
+ // Deployed, the names the gateway assigns at registration (placeholders
448
+ // until installed); in dev its rule, so a local bucket is named like a
449
+ // deployed one.
450
+ for (const binding of manifest.resources?.r2 ?? []) {
451
+ if (env[binding]) {
452
+ throw new Error(`r2: ${binding} reuses a binding name the Worker already has.`);
453
+ }
454
+ if (deploy) {
455
+ env[binding] = {
456
+ type: "r2",
457
+ name: provisioned(deploy.resources.r2, binding).name,
458
+ };
459
+ }
460
+ else {
461
+ env[binding] = { type: "r2", name: bucketNameFor(appWorker, binding) };
462
+ }
463
+ }
464
+ return env;
465
+ }
466
+ /**
467
+ * DATABASE_PROVIDER and the Hyperdrive configs one of the app's Workers
468
+ * binds: each config every Worker shares, and those a helper (`forWorker`)
469
+ * alone takes. Deployed, each is the config the deploy provisioned; in
470
+ * development, a local Postgres URL set as
471
+ * CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_<binding>, or nothing.
472
+ */
473
+ function addDatabaseBindings(env, options, appWorker, forWorker, deploy) {
474
+ const { manifest } = options;
475
+ if (manifest.database) {
476
+ env["DATABASE_PROVIDER"] = {
477
+ type: "text",
478
+ value: manifest.database.provider,
479
+ };
480
+ }
481
+ const configs = (manifest.resources?.hyperdrive ?? []).filter((config) => config.worker === undefined || config.worker === forWorker);
482
+ if (configs.length === 0)
483
+ return;
484
+ const local = deploy
485
+ ? {}
486
+ : localEnv(options.root ?? process.cwd(), options.ctx.mode ?? "development");
487
+ for (const config of configs) {
488
+ if (env[config.name]) {
489
+ throw new Error(`hyperdrive: ${config.name} reuses a binding name the Worker already has.`);
490
+ }
491
+ if (deploy) {
492
+ env[config.name] = {
493
+ type: "hyperdrive",
494
+ id: provisioned(deploy.resources.hyperdrive, config.name).id,
495
+ };
496
+ continue;
497
+ }
498
+ const connectionString = local[`${LOCAL_HYPERDRIVE_PREFIX}${config.name}`];
499
+ if (connectionString) {
500
+ env[config.name] = {
501
+ type: "hyperdrive",
502
+ id: localD1Id(appWorker, config.name),
503
+ dev: { connectionString },
504
+ };
505
+ }
506
+ }
507
+ }
508
+ /**
509
+ * The placement and limits of one of the app's Workers: deployed, it runs
510
+ * near the database's region; its CPU limit is the manifest's.
511
+ */
512
+ function runtimeSettings(manifest, limits, deploy) {
513
+ const region = manifest.database?.region;
514
+ return {
515
+ ...(deploy && region ? { placement: { region } } : {}),
516
+ ...(limits?.cpuMs !== undefined ? { limits: { cpuMs: limits.cpuMs } } : {}),
517
+ };
518
+ }
519
+ /**
520
+ * Durable Object and Workflow bindings, and the classes to export, for one
521
+ * of the app's Workers: the app's own (`forWorker` undefined) binds every
522
+ * class, each in the Worker that defines it, and a helper Worker binds only
523
+ * its own. Each Worker exports the classes it defines. Workflow names are
524
+ * account-wide, so all of them are named after the app's Worker.
525
+ */
526
+ function classBindings(manifest, appWorker, forWorker,
527
+ /** The bindings the Worker already has, which no class may reuse. */
528
+ taken) {
529
+ const bindings = {};
530
+ const exports = {};
531
+ const definedHere = (resource) => resource.worker === forWorker;
532
+ const definingWorker = (resource) => resource.worker ? helperWorkerName(appWorker, resource.worker) : appWorker;
533
+ for (const durableObject of manifest.resources?.durableObjects ?? []) {
534
+ if (forWorker !== undefined && !definedHere(durableObject))
535
+ continue;
536
+ bindings[durableObject.name] = {
537
+ type: "durable-object",
538
+ worker: definingWorker(durableObject),
539
+ exportName: durableObject.className,
540
+ };
541
+ if (definedHere(durableObject)) {
542
+ exports[durableObject.className] = {
543
+ type: "durable-object",
544
+ storage: "sqlite",
545
+ };
546
+ }
547
+ }
548
+ // The defining Worker exports each Workflow, and every Worker that binds
549
+ // one can start it and look up its instances.
550
+ for (const workflow of manifest.resources?.workflows ?? []) {
551
+ if (forWorker !== undefined && !definedHere(workflow))
552
+ continue;
553
+ if (taken[workflow.name] ||
554
+ bindings[workflow.name] ||
555
+ exports[workflow.className]) {
556
+ throw new Error(`workflows: ${workflow.name} (${workflow.className}) reuses a binding or class name the Worker already has.`);
557
+ }
558
+ const workflowName = workflowNameFor(appWorker, workflow.name);
559
+ if (workflowName.length > MAX_WORKFLOW_NAME_LENGTH) {
560
+ throw new Error(`workflows: ${workflow.name} would be the Workflow "${workflowName}", longer than Cloudflare's ${MAX_WORKFLOW_NAME_LENGTH} characters; shorten the binding name.`);
561
+ }
562
+ bindings[workflow.name] = {
563
+ type: "workflow",
564
+ name: workflowName,
565
+ worker: definingWorker(workflow),
566
+ exportName: workflow.className,
567
+ };
568
+ if (definedHere(workflow)) {
569
+ exports[workflow.className] = { type: "workflow", name: workflowName };
570
+ }
571
+ }
572
+ return { bindings, exports };
573
+ }
574
+ /**
575
+ * Check the names of the manifest's `secrets`, and in dev declare the ones
576
+ * set locally, with the developer's own keys for the manifest's providers
577
+ * and capabilities.
578
+ */
579
+ function addSecrets(env, options, deploy) {
580
+ const secrets = manifestSecretNames(options.manifest);
581
+ for (const secret of secrets) {
582
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(secret)) {
583
+ throw new Error(`secrets: "${secret}" isn't a valid binding name.`);
584
+ }
585
+ if (env[secret] || RESERVED_SECRET_NAME.test(secret)) {
586
+ throw new Error(`secrets: ${secret} is a name the deploy sets or a binding it builds.`);
587
+ }
588
+ }
589
+ if (!deploy) {
590
+ // With `.dev.vars` setting it, EVERYAPP_EMAIL_SENDERS stands in for the
591
+ // senders a gateway holds for an installation (`everyapp email set`).
592
+ const names = new Set([
593
+ ...secrets,
594
+ ...devGatewayKeys(options.manifest),
595
+ ...devProviderKeys(options.manifest, options.root),
596
+ ...(options.manifest.capabilities?.["email"]
597
+ ? ["EVERYAPP_EMAIL_SENDERS"]
598
+ : []),
599
+ ]);
600
+ for (const secret of locallySetSecrets(options.root ?? process.cwd(), options.ctx.mode ?? "development", [...names].filter((name) => !env[name]))) {
601
+ env[secret] = { type: "secret" };
602
+ }
603
+ }
604
+ }
605
+ /** The {@link DEV_GATEWAY_KEYS} of the manifest's providers and capabilities. */
606
+ function devGatewayKeys(manifest) {
607
+ const keysOf = (table, id) => (Object.hasOwn(table, id) ? table[id] : []);
608
+ return [
609
+ ...manifestProviderIds(manifest).flatMap((provider) => keysOf(DEV_GATEWAY_KEYS.providers, provider)),
610
+ ...Object.keys(manifest.capabilities ?? {}).flatMap((id) => keysOf(DEV_GATEWAY_KEYS.capabilities, id)),
611
+ ];
612
+ }
613
+ /** `nodejs_compat`, then the app's own flags, each once. */
614
+ function compatibilityFlags(flags) {
615
+ return [...new Set(["nodejs_compat", ...(flags ?? [])])];
616
+ }
617
+ function cronTriggers(crons) {
618
+ return (crons ?? []).map((schedule) => {
619
+ if (!/^\S+( \S+){4}$/.test(schedule)) {
620
+ throw new Error(`crons: "${schedule}" isn't a five-field cron schedule, e.g. "0 6 * * *".`);
621
+ }
622
+ return { type: "scheduled", schedule };
623
+ });
624
+ }
625
+ function observability() {
626
+ return {
627
+ ...EVERYAPP_OBSERVABILITY,
628
+ logs: { ...EVERYAPP_OBSERVABILITY.logs },
629
+ traces: { ...EVERYAPP_OBSERVABILITY.traces },
630
+ issues: { ...EVERYAPP_OBSERVABILITY.issues },
631
+ };
632
+ }
633
+ /**
634
+ * The Workflow a binding defines, named after the app's Worker:
635
+ * `every-acme-seo` and `RANK_CHECK` → `every-acme-seo--rank-check`.
636
+ * Workflow names are unique in the account, and new app ids can't contain
637
+ * `--`, so one app's Workflow is never named like another app's.
638
+ */
639
+ export function workflowNameFor(workerName, binding) {
640
+ return `${workerName}--${binding.toLowerCase().replaceAll("_", "-")}`;
641
+ }
642
+ /**
643
+ * The local D1 database id for a binding: a 32-hex string, because
644
+ * `cf d1 … --local` rejects names. The everyapp CLI computes the same id to
645
+ * apply local migrations to the database `cf dev` serves.
646
+ */
647
+ export function localD1Id(workerName, binding) {
648
+ return createHash("sha256")
649
+ .update(`${workerName}:${binding}`)
650
+ .digest("hex")
651
+ .slice(0, 32);
652
+ }
653
+ /** Where `cf dev` keeps D1 databases, under the app's state directory. */
654
+ const LOCAL_D1_OBJECT = "miniflare-D1DatabaseObject";
655
+ /**
656
+ * The sqlite file `cf dev` (and `cf d1 … --local`) keeps the local D1
657
+ * database `databaseId` in, under the app at `root`, for tools such as
658
+ * drizzle-kit studio: `.cloudflare/state/v3/d1/miniflare-D1DatabaseObject/
659
+ * <hash>.sqlite`. The database is a Durable Object named after its id, and
660
+ * the file after that object's id. `databaseId` is the {@link localD1Id}.
661
+ */
662
+ export function localD1Path(root, databaseId) {
663
+ return path.join(root, ".cloudflare", "state", "v3", "d1", LOCAL_D1_OBJECT, `${durableObjectIdFromName(LOCAL_D1_OBJECT, databaseId)}.sqlite`);
664
+ }
665
+ /** The id workerd's `idFromName(name)` gives in the namespace `uniqueKey`. */
666
+ function durableObjectIdFromName(uniqueKey, name) {
667
+ const key = createHash("sha256").update(uniqueKey).digest();
668
+ const nameHmac = createHmac("sha256", key)
669
+ .update(name)
670
+ .digest()
671
+ .subarray(0, 16);
672
+ const hmac = createHmac("sha256", key)
673
+ .update(nameHmac)
674
+ .digest()
675
+ .subarray(0, 16);
676
+ return Buffer.concat([nameHmac, hmac]).toString("hex");
677
+ }
678
+ /**
679
+ * The `secrets` set for local dev, read the way the Vite plugin reads them:
680
+ * `.dev.vars[.<mode>]` alone when present, otherwise `.env` files and the
681
+ * process environment.
682
+ */
683
+ function locallySetSecrets(root, mode, names) {
684
+ if (names.length === 0)
685
+ return [];
686
+ const values = localEnv(root, mode);
687
+ return names.filter((name) => values[name]);
688
+ }
689
+ /** The values local dev sees, read as the Vite plugin reads them. */
690
+ function localEnv(root, mode) {
691
+ const read = (name) => {
692
+ const file = path.join(root, name);
693
+ return fs.existsSync(file) ? parseEnv(fs.readFileSync(file, "utf8")) : {};
694
+ };
695
+ const devVars = [`.dev.vars.${mode}`, ".dev.vars"].find((name) => fs.existsSync(path.join(root, name)));
696
+ return devVars
697
+ ? read(devVars)
698
+ : {
699
+ ...read(".env"),
700
+ ...read(".env.local"),
701
+ ...read(`.env.${mode}`),
702
+ ...read(`.env.${mode}.local`),
703
+ ...process.env,
704
+ };
705
+ }
706
+ /** The dev Worker name, e.g. "todo" -> "every-todo". */
707
+ function workerNameFor(appId) {
708
+ return appId.startsWith("every-") ? appId : `every-${appId}`;
709
+ }
710
+ /**
711
+ * Copies of the gateway's naming rules (@every-app/perimeter/manifest's
712
+ * resourceNameFor and bucketNameFor): `DB` and `KV` (`R2` for a bucket) take
713
+ * the Worker's name, any other binding `<worker>--<binding>`.
714
+ */
715
+ function resourceNameFor(workerName, binding) {
716
+ return binding === "DB" || binding === "KV"
717
+ ? workerName
718
+ : suffixedName(workerName, binding);
719
+ }
720
+ function bucketNameFor(workerName, binding) {
721
+ return binding === "R2" ? workerName : suffixedName(workerName, binding);
722
+ }
723
+ function suffixedName(workerName, binding) {
724
+ return `${workerName}--${binding.toLowerCase().replaceAll("_", "-")}`;
725
+ }
726
+ function assertAppId(id) {
727
+ if (typeof id !== "string" || !/^[a-z]([a-z0-9-]*[a-z0-9])?$/.test(id)) {
728
+ throw new Error(`everyapp.config.ts id must be kebab-case (lowercase letters, digits, hyphens); got ${JSON.stringify(id)}.`);
729
+ }
730
+ }
731
+ /** The target's resource for a manifest binding, which it always has. */
732
+ function provisioned(resources, binding) {
733
+ const resource = resources?.[binding];
734
+ if (resource === undefined) {
735
+ throw new Error(`The build has no placeholder for binding "${binding}".`);
736
+ }
737
+ return resource;
738
+ }