@reventlessdev/reventless-aws 3.0.0-alpha.343 → 3.0.0-alpha.344

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,13 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # 3.0.0-alpha.344 (2026-09-09)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **aws:** the archive asks what the layer holds, not what resolves ([fca352f](https://github.com/ReventlessDev/reventless-core/commit/fca352f7a19f0518a84944d4efd23eb98285f6bf))
11
+
12
+
6
13
  # 3.0.0-alpha.343 (2026-09-08)
7
14
 
8
15
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-aws",
3
- "version": "3.0.0-alpha.343",
3
+ "version": "3.0.0-alpha.344",
4
4
  "description": "AWS adapters for Reventless",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -15,18 +15,18 @@
15
15
  "@aws-sdk/s3-request-presigner": "3.970.0",
16
16
  "sury": "11.0.0-rc.2",
17
17
  "uuid": "^13.0.0",
18
- "@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.16",
19
18
  "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
20
19
  "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
21
- "@reventlessdev/rescript-node": "2.0.0-alpha.10",
22
20
  "@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.11",
23
- "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19",
21
+ "@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.16",
22
+ "@reventlessdev/rescript-node": "2.0.0-alpha.10",
24
23
  "@reventlessdev/rescript-uuid": "2.0.0-alpha.0",
25
- "@reventlessdev/reventless-infra": "3.0.0-alpha.161",
24
+ "@reventlessdev/reventless-core": "3.0.0-alpha.260",
26
25
  "@reventlessdev/reventless-interop": "3.0.0-alpha.35",
27
26
  "@reventlessdev/reventless-postgres": "3.0.0-alpha.124",
27
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.161",
28
28
  "@reventlessdev/reventless-spec": "3.0.0-alpha.133",
29
- "@reventlessdev/reventless-core": "3.0.0-alpha.260"
29
+ "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.19"
30
30
  },
31
31
  "devDependencies": {
32
32
  "rescript": "12.3.0",
@@ -349,32 +349,35 @@ let isRuntimeProvided = (specifier: string, ~pkgName: string): bool =>
349
349
  }
350
350
  )
351
351
 
352
- // Memo for isFrameworkPackage — one require.resolve per package name per deploy.
353
- let frameworkPackageCache: dict<bool> = Dict.make()
354
-
355
- /**
356
- * Whether the framework's own module resolution reaches this package: the
357
- * deploy-time stand-in for "the Lambda layer already carries it", since the
358
- * layer is built from reventless-aws's dependency closure.
359
- *
360
- * Deliberately framework-rooted only — no Pulumi-project fallback. The question
361
- * here is the opposite of `resolvePackageRoot`'s: a package the *project*
362
- * resolves but the framework does not is precisely a user package, which is
363
- * what the closure walk below must treat as a starting point rather than as
364
- * something the runtime provides.
365
- */
366
- let isFrameworkPackage = (pkgName: string): bool =>
367
- switch frameworkPackageCache->Dict.get(pkgName) {
368
- | Some(known) => known
369
- | None =>
370
- let known = try {
371
- let _ = localRequire->NodeModule.requireResolve(pkgName ++ "/package.json")
372
- true
373
- } catch {
374
- | _ => false
352
+ /** The framework's own package root: this module ships inside reventless-aws, so
353
+ the nearest package.json walking up from it is reventless-aws's own. */
354
+ let frameworkPackageRoot: string = {
355
+ let filePath = newURL(%raw("import.meta.url"))->pathname
356
+ let dirRef = ref(NodePath.dirname(filePath))
357
+ let foundRef: ref<option<string>> = ref(None)
358
+ while dirRef.contents != "/" && foundRef.contents->Option.isNone {
359
+ if NodeFs.existsSync(NodePath.join([dirRef.contents, "package.json"])) {
360
+ foundRef := Some(dirRef.contents)
361
+ } else {
362
+ dirRef := NodePath.dirname(dirRef.contents)
375
363
  }
376
- frameworkPackageCache->Dict.set(pkgName, known)
377
- known
364
+ }
365
+ foundRef.contents->Option.getOr(NodePath.dirname(filePath))
366
+ }
367
+
368
+ /** The `dependencies` a package declares — the edges the layer's own npm install
369
+ follows. devDependencies are deliberately not read: the layer is a production
370
+ install, so a dev-only package is not in it. */
371
+ let declaredDependencies = (packageRoot: string): array<string> =>
372
+ try {
373
+ NodeFs.readFileSync(NodePath.join([packageRoot, "package.json"]))
374
+ ->JSON.parseOrThrow
375
+ ->JSON.Decode.object
376
+ ->Option.flatMap(o => o->Dict.get("dependencies"))
377
+ ->Option.flatMap(JSON.Decode.object)
378
+ ->Option.mapOr([], Dict.keysToArray)
379
+ } catch {
380
+ | _ => []
378
381
  }
379
382
 
380
383
  // The distinct bare packages a package's own bundled files import, memoised per
@@ -427,6 +430,76 @@ let resolvePackageRootFrom = (~fromRoot: string, pkgName: string): option<string
427
430
  | _ => None
428
431
  }
429
432
 
433
+ /** Packages the layer carries that reventless-aws does not declare, mirroring
434
+ `includeModules` in the layer builder's config. `@rescript/runtime` is the
435
+ case that matters: it is a transitive of `rescript`, which the layer excludes
436
+ as a build tool, so the production closure misses it while the layer holds
437
+ it. Without this the walk would add 19 MB to every archive.
438
+
439
+ The layer's `excludeModules` are deliberately NOT mirrored. Those are
440
+ deploy-time-only packages (the @pulumi bindings, esbuild, the SSH stack); the
441
+ walk is import-driven, so a package no runtime file imports never comes up. */
442
+ let layerIncludedModules = Set.fromArray(["@rescript/runtime"])
443
+
444
+ // Memo for the framework's dependency closure — one walk per deploy.
445
+ let frameworkClosureCache: ref<option<dict<bool>>> = ref(None)
446
+
447
+ /**
448
+ * The packages the Lambda layer carries: reventless-aws and its transitive
449
+ * `dependencies`, which is exactly what the layer builder npm-installs.
450
+ *
451
+ * Membership rather than resolvability, and the distinction is the whole point.
452
+ * Resolvability asked whether `require.resolve` reaches a package from this
453
+ * module — but Node resolution walks *up*, so in a consumer repo with a hoisted
454
+ * `node_modules` every package at the workspace root answers yes, including the
455
+ * plugin packages and their traits. That made this predicate true for packages
456
+ * the layer has never held: they were dropped from the archive as
457
+ * layer-provided, and the plugin package itself was filtered out of the closure
458
+ * walk's starting points, so its imports were never walked at all. The failure
459
+ * lands at the first command as `Cannot find package`, on a green deploy.
460
+ *
461
+ * Declared edges are followed from the declaring package's own directory, so a
462
+ * pnpm layout resolves the same way the runtime would. A dependency that is
463
+ * declared but not installed still counts as the layer's: the archive is not the
464
+ * place to compensate for an incomplete install.
465
+ */
466
+ let frameworkPackages = (): dict<bool> =>
467
+ switch frameworkClosureCache.contents {
468
+ | Some(closure) => closure
469
+ | None =>
470
+ let closure = Dict.make()
471
+ let pending = [frameworkPackageRoot]
472
+ let next = ref(0)
473
+ while next.contents < pending->Array.length {
474
+ let root = pending->Array.getUnsafe(next.contents)
475
+ next := next.contents + 1
476
+ declaredDependencies(root)->Array.forEach(dep =>
477
+ if !(closure->Dict.has(dep)) {
478
+ closure->Dict.set(dep, true)
479
+ switch resolvePackageRootFrom(~fromRoot=root, dep) {
480
+ | Some(depRoot) => pending->Array.push(depRoot)
481
+ | None => ()
482
+ }
483
+ }
484
+ )
485
+ }
486
+ frameworkClosureCache := Some(closure)
487
+ closure
488
+ }
489
+
490
+ /**
491
+ * Whether the deployed runtime already carries this package, so the archive
492
+ * need not: reventless-aws itself, or anything in its dependency closure.
493
+ *
494
+ * A package the *project* resolves but this does not is precisely a user
495
+ * package, which the closure walk below must treat as a starting point rather
496
+ * than as something the runtime provides.
497
+ */
498
+ let isFrameworkPackage = (pkgName: string): bool =>
499
+ pkgName == "@reventlessdev/reventless-aws" ||
500
+ layerIncludedModules->Set.has(pkgName) ||
501
+ frameworkPackages()->Dict.has(pkgName)
502
+
430
503
  /**
431
504
  * Carry every package a bundled *user* package imports into the archive, and
432
505
  * every package those import in turn.
@@ -232,22 +232,28 @@ function isRuntimeProvided(specifier, pkgName) {
232
232
  }
233
233
  }
234
234
 
235
- let frameworkPackageCache = {};
235
+ let filePath = new URL(import.meta.url).pathname;
236
236
 
237
- function isFrameworkPackage(pkgName) {
238
- let known = frameworkPackageCache[pkgName];
239
- if (known !== undefined) {
240
- return known;
237
+ let dirRef = Nodepath.dirname(filePath);
238
+
239
+ let foundRef;
240
+
241
+ while (dirRef !== "/" && Stdlib_Option.isNone(foundRef)) {
242
+ if (Nodefs.existsSync(Nodepath.join(dirRef, "package.json"))) {
243
+ foundRef = dirRef;
244
+ } else {
245
+ dirRef = Nodepath.dirname(dirRef);
241
246
  }
242
- let known$1;
247
+ };
248
+
249
+ let frameworkPackageRoot = Stdlib_Option.getOr(foundRef, Nodepath.dirname(filePath));
250
+
251
+ function declaredDependencies(packageRoot) {
243
252
  try {
244
- localRequire.resolve(pkgName + "/package.json");
245
- known$1 = true;
253
+ return Stdlib_Option.mapOr(Stdlib_Option.flatMap(Stdlib_Option.flatMap(Stdlib_JSON.Decode.object(JSON.parse(Nodefs.readFileSync(Nodepath.join(packageRoot, "package.json"), "utf8"))), o => o["dependencies"]), Stdlib_JSON.Decode.object), [], prim => Object.keys(prim));
246
254
  } catch (exn) {
247
- known$1 = false;
255
+ return [];
248
256
  }
249
- frameworkPackageCache[pkgName] = known$1;
250
- return known$1;
251
257
  }
252
258
 
253
259
  let importedPackagesCache = {};
@@ -289,6 +295,47 @@ function resolvePackageRootFrom(fromRoot, pkgName) {
289
295
  }
290
296
  }
291
297
 
298
+ let layerIncludedModules = new Set(["@rescript/runtime"]);
299
+
300
+ let frameworkClosureCache = {
301
+ contents: undefined
302
+ };
303
+
304
+ function frameworkPackages() {
305
+ let closure = frameworkClosureCache.contents;
306
+ if (closure !== undefined) {
307
+ return closure;
308
+ }
309
+ let closure$1 = {};
310
+ let pending = [frameworkPackageRoot];
311
+ let next = 0;
312
+ while (next < pending.length) {
313
+ let root = pending[next];
314
+ next = next + 1 | 0;
315
+ declaredDependencies(root).forEach(dep => {
316
+ if (dep in closure$1) {
317
+ return;
318
+ }
319
+ closure$1[dep] = true;
320
+ let depRoot = resolvePackageRootFrom(root, dep);
321
+ if (depRoot !== undefined) {
322
+ pending.push(depRoot);
323
+ return;
324
+ }
325
+ });
326
+ };
327
+ frameworkClosureCache.contents = closure$1;
328
+ return closure$1;
329
+ }
330
+
331
+ function isFrameworkPackage(pkgName) {
332
+ if (pkgName === "@reventlessdev/reventless-aws" || layerIncludedModules.has(pkgName)) {
333
+ return true;
334
+ } else {
335
+ return pkgName in frameworkPackages();
336
+ }
337
+ }
338
+
292
339
  function addImportedPackageClosure(packageDirs) {
293
340
  let pending = Object.entries(packageDirs).filter(param => !isFrameworkPackage(param[0]));
294
341
  let next = 0;
@@ -410,11 +457,15 @@ export {
410
457
  isBareSpecifier,
411
458
  nodeBuiltins,
412
459
  isRuntimeProvided,
413
- frameworkPackageCache,
414
- isFrameworkPackage,
460
+ frameworkPackageRoot,
461
+ declaredDependencies,
415
462
  importedPackagesCache,
416
463
  importedPackages,
417
464
  resolvePackageRootFrom,
465
+ layerIncludedModules,
466
+ frameworkClosureCache,
467
+ frameworkPackages,
468
+ isFrameworkPackage,
418
469
  addImportedPackageClosure,
419
470
  assertRuntimeExtensionImportsResolvable,
420
471
  buildCodeArchive,
@@ -100,6 +100,32 @@ describe("Util_Bundle — bundled user packages carry their imports", () => {
100
100
  expect(bundled->Array.includes("@fixture/never-installed"))->toBe(false)
101
101
  })
102
102
 
103
+ testSync("a package the framework merely resolves is still bundled", () => {
104
+ // The layer carries reventless-aws's dependency closure and nothing else.
105
+ // A consumer with a hoisted node_modules puts every package at the root,
106
+ // where the framework's own resolution reaches it by walking up — so
107
+ // resolvability said "the layer has it" about packages the layer has never
108
+ // held, and they were dropped from the archive. `sury` above is genuinely a
109
+ // framework dependency; these are not, and must ride along.
110
+ expect(Util_Bundle.isFrameworkPackage("@fixture/closure-trait"))->toBe(false)
111
+ // A real, published, @reventlessdev-scoped package that is not a dependency
112
+ // of reventless-aws: scope is not what decides this.
113
+ expect(Util_Bundle.isFrameworkPackage("@reventlessdev/reventless-local"))->toBe(false)
114
+ })
115
+
116
+ testSync("the framework's own closure is recognised", () => {
117
+ expect(Util_Bundle.isFrameworkPackage("@reventlessdev/reventless-aws"))->toBe(true)
118
+ expect(Util_Bundle.isFrameworkPackage("sury"))->toBe(true)
119
+ })
120
+
121
+ testSync("a package the layer adds outside that closure counts as the layer's", () => {
122
+ // @rescript/runtime is a transitive of `rescript`, which the layer excludes
123
+ // as a build tool, so it is absent from the production closure while the
124
+ // layer carries it explicitly. Every compiled ReScript file imports it, and
125
+ // it is 19 MB — treating it as a user package puts that in every archive.
126
+ expect(Util_Bundle.isFrameworkPackage("@rescript/runtime"))->toBe(true)
127
+ })
128
+
103
129
  testSync("framework packages are starting points, not walked", () => {
104
130
  // Walking reventless-aws would reach its deploy-time-only imports (the
105
131
  // @pulumi bindings) and add tens of megabytes to every archive.
@@ -106,6 +106,17 @@ globalThis.describe("Util_Bundle — bundled user packages carry their imports",
106
106
  let bundled = Object.keys((Util_Bundle$ReventlessAws.addImportedPackageClosure(packageDirs), packageDirs));
107
107
  globalThis.expect(bundled.includes("@fixture/never-installed")).toBe(false);
108
108
  });
109
+ globalThis.test("a package the framework merely resolves is still bundled", () => {
110
+ globalThis.expect(Util_Bundle$ReventlessAws.isFrameworkPackage("@fixture/closure-trait")).toBe(false);
111
+ globalThis.expect(Util_Bundle$ReventlessAws.isFrameworkPackage("@reventlessdev/reventless-local")).toBe(false);
112
+ });
113
+ globalThis.test("the framework's own closure is recognised", () => {
114
+ globalThis.expect(Util_Bundle$ReventlessAws.isFrameworkPackage("@reventlessdev/reventless-aws")).toBe(true);
115
+ globalThis.expect(Util_Bundle$ReventlessAws.isFrameworkPackage("sury")).toBe(true);
116
+ });
117
+ globalThis.test("a package the layer adds outside that closure counts as the layer's", () => {
118
+ globalThis.expect(Util_Bundle$ReventlessAws.isFrameworkPackage("@rescript/runtime")).toBe(true);
119
+ });
109
120
  globalThis.test("framework packages are starting points, not walked", () => {
110
121
  let packageDirs = Object.fromEntries([[
111
122
  "@reventlessdev/reventless-aws",