@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.21 → 3.9.0-ultramodern.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -127,6 +127,129 @@ const createWorkerServiceBindings = (modernConfig, configuredWranglerServices)=>
127
127
  if (!Array.isArray(services)) throw new Error('deploy.worker.services must be an array.');
128
128
  return services.map(normalizeServiceBinding);
129
129
  };
130
+ const normalizeVpcService = (vpcService, index)=>{
131
+ const binding = assertNonEmptyString(vpcService.binding, `deploy.worker.vpcServices[${index}].binding`);
132
+ const serviceId = assertNonEmptyString(vpcService.serviceId, `deploy.worker.vpcServices[${index}].serviceId`);
133
+ const prefix = void 0 === vpcService.prefix ? void 0 : assertNonEmptyString(vpcService.prefix, `deploy.worker.vpcServices[${index}].prefix`);
134
+ return {
135
+ binding,
136
+ serviceId,
137
+ ...void 0 === prefix ? {} : {
138
+ prefix
139
+ }
140
+ };
141
+ };
142
+ const decodeRoutePrefix = (prefix)=>{
143
+ let decoded = prefix;
144
+ for(let round = 0; round < 4; round += 1){
145
+ let next;
146
+ try {
147
+ next = decodeURIComponent(decoded);
148
+ } catch {
149
+ break;
150
+ }
151
+ if (next === decoded) break;
152
+ decoded = next;
153
+ }
154
+ return decoded;
155
+ };
156
+ const normalizeRoutePrefix = (prefix)=>{
157
+ const decoded = decodeRoutePrefix(prefix);
158
+ return '/' === decoded ? decoded : decoded.replace(/\/+$/u, '');
159
+ };
160
+ const isCanonicalRoutePrefix = (prefix)=>prefix.startsWith('/') && !prefix.includes('\\') && decodeRoutePrefix(prefix) === prefix && new URL(prefix, 'https://route.invalid').pathname === prefix;
161
+ const collectWorkerBindingNames = (config)=>{
162
+ const names = new Set();
163
+ const visit = (value, inArray)=>{
164
+ if (Array.isArray(value)) {
165
+ for (const item of value)visit(item, true);
166
+ return;
167
+ }
168
+ if (!isJsonRecord(value)) return;
169
+ for (const field of inArray ? [
170
+ 'binding',
171
+ 'name'
172
+ ] : [
173
+ 'binding'
174
+ ]){
175
+ const name = value[field];
176
+ if ('string' == typeof name) names.add(name.trim());
177
+ }
178
+ for (const child of Object.values(value))visit(child, false);
179
+ };
180
+ for (const [key, value] of Object.entries(config))if ('env' !== key && 'services' !== key && 'vpc_services' !== key) {
181
+ if ('vars' === key && isJsonRecord(value)) {
182
+ for (const name of Object.keys(value))names.add(name);
183
+ continue;
184
+ }
185
+ visit(value, false);
186
+ }
187
+ return names;
188
+ };
189
+ const prefixesOverlap = (left, right)=>{
190
+ const [first, second] = [
191
+ normalizeRoutePrefix(left),
192
+ normalizeRoutePrefix(right)
193
+ ];
194
+ return '/' === first || '/' === second || first === second || first.startsWith(`${second}/`) || second.startsWith(`${first}/`);
195
+ };
196
+ const getWorkerEffectBffPrefix = (modernConfig)=>{
197
+ const bffPrefix = modernConfig.bff?.prefix;
198
+ const primaryBffPrefix = Array.isArray(bffPrefix) ? bffPrefix[0] : bffPrefix;
199
+ const isEffectApi = Boolean(modernConfig.bff) && modernConfig.bff?.runtimeFramework !== 'hono';
200
+ return isEffectApi && primaryBffPrefix ? primaryBffPrefix : void 0;
201
+ };
202
+ const effectiveServiceBindings = (serviceBindings)=>{
203
+ const bindings = Array.isArray(serviceBindings) ? serviceBindings : [];
204
+ return bindings.flatMap((service)=>isJsonRecord(service) && 'string' == typeof service.binding ? [
205
+ {
206
+ binding: service.binding.trim(),
207
+ prefix: 'string' == typeof service.prefix ? service.prefix : void 0
208
+ }
209
+ ] : []);
210
+ };
211
+ const createWorkerVpcServiceBindings = (modernConfig, configuredWranglerVpcServices, serviceBindings, workerBindingNames)=>{
212
+ const vpcServices = modernConfig.deploy?.worker?.vpcServices;
213
+ if (void 0 === vpcServices) return;
214
+ if (void 0 !== configuredWranglerVpcServices) throw new Error('Use deploy.worker.vpcServices or deploy.worker.wrangler.vpc_services, not both.');
215
+ if (!Array.isArray(vpcServices)) throw new Error('deploy.worker.vpcServices must be an array.');
216
+ const services = effectiveServiceBindings(serviceBindings);
217
+ const serviceBindingNames = new Set(services.map(({ binding })=>binding));
218
+ const effectBffPrefix = getWorkerEffectBffPrefix(modernConfig);
219
+ const routes = [
220
+ ...void 0 === effectBffPrefix ? [] : [
221
+ {
222
+ owner: 'the Effect BFF',
223
+ prefix: effectBffPrefix
224
+ }
225
+ ],
226
+ ...services.flatMap(({ binding, prefix })=>void 0 === prefix ? [] : [
227
+ {
228
+ owner: `binding "${binding}"`,
229
+ prefix
230
+ }
231
+ ])
232
+ ];
233
+ const vpcBindingNames = new Set();
234
+ return vpcServices.map((vpcService, index)=>{
235
+ const normalized = normalizeVpcService(vpcService, index);
236
+ if (serviceBindingNames.has(normalized.binding)) throw new Error(`deploy.worker.vpcServices[${index}].binding "${normalized.binding}" is already declared by deploy.worker.services.`);
237
+ if (workerBindingNames.has(normalized.binding)) throw new Error(`deploy.worker.vpcServices[${index}].binding "${normalized.binding}" is already a binding of this Worker.`);
238
+ if (vpcBindingNames.has(normalized.binding)) throw new Error(`deploy.worker.vpcServices[${index}].binding "${normalized.binding}" is declared more than once.`);
239
+ vpcBindingNames.add(normalized.binding);
240
+ if (void 0 !== normalized.prefix) {
241
+ const { prefix } = normalized;
242
+ if (!isCanonicalRoutePrefix(prefix)) throw new Error(`deploy.worker.vpcServices[${index}].prefix "${prefix}" must be a decoded URL path that starts with "/" (no query, fragment, "." or ".." segments).`);
243
+ const shadow = routes.find((route)=>prefixesOverlap(route.prefix, prefix));
244
+ if (void 0 !== shadow) throw new Error(`deploy.worker.vpcServices[${index}].prefix "${prefix}" overlaps the prefix "${shadow.prefix}" of ${shadow.owner}; the worker dispatcher would route one of them ambiguously.`);
245
+ routes.push({
246
+ owner: `binding "${normalized.binding}"`,
247
+ prefix
248
+ });
249
+ }
250
+ return normalized;
251
+ });
252
+ };
130
253
  const createWranglerServices = (serviceBindings)=>{
131
254
  if (!Array.isArray(serviceBindings)) return serviceBindings;
132
255
  if (!serviceBindings.every(isJsonRecord)) return serviceBindings;
@@ -135,8 +258,16 @@ const createWranglerServices = (serviceBindings)=>{
135
258
  return wranglerService;
136
259
  });
137
260
  };
138
- const createWorkerManifestServiceBindings = (serviceBindings)=>{
139
- if (!Array.isArray(serviceBindings)) return;
261
+ const createWorkerManifestServiceBindings = (serviceBindings, vpcServiceBindings)=>{
262
+ const vpcManifestBindings = (vpcServiceBindings ?? []).flatMap(({ binding, prefix, serviceId })=>void 0 === prefix ? [] : [
263
+ {
264
+ binding,
265
+ interface: 'fetch',
266
+ prefix,
267
+ vpcServiceId: serviceId
268
+ }
269
+ ]);
270
+ if (!Array.isArray(serviceBindings)) return vpcManifestBindings.length > 0 ? vpcManifestBindings : void 0;
140
271
  const bindings = serviceBindings;
141
272
  const manifestBindings = bindings.filter((service)=>isJsonRecord(service) && 'string' == typeof service.binding && 'string' == typeof service.service && ('string' == typeof service.prefix || Array.isArray(service.fragments) && service.fragments.length > 0)).map((service)=>({
142
273
  binding: service.binding,
@@ -149,7 +280,11 @@ const createWorkerManifestServiceBindings = (serviceBindings)=>{
149
280
  fragments: service.fragments
150
281
  }
151
282
  }));
152
- return manifestBindings.length > 0 ? manifestBindings : void 0;
283
+ const allBindings = [
284
+ ...manifestBindings,
285
+ ...vpcManifestBindings
286
+ ];
287
+ return allBindings.length > 0 ? allBindings : void 0;
153
288
  };
154
289
  const createWranglerConfig = (appDirectory, modernConfig)=>{
155
290
  const wrangler = getConfiguredWrangler(modernConfig);
@@ -157,7 +292,7 @@ const createWranglerConfig = (appDirectory, modernConfig)=>{
157
292
  const d1Databases = createWranglerD1Databases(modernConfig, wrangler.d1_databases);
158
293
  const serviceBindings = createWorkerServiceBindings(modernConfig, wrangler.services);
159
294
  const wranglerServices = createWranglerServices(serviceBindings);
160
- return {
295
+ const workerConfig = {
161
296
  $schema: 'node_modules/wrangler/config-schema.json',
162
297
  name: getConfiguredWorkerName(appDirectory, modernConfig),
163
298
  ...wrangler,
@@ -175,5 +310,33 @@ const createWranglerConfig = (appDirectory, modernConfig)=>{
175
310
  services: wranglerServices
176
311
  }
177
312
  };
313
+ const vpcServiceBindings = createWorkerVpcServiceBindings(modernConfig, wrangler.vpc_services, serviceBindings, collectWorkerBindingNames(workerConfig));
314
+ if (void 0 === vpcServiceBindings) return workerConfig;
315
+ const vpcServicesConfig = vpcServiceBindings.map(({ binding, serviceId })=>({
316
+ binding,
317
+ service_id: serviceId
318
+ }));
319
+ return {
320
+ ...workerConfig,
321
+ ...void 0 === environments ? {} : {
322
+ env: Object.fromEntries(Object.entries(environments).map(([name, environment])=>{
323
+ if (void 0 !== environment.vpc_services) throw new Error(`Use deploy.worker.vpcServices or deploy.worker.wrangler.env.${name}.vpc_services, not both.`);
324
+ const environmentBindingNames = new Set([
325
+ ...collectWorkerBindingNames(environment),
326
+ ...effectiveServiceBindings(environment.services).map(({ binding })=>binding)
327
+ ]);
328
+ const collision = vpcServiceBindings.find(({ binding })=>environmentBindingNames.has(binding));
329
+ if (void 0 !== collision) throw new Error(`deploy.worker.vpcServices binding "${collision.binding}" is already a binding of deploy.worker.wrangler.env.${name}.`);
330
+ return [
331
+ name,
332
+ {
333
+ ...environment,
334
+ vpc_services: vpcServicesConfig
335
+ }
336
+ ];
337
+ }))
338
+ },
339
+ vpc_services: vpcServicesConfig
340
+ };
178
341
  };
179
- export { createWorkerManifestServiceBindings, createWorkerServiceBindings, createWranglerConfig };
342
+ export { createWorkerManifestServiceBindings, createWorkerServiceBindings, createWorkerVpcServiceBindings, createWranglerConfig, getWorkerEffectBffPrefix };
@@ -79,7 +79,8 @@ const CLOUDFLARE_WORKER_NODE_BUILTINS = [
79
79
  'zlib'
80
80
  ];
81
81
  const CLOUDFLARE_WORKER_PLATFORM_MODULES = [
82
- 'cloudflare:sockets'
82
+ 'cloudflare:sockets',
83
+ 'cloudflare:workers'
83
84
  ];
84
85
  const CLOUDFLARE_REQUIRED_COMPATIBILITY_FLAGS = [
85
86
  'nodejs_compat',
@@ -1,21 +1,48 @@
1
1
  import "node:module";
2
- import { execFileSync } from "node:child_process";
3
2
  import { createHash } from "node:crypto";
4
3
  import { accessSync, chmodSync, constants, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
5
4
  import { createRequire } from "node:module";
6
5
  import { tmpdir } from "node:os";
7
- import { basename, dirname, join, resolve } from "node:path";
6
+ import { basename, dirname, join } from "node:path";
8
7
  const EFFECT_TSGO_PACKAGE = '@effect/tsgo';
9
- const EFFECT_TSGO_BIN = 'effect-tsgo';
8
+ const NATIVE_TYPESCRIPT_PACKAGES = [
9
+ "typescript",
10
+ "@typescript/native"
11
+ ];
10
12
  const EFFECT_TSGO_RESOLUTION_ERROR = 'Unable to resolve the Effect TS-Go compiler. Install "@effect/tsgo" and a native TypeScript backend for this build config, or set EFFECT_TSGO_BIN.';
11
13
  const executableEffectTsgoCompilers = new Map();
12
- function resolveEffectTsgoCli(from) {
14
+ function readJson(fileName) {
15
+ return JSON.parse(readFileSync(fileName, 'utf-8'));
16
+ }
17
+ function resolvePackageVersion(packageRequire, packageName) {
18
+ let packageJsonPath;
19
+ try {
20
+ packageJsonPath = packageRequire.resolve(`${packageName}/package.json`);
21
+ } catch {
22
+ return;
23
+ }
24
+ const { version } = readJson(packageJsonPath);
25
+ return 'string' == typeof version ? {
26
+ packageJsonPath,
27
+ version
28
+ } : void 0;
29
+ }
30
+ function resolveEffectTsgoTypeScriptArtifact(from) {
13
31
  const projectRequire = createRequire(from);
14
- const packageJsonPath = projectRequire.resolve(`${EFFECT_TSGO_PACKAGE}/package.json`);
15
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
16
- const bin = 'string' == typeof packageJson.bin ? packageJson.bin : packageJson.bin?.[EFFECT_TSGO_BIN];
17
- if (!bin) throw new Error(EFFECT_TSGO_RESOLUTION_ERROR);
18
- return resolve(dirname(packageJsonPath), bin);
32
+ const effectTsgoPackageJsonPath = projectRequire.resolve(`${EFFECT_TSGO_PACKAGE}/package.json`);
33
+ const platformTarget = `${process.platform}-${process.arch}`;
34
+ const effectTsgoPlatformPackageJsonPath = createRequire(effectTsgoPackageJsonPath).resolve(`${EFFECT_TSGO_PACKAGE}-${platformTarget}/package.json`);
35
+ const upstream = readJson(join(dirname(effectTsgoPlatformPackageJsonPath), 'lib', 'upstream.json'));
36
+ const packagedTypeScriptVersions = upstream.components?.typescript ?? {};
37
+ for (const packageName of NATIVE_TYPESCRIPT_PACKAGES){
38
+ const typescript = resolvePackageVersion(projectRequire, packageName);
39
+ const major = typescript && /^\s*(\d+)/u.exec(typescript.version);
40
+ if (!typescript || !major || Number(major[1]) < 7) continue;
41
+ const typescriptPlatform = resolvePackageVersion(createRequire(typescript.packageJsonPath), `@typescript/typescript-${platformTarget}`);
42
+ if (!typescriptPlatform || !Object.hasOwn(packagedTypeScriptVersions, typescriptPlatform.version)) break;
43
+ return join(dirname(effectTsgoPlatformPackageJsonPath), 'artifacts', "typescript", typescriptPlatform.version, 'win32' === process.platform ? 'tsc.exe' : 'tsc');
44
+ }
45
+ throw new Error(EFFECT_TSGO_RESOLUTION_ERROR);
19
46
  }
20
47
  function resolveExecutableEffectTsgoCompiler(compilerPath) {
21
48
  if ('win32' === process.platform) return compilerPath;
@@ -76,13 +103,7 @@ function resolveEffectTsgoCompiler(options) {
76
103
  throw new Error(EFFECT_TSGO_RESOLUTION_ERROR);
77
104
  }
78
105
  try {
79
- const compiler = execFileSync(process.execPath, [
80
- resolveEffectTsgoCli(options.from),
81
- 'get-exe-path'
82
- ], {
83
- encoding: 'utf-8'
84
- }).trim();
85
- if (compiler) return resolveExecutableEffectTsgoCompiler(compiler);
106
+ return resolveExecutableEffectTsgoCompiler(resolveEffectTsgoTypeScriptArtifact(options.from));
86
107
  } catch {}
87
108
  throw new Error(EFFECT_TSGO_RESOLUTION_ERROR);
88
109
  }
@@ -7,7 +7,7 @@ import { ASSETS_BINDING, BFF_EFFECT_WORKER_DISPATCHER_EXPORT, BFF_EFFECT_WORKER_
7
7
  import { createI18nWorkerManifest } from "./i18n-worker.mjs";
8
8
  import { createCloudflareWorkerSecurityPolicy } from "./security-policies.mjs";
9
9
  import { isRecord, normalizeRelativePath } from "./utils.mjs";
10
- import { createWorkerManifestServiceBindings, createWorkerServiceBindings } from "./wrangler-config.mjs";
10
+ import { createWorkerManifestServiceBindings, createWorkerServiceBindings, createWorkerVpcServiceBindings, getWorkerEffectBffPrefix } from "./wrangler-config.mjs";
11
11
  const createMissingEffectBffWorkerError = (outputDirectory, worker)=>new Error(`Cloudflare Effect API runtime is configured, but the BFF worker bundle is missing: ${node_path.join(outputDirectory, worker)}. ${EFFECT_BFF_CLOUDFLARE_IMPORT_GUIDANCE}`);
12
12
  const createEffectBffWorkerManifest = (modernConfig)=>{
13
13
  const bff = modernConfig.bff;
@@ -81,13 +81,15 @@ const createWorkerManifest = async (outputDirectory, modernConfig, appContext, d
81
81
  } : {}
82
82
  };
83
83
  }));
84
- const bffPrefix = modernConfig.bff?.prefix;
85
- const primaryBffPrefix = Array.isArray(bffPrefix) ? bffPrefix[0] : bffPrefix;
86
84
  const isEffectApi = Boolean(modernConfig.bff) && modernConfig.bff?.runtimeFramework !== 'hono';
85
+ const effectBffPrefix = getWorkerEffectBffPrefix(modernConfig);
87
86
  const effectApiWorkerExists = await fs.pathExists(node_path.join(outputDirectory, BFF_EFFECT_WORKER_ENTRY));
88
- const serviceBindings = createWorkerManifestServiceBindings(createWorkerServiceBindings(modernConfig, void 0));
87
+ const typedServiceBindings = createWorkerServiceBindings(modernConfig, void 0);
88
+ const serviceBindings = createWorkerManifestServiceBindings(typedServiceBindings, createWorkerVpcServiceBindings(modernConfig, void 0, typedServiceBindings, new Set([
89
+ ASSETS_BINDING
90
+ ])));
89
91
  const moduleFederation = await createModuleFederationWorkerManifest(outputDirectory);
90
- if (isEffectApi && primaryBffPrefix && !effectApiWorkerExists) throw createMissingEffectBffWorkerError(outputDirectory, BFF_EFFECT_WORKER_ENTRY);
92
+ if (void 0 !== effectBffPrefix && !effectApiWorkerExists) throw createMissingEffectBffWorkerError(outputDirectory, BFF_EFFECT_WORKER_ENTRY);
91
93
  const effectBffManifest = isEffectApi ? createEffectBffWorkerManifest(modernConfig) : void 0;
92
94
  return {
93
95
  version: 1,
@@ -124,10 +126,10 @@ const createWorkerManifest = async (outputDirectory, modernConfig, appContext, d
124
126
  deliveryUnit: deliveryUnitStamp
125
127
  } : {},
126
128
  i18n: createI18nWorkerManifest(routeSpec, appContext),
127
- bff: isEffectApi && primaryBffPrefix && effectApiWorkerExists ? {
129
+ bff: void 0 !== effectBffPrefix && effectApiWorkerExists ? {
128
130
  dispatcherExport: BFF_EFFECT_WORKER_DISPATCHER_EXPORT,
129
131
  runtimeFramework: 'effect',
130
- prefix: primaryBffPrefix,
132
+ prefix: effectBffPrefix,
131
133
  worker: BFF_EFFECT_WORKER_ENTRY,
132
134
  effect: effectBffManifest
133
135
  } : void 0,
@@ -128,6 +128,129 @@ const createWorkerServiceBindings = (modernConfig, configuredWranglerServices)=>
128
128
  if (!Array.isArray(services)) throw new Error('deploy.worker.services must be an array.');
129
129
  return services.map(normalizeServiceBinding);
130
130
  };
131
+ const normalizeVpcService = (vpcService, index)=>{
132
+ const binding = assertNonEmptyString(vpcService.binding, `deploy.worker.vpcServices[${index}].binding`);
133
+ const serviceId = assertNonEmptyString(vpcService.serviceId, `deploy.worker.vpcServices[${index}].serviceId`);
134
+ const prefix = void 0 === vpcService.prefix ? void 0 : assertNonEmptyString(vpcService.prefix, `deploy.worker.vpcServices[${index}].prefix`);
135
+ return {
136
+ binding,
137
+ serviceId,
138
+ ...void 0 === prefix ? {} : {
139
+ prefix
140
+ }
141
+ };
142
+ };
143
+ const decodeRoutePrefix = (prefix)=>{
144
+ let decoded = prefix;
145
+ for(let round = 0; round < 4; round += 1){
146
+ let next;
147
+ try {
148
+ next = decodeURIComponent(decoded);
149
+ } catch {
150
+ break;
151
+ }
152
+ if (next === decoded) break;
153
+ decoded = next;
154
+ }
155
+ return decoded;
156
+ };
157
+ const normalizeRoutePrefix = (prefix)=>{
158
+ const decoded = decodeRoutePrefix(prefix);
159
+ return '/' === decoded ? decoded : decoded.replace(/\/+$/u, '');
160
+ };
161
+ const isCanonicalRoutePrefix = (prefix)=>prefix.startsWith('/') && !prefix.includes('\\') && decodeRoutePrefix(prefix) === prefix && new URL(prefix, 'https://route.invalid').pathname === prefix;
162
+ const collectWorkerBindingNames = (config)=>{
163
+ const names = new Set();
164
+ const visit = (value, inArray)=>{
165
+ if (Array.isArray(value)) {
166
+ for (const item of value)visit(item, true);
167
+ return;
168
+ }
169
+ if (!isJsonRecord(value)) return;
170
+ for (const field of inArray ? [
171
+ 'binding',
172
+ 'name'
173
+ ] : [
174
+ 'binding'
175
+ ]){
176
+ const name = value[field];
177
+ if ('string' == typeof name) names.add(name.trim());
178
+ }
179
+ for (const child of Object.values(value))visit(child, false);
180
+ };
181
+ for (const [key, value] of Object.entries(config))if ('env' !== key && 'services' !== key && 'vpc_services' !== key) {
182
+ if ('vars' === key && isJsonRecord(value)) {
183
+ for (const name of Object.keys(value))names.add(name);
184
+ continue;
185
+ }
186
+ visit(value, false);
187
+ }
188
+ return names;
189
+ };
190
+ const prefixesOverlap = (left, right)=>{
191
+ const [first, second] = [
192
+ normalizeRoutePrefix(left),
193
+ normalizeRoutePrefix(right)
194
+ ];
195
+ return '/' === first || '/' === second || first === second || first.startsWith(`${second}/`) || second.startsWith(`${first}/`);
196
+ };
197
+ const getWorkerEffectBffPrefix = (modernConfig)=>{
198
+ const bffPrefix = modernConfig.bff?.prefix;
199
+ const primaryBffPrefix = Array.isArray(bffPrefix) ? bffPrefix[0] : bffPrefix;
200
+ const isEffectApi = Boolean(modernConfig.bff) && modernConfig.bff?.runtimeFramework !== 'hono';
201
+ return isEffectApi && primaryBffPrefix ? primaryBffPrefix : void 0;
202
+ };
203
+ const effectiveServiceBindings = (serviceBindings)=>{
204
+ const bindings = Array.isArray(serviceBindings) ? serviceBindings : [];
205
+ return bindings.flatMap((service)=>isJsonRecord(service) && 'string' == typeof service.binding ? [
206
+ {
207
+ binding: service.binding.trim(),
208
+ prefix: 'string' == typeof service.prefix ? service.prefix : void 0
209
+ }
210
+ ] : []);
211
+ };
212
+ const createWorkerVpcServiceBindings = (modernConfig, configuredWranglerVpcServices, serviceBindings, workerBindingNames)=>{
213
+ const vpcServices = modernConfig.deploy?.worker?.vpcServices;
214
+ if (void 0 === vpcServices) return;
215
+ if (void 0 !== configuredWranglerVpcServices) throw new Error('Use deploy.worker.vpcServices or deploy.worker.wrangler.vpc_services, not both.');
216
+ if (!Array.isArray(vpcServices)) throw new Error('deploy.worker.vpcServices must be an array.');
217
+ const services = effectiveServiceBindings(serviceBindings);
218
+ const serviceBindingNames = new Set(services.map(({ binding })=>binding));
219
+ const effectBffPrefix = getWorkerEffectBffPrefix(modernConfig);
220
+ const routes = [
221
+ ...void 0 === effectBffPrefix ? [] : [
222
+ {
223
+ owner: 'the Effect BFF',
224
+ prefix: effectBffPrefix
225
+ }
226
+ ],
227
+ ...services.flatMap(({ binding, prefix })=>void 0 === prefix ? [] : [
228
+ {
229
+ owner: `binding "${binding}"`,
230
+ prefix
231
+ }
232
+ ])
233
+ ];
234
+ const vpcBindingNames = new Set();
235
+ return vpcServices.map((vpcService, index)=>{
236
+ const normalized = normalizeVpcService(vpcService, index);
237
+ if (serviceBindingNames.has(normalized.binding)) throw new Error(`deploy.worker.vpcServices[${index}].binding "${normalized.binding}" is already declared by deploy.worker.services.`);
238
+ if (workerBindingNames.has(normalized.binding)) throw new Error(`deploy.worker.vpcServices[${index}].binding "${normalized.binding}" is already a binding of this Worker.`);
239
+ if (vpcBindingNames.has(normalized.binding)) throw new Error(`deploy.worker.vpcServices[${index}].binding "${normalized.binding}" is declared more than once.`);
240
+ vpcBindingNames.add(normalized.binding);
241
+ if (void 0 !== normalized.prefix) {
242
+ const { prefix } = normalized;
243
+ if (!isCanonicalRoutePrefix(prefix)) throw new Error(`deploy.worker.vpcServices[${index}].prefix "${prefix}" must be a decoded URL path that starts with "/" (no query, fragment, "." or ".." segments).`);
244
+ const shadow = routes.find((route)=>prefixesOverlap(route.prefix, prefix));
245
+ if (void 0 !== shadow) throw new Error(`deploy.worker.vpcServices[${index}].prefix "${prefix}" overlaps the prefix "${shadow.prefix}" of ${shadow.owner}; the worker dispatcher would route one of them ambiguously.`);
246
+ routes.push({
247
+ owner: `binding "${normalized.binding}"`,
248
+ prefix
249
+ });
250
+ }
251
+ return normalized;
252
+ });
253
+ };
131
254
  const createWranglerServices = (serviceBindings)=>{
132
255
  if (!Array.isArray(serviceBindings)) return serviceBindings;
133
256
  if (!serviceBindings.every(isJsonRecord)) return serviceBindings;
@@ -136,8 +259,16 @@ const createWranglerServices = (serviceBindings)=>{
136
259
  return wranglerService;
137
260
  });
138
261
  };
139
- const createWorkerManifestServiceBindings = (serviceBindings)=>{
140
- if (!Array.isArray(serviceBindings)) return;
262
+ const createWorkerManifestServiceBindings = (serviceBindings, vpcServiceBindings)=>{
263
+ const vpcManifestBindings = (vpcServiceBindings ?? []).flatMap(({ binding, prefix, serviceId })=>void 0 === prefix ? [] : [
264
+ {
265
+ binding,
266
+ interface: 'fetch',
267
+ prefix,
268
+ vpcServiceId: serviceId
269
+ }
270
+ ]);
271
+ if (!Array.isArray(serviceBindings)) return vpcManifestBindings.length > 0 ? vpcManifestBindings : void 0;
141
272
  const bindings = serviceBindings;
142
273
  const manifestBindings = bindings.filter((service)=>isJsonRecord(service) && 'string' == typeof service.binding && 'string' == typeof service.service && ('string' == typeof service.prefix || Array.isArray(service.fragments) && service.fragments.length > 0)).map((service)=>({
143
274
  binding: service.binding,
@@ -150,7 +281,11 @@ const createWorkerManifestServiceBindings = (serviceBindings)=>{
150
281
  fragments: service.fragments
151
282
  }
152
283
  }));
153
- return manifestBindings.length > 0 ? manifestBindings : void 0;
284
+ const allBindings = [
285
+ ...manifestBindings,
286
+ ...vpcManifestBindings
287
+ ];
288
+ return allBindings.length > 0 ? allBindings : void 0;
154
289
  };
155
290
  const createWranglerConfig = (appDirectory, modernConfig)=>{
156
291
  const wrangler = getConfiguredWrangler(modernConfig);
@@ -158,7 +293,7 @@ const createWranglerConfig = (appDirectory, modernConfig)=>{
158
293
  const d1Databases = createWranglerD1Databases(modernConfig, wrangler.d1_databases);
159
294
  const serviceBindings = createWorkerServiceBindings(modernConfig, wrangler.services);
160
295
  const wranglerServices = createWranglerServices(serviceBindings);
161
- return {
296
+ const workerConfig = {
162
297
  $schema: 'node_modules/wrangler/config-schema.json',
163
298
  name: getConfiguredWorkerName(appDirectory, modernConfig),
164
299
  ...wrangler,
@@ -176,5 +311,33 @@ const createWranglerConfig = (appDirectory, modernConfig)=>{
176
311
  services: wranglerServices
177
312
  }
178
313
  };
314
+ const vpcServiceBindings = createWorkerVpcServiceBindings(modernConfig, wrangler.vpc_services, serviceBindings, collectWorkerBindingNames(workerConfig));
315
+ if (void 0 === vpcServiceBindings) return workerConfig;
316
+ const vpcServicesConfig = vpcServiceBindings.map(({ binding, serviceId })=>({
317
+ binding,
318
+ service_id: serviceId
319
+ }));
320
+ return {
321
+ ...workerConfig,
322
+ ...void 0 === environments ? {} : {
323
+ env: Object.fromEntries(Object.entries(environments).map(([name, environment])=>{
324
+ if (void 0 !== environment.vpc_services) throw new Error(`Use deploy.worker.vpcServices or deploy.worker.wrangler.env.${name}.vpc_services, not both.`);
325
+ const environmentBindingNames = new Set([
326
+ ...collectWorkerBindingNames(environment),
327
+ ...effectiveServiceBindings(environment.services).map(({ binding })=>binding)
328
+ ]);
329
+ const collision = vpcServiceBindings.find(({ binding })=>environmentBindingNames.has(binding));
330
+ if (void 0 !== collision) throw new Error(`deploy.worker.vpcServices binding "${collision.binding}" is already a binding of deploy.worker.wrangler.env.${name}.`);
331
+ return [
332
+ name,
333
+ {
334
+ ...environment,
335
+ vpc_services: vpcServicesConfig
336
+ }
337
+ ];
338
+ }))
339
+ },
340
+ vpc_services: vpcServicesConfig
341
+ };
179
342
  };
180
- export { createWorkerManifestServiceBindings, createWorkerServiceBindings, createWranglerConfig };
343
+ export { createWorkerManifestServiceBindings, createWorkerServiceBindings, createWorkerVpcServiceBindings, createWranglerConfig, getWorkerEffectBffPrefix };
@@ -80,7 +80,8 @@ const CLOUDFLARE_WORKER_NODE_BUILTINS = [
80
80
  'zlib'
81
81
  ];
82
82
  const CLOUDFLARE_WORKER_PLATFORM_MODULES = [
83
- 'cloudflare:sockets'
83
+ 'cloudflare:sockets',
84
+ 'cloudflare:workers'
84
85
  ];
85
86
  const CLOUDFLARE_REQUIRED_COMPATIBILITY_FLAGS = [
86
87
  'nodejs_compat',
@@ -130,7 +130,12 @@ export declare const createWorkerManifest: (outputDirectory: string, modernConfi
130
130
  };
131
131
  } | undefined;
132
132
  } | undefined;
133
- serviceBindings?: {
133
+ serviceBindings?: ({
134
+ binding: string;
135
+ interface: string;
136
+ prefix: string;
137
+ vpcServiceId: string;
138
+ } | {
134
139
  binding: string;
135
140
  service: string;
136
141
  interface: string;
@@ -141,7 +146,7 @@ export declare const createWorkerManifest: (outputDirectory: string, modernConfi
141
146
  boundaryId: string;
142
147
  path: string;
143
148
  }[] | undefined;
144
- }[] | undefined;
149
+ })[] | undefined;
145
150
  }>;
146
151
  export declare const createWorkerModuleLoaders: (manifest: any) => string;
147
152
  export declare const getPublicAssetExcludes: (appDirectory: string, modernConfig: CloudflareModernConfig) => string[];
@@ -11,7 +11,22 @@ export declare const createWorkerServiceBindings: (modernConfig: CloudflareModer
11
11
  path: string;
12
12
  }[] | undefined;
13
13
  }[] | JsonValue | undefined;
14
- export declare const createWorkerManifestServiceBindings: (serviceBindings: ReturnType<typeof createWorkerServiceBindings>) => {
14
+ /**
15
+ * The prefix the worker's Effect BFF dispatcher owns. It runs before any
16
+ * service binding, so it takes part in route-overlap checks.
17
+ */
18
+ export declare const getWorkerEffectBffPrefix: (modernConfig: CloudflareModernConfig) => string | undefined;
19
+ export declare const createWorkerVpcServiceBindings: (modernConfig: CloudflareModernConfig, configuredWranglerVpcServices: JsonValue | undefined, serviceBindings: ReturnType<typeof createWorkerServiceBindings>, workerBindingNames: ReadonlySet<string>) => {
20
+ binding: string;
21
+ serviceId: string;
22
+ prefix?: string | undefined;
23
+ }[] | undefined;
24
+ export declare const createWorkerManifestServiceBindings: (serviceBindings: ReturnType<typeof createWorkerServiceBindings>, vpcServiceBindings: ReturnType<typeof createWorkerVpcServiceBindings>) => ({
25
+ binding: string;
26
+ interface: string;
27
+ prefix: string;
28
+ vpcServiceId: string;
29
+ } | {
15
30
  binding: string;
16
31
  service: string;
17
32
  interface: string;
@@ -22,7 +37,7 @@ export declare const createWorkerManifestServiceBindings: (serviceBindings: Retu
22
37
  boundaryId: string;
23
38
  path: string;
24
39
  }[] | undefined;
25
- }[] | undefined;
40
+ })[] | undefined;
26
41
  export declare const createWranglerConfig: (appDirectory: string, modernConfig: CloudflareModernConfig) => {
27
42
  $schema: string;
28
43
  name: string;
@@ -34,10 +49,38 @@ export declare const createWranglerConfig: (appDirectory: string, modernConfig:
34
49
  binding: string;
35
50
  run_worker_first: boolean;
36
51
  };
37
- env?: {
52
+ env?: Record<string, Record<string, JsonValue>> | undefined;
53
+ d1_databases?: {
54
+ binding: string;
55
+ database_name: string;
56
+ database_id: string;
57
+ migrations_dir?: string | undefined;
58
+ preview_database_id?: string | undefined;
59
+ remote?: boolean | undefined;
60
+ }[] | JsonValue | undefined;
61
+ services?: string | number | boolean | JsonValue[] | {
62
+ binding: string;
63
+ service: string;
64
+ }[] | {
65
+ [key: string]: JsonValue;
66
+ } | null | undefined;
67
+ } | {
68
+ $schema: string;
69
+ name: string;
70
+ compatibility_date: string;
71
+ main: string;
72
+ compatibility_flags: JsonValue[];
73
+ assets: {
74
+ directory: string;
75
+ binding: string;
76
+ run_worker_first: boolean;
77
+ };
78
+ env?: Record<string, Record<string, JsonValue>> | {
38
79
  [k: string]: {
39
- compatibility_date?: string | undefined;
40
- compatibility_flags?: JsonValue[] | undefined;
80
+ vpc_services: {
81
+ binding: string;
82
+ service_id: string;
83
+ }[];
41
84
  };
42
85
  } | undefined;
43
86
  d1_databases?: {
@@ -54,4 +97,8 @@ export declare const createWranglerConfig: (appDirectory: string, modernConfig:
54
97
  }[] | {
55
98
  [key: string]: JsonValue;
56
99
  } | null | undefined;
100
+ vpc_services: {
101
+ binding: string;
102
+ service_id: string;
103
+ }[];
57
104
  };
@@ -13,5 +13,5 @@ export declare const CLOUDFLARE_WORKER_BUNDLE_FORMAT = "commonjs";
13
13
  export declare const CLOUDFLARE_ROUTE_DATA_HANDLER_EXPORT = "handleRouteDataRequest";
14
14
  export declare const getCloudflareWorkerRouteDataEntryName: (entryName: string) => string;
15
15
  export declare const CLOUDFLARE_WORKER_NODE_BUILTINS: readonly ['_http_agent', '_http_client', '_http_common', '_http_incoming', '_http_outgoing', '_http_server', '_tls_common', '_tls_wrap', 'assert', 'assert/strict', 'async_hooks', 'buffer', 'child_process', 'cluster', 'console', 'constants', 'crypto', 'dgram', 'diagnostics_channel', 'dns', 'dns/promises', 'domain', 'events', 'fs', 'fs/promises', 'http', 'http2', 'https', 'inspector', 'inspector/promises', 'module', 'net', 'os', 'path', 'path/posix', 'path/win32', 'perf_hooks', 'process', 'punycode', 'querystring', 'readline', 'readline/promises', 'repl', 'sqlite', 'stream', 'stream/consumers', 'stream/promises', 'stream/web', 'string_decoder', 'sys', 'test', 'timers', 'timers/promises', 'tls', 'trace_events', 'tty', 'url', 'util', 'util/types', 'v8', 'vm', 'wasi', 'worker_threads', 'zlib'];
16
- export declare const CLOUDFLARE_WORKER_PLATFORM_MODULES: readonly ['cloudflare:sockets'];
16
+ export declare const CLOUDFLARE_WORKER_PLATFORM_MODULES: readonly ['cloudflare:sockets', 'cloudflare:workers'];
17
17
  export declare const CLOUDFLARE_REQUIRED_COMPATIBILITY_FLAGS: readonly ['nodejs_compat', 'global_fetch_strictly_public'];