@reventlessdev/reventless-local 3.0.0-alpha.300 → 3.0.0-alpha.302
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 +30 -0
- package/package.json +11 -11
- package/src/EmitCapabilities.res +12 -5
- package/src/EmitCapabilities.res.mjs +13 -1
- package/src/Platform.res +6 -7
- package/src/Platform.res.mjs +10 -12
- package/src/adapter/Auth/{UserStore.res → AccountStore.res} +17 -17
- package/src/adapter/Auth/{UserStore.res.mjs → AccountStore.res.mjs} +11 -11
- package/src/adapter/Auth/LocalAuth.res +5 -5
- package/src/adapter/LocalCapabilities.res +2 -2
- package/src/adapter/LocalCapabilities.res.mjs +1 -1
- package/src/adapter/ObjectStore/ObjectStoreStorage_FileSystem.res +1 -1
- package/src/capability/{Capability_IdentityProvider_UserStore.res → Capability_IdentityProvider_AccountStore.res} +1 -1
- package/tests/EmitCapabilitiesTest.res +6 -0
- package/tests/EmitCapabilitiesTest.res.mjs +6 -0
- package/tests/adapter/{LocalAuthUserStoreTest.res → LocalAuthAccountStoreTest.res} +48 -48
- package/tests/adapter/{LocalAuthUserStoreTest.res.mjs → LocalAuthAccountStoreTest.res.mjs} +44 -44
- package/tests/capability/{Capability_IdentityProvider_UserStoreTest.res → Capability_IdentityProvider_AccountStoreTest.res} +15 -13
- package/tests/capability/{Capability_IdentityProvider_UserStoreTest.res.mjs → Capability_IdentityProvider_AccountStoreTest.res.mjs} +15 -15
- package/tests/capability/LocalCapabilitiesTest.res +2 -2
- package/tests/capability/LocalCapabilitiesTest.res.mjs +3 -3
- package/tests/components/dcb/DcbRepeatTest.res +291 -0
- package/tests/components/dcb/DcbRepeatTest.res.mjs +420 -0
- package/tests/components/readmodel/DcbReadModelTypoFailFastTest.res +71 -15
- package/tests/components/readmodel/DcbReadModelTypoFailFastTest.res.mjs +119 -14
- package/tests/plugin/Plugin_Fixtures.res +1 -3
- package/tests/plugin/Plugin_Fixtures.res.mjs +1 -2
- /package/src/capability/{Capability_IdentityProvider_UserStore.res.mjs → Capability_IdentityProvider_AccountStore.res.mjs} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,36 @@
|
|
|
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.302 (2026-10-29)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **local:** emit-capabilities writes an empty manifest for a plugin with no parts yet ([56586a6](https://github.com/ReventlessDev/reventless-core/commit/56586a687aa4658e2357c836878b44050b424d41))
|
|
11
|
+
* refactor!: drop `dcbSources`, the cross-plugin DCB subscription nobody declared ([cf3b66d](https://github.com/ReventlessDev/reventless-core/commit/cf3b66d8c1b5d87673197b64eaee174598bb8253))
|
|
12
|
+
* refactor!: a read model hears another plugin only through an Extension and a slice ([15c7cae](https://github.com/ReventlessDev/reventless-core/commit/15c7cae214d5aa04404779fcd87a8429aad1965c))
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **core:** a handler drops a command it has already handled ([605862b](https://github.com/ReventlessDev/reventless-core/commit/605862bba765b1501eb19d08f3924bf619159e21))
|
|
16
|
+
|
|
17
|
+
### BREAKING CHANGES
|
|
18
|
+
|
|
19
|
+
* `Reventless.Plugin.extensionDefinition` has no `dcbSources`
|
|
20
|
+
field, and `Platform_PluginExtensions.dcbSources` is gone from the GraphQL
|
|
21
|
+
schema. A query that selects it fails as a whole; drop the selection.
|
|
22
|
+
* `ReventlessInfra.Extension.T.make` and the core
|
|
23
|
+
`Extension_Builder.make` no longer take `~readModelNamesForSourceName` and
|
|
24
|
+
`~publishToReadModels`. Both arguments did nothing; drop them at the call site.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# 3.0.0-alpha.301 (2026-10-25)
|
|
29
|
+
|
|
30
|
+
**Note:** Version bump only for package @reventlessdev/reventless-local
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
6
36
|
# 3.0.0-alpha.300 (2026-10-25)
|
|
7
37
|
|
|
8
38
|
**Note:** Version bump only for package @reventlessdev/reventless-local
|
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.302",
|
|
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,19 +34,19 @@
|
|
|
34
34
|
"graphql-yoga": "^5.21.0",
|
|
35
35
|
"sury": "11.0.0",
|
|
36
36
|
"ws": "^8.18.0",
|
|
37
|
-
"@reventlessdev/rescript-jest": "1.0.0-alpha.13",
|
|
38
|
-
"@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.29",
|
|
39
37
|
"@reventlessdev/rescript-effect": "1.0.0-alpha.1",
|
|
40
|
-
"@reventlessdev/rescript-
|
|
38
|
+
"@reventlessdev/rescript-graphql-yoga": "1.0.0-alpha.29",
|
|
39
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.13",
|
|
41
40
|
"@reventlessdev/rescript-node": "2.0.0-alpha.23",
|
|
41
|
+
"@reventlessdev/rescript-mcp-sdk": "1.0.0-alpha.24",
|
|
42
42
|
"@reventlessdev/rescript-pulumi-pulumi": "3.0.0-alpha.0",
|
|
43
|
-
"@reventlessdev/reventless-core": "3.0.0-alpha.
|
|
44
|
-
"@reventlessdev/reventless-gwt": "1.0.0-alpha.
|
|
45
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.
|
|
46
|
-
"@reventlessdev/reventless-postgres": "3.0.0-alpha.
|
|
47
|
-
"@reventlessdev/reventless-
|
|
48
|
-
"@reventlessdev/reventless-
|
|
49
|
-
"@reventlessdev/reventless-
|
|
43
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.311",
|
|
44
|
+
"@reventlessdev/reventless-gwt": "1.0.0-alpha.259",
|
|
45
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.208",
|
|
46
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.175",
|
|
47
|
+
"@reventlessdev/reventless-seed": "1.0.0-alpha.38",
|
|
48
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.179",
|
|
49
|
+
"@reventlessdev/reventless-graphql-server": "1.0.0-alpha.159"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"rescript": "12.3.1",
|
package/src/EmitCapabilities.res
CHANGED
|
@@ -25,9 +25,19 @@
|
|
|
25
25
|
|
|
26
26
|
type platform
|
|
27
27
|
type localPlatformExports = {"Make": unit => platform}
|
|
28
|
-
|
|
28
|
+
// A plugin with no parts yet has no structure: `generate-plugin` leaves `pluginStructure` out of
|
|
29
|
+
// its composition root.
|
|
30
|
+
type builtPlugin = {"pluginStructure": Nullable.t<Reventless.Plugin.pluginStructure>}
|
|
29
31
|
type compositionExports = {"Make": platform => builtPlugin}
|
|
30
32
|
|
|
33
|
+
/** The manifest's text for a plugin's structure. A plugin with no parts yet has none and
|
|
34
|
+
declares nothing: its manifest says so, as one for a structure with no declarations does. */
|
|
35
|
+
let manifestText = (structure: option<Reventless.Plugin.pluginStructure>): string =>
|
|
36
|
+
switch structure {
|
|
37
|
+
| Some(structure) => Reventless.CapabilityManifest.renderForStructure(structure)
|
|
38
|
+
| None => Reventless.CapabilityManifest.render({capabilities: []})
|
|
39
|
+
}
|
|
40
|
+
|
|
31
41
|
// ── Which module holds the composition root ──────────────────────────────────
|
|
32
42
|
|
|
33
43
|
/** `Plugin.res.mjs` is what `generate-plugin` emits, so it is the default and
|
|
@@ -99,10 +109,7 @@ let main = () =>
|
|
|
99
109
|
let built = composition["Make"](platform)
|
|
100
110
|
|
|
101
111
|
let manifestPath = NodePath.join([srcDir, "capabilities.json"])
|
|
102
|
-
NodeFs.writeFileSync(
|
|
103
|
-
manifestPath,
|
|
104
|
-
Reventless.CapabilityManifest.renderForStructure(built["pluginStructure"]),
|
|
105
|
-
)
|
|
112
|
+
NodeFs.writeFileSync(manifestPath, manifestText(built["pluginStructure"]->Nullable.toOption))
|
|
106
113
|
Console.log("Generated: " ++ manifestPath)
|
|
107
114
|
|
|
108
115
|
// Applying the platform functor wires in-process infrastructure; exit
|
|
@@ -4,9 +4,20 @@ import * as Nodefs from "node:fs";
|
|
|
4
4
|
import * as Nodeurl from "node:url";
|
|
5
5
|
import * as Nodepath from "node:path";
|
|
6
6
|
import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
|
|
7
|
+
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
7
8
|
import * as CliArgs$Reventless from "@reventlessdev/reventless-spec/src/CliArgs.res.mjs";
|
|
8
9
|
import * as CapabilityManifest$Reventless from "@reventlessdev/reventless-spec/src/components/CapabilityManifest.res.mjs";
|
|
9
10
|
|
|
11
|
+
function manifestText(structure) {
|
|
12
|
+
if (structure !== undefined) {
|
|
13
|
+
return CapabilityManifest$Reventless.renderForStructure(structure);
|
|
14
|
+
} else {
|
|
15
|
+
return CapabilityManifest$Reventless.render({
|
|
16
|
+
capabilities: []
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
10
21
|
function compositionModulePath(srcDir, moduleArg) {
|
|
11
22
|
let withExtension = name => {
|
|
12
23
|
if (name.endsWith(".mjs")) {
|
|
@@ -84,13 +95,14 @@ function main() {
|
|
|
84
95
|
let composition = await import(Nodeurl.pathToFileURL(modulePath).href);
|
|
85
96
|
let built = composition.Make(platform);
|
|
86
97
|
let manifestPath = Nodepath.join(srcDir, "capabilities.json");
|
|
87
|
-
Nodefs.writeFileSync(manifestPath,
|
|
98
|
+
Nodefs.writeFileSync(manifestPath, manifestText(Primitive_option.fromNullable(built.pluginStructure)), "utf8");
|
|
88
99
|
console.log("Generated: " + manifestPath);
|
|
89
100
|
process.exit(0);
|
|
90
101
|
});
|
|
91
102
|
}
|
|
92
103
|
|
|
93
104
|
export {
|
|
105
|
+
manifestText,
|
|
94
106
|
compositionModulePath,
|
|
95
107
|
fail,
|
|
96
108
|
usage,
|
package/src/Platform.res
CHANGED
|
@@ -109,7 +109,7 @@ module MakeWithConfig = (
|
|
|
109
109
|
// What this platform provides for the roles its plugins need: every account it
|
|
110
110
|
// can sign in as, built-in and loaded alike, or the file the store will load
|
|
111
111
|
// when the servers start — after the plugins are built and checked.
|
|
112
|
-
Reventless.Role.provideGroupsFrom(
|
|
112
|
+
Reventless.Role.provideGroupsFrom(AccountStore.providedGroups)
|
|
113
113
|
// Activate Pulumi mock mode — must happen before any component creation.
|
|
114
114
|
// Idempotent, so safe to call even if TestRunner.setup() was already called.
|
|
115
115
|
let _ = TestRunner.setup()
|
|
@@ -1255,7 +1255,6 @@ module MakeWithConfig = (
|
|
|
1255
1255
|
((_, ext: ReventlessInfra.Extension.outputs)) => {
|
|
1256
1256
|
Reventless.Plugin.name: ext.name,
|
|
1257
1257
|
extensionPointName: ext.extensionPointName,
|
|
1258
|
-
dcbSources: [],
|
|
1259
1258
|
},
|
|
1260
1259
|
),
|
|
1261
1260
|
extensionProtocols: [],
|
|
@@ -1377,11 +1376,11 @@ module MakeWithConfig = (
|
|
|
1377
1376
|
// this is a no-op (start() is called inline inside makePlatform/deployPlugin).
|
|
1378
1377
|
let startServers = () => {
|
|
1379
1378
|
if Config.splitApi {
|
|
1380
|
-
// Hydrate Login store from .reventless/
|
|
1379
|
+
// Hydrate Login store from .reventless/accounts.yaml (or ~accounts/~accountsFile
|
|
1381
1380
|
// if provided programmatically) before the Domain server accepts
|
|
1382
1381
|
// requests. Unified mode does this in makePlatform; split mode defers
|
|
1383
1382
|
// server startup to here so it must do the same.
|
|
1384
|
-
|
|
1383
|
+
AccountStore.autoLoadOnce()
|
|
1385
1384
|
DomainGraphQL_Server.start(~port=domainPort, ())
|
|
1386
1385
|
DomainMCP_Server.start(~port=domainMcpPort, ())
|
|
1387
1386
|
PlatformGraphQL_Server.start(
|
|
@@ -1579,7 +1578,7 @@ module MakeWithConfig = (
|
|
|
1579
1578
|
geocoderPlaceIndex?: ReventlessInfra.Platform.geocoderIndex,
|
|
1580
1579
|
messagingSender?: ReventlessInfra.Platform.messagingSender,
|
|
1581
1580
|
// Local only, until the AWS platform has a provider to pass the same way:
|
|
1582
|
-
// `
|
|
1581
|
+
// `Capability_IdentityProvider_AccountStore.make()`, writing the accounts file
|
|
1583
1582
|
// the auth adapter loads.
|
|
1584
1583
|
identityProvider?: Reventless.IdentityProvider.t,
|
|
1585
1584
|
uploadBucket?: ReventlessInfra.Platform.objectStore,
|
|
@@ -2350,7 +2349,7 @@ module MakeWithConfig = (
|
|
|
2350
2349
|
// (domain and platform) can register their schema first.
|
|
2351
2350
|
// In unified mode, start immediately (backwards-compatible).
|
|
2352
2351
|
if !Config.splitApi {
|
|
2353
|
-
|
|
2352
|
+
AccountStore.autoLoadOnce()
|
|
2354
2353
|
DomainGraphQL_Server.start()
|
|
2355
2354
|
DomainMCP_Server.start()
|
|
2356
2355
|
}
|
|
@@ -2534,7 +2533,7 @@ module MakeWithConfig = (
|
|
|
2534
2533
|
|
|
2535
2534
|
// The role check below reads the accounts as they stand; a user store
|
|
2536
2535
|
// loaded from elsewhere after this would leave it compared with the wrong ones.
|
|
2537
|
-
|
|
2536
|
+
AccountStore.pluginChecked := true
|
|
2538
2537
|
|
|
2539
2538
|
// Set the active deploy target so resolveTargetGraphQL/MCP() and QueryDb serverRef/relayRef
|
|
2540
2539
|
// route registrations to the correct server. Mirrors AWS resolveTargetApi() pattern.
|
package/src/Platform.res.mjs
CHANGED
|
@@ -30,12 +30,12 @@ import * as AdmissionCheck$Reventless from "@reventlessdev/reventless-spec/src/t
|
|
|
30
30
|
import * as Api_Naming$ReventlessCore from "@reventlessdev/reventless-core/src/components/Api/Api_Naming.res.mjs";
|
|
31
31
|
import * as CommandOutcome$Reventless from "@reventlessdev/reventless-spec/src/components/CommandOutcome.res.mjs";
|
|
32
32
|
import * as PluginSpec$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/lifecycle/PluginSpec.res.mjs";
|
|
33
|
-
import * as UserStore$ReventlessLocal from "./adapter/Auth/UserStore.res.mjs";
|
|
34
33
|
import * as TestRunner$ReventlessLocal from "./test/TestRunner.res.mjs";
|
|
35
34
|
import * as UiFragments$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateView/UiFragments.res.mjs";
|
|
36
35
|
import * as CommandTopic$ReventlessCore from "@reventlessdev/reventless-core/src/components/CommandTopic/CommandTopic.res.mjs";
|
|
37
36
|
import * as EffectLogger$ReventlessCore from "@reventlessdev/reventless-core/src/util/EffectLogger.res.mjs";
|
|
38
37
|
import * as ShellConfig$ReventlessLocal from "./ShellConfig.res.mjs";
|
|
38
|
+
import * as AccountStore$ReventlessLocal from "./adapter/Auth/AccountStore.res.mjs";
|
|
39
39
|
import * as BackendState$ReventlessLocal from "./adapter/BackendState.res.mjs";
|
|
40
40
|
import * as ComponentType$ReventlessCore from "@reventlessdev/reventless-core/src/ComponentType.res.mjs";
|
|
41
41
|
import * as SqliteDriver$ReventlessLocal from "./adapter/SqliteDriver.res.mjs";
|
|
@@ -146,7 +146,7 @@ function decodeUiFragmentRegistryEventEnvelope(eventJson) {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
function MakeWithConfig(Config) {
|
|
149
|
-
Role$Reventless.provideGroupsFrom(
|
|
149
|
+
Role$Reventless.provideGroupsFrom(AccountStore$ReventlessLocal.providedGroups);
|
|
150
150
|
TestRunner$ReventlessLocal.setup();
|
|
151
151
|
let match = Config.backend;
|
|
152
152
|
if (typeof match !== "object") {
|
|
@@ -1063,8 +1063,7 @@ function MakeWithConfig(Config) {
|
|
|
1063
1063
|
let ext = param[1];
|
|
1064
1064
|
return {
|
|
1065
1065
|
name: ext.name,
|
|
1066
|
-
extensionPointName: ext.extensionPointName
|
|
1067
|
-
dcbSources: []
|
|
1066
|
+
extensionPointName: ext.extensionPointName
|
|
1068
1067
|
};
|
|
1069
1068
|
}),
|
|
1070
1069
|
eventCollector: param[2].name,
|
|
@@ -1151,7 +1150,7 @@ function MakeWithConfig(Config) {
|
|
|
1151
1150
|
};
|
|
1152
1151
|
let startServers = () => {
|
|
1153
1152
|
if (Config.splitApi) {
|
|
1154
|
-
|
|
1153
|
+
AccountStore$ReventlessLocal.autoLoadOnce();
|
|
1155
1154
|
DomainGraphQL_Server$ReventlessLocal.start(domainPort, undefined, undefined);
|
|
1156
1155
|
DomainMCP_Server$ReventlessLocal.start(domainMcpPort, undefined);
|
|
1157
1156
|
PlatformGraphQL_Server$ReventlessLocal.start(platformPort, Auth_GraphqlContext$ReventlessLocal.buildAuthContext, undefined);
|
|
@@ -1694,7 +1693,7 @@ function MakeWithConfig(Config) {
|
|
|
1694
1693
|
}
|
|
1695
1694
|
currentDeployTarget.contents = "Domain";
|
|
1696
1695
|
if (!Config.splitApi) {
|
|
1697
|
-
|
|
1696
|
+
AccountStore$ReventlessLocal.autoLoadOnce();
|
|
1698
1697
|
DomainGraphQL_Server$ReventlessLocal.start(undefined, undefined, undefined);
|
|
1699
1698
|
return DomainMCP_Server$ReventlessLocal.start(undefined, undefined);
|
|
1700
1699
|
}
|
|
@@ -1795,7 +1794,7 @@ function MakeWithConfig(Config) {
|
|
|
1795
1794
|
let tmp;
|
|
1796
1795
|
tmp = apiTarget === "Domain" ? "Domain" : "Platform";
|
|
1797
1796
|
log.info("Platform", undefined, `deployPlugin target=` + tmp);
|
|
1798
|
-
|
|
1797
|
+
AccountStore$ReventlessLocal.pluginChecked.contents = true;
|
|
1799
1798
|
currentDeployTarget.contents = apiTarget;
|
|
1800
1799
|
StateViewSliceMaker.QueryDbResolvers.serverRef.contents = resolveTargetGraphQL();
|
|
1801
1800
|
let tmp$1;
|
|
@@ -1948,7 +1947,7 @@ function MakeWithConfig(Config) {
|
|
|
1948
1947
|
|
|
1949
1948
|
function Make($star) {
|
|
1950
1949
|
let backend = Backend$ReventlessLocal.fromEnv();
|
|
1951
|
-
Role$Reventless.provideGroupsFrom(
|
|
1950
|
+
Role$Reventless.provideGroupsFrom(AccountStore$ReventlessLocal.providedGroups);
|
|
1952
1951
|
TestRunner$ReventlessLocal.setup();
|
|
1953
1952
|
if (typeof backend !== "object") {
|
|
1954
1953
|
BackendState$ReventlessLocal.setMemory();
|
|
@@ -2858,8 +2857,7 @@ function Make($star) {
|
|
|
2858
2857
|
let ext = param[1];
|
|
2859
2858
|
return {
|
|
2860
2859
|
name: ext.name,
|
|
2861
|
-
extensionPointName: ext.extensionPointName
|
|
2862
|
-
dcbSources: []
|
|
2860
|
+
extensionPointName: ext.extensionPointName
|
|
2863
2861
|
};
|
|
2864
2862
|
}),
|
|
2865
2863
|
eventCollector: param[2].name,
|
|
@@ -2945,7 +2943,7 @@ function Make($star) {
|
|
|
2945
2943
|
});
|
|
2946
2944
|
};
|
|
2947
2945
|
let startServers = () => {
|
|
2948
|
-
|
|
2946
|
+
AccountStore$ReventlessLocal.autoLoadOnce();
|
|
2949
2947
|
DomainGraphQL_Server$ReventlessLocal.start(domainPort, undefined, undefined);
|
|
2950
2948
|
DomainMCP_Server$ReventlessLocal.start(domainMcpPort, undefined);
|
|
2951
2949
|
PlatformGraphQL_Server$ReventlessLocal.start(platformPort, Auth_GraphqlContext$ReventlessLocal.buildAuthContext, undefined);
|
|
@@ -3576,7 +3574,7 @@ function Make($star) {
|
|
|
3576
3574
|
let tmp;
|
|
3577
3575
|
tmp = apiTarget === "Domain" ? "Domain" : "Platform";
|
|
3578
3576
|
log.info("Platform", undefined, `deployPlugin target=` + tmp);
|
|
3579
|
-
|
|
3577
|
+
AccountStore$ReventlessLocal.pluginChecked.contents = true;
|
|
3580
3578
|
currentDeployTarget.contents = apiTarget;
|
|
3581
3579
|
StateViewSliceMaker.QueryDbResolvers.serverRef.contents = resolveTargetGraphQL();
|
|
3582
3580
|
let tmp$1;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// In-memory user store loader. Hydrates `LocalAuth.Login.store` from
|
|
2
2
|
// three resolution paths (first match wins):
|
|
3
|
-
// 1. `~
|
|
4
|
-
// 2. `~
|
|
5
|
-
// 3. `.reventless/
|
|
3
|
+
// 1. `~accounts` arg — explicit list passed by the caller
|
|
4
|
+
// 2. `~accountsFile` arg — explicit YAML path
|
|
5
|
+
// 3. `.reventless/accounts.yaml` (relative to process.cwd()) — auto-discovery
|
|
6
6
|
//
|
|
7
7
|
// All three are silent on absence: when no path is provided and no default
|
|
8
8
|
// file exists, the store stays empty and `LocalAuth.Login.issue` rejects
|
|
9
9
|
// every credential. A one-line stdout hint is printed so developers know to
|
|
10
|
-
// either set the file or pass `~
|
|
10
|
+
// either set the file or pass `~accounts` programmatically.
|
|
11
11
|
|
|
12
12
|
open Reventless
|
|
13
13
|
|
|
@@ -45,13 +45,13 @@ let _defaultPath = AccountsManifest.defaultPath
|
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* Hydrates the Login store. Returns the resolution path actually used
|
|
48
|
-
* (`
|
|
48
|
+
* (`InlineAccounts`, `AccountsFile(path)`, `DefaultFile(path)`, or `Empty`).
|
|
49
49
|
* Errors from a *resolved* path are returned as `Error`; absence of the
|
|
50
50
|
* auto-discovered default file is silent.
|
|
51
51
|
*/
|
|
52
52
|
type resolution =
|
|
53
|
-
|
|
|
54
|
-
|
|
|
53
|
+
| InlineAccounts
|
|
54
|
+
| AccountsFile(string)
|
|
55
55
|
| DefaultFile(string)
|
|
56
56
|
| Empty
|
|
57
57
|
|
|
@@ -79,29 +79,29 @@ makes it from). Never the working directory once another source was loaded.
|
|
|
79
79
|
let providedGroups = (): array<string> =>
|
|
80
80
|
LocalAuth.knownGroups()->Array.concat(resolved.contents ? [] : AccountsManifest.declaredGroups())
|
|
81
81
|
|
|
82
|
-
let load = (~
|
|
82
|
+
let load = (~accounts: option<array<entry>>=?, ~accountsFile: option<string>=?, ()): result<
|
|
83
83
|
resolution,
|
|
84
84
|
string,
|
|
85
85
|
> => {
|
|
86
86
|
// The default file is what the check already read; any other source arrives
|
|
87
87
|
// after it, and the check would have compared the plugins with other accounts.
|
|
88
|
-
if pluginChecked.contents && (
|
|
88
|
+
if pluginChecked.contents && (accounts->Option.isSome || accountsFile->Option.isSome) {
|
|
89
89
|
JsError.throwWithMessage(
|
|
90
|
-
"
|
|
90
|
+
"AccountStore.load(~accounts / ~accountsFile) after a plugin was built — the check that every role it needs has a group read other accounts. Call AccountStore.load before deploying plugins.",
|
|
91
91
|
)
|
|
92
92
|
}
|
|
93
93
|
resolved := true
|
|
94
|
-
switch
|
|
94
|
+
switch accounts {
|
|
95
95
|
| Some(entries) =>
|
|
96
96
|
_registerEntries(entries)
|
|
97
|
-
Ok(
|
|
97
|
+
Ok(InlineAccounts)
|
|
98
98
|
| None =>
|
|
99
|
-
switch
|
|
99
|
+
switch accountsFile {
|
|
100
100
|
| Some(path) =>
|
|
101
101
|
parseFile(path)->Result.map(entries => {
|
|
102
102
|
_registerEntries(entries)
|
|
103
103
|
fromFile := entries->Array.map(e => e.username)
|
|
104
|
-
|
|
104
|
+
AccountsFile(path)
|
|
105
105
|
})
|
|
106
106
|
| None =>
|
|
107
107
|
let defaultPath = _defaultPath()
|
|
@@ -113,7 +113,7 @@ let load = (~users: option<array<entry>>=?, ~usersFile: option<string>=?, ()): r
|
|
|
113
113
|
})
|
|
114
114
|
} else {
|
|
115
115
|
Console.log(
|
|
116
|
-
"[LocalAuth] no
|
|
116
|
+
"[LocalAuth] no accounts configured — POST /__inmemory/login will reject all. " ++ "Provide ~accounts / ~accountsFile or create .reventless/accounts.yaml relative to cwd.",
|
|
117
117
|
)
|
|
118
118
|
Ok(Empty)
|
|
119
119
|
}
|
|
@@ -212,14 +212,14 @@ let watch = (path: string): unit => {
|
|
|
212
212
|
|
|
213
213
|
/**
|
|
214
214
|
* Idempotent auto-discovery. Platform.startServers calls this before
|
|
215
|
-
* `DomainGraphQL_Server.start`, so a `.reventless/
|
|
215
|
+
* `DomainGraphQL_Server.start`, so a `.reventless/accounts.yaml` in cwd
|
|
216
216
|
* hydrates the Login store automatically, and follows the file from then on.
|
|
217
217
|
* No-op once `load` has been called from anywhere.
|
|
218
218
|
*/
|
|
219
219
|
let autoLoadOnce = (): unit =>
|
|
220
220
|
if !resolved.contents {
|
|
221
221
|
switch load() {
|
|
222
|
-
| Ok(DefaultFile(path)) | Ok(
|
|
222
|
+
| Ok(DefaultFile(path)) | Ok(AccountsFile(path)) => watch(path)
|
|
223
223
|
| _ => ()
|
|
224
224
|
}
|
|
225
225
|
}
|
|
@@ -49,25 +49,25 @@ function providedGroups() {
|
|
|
49
49
|
return LocalAuth$ReventlessLocal.knownGroups().concat(resolved.contents ? [] : AccountsManifest$Reventless.declaredGroups());
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
function load(
|
|
53
|
-
if (pluginChecked.contents && (Stdlib_Option.isSome(
|
|
54
|
-
Stdlib_JsError.throwWithMessage("
|
|
52
|
+
function load(accounts, accountsFile, param) {
|
|
53
|
+
if (pluginChecked.contents && (Stdlib_Option.isSome(accounts) || Stdlib_Option.isSome(accountsFile))) {
|
|
54
|
+
Stdlib_JsError.throwWithMessage("AccountStore.load(~accounts / ~accountsFile) after a plugin was built — the check that every role it needs has a group read other accounts. Call AccountStore.load before deploying plugins.");
|
|
55
55
|
}
|
|
56
56
|
resolved.contents = true;
|
|
57
|
-
if (
|
|
58
|
-
_registerEntries(
|
|
57
|
+
if (accounts !== undefined) {
|
|
58
|
+
_registerEntries(accounts);
|
|
59
59
|
return {
|
|
60
60
|
TAG: "Ok",
|
|
61
|
-
_0: "
|
|
61
|
+
_0: "InlineAccounts"
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
|
-
if (
|
|
65
|
-
return Stdlib_Result.map(AccountsManifest$Reventless.parseFile(
|
|
64
|
+
if (accountsFile !== undefined) {
|
|
65
|
+
return Stdlib_Result.map(AccountsManifest$Reventless.parseFile(accountsFile), entries => {
|
|
66
66
|
_registerEntries(entries);
|
|
67
67
|
fromFile.contents = entries.map(e => e.username);
|
|
68
68
|
return {
|
|
69
|
-
TAG: "
|
|
70
|
-
_0:
|
|
69
|
+
TAG: "AccountsFile",
|
|
70
|
+
_0: accountsFile
|
|
71
71
|
};
|
|
72
72
|
});
|
|
73
73
|
}
|
|
@@ -82,7 +82,7 @@ function load(users, usersFile, param) {
|
|
|
82
82
|
};
|
|
83
83
|
});
|
|
84
84
|
} else {
|
|
85
|
-
console.log("[LocalAuth] no
|
|
85
|
+
console.log("[LocalAuth] no accounts configured — POST /__inmemory/login will reject all. Provide ~accounts / ~accountsFile or create .reventless/accounts.yaml relative to cwd.");
|
|
86
86
|
return {
|
|
87
87
|
TAG: "Ok",
|
|
88
88
|
_0: "Empty"
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// none of the above → Authenticated(defaultUser) (dev default)
|
|
9
9
|
//
|
|
10
10
|
// Stage A4 adds the `Login` submodule (token issuance + verification) and
|
|
11
|
-
// the companion `
|
|
11
|
+
// the companion `AccountStore` module (YAML hydration).
|
|
12
12
|
|
|
13
13
|
open Reventless
|
|
14
14
|
|
|
@@ -33,7 +33,7 @@ let adminUser = (): Identity.t => {
|
|
|
33
33
|
// ── User registry ─────────────────────────────────────────────────────────
|
|
34
34
|
//
|
|
35
35
|
// A `dict<Identity.t>` keyed by `username`. A4 hydrates this from
|
|
36
|
-
// `.reventless/
|
|
36
|
+
// `.reventless/accounts.yaml`; tests inject via `registerUser`. The built-in
|
|
37
37
|
// `admin` is answered on lookup, for the reason above, unless one is registered.
|
|
38
38
|
|
|
39
39
|
let initialUsers = () => Dict.fromArray([("user", defaultUser)])
|
|
@@ -86,7 +86,7 @@ let getHeader = (headers: dict<string>, name: string): option<string> =>
|
|
|
86
86
|
module Login = {
|
|
87
87
|
type credentials = {password: string, identity: Identity.t}
|
|
88
88
|
|
|
89
|
-
// Hydrated by `
|
|
89
|
+
// Hydrated by `AccountStore` at platform start; tests can inject directly.
|
|
90
90
|
let store: ref<dict<credentials>> = ref(Dict.make())
|
|
91
91
|
|
|
92
92
|
let setCredentials = (~username: string, ~password: string, ~identity: Identity.t): unit => {
|
|
@@ -127,13 +127,13 @@ module Login = {
|
|
|
127
127
|
// that has no `.reventless/` to write to.
|
|
128
128
|
//
|
|
129
129
|
// The directory is used but never created. Its presence is what distinguishes
|
|
130
|
-
// a platform someone develops against — `pnpm run setup` puts `
|
|
130
|
+
// a platform someone develops against — `pnpm run setup` puts `accounts.yaml`
|
|
131
131
|
// there, so anything with logins to preserve has one — from a unit test, and
|
|
132
132
|
// an auth module that made directories would leave one in every test's cwd.
|
|
133
133
|
//
|
|
134
134
|
// Local dev only, as the whole module is: these tokens are not security-grade
|
|
135
135
|
// and AWS never sees them. The file sits in a gitignored directory beside
|
|
136
|
-
// `
|
|
136
|
+
// `accounts.yaml`, which already holds plaintext dev passwords.
|
|
137
137
|
|
|
138
138
|
let _secretFileName = "token-secret"
|
|
139
139
|
let _secretDir = (): string => NodePath.join([NodeProcess.cwd(), ".reventless"])
|
|
@@ -31,7 +31,7 @@ let registerMessagingSender = (sender: string) => messagingSenderRef := sender
|
|
|
31
31
|
let identityProviderRef = ref(Reventless.Capabilities.none.identityProvider)
|
|
32
32
|
|
|
33
33
|
/** Provision the identity provider. Called by `makePlatform` from the handle a
|
|
34
|
-
root declared, `
|
|
34
|
+
root declared, `Capability_IdentityProvider_AccountStore.make()`. */
|
|
35
35
|
let registerIdentityProvider = (provider: Reventless.IdentityProvider.t) =>
|
|
36
36
|
identityProviderRef := provider
|
|
37
37
|
|
|
@@ -70,7 +70,7 @@ let unmetWarning = (~declared: array<(string, string)>): option<string> =>
|
|
|
70
70
|
| Messaging =>
|
|
71
71
|
`\n Messaging is \`Capability_Messaging_Log.make()\`, passed to the platform as ` ++ `\`~messagingSender\`.`
|
|
72
72
|
| IdentityProvider =>
|
|
73
|
-
`\n IdentityProvider is \`
|
|
73
|
+
`\n IdentityProvider is \`Capability_IdentityProvider_AccountStore.make()\`, passed to the ` ++ `platform as \`~identityProvider\`.`
|
|
74
74
|
// Never unmet here: the local platform always provisions one.
|
|
75
75
|
| Stash => ``
|
|
76
76
|
}
|
|
@@ -43,7 +43,7 @@ function unmetWarning(declared) {
|
|
|
43
43
|
case "Messaging" :
|
|
44
44
|
return `\n Messaging is \`Capability_Messaging_Log.make()\`, passed to the platform as \`~messagingSender\`.`;
|
|
45
45
|
case "IdentityProvider" :
|
|
46
|
-
return `\n IdentityProvider is \`
|
|
46
|
+
return `\n IdentityProvider is \`Capability_IdentityProvider_AccountStore.make()\`, passed to the platform as \`~identityProvider\`.`;
|
|
47
47
|
case "Stash" :
|
|
48
48
|
return ``;
|
|
49
49
|
}
|
|
@@ -205,7 +205,7 @@ let deleteOffloadAll = (~root: string): int => {
|
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
// Removes the three trees, leaving the rest of the root (the database file, the
|
|
208
|
-
// dev `
|
|
208
|
+
// dev `accounts.yaml`) untouched.
|
|
209
209
|
let reset = (~root: string): unit =>
|
|
210
210
|
[objectsDir(~root), metaDir(~root), offloadDir(~root)]->Array.forEach(dir =>
|
|
211
211
|
try NodeFs.rmSync(dir, {recursive: true, force: true}) catch {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// `~identityProvider` in `~hostUiBundle`, and the platform puts it on the
|
|
5
5
|
// capability record every slice's `translate` receives.
|
|
6
6
|
//
|
|
7
|
-
// Its store is `.reventless/
|
|
7
|
+
// Its store is `.reventless/accounts.yaml`, the file the in-memory auth adapter
|
|
8
8
|
// already loads at startup, so an account made here survives a restart with no
|
|
9
9
|
// second store to keep in step. Every write goes to the file and to the live
|
|
10
10
|
// `LocalAuth.Login` store together, so the running process and the next one
|
|
@@ -19,6 +19,12 @@ describe("EmitCapabilities.parseArgs", () => {
|
|
|
19
19
|
)
|
|
20
20
|
})
|
|
21
21
|
|
|
22
|
+
describe("EmitCapabilities.manifestText", () => {
|
|
23
|
+
testSync("a plugin with no parts yet declares nothing", () =>
|
|
24
|
+
expect(EmitCapabilities.manifestText(None))->toBe(`{\n "capabilities": []\n}\n`)
|
|
25
|
+
)
|
|
26
|
+
})
|
|
27
|
+
|
|
22
28
|
describe("emit-capabilities on the command line", () => {
|
|
23
29
|
module CliArgs = Reventless.CliArgs
|
|
24
30
|
let usage = EmitCapabilities.usage->String.trim
|
|
@@ -30,6 +30,12 @@ globalThis.describe("EmitCapabilities.parseArgs", () => {
|
|
|
30
30
|
});
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
+
globalThis.describe("EmitCapabilities.manifestText", () => {
|
|
34
|
+
globalThis.test("a plugin with no parts yet declares nothing", () => {
|
|
35
|
+
globalThis.expect(EmitCapabilities$ReventlessLocal.manifestText(undefined)).toBe(`{\n "capabilities": []\n}\n`);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
33
39
|
globalThis.describe("emit-capabilities on the command line", () => {
|
|
34
40
|
let usage = EmitCapabilities$ReventlessLocal.usage.trim();
|
|
35
41
|
globalThis.test("--help anywhere prints the usage to stdout and exits 0", async () => {
|