@reventlessdev/reventless-local 3.0.0-alpha.187 → 3.0.0-alpha.189
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 +27 -0
- package/package.json +9 -9
- package/src/Platform.res +34 -0
- package/src/Platform.res.mjs +17 -0
- package/src/adapter/DomainGraphQL_Server.res +1 -1
- package/src/adapter/LocalObjectStore.res +1 -1
- package/src/adapter/LocalUploadResolvers.res +1 -1
- package/tests/components/task/TaskTest.res +6 -3
- package/tests/components/task/TaskTest.res.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,33 @@
|
|
|
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.189 (2026-08-02)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **aws,core,spec,seed-aws:** expire uploads nobody committed a reference to ([f63e84c](https://github.com/ReventlessDev/reventless-core/commit/f63e84c1a11cc350b799a6f69a2e7427cf1ea6e9))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# 3.0.0-alpha.188 (2026-08-01)
|
|
14
|
+
|
|
15
|
+
* feat(aws,core,spec)!: qualify store prefixes by plugin and refuse name collisions ([da39405](https://github.com/ReventlessDev/reventless-core/commit/da394059d9f8f981bf7adc79e2c1ce2b429e0267))
|
|
16
|
+
* feat(core,aws)!: name task buckets for their plugin and task ([05203cb](https://github.com/ReventlessDev/reventless-core/commit/05203cb3b0b019401a6f32cf78109f397b6799d5))
|
|
17
|
+
|
|
18
|
+
### BREAKING CHANGES
|
|
19
|
+
|
|
20
|
+
* objects minted under the old bare prefix are orphaned and their
|
|
21
|
+
refs unresolvable. The migration is `seed:reset` for the owning plugin, then
|
|
22
|
+
re-seed. Legacy-prefix grandfathering was considered and deliberately dropped —
|
|
23
|
+
it would have added a permanent prefix SET across the deploy argument, both store
|
|
24
|
+
configs, the presign IAM fan-out, the release scope check and the stack output,
|
|
25
|
+
to spare a disposable stack one wipe.
|
|
26
|
+
* a bucket name change is a replace. TaskBucket_S3 now sets
|
|
27
|
+
forceDestroy on disposable stacks (Util_StoreLayout.protectionFor), so pr-*
|
|
28
|
+
stacks recreate cleanly; protected stacks must have the task bucket drained by
|
|
29
|
+
hand before the first deploy after this, or the replace fails BucketNotEmpty.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
6
33
|
# 3.0.0-alpha.187 (2026-08-01)
|
|
7
34
|
|
|
8
35
|
### Features
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-local",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.189",
|
|
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": {
|
|
@@ -37,20 +37,20 @@
|
|
|
37
37
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.32",
|
|
38
38
|
"@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.26",
|
|
39
39
|
"@reventlessdev/rescript-jest": "1.0.0-alpha.10",
|
|
40
|
-
"@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.21",
|
|
41
40
|
"@reventlessdev/rescript-node": "2.0.0-alpha.0",
|
|
41
|
+
"@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.21",
|
|
42
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.201",
|
|
42
43
|
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.18",
|
|
43
|
-
"@reventlessdev/reventless-
|
|
44
|
-
"@reventlessdev/reventless-
|
|
45
|
-
"@reventlessdev/reventless-
|
|
46
|
-
"@reventlessdev/reventless-
|
|
47
|
-
"@reventlessdev/reventless-
|
|
48
|
-
"@reventlessdev/reventless-spec": "3.0.0-alpha.91"
|
|
44
|
+
"@reventlessdev/reventless-gwt": "1.0.0-alpha.148",
|
|
45
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.118",
|
|
46
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.65",
|
|
47
|
+
"@reventlessdev/reventless-graphql-server": "1.0.0-alpha.49",
|
|
48
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.93"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"rescript": "12.3.0",
|
|
52
52
|
"sury-ppx": "11.0.0-alpha.2",
|
|
53
|
-
"@reventlessdev/reventless-ppx": "1.0.0-alpha.
|
|
53
|
+
"@reventlessdev/reventless-ppx": "1.0.0-alpha.60"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"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
|
package/src/Platform.res.mjs
CHANGED
|
@@ -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";
|
|
@@ -131,7 +131,7 @@ let handleLogout = (_req: nodeRequest, res: nodeResponse): unit => {
|
|
|
131
131
|
// PUT /{prefix}/{key} raw bytes → 200 (store)
|
|
132
132
|
// GET /{prefix}/{key} → the stored bytes
|
|
133
133
|
// Minting (`Upload_Presign`) and release (`Upload_Release`) are platform-API
|
|
134
|
-
// mutations under route B (see [docs/plans/upload-release-path.md], resolved in
|
|
134
|
+
// mutations under route B (see [docs/plans/done/upload-release-path.md], resolved in
|
|
135
135
|
// Platform.res against this same `LocalObjectStore`); the presign returns
|
|
136
136
|
// `uploadUrl == storageRef == /{prefix}/{key}`, so the byte PUT below is unchanged.
|
|
137
137
|
|
|
@@ -57,7 +57,7 @@ let put = (~key: string, ~bytes: buffer, ~contentType: string): unit =>
|
|
|
57
57
|
let get = (~key: string): option<entry> => objects->Dict.get(key)
|
|
58
58
|
|
|
59
59
|
// Remove a stored object. Idempotent — deleting an absent key is a no-op, matching
|
|
60
|
-
// the release contract (see docs/plans/upload-release-path.md, Step 3). The dev
|
|
60
|
+
// the release contract (see docs/plans/done/upload-release-path.md, Step 3). The dev
|
|
61
61
|
// store has no identities or clock, so the release resolver enforces only the
|
|
62
62
|
// *shape* of the rule (key under a served prefix), not the identity/age conditions.
|
|
63
63
|
let delete = (~key: string): unit => objects->Dict.delete(key)
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// The dev store has no identities and no clock, so release enforces only the *shape*
|
|
6
6
|
// of the rule (key under a served prefix), not the identity/age conditions AWS applies
|
|
7
7
|
// — dev parity is about the client seeing the same contract, not reproducing AWS's
|
|
8
|
-
// guarantees. See [docs/plans/upload-release-path.md] § Step 3.
|
|
8
|
+
// guarantees. See [docs/plans/done/upload-release-path.md] § Step 3.
|
|
9
9
|
|
|
10
10
|
// Mint a same-origin `/{prefix}/{uuid}/{fileName}` ref: both the PUT target and the
|
|
11
11
|
// stored value, mirroring the AWS presign ticket's shape.
|
|
@@ -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
|
|
94
|
-
//
|
|
95
|
-
|
|
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("
|
|
41
|
+
globalThis.expect(id).toBe("one-bucket-task-reports");
|
|
42
42
|
});
|
|
43
43
|
});
|
|
44
44
|
});
|