@samebase/alchemy-convex 0.8.0 → 0.10.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 CHANGED
@@ -91,6 +91,55 @@ Effect.gen(function* () {
91
91
  State always holds `rootDir`, `"."` included, so a reader of the state finds the app directory of
92
92
  each deployment.
93
93
 
94
+ ### Resources associated with an installed component
95
+
96
+ Keep the component's native Alchemy resource declarations. Connect their namespace to the installed
97
+ component name in the consuming app's stack. The name comes from the app's `convex.config.ts`:
98
+ `app.use(mail, { name: "mail" })` installs the component as `"mail"`.
99
+
100
+ ```ts
101
+ import { bindComponent } from "@samebase/alchemy-convex";
102
+ import * as Effect from "effect/Effect";
103
+
104
+ // `production` is this app's Convex.Deployment. EmailInbox creates its resources under "Mail".
105
+ Effect.gen(function* () {
106
+ const inbox = yield* EmailInbox("Mail", {
107
+ convexSiteUrl: production.siteUrl,
108
+ zone,
109
+ sendingDomain,
110
+ });
111
+ yield* bindComponent({
112
+ deployment: production,
113
+ component: "mail",
114
+ namespace: inbox.worker.Namespace,
115
+ });
116
+ });
117
+ ```
118
+
119
+ The deployment's Alchemy state row stores this entry in its native `bindings` array:
120
+
121
+ ```json
122
+ {
123
+ "sid": "convex.component:[\"Mail\"]",
124
+ "data": { "type": "component", "path": "mail", "namespace": ["Mail"] }
125
+ }
126
+ ```
127
+
128
+ Readers inspect each binding's `data`. The deployment attributes identify the Convex deployment;
129
+ the row's stack and stage identify the Alchemy scope. The live repository reader includes the
130
+ binding through its existing resource JSON. No extra resource list, state table, or subscription
131
+ is needed.
132
+
133
+ Use actual namespace ancestry when grouping resources. A slash in a resource's logical ID is not a
134
+ namespace boundary. For a nested install, join the installed names with slashes, such as
135
+ `"workspace/mail"`. An undefined namespace means the stack root. Each namespace can be assigned to
136
+ one component per deployment; a component can have multiple namespaces. A more specific child
137
+ namespace takes precedence when displaying resources.
138
+
139
+ The connection is declared metadata. It does not check that the component is installed, push code,
140
+ set environment values, or change resource deletion policies. Removing the call removes the binding
141
+ on the next apply.
142
+
94
143
  ## Credentials
95
144
 
96
145
  Credentials are a layer, never props, so they do not go into Alchemy state. `Convex.providers()`
@@ -135,11 +184,20 @@ is set must be the slug of that team, checked against its projects, and a token
135
184
  fails the run. The Convex CLI login needs the slug or the id. The first run of a team creates
136
185
  the project `alchemy-state` and pushes the store functions to its
137
186
  production deployment with the Convex CLI of the app; a later version of this package pushes a
138
- newer store when it finds an older one, and never an older one over a newer one. Each run makes a
187
+ newer store when it finds an older one. A store that is newer than the package fails the run with
188
+ both versions, so the repositories of a team upgrade the package together. Each run makes a
139
189
  deploy key of that deployment that can only run the store's internal functions, and deletes it at
140
190
  the end. Rows are in the tables `resources` and `outputs` in the dashboard, under the stack name.
141
191
  The store has no lock: run one deploy of a stage at a time, such as with a CI concurrency group.
142
- The names of the store functions are a wire contract: never renamed or removed.
192
+ The store and its readers (Samebase) are released and upgraded together.
193
+
194
+ Store version 3 supports native subscriptions. A trusted server checks repository access, then
195
+ calls internal `reader:authorize({ repository, subject })` with a deployment credential. It gives
196
+ the returned token to a separate `ConvexClient` connected to the state deployment. The client
197
+ subscribes to `reader:repository({})`; the verified token selects the repository. The token has a
198
+ five-minute lifetime and permits redacted reads only. The server checks access again on renewal.
199
+ The store uses its own native signing key and JWKS, so no extra signing secret is required.
200
+ See [the reader contract](docs/design/convex-state.md#repository-subscriptions-090).
143
201
 
144
202
  ### What the output records about a run
145
203
 
package/lib/index.d.ts CHANGED
@@ -1,14 +1,16 @@
1
- import * as Context from "effect/Context";
1
+ import { Stack } from "alchemy/Stack";
2
2
  import * as Effect from "effect/Effect";
3
+ import * as Schema from "effect/Schema";
4
+ import * as Context from "effect/Context";
3
5
  import * as Layer from "effect/Layer";
4
6
  import * as Redacted from "effect/Redacted";
5
- import * as Schema from "effect/Schema";
6
7
  import { Resource } from "alchemy";
7
8
  import * as Provider from "alchemy/Provider";
8
9
  import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
9
10
  import { createDeploymentClient, createManagementClient } from "@convex-dev/platform";
10
11
  import "effect/Scope";
11
12
  import { State, StateStoreError } from "alchemy/State";
13
+ import { NamespaceNode } from "alchemy/Namespace";
12
14
  import { InputProps } from "alchemy/Input";
13
15
  import { components } from "@convex-dev/platform/managementApi";
14
16
  //#region src/Credentials.d.ts
@@ -250,7 +252,7 @@ interface DeploymentAttributes extends DeploymentTarget {
250
252
  /** The names of the `env` variables that this resource set on the deployment, sorted. */
251
253
  readonly envNames: readonly string[];
252
254
  }
253
- export type Deployment = Resource<"Convex.Deployment", DeploymentProps, DeploymentAttributes, never, Providers>;
255
+ export type Deployment = Resource<"Convex.Deployment", DeploymentProps, DeploymentAttributes, ComponentBinding, Providers>;
254
256
  /** The props of a `Convex.Deployment` call: Alchemy inputs, with `rootDir` a plain string. */
255
257
  type DeploymentInput = DeploymentProps extends (infer P) ? P extends DeploymentProps ? InputProps<P, "rootDir"> : never : never;
256
258
  declare const AbsoluteRootDir_base: Schema.Class<AbsoluteRootDir, Schema.TaggedStruct<"AbsoluteRootDir", {
@@ -310,6 +312,26 @@ export declare class AmbiguousDeployment extends AmbiguousDeployment_base {
310
312
  get message(): string;
311
313
  }
312
314
  //#endregion
315
+ //#region src/Component.d.ts
316
+ /** A component's resources occupy this namespace in the deployment's Alchemy stack and stage. */
317
+ interface ComponentBinding {
318
+ readonly type: "component";
319
+ readonly path: string;
320
+ readonly namespace: readonly string[];
321
+ }
322
+ /**
323
+ * Associate a native namespace with an installed component. `component` is the
324
+ * installed name from the app's `convex.config.ts`; a nested install joins the
325
+ * names with slashes, such as `"workspace/mail"`. An undefined namespace
326
+ * associates the stack root. This records display metadata; it does not install
327
+ * the component, set its environment, or change removal policies.
328
+ */
329
+ export declare const bindComponent: (options: {
330
+ deployment: Deployment;
331
+ component: string;
332
+ namespace: NamespaceNode | undefined;
333
+ }) => Effect.Effect<undefined, never, Stack>;
334
+ //#endregion
313
335
  //#region src/Project.d.ts
314
336
  /** Region names the Management API accepts, from the generated schema. */
315
337
  type DeploymentRegion = components["schemas"]["RegionName"];
@@ -448,5 +470,5 @@ export declare const state: (options?: {
448
470
  readonly team?: string | number;
449
471
  }) => Layer.Layer<State, never, ChildProcessSpawner.ChildProcessSpawner>;
450
472
  //#endregion
451
- export type { DeploymentAttributes, DeploymentInput, DeploymentProps, DeploymentRegion, EnvValue, ProjectAttributes, ProjectProps, RunRecord, Source };
473
+ export type { ComponentBinding, DeploymentAttributes, DeploymentInput, DeploymentProps, DeploymentRegion, EnvValue, ProjectAttributes, ProjectProps, RunRecord, Source };
452
474
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/Credentials.ts","../src/ManagementApi.ts","../src/Providers.ts","../src/Push.ts","../src/Deployment.ts","../src/Project.ts","../src/RunRecord.ts","../src/State.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/Credentials.ts","../src/ManagementApi.ts","../src/Providers.ts","../src/Push.ts","../src/Deployment.ts","../src/Component.ts","../src/Project.ts","../src/RunRecord.ts","../src/State.ts"],"mappings":"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"}
package/lib/index.js CHANGED
@@ -1,22 +1,23 @@
1
1
  import { createRequire } from "node:module";
2
+ import { toPath } from "alchemy/FQN";
3
+ import { Stack } from "alchemy/Stack";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Schema from "effect/Schema";
2
6
  import { access, mkdtemp, readFile, realpath, rm, writeFile } from "node:fs/promises";
3
7
  import { homedir, tmpdir } from "node:os";
4
8
  import { dirname, join, posix, relative, resolve, sep, win32 } from "node:path";
5
9
  import * as Config from "effect/Config";
6
10
  import * as Context from "effect/Context";
7
- import * as Effect from "effect/Effect";
8
11
  import * as Layer from "effect/Layer";
9
12
  import * as Option from "effect/Option";
10
13
  import * as Redacted from "effect/Redacted";
11
14
  import * as Result from "effect/Result";
12
- import * as Schema from "effect/Schema";
13
15
  import { Resource } from "alchemy";
14
16
  import { AdoptPolicy, OwnedBySomeoneElse, Unowned } from "alchemy/AdoptPolicy";
15
- import { isResolved } from "alchemy/Diff";
17
+ import { diffBindings, isResolved } from "alchemy/Diff";
16
18
  import * as Provider from "alchemy/Provider";
17
19
  import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
18
20
  import { AlchemyContext } from "alchemy/AlchemyContext";
19
- import { Stack } from "alchemy/Stack";
20
21
  import { createDeploymentClient, createManagementClient } from "@convex-dev/platform";
21
22
  import * as Schedule from "effect/Schedule";
22
23
  import { createHash } from "node:crypto";
@@ -29,6 +30,31 @@ import { execFile } from "node:child_process";
29
30
  import { fileURLToPath } from "node:url";
30
31
  import * as Scope from "effect/Scope";
31
32
  import { STATE_STORE_VERSION, State, StateStoreError, encodeState, reviveState } from "alchemy/State";
33
+ //#region src/Component.ts
34
+ /**
35
+ * Associate a native namespace with an installed component. `component` is the
36
+ * installed name from the app's `convex.config.ts`; a nested install joins the
37
+ * names with slashes, such as `"workspace/mail"`. An undefined namespace
38
+ * associates the stack root. This records display metadata; it does not install
39
+ * the component, set its environment, or change removal policies.
40
+ */
41
+ const bindComponent = (options) => Effect.gen(function* () {
42
+ if (options.component === "") return yield* Effect.die(/* @__PURE__ */ new Error("The component name is empty."));
43
+ const namespace = toPath(options.namespace);
44
+ const sid = `convex.component:${JSON.stringify(namespace)}`;
45
+ const previous = (yield* Stack).bindings[options.deployment.FQN]?.find((binding) => binding.sid === sid);
46
+ if (previous !== void 0) {
47
+ const existing = Schema.decodeUnknownSync(Schema.Struct({ path: Schema.String }))(previous.data);
48
+ if (existing.path !== options.component) return yield* Effect.die(/* @__PURE__ */ new Error(`Alchemy namespace ${JSON.stringify(namespace)} is already bound to ${existing.path}.`));
49
+ return;
50
+ }
51
+ yield* options.deployment.bind(sid, {
52
+ type: "component",
53
+ path: options.component,
54
+ namespace
55
+ });
56
+ });
57
+ //#endregion
32
58
  //#region src/Credentials.ts
33
59
  var Credentials = class extends Context.Service()("@samebase/alchemy-convex/Credentials") {};
34
60
  /** Environment variable for the access token, read by {@link fromConfig}. */
@@ -898,15 +924,34 @@ const DeploymentProvider = () => Provider.effect(DeploymentResource, Effect.gen(
898
924
  "type",
899
925
  "previewName"
900
926
  ],
901
- diff: Effect.fn(function* ({ news, olds, output }) {
927
+ diff: Effect.fn(function* ({ news, olds, output, oldBindings, newBindings }) {
902
928
  if (!isResolved(news) || output === void 0) return void 0;
903
929
  yield* checkIdentity(output, news);
930
+ if (!isResolved(newBindings)) return void 0;
904
931
  const rootDir = news.rootDir ?? ".";
905
932
  if (news.source !== void 0) yield* checkSource(rootDir, news.source);
906
933
  if (olds.rootDir !== rootDir) return { action: "update" };
907
934
  const access = yield* accessFor(addressOf(news), changesEnv(news, output));
908
- if ((yield* live(access, output))?.name !== output.name) return { action: "update" };
909
- return (yield* wantedHash(news, rootDir, output.name)) === output.hash ? { action: "noop" } : { action: "update" };
935
+ const current = yield* live(access, output);
936
+ if (current?.name !== output.name) return { action: "update" };
937
+ if ((yield* wantedHash(news, rootDir, output.name)) !== output.hash) return { action: "update" };
938
+ if (diffBindings(oldBindings, newBindings).every((binding) => binding.action === "noop")) return { action: "noop" };
939
+ return {
940
+ action: "update",
941
+ stables: [
942
+ "hash",
943
+ "envNames",
944
+ ...[
945
+ "projectId",
946
+ "type",
947
+ "previewName",
948
+ "name",
949
+ "reference",
950
+ "url",
951
+ "siteUrl"
952
+ ].filter((key) => current[key] === output[key])
953
+ ]
954
+ };
910
955
  }),
911
956
  read: Effect.fn(function* ({ olds, output }) {
912
957
  if (output !== void 0) {
@@ -1555,8 +1600,18 @@ const makeFunctionApi = (url, key) => ({ call: (path, args) => post(`${url}/api/
1555
1600
  /** The value of a reply, or the Convex message, which names the function and the cause, such as a missing key action. */
1556
1601
  const valueOf = (reply) => reply.status === "success" ? Effect.succeed(reply.value) : Effect.fail(failure(reply.errorMessage.trim()));
1557
1602
  /** The functions of the store with Alchemy's encoding: the JSON text of encodeState. */
1558
- const remoteOf = (api) => {
1603
+ const remoteOf = (api, record) => {
1559
1604
  const run = (name, args) => api.call(`state:${name}`, args).pipe(Effect.flatMap(valueOf));
1605
+ const write = (name, args) => Effect.gen(function* () {
1606
+ const currentRun = record === void 0 ? void 0 : {
1607
+ ...record,
1608
+ recordedAt: new Date(yield* Clock.currentTimeMillis).toISOString()
1609
+ };
1610
+ return yield* run(name, {
1611
+ ...args,
1612
+ run: currentRun
1613
+ });
1614
+ });
1560
1615
  const strings = (name, args) => run(name, args).pipe(Effect.flatMap(decode(Schema.Array(Schema.String), name)));
1561
1616
  const optional = (name, args) => run(name, args).pipe(Effect.flatMap(decode(Schema.NullOr(Schema.String), name)));
1562
1617
  const encode = (value) => JSON.stringify(encodeState(value));
@@ -1580,13 +1635,13 @@ const remoteOf = (api) => {
1580
1635
  stack: r.stack,
1581
1636
  stage: r.stage
1582
1637
  }).pipe(Effect.map((text) => text === null ? void 0 : JSON.parse(text, reviveState))),
1583
- set: (r) => run("set", {
1638
+ set: (r) => write("set", {
1584
1639
  stack: r.stack,
1585
1640
  stage: r.stage,
1586
1641
  fqn: r.fqn,
1587
1642
  value: encode(r.value)
1588
1643
  }),
1589
- delete: (r) => run("remove", {
1644
+ delete: (r) => write("remove", {
1590
1645
  stack: r.stack,
1591
1646
  stage: r.stage,
1592
1647
  fqn: r.fqn
@@ -1595,7 +1650,7 @@ const remoteOf = (api) => {
1595
1650
  stack: r.stack,
1596
1651
  stage: r.stage
1597
1652
  }),
1598
- setOutput: (r) => run("setOutput", {
1653
+ setOutput: (r) => write("setOutput", {
1599
1654
  stack: r.stack,
1600
1655
  stage: r.stage,
1601
1656
  value: encode(r.value)
@@ -1618,7 +1673,7 @@ const storeDirectory = () => join(dirname(fileURLToPath(import.meta.url)), "..",
1618
1673
  */
1619
1674
  const pushStore = (options, deployment) => Effect.gen(function* () {
1620
1675
  const cli = yield* installedCli(join(options.store, ".."));
1621
- yield* Effect.logInfo(`Convex state: pushing the store version 2 to ${deployment.name}`);
1676
+ yield* Effect.logInfo(`Convex state: pushing the store version 3 to ${deployment.name}`);
1622
1677
  yield* withPushKey(options.api, deployment.name, PUSH_KEY_ACTIONS, (key) => push(options.spawner, key, {
1623
1678
  deployment: deployment.name,
1624
1679
  url: deployment.deploymentUrl,
@@ -1681,17 +1736,17 @@ const makeConvexState = (options) => Effect.gen(function* () {
1681
1736
  current = next;
1682
1737
  return next.call(path, args);
1683
1738
  })))) };
1684
- const version = yield* storeVersion(functions);
1685
- if (version === void 0 || version < 2) {
1739
+ let version = yield* storeVersion(functions);
1740
+ if (version === void 0 || version < 3) {
1686
1741
  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.`);
1687
- const now = yield* storeVersion(functions);
1688
- if (now === void 0 || now < 2) {
1742
+ version = yield* storeVersion(functions);
1743
+ if (version === void 0 || version < 3) {
1689
1744
  yield* pushStore(options, deployment);
1690
- const pushed = yield* storeVersion(functions);
1691
- if (pushed === void 0 || pushed < 2) return yield* failure(`${deployment.name} answers store version ${pushed ?? "none"} after the push of version 2.`);
1745
+ version = yield* storeVersion(functions);
1692
1746
  }
1693
- } else if (version > 2) yield* Effect.logInfo(`Convex state: the store of team ${team} is version ${version}, newer than version 2 of this package, and it stays.`);
1694
- const remote = remoteOf(functions);
1747
+ }
1748
+ if (version !== 3) return yield* failure(`${deployment.name} answers store version ${version ?? "none"}, and this package needs store version 3.`);
1749
+ const remote = remoteOf(functions, options.run);
1695
1750
  /** The stack's output plus the record, when the run has one and the output has a place for it. */
1696
1751
  const documentOf = (request) => Effect.gen(function* () {
1697
1752
  const { run } = options;
@@ -1759,6 +1814,6 @@ const state = (options = {}) => Layer.effect(State, Effect.gen(function* () {
1759
1814
  }).pipe(Effect.provideContext(context), Effect.orDie));
1760
1815
  }));
1761
1816
  //#endregion
1762
- export { ACCESS_TOKEN_ENV, AbsoluteRootDir, AmbiguousDeployment, AmbiguousProject, CodePushFailed, ConvexApiError, ConvexAppNotFound, ConvexCliNotFound, CreatedProjectNotFound, Credentials, CredentialsError, DEPLOY_KEY_ENV, Deployment, DeploymentIdentityChange, EnvironmentNeedsDeployKey, ManagementApi, ProductionDeploymentNotFound, Project, ProjectDeploymentChange, ProjectNotFound, ProjectTeamChange, Providers, PushTargetMismatch, RUN_RECORD_KEY, RunRecordError, STORE_PROJECT_NAME, VariablesOwnedBySomeoneElse, fromConfig, fromValues, parseDeployOutput, providers, runRecord, state, withRunRecord };
1817
+ export { ACCESS_TOKEN_ENV, AbsoluteRootDir, AmbiguousDeployment, AmbiguousProject, CodePushFailed, ConvexApiError, ConvexAppNotFound, ConvexCliNotFound, CreatedProjectNotFound, Credentials, CredentialsError, DEPLOY_KEY_ENV, Deployment, DeploymentIdentityChange, EnvironmentNeedsDeployKey, ManagementApi, ProductionDeploymentNotFound, Project, ProjectDeploymentChange, ProjectNotFound, ProjectTeamChange, Providers, PushTargetMismatch, RUN_RECORD_KEY, RunRecordError, STORE_PROJECT_NAME, VariablesOwnedBySomeoneElse, bindComponent, fromConfig, fromValues, parseDeployOutput, providers, runRecord, state, withRunRecord };
1763
1818
 
1764
1819
  //# sourceMappingURL=index.js.map