@reventlessdev/reventless-local 3.0.0-alpha.187 → 3.0.0-alpha.188

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,26 @@
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.188 (2026-08-01)
7
+
8
+ * feat(aws,core,spec)!: qualify store prefixes by plugin and refuse name collisions ([da39405](https://github.com/ReventlessDev/reventless-core/commit/da394059d9f8f981bf7adc79e2c1ce2b429e0267))
9
+ * feat(core,aws)!: name task buckets for their plugin and task ([05203cb](https://github.com/ReventlessDev/reventless-core/commit/05203cb3b0b019401a6f32cf78109f397b6799d5))
10
+
11
+ ### BREAKING CHANGES
12
+
13
+ * objects minted under the old bare prefix are orphaned and their
14
+ refs unresolvable. The migration is `seed:reset` for the owning plugin, then
15
+ re-seed. Legacy-prefix grandfathering was considered and deliberately dropped —
16
+ it would have added a permanent prefix SET across the deploy argument, both store
17
+ configs, the presign IAM fan-out, the release scope check and the stack output,
18
+ to spare a disposable stack one wipe.
19
+ * a bucket name change is a replace. TaskBucket_S3 now sets
20
+ forceDestroy on disposable stacks (Util_StoreLayout.protectionFor), so pr-*
21
+ stacks recreate cleanly; protected stacks must have the task bucket drained by
22
+ hand before the first deploy after this, or the replace fails BucketNotEmpty.
23
+
24
+
25
+
6
26
  # 3.0.0-alpha.187 (2026-08-01)
7
27
 
8
28
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reventlessdev/reventless-local",
3
- "version": "3.0.0-alpha.187",
3
+ "version": "3.0.0-alpha.188",
4
4
  "description": "Local platform for Reventless (in-memory or SQLite backend, for development and testing without AWS)",
5
5
  "license": "Apache-2.0",
6
6
  "bin": {
@@ -34,18 +34,18 @@
34
34
  "graphql-yoga": "^5.21.0",
35
35
  "sury": "11.0.0-alpha.4",
36
36
  "ws": "^8.18.0",
37
- "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
38
- "@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.26",
39
37
  "@reventlessdev/rescript-jest": "1.0.0-alpha.10",
40
- "@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.21",
38
+ "@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.26",
39
+ "@reventlessdev/rescript-effect": "0.1.0-alpha.32",
41
40
  "@reventlessdev/rescript-node": "2.0.0-alpha.0",
41
+ "@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.21",
42
42
  "@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
43
- "@reventlessdev/reventless-core": "3.0.0-alpha.199",
44
- "@reventlessdev/reventless-graphql-server": "1.0.0-alpha.47",
45
- "@reventlessdev/reventless-infra": "3.0.0-alpha.116",
46
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.63",
47
- "@reventlessdev/reventless-gwt": "1.0.0-alpha.146",
48
- "@reventlessdev/reventless-spec": "3.0.0-alpha.91"
43
+ "@reventlessdev/reventless-core": "3.0.0-alpha.200",
44
+ "@reventlessdev/reventless-graphql-server": "1.0.0-alpha.48",
45
+ "@reventlessdev/reventless-gwt": "1.0.0-alpha.147",
46
+ "@reventlessdev/reventless-postgres": "3.0.0-alpha.64",
47
+ "@reventlessdev/reventless-spec": "3.0.0-alpha.92",
48
+ "@reventlessdev/reventless-infra": "3.0.0-alpha.117"
49
49
  },
50
50
  "devDependencies": {
51
51
  "rescript": "12.3.0",
package/src/Platform.res CHANGED
@@ -865,6 +865,40 @@ module MakeWithConfig = (
865
865
  }
866
866
  })
867
867
  })
868
+
869
+ // Two plugins declaring one store name is refused by the deployed platform
870
+ // before it provisions anything. Local provisions no stores, so nothing here
871
+ // breaks — but this is the only place a developer meets the rule while the
872
+ // fix is still free: once a store has deployed, its minted refs are in an
873
+ // append-only event log and renaming it strands them. Hence a warning at
874
+ // composition, and the authoritative refusal at deploy.
875
+ let declared =
876
+ pluginStructuresStore.contents
877
+ ->Dict.valuesToArray
878
+ ->Array.flatMap(structure =>
879
+ structure.requiredStores
880
+ ->Option.getOr([])
881
+ ->Array.map(qualified => {
882
+ ReventlessCore.StorePrefixCollision.qualified,
883
+ // Local provisions no stores, so it has no prefix of its own — it
884
+ // restates the deployed rule (`{plugin}/{store}`) so the warning it
885
+ // gives matches the refusal a deploy would give. Reporting a collision
886
+ // the deployed platform does not have would be worse than silence.
887
+ prefix: switch qualified->String.indexOfOpt(".") {
888
+ | Some(i) =>
889
+ qualified->String.slice(~start=0, ~end=i) ++
890
+ "/" ++
891
+ qualified->String.slice(~start=i + 1, ~end=qualified->String.length)
892
+ | None => qualified
893
+ },
894
+ })
895
+ )
896
+ ReventlessCore.StorePrefixCollision.collisionsFor(~stores=declared)->Array.forEach(c =>
897
+ log.warn(
898
+ ~comp="Platform:plugins",
899
+ ReventlessCore.StorePrefixCollision.collisionMessage(c),
900
+ )
901
+ )
868
902
  }
869
903
 
870
904
  // Bus keys for the admin Plugin aggregate (name-keyed). ComponentType.toName maps
@@ -55,6 +55,7 @@ import * as PlatformMCP_Server$ReventlessLocal from "./adapter/PlatformMCP_Serve
55
55
  import * as Auth_GraphqlContext$ReventlessLocal from "./adapter/Auth/Auth_GraphqlContext.res.mjs";
56
56
  import * as PgProjectionCatchup$ReventlessLocal from "./adapter/PgProjectionCatchup.res.mjs";
57
57
  import * as PluginsReadModelSpec$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginsReadModelSpec.res.mjs";
58
+ import * as StorePrefixCollision$ReventlessCore from "@reventlessdev/reventless-core/src/util/StorePrefixCollision.res.mjs";
58
59
  import * as DomainGraphQL_Server$ReventlessLocal from "./adapter/DomainGraphQL_Server.res.mjs";
59
60
  import * as EventPublish_Callback$ReventlessCore from "@reventlessdev/reventless-core/src/components/EventLog/EventPublish_Callback.res.mjs";
60
61
  import * as LocalUploadResolvers$ReventlessLocal from "./adapter/LocalUploadResolvers.res.mjs";
@@ -824,6 +825,14 @@ function MakeWithConfig(Config) {
824
825
  }
825
826
  });
826
827
  });
828
+ let declared = Object.values(pluginStructuresStore.contents).flatMap(structure => Stdlib_Option.getOr(structure.requiredStores, []).map(qualified => {
829
+ let i = Stdlib_String.indexOfOpt(qualified, ".");
830
+ return {
831
+ qualified: qualified,
832
+ prefix: i !== undefined ? qualified.slice(0, i) + "/" + qualified.slice(i + 1 | 0, qualified.length) : qualified
833
+ };
834
+ }));
835
+ StorePrefixCollision$ReventlessCore.collisionsFor(declared).forEach(c => log.warn("Platform:plugins", undefined, StorePrefixCollision$ReventlessCore.collisionMessage(c)));
827
836
  };
828
837
  let pluginCmdTopicKey = PluginSpec$ReventlessCore.name + "AggrCmdTopic";
829
838
  let pluginEventTopicKey = PluginSpec$ReventlessCore.name + "AggrEventTopic";
@@ -2500,6 +2509,14 @@ function Make($star) {
2500
2509
  }
2501
2510
  });
2502
2511
  });
2512
+ let declared = Object.values(pluginStructuresStore.contents).flatMap(structure => Stdlib_Option.getOr(structure.requiredStores, []).map(qualified => {
2513
+ let i = Stdlib_String.indexOfOpt(qualified, ".");
2514
+ return {
2515
+ qualified: qualified,
2516
+ prefix: i !== undefined ? qualified.slice(0, i) + "/" + qualified.slice(i + 1 | 0, qualified.length) : qualified
2517
+ };
2518
+ }));
2519
+ StorePrefixCollision$ReventlessCore.collisionsFor(declared).forEach(c => log.warn("Platform:plugins", undefined, StorePrefixCollision$ReventlessCore.collisionMessage(c)));
2503
2520
  };
2504
2521
  let pluginCmdTopicKey = PluginSpec$ReventlessCore.name + "AggrCmdTopic";
2505
2522
  let pluginEventTopicKey = PluginSpec$ReventlessCore.name + "AggrEventTopic";
@@ -90,9 +90,12 @@ describe("Task_Builder.Make:", () => {
90
90
  let bucketNames = outputs.bucketNames->Option.getUnsafe
91
91
  let idOutput = bucketNames->Dict.get("Reports")->Option.getUnsafe
92
92
  let id = await idOutput->TestRunner.resolve
93
- // In-memory bucket id = resource name: taskName ++ pascalCase(bucketName) ++ "Bucket"
94
- // = "OneBucketTask" ++ "Reports" ++ "Bucket". The lookup key stays "Reports".
95
- expect(id)->toBe("OneBucketTaskReportsBucket")
93
+ // In-memory bucket id = the bucket's resource name, which is kebab-cased
94
+ // and plugin-qualified because S3 lowercases a bucket name and a PascalCase
95
+ // one collapses into a run-on. No plugin segment here: the task is built
96
+ // outside any plugin, so there is no ambient plugin to qualify with.
97
+ // The runtime lookup key is a separate string and stays "Reports".
98
+ expect(id)->toBe("one-bucket-task-reports")
96
99
  })
97
100
  })
98
101
  })
@@ -38,7 +38,7 @@ globalThis.describe("Task_Builder.Make:", () => {
38
38
  let bucketNames = outputs.bucketNames;
39
39
  let idOutput = bucketNames["Reports"];
40
40
  let id = await TestRunner$ReventlessLocal.resolve(idOutput);
41
- globalThis.expect(id).toBe("OneBucketTaskReportsBucket");
41
+ globalThis.expect(id).toBe("one-bucket-task-reports");
42
42
  });
43
43
  });
44
44
  });