@samebase/alchemy-convex 0.15.0 → 0.16.0
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/README.md +7 -16
- package/lib/{HubClient-C_nvjlvW.js → HubClient-BYBV7cF5.js} +22 -47
- package/lib/{HubClient-C_nvjlvW.js.map → HubClient-BYBV7cF5.js.map} +1 -1
- package/lib/cli.js +1 -1
- package/lib/index.d.ts +6 -18
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +2 -2
- package/package.json +1 -17
- package/src/HubClient.ts +17 -15
- package/src/State.ts +24 -77
- package/src/index.ts +2 -1
- package/src/store/component/reader.ts +1 -1
- package/src/store/component/schema.ts +1 -1
- package/src/store/component/state.ts +1 -1
- package/src/store/convex/auth.config.ts +12 -2
- package/src/store/convex/convex.config.ts +1 -1
- package/src/store/convex/reader.ts +49 -3
- package/src/store/convex/state.ts +121 -21
- package/src/store/convex/version.ts +6 -10
- package/src/store/package.json +1 -1
- package/src/store/authConfig.ts +0 -17
- package/src/store/functions.ts +0 -214
package/README.md
CHANGED
|
@@ -167,7 +167,8 @@ values explicitly: `Convex.providers(Convex.fromValues({ accessToken, deployKey
|
|
|
167
167
|
## State in Convex
|
|
168
168
|
|
|
169
169
|
`state: Convex.state({ team })` keeps the Alchemy state of every stack and stage of a Convex team in
|
|
170
|
-
one project of that team, `
|
|
170
|
+
one project of that team, `alchemy-state-store`, the same name as the Cloudflare state store of
|
|
171
|
+
Alchemy, so a repository holds one secret and a stack can create
|
|
171
172
|
and destroy its own project. [docs/design/convex-state.md](docs/design/convex-state.md) records the
|
|
172
173
|
decisions and the measurements.
|
|
173
174
|
|
|
@@ -185,26 +186,16 @@ Every run needs the team token in `CONVEX_ACCESS_TOKEN`, or the Convex CLI login
|
|
|
185
186
|
team access token names its team, so `Convex.state()` needs no `team` with one; a `team` slug that
|
|
186
187
|
is set must be the slug of that team, checked against its projects, and a token of another team
|
|
187
188
|
fails the run. The Convex CLI login needs the slug or the id. The first run of a team creates
|
|
188
|
-
the project `
|
|
189
|
+
the project `alchemy-state-store` and pushes the store functions to its
|
|
189
190
|
production deployment with the Convex CLI of the app; a later version of this package pushes a
|
|
190
|
-
newer store
|
|
191
|
-
|
|
191
|
+
newer store when it finds an older one. A store that is newer than the package fails the run with
|
|
192
|
+
both versions, so the repositories of a team upgrade the package together. Each run makes a
|
|
192
193
|
deploy key of that deployment that can only run the store's internal functions, and deletes it at
|
|
193
194
|
the end. Rows are in the tables `resources` and `outputs` of the component `alchemyState` in the
|
|
194
195
|
dashboard, under the stack name.
|
|
195
196
|
The store has no lock: run one deploy of a stage at a time, such as with a CI concurrency group.
|
|
196
197
|
The store and its readers (Samebase) are released and upgraded together.
|
|
197
198
|
|
|
198
|
-
The base app of a team can serve the store in the `samebase` project, next to its own components.
|
|
199
|
-
It mounts the component from `@samebase/alchemy-convex/state/convex.config`. It exports the
|
|
200
|
-
store functions of `@samebase/alchemy-convex/state/functions`:
|
|
201
|
-
`stateFunctions(components.alchemyState, "base")` and `readerFunctions(components.alchemyState)`.
|
|
202
|
-
It exports its auth config from `@samebase/alchemy-convex/state/auth.config`. Then
|
|
203
|
-
`state:host` answers `"base"`, and `Convex.state()` never pushes over that app: a base
|
|
204
|
-
that is older than the package fails the run with `OutdatedBase`, and the base app upgrades its
|
|
205
|
-
`@samebase/alchemy-convex`. See
|
|
206
|
-
[the design note](docs/design/convex-state.md#the-stores-functions-are-exported-and-a-base-is-never-pushed-over-0140).
|
|
207
|
-
|
|
208
199
|
Store version 3 supports native subscriptions. A trusted server checks repository access, then
|
|
209
200
|
calls internal `reader:authorize({ repository, subject })` with a deployment credential. It gives
|
|
210
201
|
the returned token to a separate `ConvexClient` connected to the state deployment. The client
|
|
@@ -284,8 +275,8 @@ call.
|
|
|
284
275
|
has every action, because the command can do more than a push.
|
|
285
276
|
|
|
286
277
|
- `Convex.HubClient`: `{ team, app, redirectUri, deploymentType, clientName, softwareVersion? }`
|
|
287
|
-
registers one deployment as a client of the identity hub of the team. The hub is
|
|
288
|
-
|
|
278
|
+
registers one deployment as a client of the identity hub of the team. The hub is the production
|
|
279
|
+
deployment of the project `samebase`. The resource finds it by name and runs
|
|
289
280
|
its internal function `identityBroker:registerClient` with a short-lived key, so the stack needs no
|
|
290
281
|
other credential and no hub address. The first client of an `app` creates the app. Outputs
|
|
291
282
|
`issuer`, `clientId`, `clientSecret`, `redirectUri`, `deploymentType`, and `clientName`, which the
|
|
@@ -1510,7 +1510,7 @@ const withRunRecord = (output, record) => isPlainObject(output) ? "samebase.run"
|
|
|
1510
1510
|
//#endregion
|
|
1511
1511
|
//#region src/State.ts
|
|
1512
1512
|
/** The name of the state project in each team. */
|
|
1513
|
-
const STORE_PROJECT_NAME = "
|
|
1513
|
+
const STORE_PROJECT_NAME = "alchemy-state-store";
|
|
1514
1514
|
/**
|
|
1515
1515
|
* The actions of the key of one run: the internal queries and mutations of
|
|
1516
1516
|
* the store, nothing else. Measured: `deployment:data:view` and
|
|
@@ -1625,26 +1625,6 @@ const remoteOf = (api, record) => {
|
|
|
1625
1625
|
/** The version that the store answers, or undefined when the deployment has no `state:version`. */
|
|
1626
1626
|
const storeVersion = (api) => api.call("state:version", {}).pipe(Effect.flatMap((reply) => reply.status === "error" && MISSING_FUNCTION.test(reply.errorMessage) ? Effect.succeed(void 0) : valueOf(reply).pipe(Effect.flatMap(decode(Schema.Number, "store version")))));
|
|
1627
1627
|
/**
|
|
1628
|
-
* The app that serves the store. A store without `state:host` is a seed: it
|
|
1629
|
-
* was pushed before 0.14.0, and only the store app of this package served
|
|
1630
|
-
* those stores.
|
|
1631
|
-
*/
|
|
1632
|
-
const storeHost = (api) => api.call("state:host", {}).pipe(Effect.flatMap((reply) => reply.status === "error" && MISSING_FUNCTION.test(reply.errorMessage) ? Effect.succeed("seed") : valueOf(reply).pipe(Effect.flatMap(decode(Schema.Literals(["seed", "base"]), "store host")))));
|
|
1633
|
-
/**
|
|
1634
|
-
* The base app of the team serves an older store. Convex.state() never
|
|
1635
|
-
* pushes its store app over a base, because the push replaces every function
|
|
1636
|
-
* of the deployment and so erases the base app.
|
|
1637
|
-
*/
|
|
1638
|
-
var OutdatedBase = class extends Schema.TaggedError()("OutdatedBase", {
|
|
1639
|
-
team: Schema.Union([Schema.String, Schema.Number]),
|
|
1640
|
-
deployment: Schema.String,
|
|
1641
|
-
version: Schema.Number
|
|
1642
|
-
}) {
|
|
1643
|
-
get message() {
|
|
1644
|
-
return `The base of team ${this.team} (${this.deployment}) answers store version ${this.version}, and this package needs store version 4. Convex.state does not push over a base. Upgrade the dependency @samebase/alchemy-convex of the base app to the version of this package, and push the base app.`;
|
|
1645
|
-
}
|
|
1646
|
-
};
|
|
1647
|
-
/**
|
|
1648
1628
|
* The functions directory of the store app, `src/store/convex` of this
|
|
1649
1629
|
* package. This file is `src/State.ts` in a checkout and a chunk in `lib/`
|
|
1650
1630
|
* in the installed package, which ships `src` too, so the directory is
|
|
@@ -1700,15 +1680,15 @@ const createStoreProject = (api, teamId, team) => Effect.gen(function* () {
|
|
|
1700
1680
|
return oldest;
|
|
1701
1681
|
});
|
|
1702
1682
|
/**
|
|
1703
|
-
* The StateService over the `
|
|
1704
|
-
* created, its store
|
|
1705
|
-
*
|
|
1683
|
+
* The StateService over the `alchemy-state-store` project of the team: found or
|
|
1684
|
+
* created, its store pushed when it is missing or older, and read and
|
|
1685
|
+
* written with the keys of this run.
|
|
1706
1686
|
*/
|
|
1707
1687
|
const makeConvexState = (options) => Effect.gen(function* () {
|
|
1708
1688
|
const { api } = options;
|
|
1709
1689
|
const teamId = yield* api.resolveTeamId(options.team);
|
|
1710
1690
|
const team = options.team ?? teamId;
|
|
1711
|
-
const project = (yield* findProjectByName(api, teamId, "
|
|
1691
|
+
const project = (yield* findProjectByName(api, teamId, "alchemy-state-store")) ?? (yield* createStoreProject(api, teamId, team));
|
|
1712
1692
|
const deployment = yield* prodDeploymentOf(api, project.id);
|
|
1713
1693
|
if (deployment === void 0) return yield* new ProductionDeploymentNotFound({ projectId: project.id });
|
|
1714
1694
|
const keys = [];
|
|
@@ -1722,21 +1702,15 @@ const makeConvexState = (options) => Effect.gen(function* () {
|
|
|
1722
1702
|
current = next;
|
|
1723
1703
|
return next.call(path, args);
|
|
1724
1704
|
})))) };
|
|
1725
|
-
const needsPush = (found) => found === void 0 ? Effect.succeed(true) : found < 4 ? storeHost(functions).pipe(Effect.map((host) => host === "seed")) : Effect.succeed(false);
|
|
1726
1705
|
let version = yield* storeVersion(functions);
|
|
1727
|
-
if (
|
|
1706
|
+
if (version === void 0 || version < 4) {
|
|
1728
1707
|
if (version === void 0 && deployment.lastDeployTime) return yield* failure(`project ${STORE_PROJECT_NAME} (${project.id}) of team ${team} has code that is not the state store, and Convex.state does not push over it. Rename that project in the Convex dashboard.`);
|
|
1729
1708
|
version = yield* storeVersion(functions);
|
|
1730
|
-
if (
|
|
1709
|
+
if (version === void 0 || version < 4) {
|
|
1731
1710
|
yield* pushStore(options, deployment);
|
|
1732
1711
|
version = yield* storeVersion(functions);
|
|
1733
1712
|
}
|
|
1734
1713
|
}
|
|
1735
|
-
if (version !== void 0 && version < 4 && (yield* storeHost(functions)) === "base") return yield* new OutdatedBase({
|
|
1736
|
-
team,
|
|
1737
|
-
deployment: deployment.name,
|
|
1738
|
-
version
|
|
1739
|
-
});
|
|
1740
1714
|
if (version !== 4) return yield* failure(`${deployment.name} answers store version ${version ?? "none"}, and this package needs store version 4.`);
|
|
1741
1715
|
const remote = remoteOf(functions, options.run);
|
|
1742
1716
|
/** The stack's output plus the record, when the run has one and the output has a place for it. */
|
|
@@ -1778,12 +1752,11 @@ const makeConvexState = (options) => Effect.gen(function* () {
|
|
|
1778
1752
|
};
|
|
1779
1753
|
});
|
|
1780
1754
|
/**
|
|
1781
|
-
* Alchemy state in the `
|
|
1755
|
+
* Alchemy state in the `alchemy-state-store` project of `team`, with the team token
|
|
1782
1756
|
* in CONVEX_ACCESS_TOKEN or the Convex CLI login. A team access token names
|
|
1783
1757
|
* its team, so `team` is optional with one; the Convex CLI login needs the
|
|
1784
1758
|
* slug or the id. The first use of the run finds or creates the project and
|
|
1785
|
-
* pushes the store
|
|
1786
|
-
* base fails the run with {@link OutdatedBase}. `alchemy plan` on another
|
|
1759
|
+
* pushes the store when it is missing or older. `alchemy plan` on another
|
|
1787
1760
|
* stack never touches Convex.
|
|
1788
1761
|
*/
|
|
1789
1762
|
const state = (options = {}) => Layer.effect(State, Effect.gen(function* () {
|
|
@@ -1808,14 +1781,16 @@ const state = (options = {}) => Layer.effect(State, Effect.gen(function* () {
|
|
|
1808
1781
|
}));
|
|
1809
1782
|
//#endregion
|
|
1810
1783
|
//#region src/HubClient.ts
|
|
1784
|
+
/** The name of the project of the hub in each team. */
|
|
1785
|
+
const HUB_PROJECT_NAME = "samebase";
|
|
1811
1786
|
const HubClient = Resource("IdentityHub.Client");
|
|
1812
|
-
/** The
|
|
1787
|
+
/** The hub of the team could not do the call. The reason is the Convex message: it holds no secret. */
|
|
1813
1788
|
var HubClientFailed = class extends Schema.TaggedError()("HubClientFailed", {
|
|
1814
1789
|
team: Schema.Union([Schema.String, Schema.Number]),
|
|
1815
1790
|
reason: Schema.String
|
|
1816
1791
|
}) {
|
|
1817
1792
|
get message() {
|
|
1818
|
-
return `Convex.HubClient: the
|
|
1793
|
+
return `Convex.HubClient: the hub of team ${this.team}: ${this.reason}.`;
|
|
1819
1794
|
}
|
|
1820
1795
|
};
|
|
1821
1796
|
/** The reply of `identityBroker:registerClient`. */
|
|
@@ -1824,18 +1799,18 @@ const Registered = Schema.Struct({
|
|
|
1824
1799
|
clientId: Schema.NonEmptyString,
|
|
1825
1800
|
clientSecret: Schema.NonEmptyString
|
|
1826
1801
|
});
|
|
1827
|
-
/** Runs `use` with the function API of the
|
|
1828
|
-
const
|
|
1802
|
+
/** Runs `use` with the function API of the hub of `team`, with a key of this call only. */
|
|
1803
|
+
const withHub = (api, team, use) => Effect.gen(function* () {
|
|
1829
1804
|
const teamId = yield* api.resolveTeamId(team);
|
|
1830
|
-
const project = yield* findProjectByName(api, teamId,
|
|
1805
|
+
const project = yield* findProjectByName(api, teamId, HUB_PROJECT_NAME);
|
|
1831
1806
|
if (project === void 0) return yield* new HubClientFailed({
|
|
1832
1807
|
team,
|
|
1833
|
-
reason: `no project ${
|
|
1808
|
+
reason: `no project ${HUB_PROJECT_NAME}, which holds the hub`
|
|
1834
1809
|
});
|
|
1835
1810
|
const deployment = yield* prodDeploymentOf(api, project.id);
|
|
1836
1811
|
if (deployment === void 0) return yield* new HubClientFailed({
|
|
1837
1812
|
team,
|
|
1838
|
-
reason: `project ${
|
|
1813
|
+
reason: `project ${HUB_PROJECT_NAME} has no production deployment`
|
|
1839
1814
|
});
|
|
1840
1815
|
return yield* withPushKey(api, deployment.name, RUN_KEY_ACTIONS, (key) => use(makeFunctionApi(deployment.deploymentUrl, key)));
|
|
1841
1816
|
});
|
|
@@ -1851,7 +1826,7 @@ const runHub = (functions, team, name, args) => functions.call(`identityBroker:$
|
|
|
1851
1826
|
* Registers one client. An attempt that a lost reply repeats leaves one spare
|
|
1852
1827
|
* client on the hub, which signs nobody in.
|
|
1853
1828
|
*/
|
|
1854
|
-
const registerHubClient = (api, props) =>
|
|
1829
|
+
const registerHubClient = (api, props) => withHub(api, props.team, (functions) => runHub(functions, props.team, "registerClient", {
|
|
1855
1830
|
appId: props.app,
|
|
1856
1831
|
redirectUri: props.redirectUri,
|
|
1857
1832
|
deploymentType: props.deploymentType,
|
|
@@ -1866,7 +1841,7 @@ const registerHubClient = (api, props) => withBase(api, props.team, (functions)
|
|
|
1866
1841
|
clientId,
|
|
1867
1842
|
clientSecret: Redacted.make(clientSecret)
|
|
1868
1843
|
})));
|
|
1869
|
-
const unregister = (api, team, clientId) =>
|
|
1844
|
+
const unregister = (api, team, clientId) => withHub(api, team, (functions) => runHub(functions, team, "deleteClient", { clientId }));
|
|
1870
1845
|
/** True when the client in state serves the redirect URI and deployment type of `props`. */
|
|
1871
1846
|
const registeredFor = (output, props) => output.redirectUri === props.redirectUri && output.deploymentType === props.deploymentType;
|
|
1872
1847
|
const HubClientProvider = () => Provider.effect(HubClient, Effect.gen(function* () {
|
|
@@ -1933,6 +1908,6 @@ const registerDevelopmentClient = (options) => Effect.gen(function* () {
|
|
|
1933
1908
|
return callbackUrl;
|
|
1934
1909
|
});
|
|
1935
1910
|
//#endregion
|
|
1936
|
-
export { PushTargetMismatch as A, DEPLOY_KEY_ENV as B, DeploymentProvider as C, ConvexAppNotFound as D, CodePushFailed as E, ManagementApi as F, fromValues as H, ManagementApiLive as I, ACCESS_TOKEN_ENV as L, installedCli as M, parseDeployOutput as N, ConvexCliNotFound as O, ConvexApiError as P, Credentials as R, DeploymentIdentityChange as S, ProductionDeploymentNotFound as T, fromConfig as V, ProjectProvider as _,
|
|
1911
|
+
export { PushTargetMismatch as A, DEPLOY_KEY_ENV as B, DeploymentProvider as C, ConvexAppNotFound as D, CodePushFailed as E, ManagementApi as F, fromValues as H, ManagementApiLive as I, ACCESS_TOKEN_ENV as L, installedCli as M, parseDeployOutput as N, ConvexCliNotFound as O, ConvexApiError as P, Credentials as R, DeploymentIdentityChange as S, ProductionDeploymentNotFound as T, fromConfig as V, ProjectProvider as _, registerDevelopmentClient as a, AmbiguousDeployment as b, RUN_RECORD_KEY as c, withRunRecord as d, AmbiguousProject as f, ProjectNotFound as g, ProjectDeploymentChange as h, HubClientProvider as i, VariablesOwnedBySomeoneElse as j, EnvironmentNeedsDeployKey as k, RunRecordError as l, Project as m, HubClient as n, STORE_PROJECT_NAME as o, CreatedProjectNotFound as p, HubClientFailed as r, state as s, HUB_PROJECT_NAME as t, runRecord as u, ProjectTeamChange as v, DeploymentResource as w, Deployment as x, AbsoluteRootDir as y, CredentialsError as z };
|
|
1937
1912
|
|
|
1938
|
-
//# sourceMappingURL=HubClient-
|
|
1913
|
+
//# sourceMappingURL=HubClient-BYBV7cF5.js.map
|