@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.
Files changed (28) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/package.json +11 -11
  3. package/src/EmitCapabilities.res +12 -5
  4. package/src/EmitCapabilities.res.mjs +13 -1
  5. package/src/Platform.res +6 -7
  6. package/src/Platform.res.mjs +10 -12
  7. package/src/adapter/Auth/{UserStore.res → AccountStore.res} +17 -17
  8. package/src/adapter/Auth/{UserStore.res.mjs → AccountStore.res.mjs} +11 -11
  9. package/src/adapter/Auth/LocalAuth.res +5 -5
  10. package/src/adapter/LocalCapabilities.res +2 -2
  11. package/src/adapter/LocalCapabilities.res.mjs +1 -1
  12. package/src/adapter/ObjectStore/ObjectStoreStorage_FileSystem.res +1 -1
  13. package/src/capability/{Capability_IdentityProvider_UserStore.res → Capability_IdentityProvider_AccountStore.res} +1 -1
  14. package/tests/EmitCapabilitiesTest.res +6 -0
  15. package/tests/EmitCapabilitiesTest.res.mjs +6 -0
  16. package/tests/adapter/{LocalAuthUserStoreTest.res → LocalAuthAccountStoreTest.res} +48 -48
  17. package/tests/adapter/{LocalAuthUserStoreTest.res.mjs → LocalAuthAccountStoreTest.res.mjs} +44 -44
  18. package/tests/capability/{Capability_IdentityProvider_UserStoreTest.res → Capability_IdentityProvider_AccountStoreTest.res} +15 -13
  19. package/tests/capability/{Capability_IdentityProvider_UserStoreTest.res.mjs → Capability_IdentityProvider_AccountStoreTest.res.mjs} +15 -15
  20. package/tests/capability/LocalCapabilitiesTest.res +2 -2
  21. package/tests/capability/LocalCapabilitiesTest.res.mjs +3 -3
  22. package/tests/components/dcb/DcbRepeatTest.res +291 -0
  23. package/tests/components/dcb/DcbRepeatTest.res.mjs +420 -0
  24. package/tests/components/readmodel/DcbReadModelTypoFailFastTest.res +71 -15
  25. package/tests/components/readmodel/DcbReadModelTypoFailFastTest.res.mjs +119 -14
  26. package/tests/plugin/Plugin_Fixtures.res +1 -3
  27. package/tests/plugin/Plugin_Fixtures.res.mjs +1 -2
  28. /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.300",
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-mcp-sdk": "1.0.0-alpha.24",
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.309",
44
- "@reventlessdev/reventless-gwt": "1.0.0-alpha.257",
45
- "@reventlessdev/reventless-infra": "3.0.0-alpha.206",
46
- "@reventlessdev/reventless-postgres": "3.0.0-alpha.173",
47
- "@reventlessdev/reventless-graphql-server": "1.0.0-alpha.157",
48
- "@reventlessdev/reventless-seed": "1.0.0-alpha.37",
49
- "@reventlessdev/reventless-spec": "3.0.0-alpha.177"
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",
@@ -25,9 +25,19 @@
25
25
 
26
26
  type platform
27
27
  type localPlatformExports = {"Make": unit => platform}
28
- type builtPlugin = {"pluginStructure": Reventless.Plugin.pluginStructure}
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, CapabilityManifest$Reventless.renderForStructure(built.pluginStructure), "utf8");
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(UserStore.providedGroups)
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/users.yaml (or ~users/~usersFile
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
- UserStore.autoLoadOnce()
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
- // `Capability_IdentityProvider_UserStore.make()`, writing the accounts file
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
- UserStore.autoLoadOnce()
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
- UserStore.pluginChecked := true
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.
@@ -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(UserStore$ReventlessLocal.providedGroups);
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
- UserStore$ReventlessLocal.autoLoadOnce();
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
- UserStore$ReventlessLocal.autoLoadOnce();
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
- UserStore$ReventlessLocal.pluginChecked.contents = true;
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(UserStore$ReventlessLocal.providedGroups);
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
- UserStore$ReventlessLocal.autoLoadOnce();
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
- UserStore$ReventlessLocal.pluginChecked.contents = true;
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. `~users` arg — explicit list passed by the caller
4
- // 2. `~usersFile` arg — explicit YAML path
5
- // 3. `.reventless/users.yaml` (relative to process.cwd()) — auto-discovery
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 `~users` programmatically.
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
- * (`InlineUsers`, `UsersFile(path)`, `DefaultFile(path)`, or `Empty`).
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
- | InlineUsers
54
- | UsersFile(string)
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 = (~users: option<array<entry>>=?, ~usersFile: option<string>=?, ()): result<
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 && (users->Option.isSome || usersFile->Option.isSome) {
88
+ if pluginChecked.contents && (accounts->Option.isSome || accountsFile->Option.isSome) {
89
89
  JsError.throwWithMessage(
90
- "UserStore.load(~users / ~usersFile) after a plugin was built — the check that every role it needs has a group read other accounts. Call UserStore.load before deploying plugins.",
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 users {
94
+ switch accounts {
95
95
  | Some(entries) =>
96
96
  _registerEntries(entries)
97
- Ok(InlineUsers)
97
+ Ok(InlineAccounts)
98
98
  | None =>
99
- switch usersFile {
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
- UsersFile(path)
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 users configured — POST /__inmemory/login will reject all. " ++ "Provide ~users / ~usersFile or create .reventless/users.yaml relative to cwd.",
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/users.yaml` in cwd
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(UsersFile(path)) => watch(path)
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(users, usersFile, param) {
53
- if (pluginChecked.contents && (Stdlib_Option.isSome(users) || Stdlib_Option.isSome(usersFile))) {
54
- Stdlib_JsError.throwWithMessage("UserStore.load(~users / ~usersFile) after a plugin was built — the check that every role it needs has a group read other accounts. Call UserStore.load before deploying plugins.");
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 (users !== undefined) {
58
- _registerEntries(users);
57
+ if (accounts !== undefined) {
58
+ _registerEntries(accounts);
59
59
  return {
60
60
  TAG: "Ok",
61
- _0: "InlineUsers"
61
+ _0: "InlineAccounts"
62
62
  };
63
63
  }
64
- if (usersFile !== undefined) {
65
- return Stdlib_Result.map(AccountsManifest$Reventless.parseFile(usersFile), entries => {
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: "UsersFile",
70
- _0: usersFile
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 users configured — POST /__inmemory/login will reject all. Provide ~users / ~usersFile or create .reventless/users.yaml relative to cwd.");
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 `UserStore` module (YAML hydration).
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/users.yaml`; tests inject via `registerUser`. The built-in
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 `UserStore` at platform start; tests can inject directly.
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 `users.yaml`
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
- // `users.yaml`, which already holds plaintext dev passwords.
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, `Capability_IdentityProvider_UserStore.make()`. */
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 \`Capability_IdentityProvider_UserStore.make()\`, passed to the ` ++ `platform as \`~identityProvider\`.`
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 \`Capability_IdentityProvider_UserStore.make()\`, passed to the platform as \`~identityProvider\`.`;
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 `users.yaml`) untouched.
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/users.yaml`, the file the in-memory auth adapter
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 () => {