@samebase/alchemy-convex 0.4.0 → 0.5.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 +26 -2
- package/lib/index.d.ts +42 -30
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +187 -1
- package/lib/index.js.map +1 -1
- package/package.json +18 -15
- package/src/State.ts +316 -0
- package/src/component/_generated/api.ts +50 -0
- package/src/component/_generated/component.ts +92 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +169 -0
- package/src/component/convex.config.ts +5 -0
- package/src/component/schema.ts +19 -0
- package/src/component/state.ts +169 -0
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@samebase/alchemy-convex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "The Convex plane for Alchemy v2: projects, deployments, environment variables, and the push of the functions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"alchemy",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
".": {
|
|
32
32
|
"types": "./lib/index.d.ts",
|
|
33
33
|
"import": "./lib/index.js"
|
|
34
|
-
}
|
|
34
|
+
},
|
|
35
|
+
"./convex.config": "./src/component/convex.config.ts"
|
|
35
36
|
},
|
|
36
37
|
"publishConfig": {
|
|
37
38
|
"access": "public"
|
|
@@ -51,25 +52,27 @@
|
|
|
51
52
|
"@convex-dev/platform": "0.1.11"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|
|
54
|
-
"@arethetypeswrong/core": "
|
|
55
|
-
"@effect/platform-node": "4.0.
|
|
56
|
-
"@effect/vitest": "4.0.
|
|
57
|
-
"@types/node": "
|
|
58
|
-
"alchemy": "2.0.0-beta.
|
|
59
|
-
"convex": "
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
55
|
+
"@arethetypeswrong/core": "0.18.5",
|
|
56
|
+
"@effect/platform-node": "4.0.4",
|
|
57
|
+
"@effect/vitest": "4.0.4",
|
|
58
|
+
"@types/node": "24.19.2",
|
|
59
|
+
"alchemy": "2.0.0-beta.83",
|
|
60
|
+
"convex": "1.46.0",
|
|
61
|
+
"convex-test": "0.0.60",
|
|
62
|
+
"effect": "4.0.4",
|
|
63
|
+
"publint": "0.3.25",
|
|
64
|
+
"typescript": "6.0.3",
|
|
65
|
+
"vite-plus": "1.1.0",
|
|
66
|
+
"vitest": "5.0.3"
|
|
65
67
|
},
|
|
66
68
|
"peerDependencies": {
|
|
67
69
|
"@effect/platform-node": "^4.0.0",
|
|
68
|
-
"alchemy": "2.0.0-beta.
|
|
70
|
+
"alchemy": "2.0.0-beta.83",
|
|
71
|
+
"convex": "^1.46.0",
|
|
69
72
|
"effect": "^4.0.0"
|
|
70
73
|
},
|
|
71
74
|
"engines": {
|
|
72
75
|
"node": ">=22"
|
|
73
76
|
},
|
|
74
|
-
"packageManager": "pnpm@11.
|
|
77
|
+
"packageManager": "pnpm@11.28.5"
|
|
75
78
|
}
|
package/src/State.ts
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
// Alchemy state in Convex: `state: Convex.state()` in `Alchemy.Stack`.
|
|
2
|
+
//
|
|
3
|
+
// The state of every stage lives in the alchemyState component
|
|
4
|
+
// (`@samebase/alchemy-convex/convex.config`), which the app mounts and pushes
|
|
5
|
+
// to the production deployment of its project. The store calls the component
|
|
6
|
+
// functions through POST <deployment>/api/function with a component path, the
|
|
7
|
+
// call that `convex run --component` makes, with the deploy key in
|
|
8
|
+
// CONVEX_STATE_DEPLOY_KEY. Measured: a component query needs the key action
|
|
9
|
+
// `deployment:data:view`, a component mutation `deployment:data:write`, and a
|
|
10
|
+
// call without a key gets HTTP 403. See docs/design/convex-state.md.
|
|
11
|
+
//
|
|
12
|
+
// Bootstrap: Alchemy reads state before the first push, when the deployment
|
|
13
|
+
// has no alchemyState component yet. The store asks the component list of the
|
|
14
|
+
// deployment, and only a deployment without the component means empty state.
|
|
15
|
+
// The store then keeps the state of the run in memory and moves it to the
|
|
16
|
+
// component on the first write after the component is active: the first
|
|
17
|
+
// write after the push. A run that ends with its state still in memory fails
|
|
18
|
+
// at the stack output. An unmounted component keeps its rows, so it fails.
|
|
19
|
+
//
|
|
20
|
+
// The function names of the component are a wire contract. An older
|
|
21
|
+
// component must answer a newer store, so a name is never renamed or removed.
|
|
22
|
+
import * as Config from "effect/Config";
|
|
23
|
+
import * as Effect from "effect/Effect";
|
|
24
|
+
import * as Layer from "effect/Layer";
|
|
25
|
+
import * as Redacted from "effect/Redacted";
|
|
26
|
+
import * as Schedule from "effect/Schedule";
|
|
27
|
+
import * as Schema from "effect/Schema";
|
|
28
|
+
import {
|
|
29
|
+
encodeState,
|
|
30
|
+
InMemoryService,
|
|
31
|
+
type PersistedState,
|
|
32
|
+
type ReplacedResourceState,
|
|
33
|
+
reviveState,
|
|
34
|
+
State,
|
|
35
|
+
type StateService,
|
|
36
|
+
STATE_STORE_VERSION,
|
|
37
|
+
StateStoreError,
|
|
38
|
+
} from "alchemy/State";
|
|
39
|
+
import { keyTargetOf } from "./Push.ts";
|
|
40
|
+
|
|
41
|
+
/** Environment variable with the deploy key of the deployment that holds the state. */
|
|
42
|
+
export const STATE_DEPLOY_KEY_ENV = "CONVEX_STATE_DEPLOY_KEY";
|
|
43
|
+
|
|
44
|
+
/** The component path of alchemyState in the app: its default name. */
|
|
45
|
+
const COMPONENT_PATH = "alchemyState";
|
|
46
|
+
|
|
47
|
+
/** The body of POST /api/function and /api/query, with HTTP 200 or 560 for a function error. */
|
|
48
|
+
const Reply = Schema.Union([
|
|
49
|
+
Schema.Struct({ status: Schema.Literal("success"), value: Schema.Unknown }),
|
|
50
|
+
Schema.Struct({ status: Schema.Literal("error"), errorMessage: Schema.String }),
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
/** The value of `_system/frontend/components:list`: the root has path "", a removed mount is "unmounted". */
|
|
54
|
+
const Components = Schema.Array(Schema.Struct({ path: Schema.String, state: Schema.String }));
|
|
55
|
+
|
|
56
|
+
const failure = (message: string) => new StateStoreError({ message: `Convex state: ${message}` });
|
|
57
|
+
|
|
58
|
+
/** No reply, HTTP 429, or HTTP 5xx: a new attempt can succeed. Every call of the store is idempotent. */
|
|
59
|
+
const isTransient = (error: StateStoreError) =>
|
|
60
|
+
error.http !== undefined &&
|
|
61
|
+
(error.http.status === undefined || error.http.status === 429 || error.http.status >= 500);
|
|
62
|
+
|
|
63
|
+
const post = (
|
|
64
|
+
url: string,
|
|
65
|
+
headers: Record<string, string>,
|
|
66
|
+
body: unknown,
|
|
67
|
+
): Effect.Effect<unknown, StateStoreError> =>
|
|
68
|
+
Effect.tryPromise({
|
|
69
|
+
try: () =>
|
|
70
|
+
fetch(url, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
73
|
+
body: JSON.stringify(body),
|
|
74
|
+
}),
|
|
75
|
+
catch: () => new StateStoreError({ message: `Convex state: POST ${url} failed`, http: {} }),
|
|
76
|
+
}).pipe(
|
|
77
|
+
Effect.flatMap((response) =>
|
|
78
|
+
response.ok || response.status === 560
|
|
79
|
+
? Effect.tryPromise({
|
|
80
|
+
try: (): Promise<unknown> => response.json(),
|
|
81
|
+
catch: () => failure(`${url} gave no JSON`),
|
|
82
|
+
})
|
|
83
|
+
: Effect.fail(
|
|
84
|
+
new StateStoreError({
|
|
85
|
+
message: `Convex state: POST ${url} failed with HTTP ${response.status}`,
|
|
86
|
+
http: { status: response.status },
|
|
87
|
+
}),
|
|
88
|
+
),
|
|
89
|
+
),
|
|
90
|
+
Effect.retry({
|
|
91
|
+
while: isTransient,
|
|
92
|
+
schedule: Schedule.exponential("100 millis").pipe(Schedule.jittered),
|
|
93
|
+
times: 5,
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const decode =
|
|
98
|
+
<S extends Schema.Codec<unknown, unknown>>(schema: S, what: string) =>
|
|
99
|
+
(value: unknown) =>
|
|
100
|
+
Schema.decodeUnknownEffect(schema)(value).pipe(Effect.mapError(() => failure(`bad ${what}`)));
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The deployment URL of a deploy key. The Convex CLI makes the same unofficial
|
|
104
|
+
* call for every deploy key (convex 1.46 `cli/lib/deploymentSelection.js`).
|
|
105
|
+
*/
|
|
106
|
+
const urlForKey = (key: Redacted.Redacted<string>) =>
|
|
107
|
+
post(
|
|
108
|
+
"https://api.convex.dev/api/deployment/url_for_key",
|
|
109
|
+
{},
|
|
110
|
+
{ deployKey: Redacted.value(key) },
|
|
111
|
+
).pipe(Effect.flatMap(decode(Schema.String, "deployment URL")));
|
|
112
|
+
|
|
113
|
+
/** The Convex function API of one deployment, with an admin key. */
|
|
114
|
+
export interface FunctionApi {
|
|
115
|
+
/** Runs a function of the component, or a system function of the root with `componentPath` undefined. */
|
|
116
|
+
readonly call: (
|
|
117
|
+
path: string,
|
|
118
|
+
args: Record<string, string | undefined>,
|
|
119
|
+
componentPath?: string,
|
|
120
|
+
) => Effect.Effect<unknown, StateStoreError>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export const makeFunctionApi = (url: string, key: Redacted.Redacted<string>): FunctionApi => ({
|
|
124
|
+
call: (path, args, componentPath) =>
|
|
125
|
+
post(
|
|
126
|
+
// A component function answers only through /api/function. A system query
|
|
127
|
+
// of the root answers only through /api/query (measured).
|
|
128
|
+
`${url}/api/${componentPath === undefined ? "query" : "function"}`,
|
|
129
|
+
{ Authorization: `Convex ${Redacted.value(key)}` },
|
|
130
|
+
{ ...(componentPath === undefined ? {} : { componentPath }), path, args, format: "json" },
|
|
131
|
+
).pipe(
|
|
132
|
+
Effect.flatMap(decode(Reply, `reply of ${path}`)),
|
|
133
|
+
Effect.flatMap((reply) =>
|
|
134
|
+
reply.status === "success"
|
|
135
|
+
? Effect.succeed(reply.value)
|
|
136
|
+
: // The message names the function and the cause, such as a missing key
|
|
137
|
+
// action. The key is a header, and Convex does not echo arguments.
|
|
138
|
+
Effect.fail(failure(reply.errorMessage.trim())),
|
|
139
|
+
),
|
|
140
|
+
),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
/** The state of the alchemyState mount: "active", "unmounted" (its rows are kept), or undefined. */
|
|
144
|
+
const mountOf = (api: FunctionApi) =>
|
|
145
|
+
api.call("_system/frontend/components:list", {}).pipe(
|
|
146
|
+
Effect.flatMap(decode(Components, "component list")),
|
|
147
|
+
Effect.map((components) => components.find((c) => c.path === COMPONENT_PATH)?.state),
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
/** The component functions with Alchemy's encoding: the JSON text of encodeState. */
|
|
151
|
+
const remoteOf = (api: FunctionApi) => {
|
|
152
|
+
const run = (name: string, args: Record<string, string | undefined>) =>
|
|
153
|
+
api.call(`state:${name}`, args, COMPONENT_PATH);
|
|
154
|
+
const strings = (name: string, args: Record<string, string>) =>
|
|
155
|
+
run(name, args).pipe(Effect.flatMap(decode(Schema.Array(Schema.String), name)));
|
|
156
|
+
const optional = (name: string, args: Record<string, string>) =>
|
|
157
|
+
run(name, args).pipe(Effect.flatMap(decode(Schema.NullOr(Schema.String), name)));
|
|
158
|
+
const encode = (value: unknown) => JSON.stringify(encodeState(value));
|
|
159
|
+
return {
|
|
160
|
+
listStacks: () => strings("stacks", {}),
|
|
161
|
+
listStages: (stack: string) => strings("stages", { stack }),
|
|
162
|
+
list: (r: { stack: string; stage: string }) =>
|
|
163
|
+
strings("list", { stack: r.stack, stage: r.stage }),
|
|
164
|
+
// Like Alchemy's own stores, the store gives back the state that it got.
|
|
165
|
+
get: (r: { stack: string; stage: string; fqn: string }) =>
|
|
166
|
+
optional("get", { stack: r.stack, stage: r.stage, fqn: r.fqn }).pipe(
|
|
167
|
+
Effect.map((text): PersistedState | undefined =>
|
|
168
|
+
text === null ? undefined : JSON.parse(text, reviveState),
|
|
169
|
+
),
|
|
170
|
+
),
|
|
171
|
+
getReplacedResources: (r: { stack: string; stage: string }) =>
|
|
172
|
+
strings("replaced", { stack: r.stack, stage: r.stage }).pipe(
|
|
173
|
+
Effect.map((texts) =>
|
|
174
|
+
texts.map((text): ReplacedResourceState => JSON.parse(text, reviveState)),
|
|
175
|
+
),
|
|
176
|
+
),
|
|
177
|
+
getOutput: (r: { stack: string; stage: string }) =>
|
|
178
|
+
optional("getOutput", { stack: r.stack, stage: r.stage }).pipe(
|
|
179
|
+
Effect.map((text): unknown => (text === null ? undefined : JSON.parse(text, reviveState))),
|
|
180
|
+
),
|
|
181
|
+
set: (r: { stack: string; stage: string; fqn: string; value: PersistedState }) =>
|
|
182
|
+
run("set", { stack: r.stack, stage: r.stage, fqn: r.fqn, value: encode(r.value) }),
|
|
183
|
+
delete: (r: { stack: string; stage: string; fqn: string }) =>
|
|
184
|
+
run("remove", { stack: r.stack, stage: r.stage, fqn: r.fqn }),
|
|
185
|
+
deleteStack: (r: { stack: string; stage?: string }) =>
|
|
186
|
+
run("removeStack", { stack: r.stack, stage: r.stage }),
|
|
187
|
+
setOutput: (r: { stack: string; stage: string; value: unknown }) =>
|
|
188
|
+
run("setOutput", { stack: r.stack, stage: r.stage, value: encode(r.value) }),
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const unmounted = failure(
|
|
193
|
+
`the ${COMPONENT_PATH} component of the deployment is unmounted. Its rows are kept: mount @samebase/alchemy-convex/convex.config in convex/convex.config.ts again.`,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* The StateService over `api`. Without an alchemyState component at
|
|
198
|
+
* the start, the state of the run stays in memory until a write finds the
|
|
199
|
+
* component active.
|
|
200
|
+
*/
|
|
201
|
+
export const makeConvexState = (api: FunctionApi) =>
|
|
202
|
+
Effect.gen(function* () {
|
|
203
|
+
const remote = remoteOf(api);
|
|
204
|
+
const mount = yield* mountOf(api);
|
|
205
|
+
if (mount === "unmounted") return yield* unmounted;
|
|
206
|
+
let pending = mount === undefined;
|
|
207
|
+
|
|
208
|
+
// The records that InMemoryService writes to, read again by the flush.
|
|
209
|
+
const rows: Record<string, Record<string, Record<string, PersistedState>>> = {};
|
|
210
|
+
const outputs: Record<string, Record<string, unknown>> = {};
|
|
211
|
+
const memory = yield* InMemoryService(
|
|
212
|
+
// @ts-expect-error InMemoryService types the records as ResourceState, and its set stores every PersistedState.
|
|
213
|
+
rows,
|
|
214
|
+
outputs,
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
/** Moves the memory to the component once it is active. Rows that the run did not read stop it. */
|
|
218
|
+
const flush = Effect.gen(function* () {
|
|
219
|
+
const now = yield* mountOf(api);
|
|
220
|
+
if (now === undefined) return;
|
|
221
|
+
if (now !== "active") return yield* unmounted;
|
|
222
|
+
const stages = [...new Set([...Object.keys(rows), ...Object.keys(outputs)])].flatMap(
|
|
223
|
+
(stack) =>
|
|
224
|
+
[
|
|
225
|
+
...new Set([...Object.keys(rows[stack] ?? {}), ...Object.keys(outputs[stack] ?? {})]),
|
|
226
|
+
].map((stage) => ({ stack, stage })),
|
|
227
|
+
);
|
|
228
|
+
for (const { stack, stage } of stages) {
|
|
229
|
+
const found = yield* remote.list({ stack, stage });
|
|
230
|
+
if (found.length > 0) {
|
|
231
|
+
return yield* failure(
|
|
232
|
+
`${stack}/${stage} has ${found.length} rows in the deployment that this run did not read, so the run wrote none. Run it again.`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
for (const { stack, stage } of stages) {
|
|
237
|
+
for (const [fqn, value] of Object.entries(rows[stack]?.[stage] ?? {})) {
|
|
238
|
+
yield* remote.set({ stack, stage, fqn, value });
|
|
239
|
+
}
|
|
240
|
+
const byStage = outputs[stack];
|
|
241
|
+
if (byStage !== undefined && stage in byStage) {
|
|
242
|
+
yield* remote.setOutput({ stack, stage, value: byStage[stage] });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
pending = false;
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const read = <A>(
|
|
249
|
+
inMemory: Effect.Effect<A, StateStoreError>,
|
|
250
|
+
inConvex: Effect.Effect<A, StateStoreError>,
|
|
251
|
+
) => Effect.suspend(() => (pending ? inMemory : inConvex));
|
|
252
|
+
const write = <A>(
|
|
253
|
+
inMemory: Effect.Effect<A, StateStoreError>,
|
|
254
|
+
inConvex: Effect.Effect<unknown, StateStoreError>,
|
|
255
|
+
result: A,
|
|
256
|
+
) =>
|
|
257
|
+
Effect.suspend(() =>
|
|
258
|
+
pending ? Effect.tap(inMemory, () => flush) : Effect.as(inConvex, result),
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const service: StateService = {
|
|
262
|
+
id: "convex",
|
|
263
|
+
getVersion: () => Effect.succeed(STATE_STORE_VERSION),
|
|
264
|
+
listStacks: () => read(memory.listStacks(), remote.listStacks()),
|
|
265
|
+
listStages: (stack) => read(memory.listStages(stack), remote.listStages(stack)),
|
|
266
|
+
list: (request) => read(memory.list(request), remote.list(request)),
|
|
267
|
+
get: (request) => read(memory.get(request), remote.get(request)),
|
|
268
|
+
getReplacedResources: (request) =>
|
|
269
|
+
read(memory.getReplacedResources(request), remote.getReplacedResources(request)),
|
|
270
|
+
getOutput: (request) => read(memory.getOutput(request), remote.getOutput(request)),
|
|
271
|
+
set: (request) => write(memory.set(request), remote.set(request), request.value),
|
|
272
|
+
delete: (request) => write(memory.delete(request), remote.delete(request), undefined),
|
|
273
|
+
deleteStack: (request) =>
|
|
274
|
+
write(memory.deleteStack(request), remote.deleteStack(request), undefined),
|
|
275
|
+
// Alchemy writes the stack output last, so state still in memory here is lost.
|
|
276
|
+
setOutput: (request) =>
|
|
277
|
+
write(memory.setOutput(request), remote.setOutput(request), request.value).pipe(
|
|
278
|
+
Effect.tap(() =>
|
|
279
|
+
pending
|
|
280
|
+
? Effect.fail(
|
|
281
|
+
failure(
|
|
282
|
+
`the deployment of ${STATE_DEPLOY_KEY_ENV} has no ${COMPONENT_PATH} component at the end of the run, so this run saved no state. Mount @samebase/alchemy-convex/convex.config in convex/convex.config.ts.`,
|
|
283
|
+
),
|
|
284
|
+
)
|
|
285
|
+
: Effect.void,
|
|
286
|
+
),
|
|
287
|
+
),
|
|
288
|
+
};
|
|
289
|
+
return service;
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Alchemy state in the alchemyState component of the deployment that the
|
|
294
|
+
* deploy key in CONVEX_STATE_DEPLOY_KEY names: the production deployment of
|
|
295
|
+
* the project. The key needs the actions `deployment:data:view` and
|
|
296
|
+
* `deployment:data:write`.
|
|
297
|
+
*/
|
|
298
|
+
export const state = (): Layer.Layer<State> =>
|
|
299
|
+
Layer.effect(
|
|
300
|
+
State,
|
|
301
|
+
Effect.gen(function* () {
|
|
302
|
+
// The stack's ConfigProvider, for the read of the key on first use.
|
|
303
|
+
const context = yield* Effect.context<never>();
|
|
304
|
+
return yield* Effect.cached(
|
|
305
|
+
Effect.gen(function* () {
|
|
306
|
+
const key = yield* Config.Redacted(STATE_DEPLOY_KEY_ENV);
|
|
307
|
+
if (keyTargetOf(Redacted.value(key)).kind !== "deployment") {
|
|
308
|
+
return yield* failure(
|
|
309
|
+
`${STATE_DEPLOY_KEY_ENV} must be the deploy key of one deployment, such as prod:<name>|..., not a preview deploy key.`,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
return yield* makeConvexState(makeFunctionApi(yield* urlForKey(key), key));
|
|
313
|
+
}).pipe(Effect.provideContext(context), Effect.orDie),
|
|
314
|
+
);
|
|
315
|
+
}),
|
|
316
|
+
);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/**
|
|
3
|
+
* Generated `api` utility.
|
|
4
|
+
*
|
|
5
|
+
* THIS CODE IS AUTOMATICALLY GENERATED.
|
|
6
|
+
*
|
|
7
|
+
* To regenerate, run `npx convex dev`.
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type * as state from "../state.js";
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
ApiFromModules,
|
|
15
|
+
FilterApi,
|
|
16
|
+
FunctionReference,
|
|
17
|
+
} from "convex/server";
|
|
18
|
+
import { anyApi, componentsGeneric } from "convex/server";
|
|
19
|
+
|
|
20
|
+
const fullApi: ApiFromModules<{
|
|
21
|
+
state: typeof state;
|
|
22
|
+
}> = anyApi as any;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A utility for referencing Convex functions in your app's public API.
|
|
26
|
+
*
|
|
27
|
+
* Usage:
|
|
28
|
+
* ```js
|
|
29
|
+
* const myFunctionReference = api.myModule.myFunction;
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export const api: FilterApi<
|
|
33
|
+
typeof fullApi,
|
|
34
|
+
FunctionReference<any, "public">
|
|
35
|
+
> = anyApi as any;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A utility for referencing Convex functions in your app's internal API.
|
|
39
|
+
*
|
|
40
|
+
* Usage:
|
|
41
|
+
* ```js
|
|
42
|
+
* const myFunctionReference = internal.myModule.myFunction;
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export const internal: FilterApi<
|
|
46
|
+
typeof fullApi,
|
|
47
|
+
FunctionReference<any, "internal">
|
|
48
|
+
> = anyApi as any;
|
|
49
|
+
|
|
50
|
+
export const components = componentsGeneric() as unknown as {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/**
|
|
3
|
+
* Generated `ComponentApi` utility.
|
|
4
|
+
*
|
|
5
|
+
* THIS CODE IS AUTOMATICALLY GENERATED.
|
|
6
|
+
*
|
|
7
|
+
* To regenerate, run `npx convex dev`.
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { FunctionReference } from "convex/server";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A utility for referencing a Convex component's exposed API.
|
|
15
|
+
*
|
|
16
|
+
* Useful when expecting a parameter like `components.myComponent`.
|
|
17
|
+
* Usage:
|
|
18
|
+
* ```ts
|
|
19
|
+
* async function myFunction(ctx: QueryCtx, component: ComponentApi) {
|
|
20
|
+
* return ctx.runQuery(component.someFile.someQuery, { ...args });
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export type ComponentApi<Name extends string | undefined = string | undefined> =
|
|
25
|
+
{
|
|
26
|
+
state: {
|
|
27
|
+
get: FunctionReference<
|
|
28
|
+
"query",
|
|
29
|
+
"internal",
|
|
30
|
+
{ fqn: string; stack: string; stage: string },
|
|
31
|
+
string | null,
|
|
32
|
+
Name
|
|
33
|
+
>;
|
|
34
|
+
getOutput: FunctionReference<
|
|
35
|
+
"query",
|
|
36
|
+
"internal",
|
|
37
|
+
{ stack: string; stage: string },
|
|
38
|
+
string | null,
|
|
39
|
+
Name
|
|
40
|
+
>;
|
|
41
|
+
list: FunctionReference<
|
|
42
|
+
"query",
|
|
43
|
+
"internal",
|
|
44
|
+
{ stack: string; stage: string },
|
|
45
|
+
Array<string>,
|
|
46
|
+
Name
|
|
47
|
+
>;
|
|
48
|
+
remove: FunctionReference<
|
|
49
|
+
"mutation",
|
|
50
|
+
"internal",
|
|
51
|
+
{ fqn: string; stack: string; stage: string },
|
|
52
|
+
null,
|
|
53
|
+
Name
|
|
54
|
+
>;
|
|
55
|
+
removeStack: FunctionReference<
|
|
56
|
+
"mutation",
|
|
57
|
+
"internal",
|
|
58
|
+
{ stack: string; stage?: string },
|
|
59
|
+
null,
|
|
60
|
+
Name
|
|
61
|
+
>;
|
|
62
|
+
replaced: FunctionReference<
|
|
63
|
+
"query",
|
|
64
|
+
"internal",
|
|
65
|
+
{ stack: string; stage: string },
|
|
66
|
+
Array<string>,
|
|
67
|
+
Name
|
|
68
|
+
>;
|
|
69
|
+
set: FunctionReference<
|
|
70
|
+
"mutation",
|
|
71
|
+
"internal",
|
|
72
|
+
{ fqn: string; stack: string; stage: string; value: string },
|
|
73
|
+
null,
|
|
74
|
+
Name
|
|
75
|
+
>;
|
|
76
|
+
setOutput: FunctionReference<
|
|
77
|
+
"mutation",
|
|
78
|
+
"internal",
|
|
79
|
+
{ stack: string; stage: string; value: string },
|
|
80
|
+
null,
|
|
81
|
+
Name
|
|
82
|
+
>;
|
|
83
|
+
stacks: FunctionReference<"query", "internal", {}, Array<string>, Name>;
|
|
84
|
+
stages: FunctionReference<
|
|
85
|
+
"query",
|
|
86
|
+
"internal",
|
|
87
|
+
{ stack: string },
|
|
88
|
+
Array<string>,
|
|
89
|
+
Name
|
|
90
|
+
>;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/**
|
|
3
|
+
* Generated data model types.
|
|
4
|
+
*
|
|
5
|
+
* THIS CODE IS AUTOMATICALLY GENERATED.
|
|
6
|
+
*
|
|
7
|
+
* To regenerate, run `npx convex dev`.
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
DataModelFromSchemaDefinition,
|
|
13
|
+
DocumentByName,
|
|
14
|
+
TableNamesInDataModel,
|
|
15
|
+
SystemTableNames,
|
|
16
|
+
} from "convex/server";
|
|
17
|
+
import type { GenericId } from "convex/values";
|
|
18
|
+
import schema from "../schema.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The names of all of your Convex tables.
|
|
22
|
+
*/
|
|
23
|
+
export type TableNames = TableNamesInDataModel<DataModel>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The type of a document stored in Convex.
|
|
27
|
+
*
|
|
28
|
+
* @typeParam TableName - A string literal type of the table name (like "users").
|
|
29
|
+
*/
|
|
30
|
+
export type Doc<TableName extends TableNames> = DocumentByName<
|
|
31
|
+
DataModel,
|
|
32
|
+
TableName
|
|
33
|
+
>;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* An identifier for a document in Convex.
|
|
37
|
+
*
|
|
38
|
+
* Convex documents are uniquely identified by their `Id`, which is accessible
|
|
39
|
+
* on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids).
|
|
40
|
+
*
|
|
41
|
+
* Documents can be loaded using `db.get(tableName, id)` in query and mutation functions.
|
|
42
|
+
*
|
|
43
|
+
* IDs are just strings at runtime, but this type can be used to distinguish them from other
|
|
44
|
+
* strings when type checking.
|
|
45
|
+
*
|
|
46
|
+
* @typeParam TableName - A string literal type of the table name (like "users").
|
|
47
|
+
*/
|
|
48
|
+
export type Id<TableName extends TableNames | SystemTableNames> =
|
|
49
|
+
GenericId<TableName>;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A type describing your Convex data model.
|
|
53
|
+
*
|
|
54
|
+
* This type includes information about what tables you have, the type of
|
|
55
|
+
* documents stored in those tables, and the indexes defined on them.
|
|
56
|
+
*
|
|
57
|
+
* This type is used to parameterize methods like `queryGeneric` and
|
|
58
|
+
* `mutationGeneric` to make them type-safe.
|
|
59
|
+
*/
|
|
60
|
+
export type DataModel = DataModelFromSchemaDefinition<typeof schema>;
|