@gaganref/confect-server 10.3.1-fork.9 → 10.3.1-fork.90
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/dist/ActionCall.d.ts +30 -0
- package/dist/ActionCall.d.ts.map +1 -0
- package/dist/ActionCall.js +41 -0
- package/dist/ActionCall.js.map +1 -0
- package/dist/ActionRunner.d.ts +1 -1
- package/dist/ActionRunner.d.ts.map +1 -1
- package/dist/ActionRunner.js +3 -3
- package/dist/ActionRunner.js.map +1 -1
- package/dist/AiGatewayDecisionClient.js +1 -1
- package/dist/AiGatewayLanguageClient.js +1 -1
- package/dist/Auth.d.ts +31 -2
- package/dist/Auth.d.ts.map +1 -1
- package/dist/Auth.js +23 -3
- package/dist/Auth.js.map +1 -1
- package/dist/Callback.d.ts +1 -1
- package/dist/Callback.d.ts.map +1 -1
- package/dist/Callback.js +6 -6
- package/dist/Callback.js.map +1 -1
- package/dist/ComponentCtx.d.ts +4 -0
- package/dist/ComponentCtx.d.ts.map +1 -1
- package/dist/ConvexConfigProvider.js +2 -2
- package/dist/ConvexFunctionDefinitions.d.ts.map +1 -1
- package/dist/ConvexFunctionDefinitions.js +4 -4
- package/dist/ConvexFunctionDefinitions.js.map +1 -1
- package/dist/ConvexLogger.js +1 -1
- package/dist/CronJobs.d.ts.map +1 -1
- package/dist/CronJobs.js +7 -6
- package/dist/CronJobs.js.map +1 -1
- package/dist/DatabaseSchema.js +2 -2
- package/dist/DatabaseWriter.d.ts +44 -2
- package/dist/DatabaseWriter.d.ts.map +1 -1
- package/dist/DatabaseWriter.js +59 -9
- package/dist/DatabaseWriter.js.map +1 -1
- package/dist/ExecutionMetadata.js +1 -1
- package/dist/FunctionImpl.js +2 -2
- package/dist/FunctionRegistryItem.d.ts +1 -0
- package/dist/FunctionRegistryItem.d.ts.map +1 -1
- package/dist/FunctionRegistryItem.js +2 -1
- package/dist/FunctionRegistryItem.js.map +1 -1
- package/dist/GroupImpl.d.ts +21 -2
- package/dist/GroupImpl.d.ts.map +1 -1
- package/dist/GroupImpl.js +21 -22
- package/dist/GroupImpl.js.map +1 -1
- package/dist/GroupRegistry.d.ts +1 -0
- package/dist/GroupRegistry.d.ts.map +1 -1
- package/dist/GroupRegistry.js +10 -3
- package/dist/GroupRegistry.js.map +1 -1
- package/dist/Handler.d.ts +8 -4
- package/dist/Handler.d.ts.map +1 -1
- package/dist/Handler.js.map +1 -1
- package/dist/HttpRouter.d.ts +3 -0
- package/dist/HttpRouter.d.ts.map +1 -1
- package/dist/HttpRouter.js +29 -10
- package/dist/HttpRouter.js.map +1 -1
- package/dist/MiddlewareImpl.js +1 -1
- package/dist/MountInit.d.ts +31 -0
- package/dist/MountInit.d.ts.map +1 -0
- package/dist/MountInit.js +49 -0
- package/dist/MountInit.js.map +1 -0
- package/dist/MutationRunner.d.ts +1 -1
- package/dist/MutationRunner.d.ts.map +1 -1
- package/dist/MutationRunner.js +3 -3
- package/dist/MutationRunner.js.map +1 -1
- package/dist/MutationTransactionContext.d.ts.map +1 -1
- package/dist/MutationTransactionContext.js +4 -3
- package/dist/MutationTransactionContext.js.map +1 -1
- package/dist/OfficialComponent.d.ts +60 -0
- package/dist/OfficialComponent.d.ts.map +1 -0
- package/dist/OfficialComponent.js +72 -0
- package/dist/OfficialComponent.js.map +1 -0
- package/dist/OrderedQuery.d.ts +4 -5
- package/dist/OrderedQuery.d.ts.map +1 -1
- package/dist/OrderedQuery.js.map +1 -1
- package/dist/QueryInitializer.js +2 -2
- package/dist/QueryRunner.js +2 -2
- package/dist/QueryStream.d.ts +120 -12
- package/dist/QueryStream.d.ts.map +1 -1
- package/dist/QueryStream.js +143 -41
- package/dist/QueryStream.js.map +1 -1
- package/dist/QueryStreamCursor.js +1 -1
- package/dist/QueryStreamIndexPrefix.js +1 -1
- package/dist/QueryStreamIndexRange.js +2 -2
- package/dist/QueryStreamKey.js +1 -1
- package/dist/QueryStreamKeyBounds.js +2 -2
- package/dist/QueryStreamKeyLayout.js +2 -2
- package/dist/QueryStreamKeyValues.js +2 -2
- package/dist/QueryStreamPagination.js +1 -1
- package/dist/QueryStreamReadBudget.d.ts +3 -2
- package/dist/QueryStreamReadBudget.d.ts.map +1 -1
- package/dist/QueryStreamReadBudget.js +8 -5
- package/dist/QueryStreamReadBudget.js.map +1 -1
- package/dist/QueryTransactionContext.js +2 -2
- package/dist/RegisteredConvexFunction.d.ts +10 -5
- package/dist/RegisteredConvexFunction.d.ts.map +1 -1
- package/dist/RegisteredConvexFunction.js +49 -29
- package/dist/RegisteredConvexFunction.js.map +1 -1
- package/dist/RegisteredFunction.d.ts +48 -22
- package/dist/RegisteredFunction.d.ts.map +1 -1
- package/dist/RegisteredFunction.js +163 -42
- package/dist/RegisteredFunction.js.map +1 -1
- package/dist/RegisteredNodeFunction.d.ts +1 -1
- package/dist/RegisteredNodeFunction.d.ts.map +1 -1
- package/dist/RegisteredNodeFunction.js +10 -6
- package/dist/RegisteredNodeFunction.js.map +1 -1
- package/dist/RegistryItems.d.ts +7 -0
- package/dist/RegistryItems.d.ts.map +1 -1
- package/dist/RegistryItems.js +8 -3
- package/dist/RegistryItems.js.map +1 -1
- package/dist/RequestMetadata.js +1 -1
- package/dist/Scheduler.d.ts +1 -1
- package/dist/Scheduler.d.ts.map +1 -1
- package/dist/Scheduler.js +14 -13
- package/dist/Scheduler.js.map +1 -1
- package/dist/Storage.js +1 -1
- package/dist/StorageActionWriter.js +1 -1
- package/dist/StorageReader.js +1 -1
- package/dist/StorageWriter.js +1 -1
- package/dist/Table.d.ts +6 -6
- package/dist/Table.d.ts.map +1 -1
- package/dist/TableInfo.d.ts +7 -7
- package/dist/TableInfo.d.ts.map +1 -1
- package/dist/TableInfo.js.map +1 -1
- package/dist/TransactionMetadata.js +1 -1
- package/dist/VectorSearch.js +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -18
- package/dist/internal/AiGatewayDecisionClient.js +1 -1
- package/dist/internal/AiGatewayLanguageClient.js +1 -1
- package/dist/internal/AiGatewayServiceToken.js +1 -1
- package/dist/internal/DocumentPatch.d.ts +16 -0
- package/dist/internal/DocumentPatch.d.ts.map +1 -0
- package/dist/internal/DocumentPatch.js +36 -0
- package/dist/internal/DocumentPatch.js.map +1 -0
- package/dist/internal/httpBody.d.ts +13 -0
- package/dist/internal/httpBody.d.ts.map +1 -0
- package/dist/internal/httpBody.js +47 -0
- package/dist/internal/httpBody.js.map +1 -0
- package/dist/internal/httpOptions.d.ts +4 -0
- package/dist/internal/httpOptions.d.ts.map +1 -0
- package/dist/internal/httpOptions.js +83 -0
- package/dist/internal/httpOptions.js.map +1 -0
- package/dist/internal/observability.d.ts +5 -0
- package/dist/internal/observability.d.ts.map +1 -0
- package/dist/internal/observability.js +21 -0
- package/dist/internal/observability.js.map +1 -0
- package/dist/internal/reservedArgument.d.ts +8 -0
- package/dist/internal/reservedArgument.d.ts.map +1 -0
- package/dist/internal/reservedArgument.js +30 -0
- package/dist/internal/reservedArgument.js.map +1 -0
- package/package.json +5 -5
- package/src/ActionCall.ts +83 -0
- package/src/ActionRunner.ts +3 -2
- package/src/Auth.ts +49 -1
- package/src/Callback.ts +3 -2
- package/src/ComponentCtx.ts +2 -0
- package/src/ConvexFunctionDefinitions.ts +2 -1
- package/src/CronJobs.ts +2 -1
- package/src/DatabaseWriter.ts +190 -26
- package/src/FunctionRegistryItem.ts +4 -0
- package/src/GroupImpl.ts +61 -8
- package/src/GroupRegistry.ts +7 -3
- package/src/Handler.ts +21 -5
- package/src/HttpRouter.ts +69 -5
- package/src/MountInit.ts +80 -0
- package/src/MutationRunner.ts +3 -2
- package/src/MutationTransactionContext.ts +2 -1
- package/src/OfficialComponent.ts +168 -0
- package/src/OrderedQuery.ts +4 -5
- package/src/QueryStream.ts +391 -61
- package/src/QueryStreamReadBudget.ts +19 -6
- package/src/RegisteredConvexFunction.ts +39 -2
- package/src/RegisteredFunction.ts +305 -49
- package/src/RegisteredNodeFunction.ts +11 -1
- package/src/RegistryItems.ts +31 -0
- package/src/Scheduler.ts +17 -19
- package/src/TableInfo.ts +18 -22
- package/src/index.ts +3 -0
- package/src/internal/DocumentPatch.ts +87 -0
- package/src/internal/httpBody.ts +72 -0
- package/src/internal/httpOptions.ts +154 -0
- package/src/internal/observability.ts +27 -0
- package/src/internal/reservedArgument.ts +56 -0
package/src/DatabaseWriter.ts
CHANGED
|
@@ -5,16 +5,19 @@ import type {
|
|
|
5
5
|
GenericDatabaseWriter,
|
|
6
6
|
WithoutSystemFields,
|
|
7
7
|
} from "convex/server";
|
|
8
|
-
import type { GenericId } from "convex/values";
|
|
8
|
+
import type { CommitTsPlaceholder, GenericId } from "convex/values";
|
|
9
9
|
import { pipe } from "effect/Function";
|
|
10
10
|
import * as Context from "effect/Context";
|
|
11
11
|
import * as Effect from "effect/Effect";
|
|
12
12
|
import * as Layer from "effect/Layer";
|
|
13
|
+
import * as Predicate from "effect/Predicate";
|
|
13
14
|
import * as Record from "effect/Record";
|
|
15
|
+
import * as Result from "effect/Result";
|
|
14
16
|
import * as DatabaseSchema from "./DatabaseSchema.js";
|
|
15
17
|
import type * as DataModel from "./DataModel.js";
|
|
16
18
|
import type { DocumentByName as DocumentByName_ } from "./DataModel.js";
|
|
17
19
|
import * as Document from "./Document.js";
|
|
20
|
+
import * as DocumentPatch from "./internal/DocumentPatch.js";
|
|
18
21
|
import * as QueryInitializer from "./QueryInitializer.js";
|
|
19
22
|
import type * as Table from "./Table.js";
|
|
20
23
|
|
|
@@ -82,6 +85,11 @@ export interface DatabaseWriterService<
|
|
|
82
85
|
? Docs[TableName]
|
|
83
86
|
: DocumentByName_<DataModel.FromSchema<DatabaseSchema_>, TableName>
|
|
84
87
|
>;
|
|
88
|
+
/**
|
|
89
|
+
* Values not known until the mutation commits. Write `vars.commitTs` into a
|
|
90
|
+
* `CommitTs` field to store the transaction's commit timestamp.
|
|
91
|
+
*/
|
|
92
|
+
readonly vars: { readonly commitTs: CommitTsPlaceholder };
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
/**
|
|
@@ -97,11 +105,54 @@ export type DatabaseWriterTag<
|
|
|
97
105
|
DatabaseWriterService<DatabaseSchema_, Docs>
|
|
98
106
|
>;
|
|
99
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Decoded documents surrounding one successful write.
|
|
110
|
+
*/
|
|
111
|
+
export type Change<S extends DatabaseSchema.AnyWithProps> = {
|
|
112
|
+
[Name in DataModel.TableNames<DataModel.FromSchema<S>>]: {
|
|
113
|
+
readonly tableName: Name;
|
|
114
|
+
} & (
|
|
115
|
+
| {
|
|
116
|
+
readonly operation: "insert";
|
|
117
|
+
readonly oldDoc: null;
|
|
118
|
+
readonly newDoc: DocumentByName_<DataModel.FromSchema<S>, Name>;
|
|
119
|
+
}
|
|
120
|
+
| {
|
|
121
|
+
readonly operation: "update";
|
|
122
|
+
readonly oldDoc: DocumentByName_<DataModel.FromSchema<S>, Name>;
|
|
123
|
+
readonly newDoc: DocumentByName_<DataModel.FromSchema<S>, Name>;
|
|
124
|
+
}
|
|
125
|
+
| {
|
|
126
|
+
readonly operation: "delete";
|
|
127
|
+
readonly oldDoc: DocumentByName_<DataModel.FromSchema<S>, Name>;
|
|
128
|
+
readonly newDoc: null;
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
}[DataModel.TableNames<DataModel.FromSchema<S>>];
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Runs after the write, in its transaction. A defect aborts the mutation.
|
|
135
|
+
*
|
|
136
|
+
* Only writes through this writer are observed. Raw `ctx.db`, `MutationCtx`
|
|
137
|
+
* writes and a second `DatabaseWriter.make` bypass the hook, so a host that
|
|
138
|
+
* relies on it keeps raw writers out of its functions. Component writers and
|
|
139
|
+
* nested `runMutation` calls each need their own hook installation.
|
|
140
|
+
*
|
|
141
|
+
* Concurrent writes to one document (for example under `Effect.all`) can
|
|
142
|
+
* interleave the read-write-read steps. Await them sequentially when the hook
|
|
143
|
+
* needs exact per-write snapshots. The writer takes no lock, because a hook
|
|
144
|
+
* that writes through the same writer would deadlock under one.
|
|
145
|
+
*/
|
|
146
|
+
export type Hook<S extends DatabaseSchema.AnyWithProps> = (
|
|
147
|
+
change: Change<S>,
|
|
148
|
+
) => Effect.Effect<void>;
|
|
149
|
+
|
|
100
150
|
export const make = <DatabaseSchema_ extends DatabaseSchema.AnyWithProps>(
|
|
101
151
|
databaseSchema: DatabaseSchema_,
|
|
102
152
|
convexDatabaseWriter: GenericDatabaseWriter<
|
|
103
153
|
DataModel.ToConvex<DataModel.FromSchema<DatabaseSchema_>>
|
|
104
154
|
>,
|
|
155
|
+
hook?: Hook<DatabaseSchema_>,
|
|
105
156
|
): DatabaseWriterService<DatabaseSchema_> => {
|
|
106
157
|
type DataModel_ = DataModel.FromSchema<DatabaseSchema_>;
|
|
107
158
|
|
|
@@ -131,38 +182,108 @@ export const make = <DatabaseSchema_ extends DatabaseSchema.AnyWithProps>(
|
|
|
131
182
|
return id as unknown as DataModel.Id<DataModel_, TableName>;
|
|
132
183
|
});
|
|
133
184
|
|
|
185
|
+
// A codec whose encoding spans fields cannot encode a patch on its own, so
|
|
186
|
+
// the patch is merged into the decoded document and the result replaces it.
|
|
187
|
+
const replacePatched = (
|
|
188
|
+
id: DataModel.Id<DataModel_, TableName>,
|
|
189
|
+
patchedValues: PatchValue<
|
|
190
|
+
Document.WithoutSystemFields<DocumentByName_<DataModel_, TableName>>
|
|
191
|
+
>,
|
|
192
|
+
) =>
|
|
193
|
+
Effect.gen(function* () {
|
|
194
|
+
const originalDecodedDoc = yield* QueryInitializer.getById<
|
|
195
|
+
Table.AnyWithProps,
|
|
196
|
+
TableName
|
|
197
|
+
>(
|
|
198
|
+
convexDatabaseWriter as any,
|
|
199
|
+
tableDef,
|
|
200
|
+
)(id as unknown as GenericId<TableName>);
|
|
201
|
+
|
|
202
|
+
const updatedEncodedDoc = yield* pipe(
|
|
203
|
+
patchedValues,
|
|
204
|
+
Record.reduce(originalDecodedDoc, (acc, value, key) =>
|
|
205
|
+
value === undefined
|
|
206
|
+
? Record.remove(acc, key)
|
|
207
|
+
: Record.set(acc, key, value),
|
|
208
|
+
),
|
|
209
|
+
Document.encode(tableDef),
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
yield* Effect.promise(() =>
|
|
213
|
+
convexDatabaseWriter.replace(
|
|
214
|
+
id as unknown as GenericId<TableName>,
|
|
215
|
+
updatedEncodedDoc as Expand<
|
|
216
|
+
BetterOmit<
|
|
217
|
+
DocumentByName<DataModel.ToConvex<DataModel_>, TableName>,
|
|
218
|
+
"_creationTime" | "_id"
|
|
219
|
+
>
|
|
220
|
+
>,
|
|
221
|
+
),
|
|
222
|
+
);
|
|
223
|
+
});
|
|
224
|
+
|
|
134
225
|
const patch = Effect.fn("DatabaseWriter.patch")(function* (
|
|
135
226
|
id: DataModel.Id<DataModel_, TableName>,
|
|
136
227
|
patchedValues: PatchValue<
|
|
137
228
|
Document.WithoutSystemFields<DocumentByName_<DataModel_, TableName>>
|
|
138
229
|
>,
|
|
139
230
|
) {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
convexDatabaseWriter as any,
|
|
145
|
-
tableDef,
|
|
146
|
-
)(id as unknown as GenericId<TableName>);
|
|
231
|
+
const encodePatch = DocumentPatch.encoder(tableDef);
|
|
232
|
+
if (encodePatch === undefined) {
|
|
233
|
+
return yield* replacePatched(id, patchedValues);
|
|
234
|
+
}
|
|
147
235
|
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
236
|
+
const storedDoc = yield* pipe(
|
|
237
|
+
Effect.promise(() =>
|
|
238
|
+
convexDatabaseWriter.get(id as unknown as GenericId<TableName>),
|
|
239
|
+
),
|
|
240
|
+
Effect.andThen((value) =>
|
|
241
|
+
Effect.fromResult(
|
|
242
|
+
Result.fromNullishOr(
|
|
243
|
+
value,
|
|
244
|
+
() =>
|
|
245
|
+
new QueryInitializer.GetByIdFailure({
|
|
246
|
+
tableName,
|
|
247
|
+
id,
|
|
248
|
+
}),
|
|
249
|
+
),
|
|
250
|
+
),
|
|
251
|
+
),
|
|
252
|
+
);
|
|
253
|
+
// The patch must encode and the patched document must decode. When
|
|
254
|
+
// either fails, the stored document decides whose fault that is.
|
|
255
|
+
const encodedPatch = yield* pipe(
|
|
256
|
+
encodePatch(patchedValues),
|
|
257
|
+
Effect.mapError(({ message }) => message),
|
|
258
|
+
Effect.tap((delta) =>
|
|
259
|
+
pipe(
|
|
260
|
+
{ ...storedDoc, ...delta },
|
|
261
|
+
Record.filter(Predicate.isNotUndefined),
|
|
262
|
+
Document.decode(tableDef),
|
|
263
|
+
Effect.mapError(({ parseError }) => parseError),
|
|
264
|
+
),
|
|
265
|
+
),
|
|
266
|
+
Effect.catch((parseError) =>
|
|
267
|
+
pipe(
|
|
268
|
+
Document.decode(storedDoc, tableDef),
|
|
269
|
+
Effect.andThen(
|
|
270
|
+
Effect.fail(
|
|
271
|
+
new Document.DocumentEncodeError({
|
|
272
|
+
tableName,
|
|
273
|
+
id,
|
|
274
|
+
parseError,
|
|
275
|
+
}),
|
|
276
|
+
),
|
|
277
|
+
),
|
|
278
|
+
),
|
|
154
279
|
),
|
|
155
|
-
Document.encode(tableDef),
|
|
156
280
|
);
|
|
157
281
|
|
|
158
282
|
yield* Effect.promise(() =>
|
|
159
|
-
convexDatabaseWriter.
|
|
283
|
+
convexDatabaseWriter.patch(
|
|
160
284
|
id as unknown as GenericId<TableName>,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
DocumentByName<DataModel.ToConvex<DataModel_>, TableName>,
|
|
164
|
-
"_creationTime" | "_id"
|
|
165
|
-
>
|
|
285
|
+
encodedPatch as Partial<
|
|
286
|
+
DocumentByName<DataModel.ToConvex<DataModel_>, TableName>
|
|
166
287
|
>,
|
|
167
288
|
),
|
|
168
289
|
);
|
|
@@ -194,16 +315,58 @@ export const make = <DatabaseSchema_ extends DatabaseSchema.AnyWithProps>(
|
|
|
194
315
|
convexDatabaseWriter.delete(id as unknown as GenericId<TableName>),
|
|
195
316
|
);
|
|
196
317
|
|
|
318
|
+
const accessor = { insert, patch, replace, delete: delete_ };
|
|
319
|
+
if (hook === undefined) return accessor;
|
|
320
|
+
|
|
321
|
+
const read = (id: DataModel.Id<DataModel_, TableName>) =>
|
|
322
|
+
QueryInitializer.getById<Table.AnyWithProps, TableName>(
|
|
323
|
+
convexDatabaseWriter as any,
|
|
324
|
+
tableDef,
|
|
325
|
+
)(id as unknown as GenericId<TableName>);
|
|
326
|
+
const notify = (
|
|
327
|
+
operation: "insert" | "update" | "delete",
|
|
328
|
+
oldDoc: unknown,
|
|
329
|
+
newDoc: unknown,
|
|
330
|
+
) =>
|
|
331
|
+
hook({ tableName, operation, oldDoc, newDoc } as Change<DatabaseSchema_>);
|
|
332
|
+
const update =
|
|
333
|
+
<A extends ReadonlyArray<unknown>, E, BeforeError>(
|
|
334
|
+
before: (
|
|
335
|
+
id: DataModel.Id<DataModel_, TableName>,
|
|
336
|
+
) => Effect.Effect<unknown, BeforeError>,
|
|
337
|
+
write: (
|
|
338
|
+
id: DataModel.Id<DataModel_, TableName>,
|
|
339
|
+
...args: A
|
|
340
|
+
) => Effect.Effect<void, E>,
|
|
341
|
+
) =>
|
|
342
|
+
(id: DataModel.Id<DataModel_, TableName>, ...args: A) =>
|
|
343
|
+
Effect.gen(function* () {
|
|
344
|
+
const oldDoc = yield* before(id);
|
|
345
|
+
yield* write(id, ...args);
|
|
346
|
+
yield* notify("update", oldDoc, yield* read(id).pipe(Effect.orDie));
|
|
347
|
+
});
|
|
348
|
+
|
|
197
349
|
return {
|
|
198
|
-
insert
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
350
|
+
insert: (document: Parameters<typeof insert>[0]) =>
|
|
351
|
+
Effect.gen(function* () {
|
|
352
|
+
const id = yield* insert(document);
|
|
353
|
+
yield* notify("insert", null, yield* read(id).pipe(Effect.orDie));
|
|
354
|
+
return id;
|
|
355
|
+
}),
|
|
356
|
+
patch: update(read, patch),
|
|
357
|
+
replace: update((id) => read(id).pipe(Effect.orDie), replace),
|
|
358
|
+
delete: (id: DataModel.Id<DataModel_, TableName>) =>
|
|
359
|
+
Effect.gen(function* () {
|
|
360
|
+
const oldDoc = yield* read(id).pipe(Effect.orDie);
|
|
361
|
+
yield* delete_(id);
|
|
362
|
+
yield* notify("delete", oldDoc, null);
|
|
363
|
+
}),
|
|
202
364
|
};
|
|
203
365
|
};
|
|
204
366
|
|
|
205
367
|
return {
|
|
206
368
|
table,
|
|
369
|
+
vars: convexDatabaseWriter.vars,
|
|
207
370
|
} as DatabaseWriterService<DatabaseSchema_>;
|
|
208
371
|
};
|
|
209
372
|
|
|
@@ -225,8 +388,9 @@ export const layer = <DatabaseSchema_ extends DatabaseSchema.AnyWithProps>(
|
|
|
225
388
|
convexDatabaseWriter: GenericDatabaseWriter<
|
|
226
389
|
DataModel.ToConvex<DataModel.FromSchema<DatabaseSchema_>>
|
|
227
390
|
>,
|
|
391
|
+
hook?: Hook<DatabaseSchema_>,
|
|
228
392
|
) =>
|
|
229
393
|
Layer.succeed(
|
|
230
394
|
DatabaseWriter<DatabaseSchema_>(),
|
|
231
|
-
make(databaseSchema, convexDatabaseWriter),
|
|
395
|
+
make(databaseSchema, convexDatabaseWriter, hook),
|
|
232
396
|
);
|
|
@@ -29,6 +29,7 @@ export interface ConfectFunctionRegistryItem {
|
|
|
29
29
|
readonly [TypeId]: TypeId;
|
|
30
30
|
readonly _tag: "Confect";
|
|
31
31
|
readonly name: string;
|
|
32
|
+
readonly reservedArgument?: string;
|
|
32
33
|
readonly functionVisibility: FunctionVisibility;
|
|
33
34
|
readonly functionType: FunctionType;
|
|
34
35
|
readonly args: Schema.Codec<any, any>;
|
|
@@ -64,6 +65,9 @@ export const make = ({
|
|
|
64
65
|
const item = Object.assign(Object.create(FunctionRegistryItemProto), {
|
|
65
66
|
_tag: "Confect" as const,
|
|
66
67
|
name: functionSpec.name,
|
|
68
|
+
...(functionSpec.reservedArgument === undefined
|
|
69
|
+
? {}
|
|
70
|
+
: { reservedArgument: functionSpec.reservedArgument }),
|
|
67
71
|
functionVisibility: functionSpec.functionVisibility,
|
|
68
72
|
functionType: functionSpec.runtimeAndFunctionType.functionType,
|
|
69
73
|
middlewareAttachments: [
|
package/src/GroupImpl.ts
CHANGED
|
@@ -5,6 +5,7 @@ import * as RegistryItems from "./RegistryItems.js";
|
|
|
5
5
|
import * as Array from "effect/Array";
|
|
6
6
|
import * as Context from "effect/Context";
|
|
7
7
|
import * as Effect from "effect/Effect";
|
|
8
|
+
import * as Data from "effect/Data";
|
|
8
9
|
import * as Layer from "effect/Layer";
|
|
9
10
|
import * as Option from "effect/Option";
|
|
10
11
|
import * as Predicate from "effect/Predicate";
|
|
@@ -111,10 +112,20 @@ export const make = <
|
|
|
111
112
|
FunctionImpl.FromGroupSpec<Group> | MiddlewareImpl.FromGroupSpec<Group>
|
|
112
113
|
>;
|
|
113
114
|
|
|
114
|
-
const
|
|
115
|
-
context
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
const findGroupImpl = <S>(context: Context.Context<S>): Option.Option<Any> =>
|
|
116
|
+
Array.findFirst(context.mapUnsafe.values(), isGroupImpl);
|
|
117
|
+
|
|
118
|
+
export class MissingRequiredMiddlewareError extends Data.TaggedError(
|
|
119
|
+
"MissingRequiredMiddlewareError",
|
|
120
|
+
)<{
|
|
121
|
+
readonly missing: ReadonlyArray<RegistryItems.MissingMiddleware>;
|
|
122
|
+
}> {
|
|
123
|
+
override get message(): string {
|
|
124
|
+
return `GroupImpl.finalize: ${this.missing.map(({ functionName, middlewareKey }) => `${functionName} requires middleware "${middlewareKey}"`).join("\n")}`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
type Finalizable = Layer.Layer<AnyUnfinalized> | Layer.Layer<AnyFinalized>;
|
|
118
129
|
|
|
119
130
|
/**
|
|
120
131
|
* Mark a `GroupImpl` layer as fully implemented. The parameter type defaults
|
|
@@ -127,20 +138,52 @@ const findUnfinalizedGroupImpl = <S>(
|
|
|
127
138
|
* registered into this group's scope are snapshotted onto the produced service
|
|
128
139
|
* value's `registeredFunctionNames` field, so consumers can verify impl
|
|
129
140
|
* completeness against a `GroupSpec`'s expected functions without having to
|
|
130
|
-
* inspect the `Registry` themselves.
|
|
141
|
+
* inspect the `Registry` themselves. Pass `{ required }` to check every
|
|
142
|
+
* registered function's middleware at layer build, including internal
|
|
143
|
+
* functions. This form also accepts a finalized layer so generated registration
|
|
144
|
+
* can enforce the backend declaration without changing authored impls. Pass
|
|
145
|
+
* `groupPath` to qualify uncovered function names in the diagnostic.
|
|
131
146
|
*/
|
|
132
|
-
export
|
|
147
|
+
export function finalize(options: {
|
|
148
|
+
readonly groupPath?: string;
|
|
149
|
+
readonly required: ReadonlyArray<
|
|
150
|
+
Pick<MiddlewareSpec.AnyMiddlewareSpec, "key">
|
|
151
|
+
>;
|
|
152
|
+
}): (group: Finalizable) => Layer.Layer<GroupImpl<"Finalized">>;
|
|
153
|
+
export function finalize(
|
|
133
154
|
group: Layer.Layer<GroupImpl<"Unfinalized">>,
|
|
155
|
+
): Layer.Layer<GroupImpl<"Finalized">>;
|
|
156
|
+
export function finalize(
|
|
157
|
+
input:
|
|
158
|
+
| Finalizable
|
|
159
|
+
| {
|
|
160
|
+
readonly groupPath?: string;
|
|
161
|
+
readonly required: ReadonlyArray<
|
|
162
|
+
Pick<MiddlewareSpec.AnyMiddlewareSpec, "key">
|
|
163
|
+
>;
|
|
164
|
+
},
|
|
165
|
+
):
|
|
166
|
+
| Layer.Layer<GroupImpl<"Finalized">>
|
|
167
|
+
| ((group: Finalizable) => Layer.Layer<GroupImpl<"Finalized">>) {
|
|
168
|
+
return "required" in input
|
|
169
|
+
? (group) => finalizeWith(group, input.required, input.groupPath)
|
|
170
|
+
: finalizeWith(input, []);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const finalizeWith = (
|
|
174
|
+
group: Layer.Layer<never>,
|
|
175
|
+
required: ReadonlyArray<Pick<MiddlewareSpec.AnyMiddlewareSpec, "key">>,
|
|
176
|
+
groupPath = "",
|
|
134
177
|
): Layer.Layer<GroupImpl<"Finalized">> =>
|
|
135
178
|
Layer.flatMap(group, (context): Layer.Layer<GroupImpl<"Finalized">> =>
|
|
136
|
-
|
|
179
|
+
findGroupImpl(context).pipe(
|
|
137
180
|
Option.match({
|
|
138
181
|
onNone: () =>
|
|
139
182
|
Layer.effect(
|
|
140
183
|
GroupImpl<"Finalized">({ finalizationStatus: "Finalized" }),
|
|
141
184
|
Effect.die(
|
|
142
185
|
new Error(
|
|
143
|
-
"GroupImpl.finalize: no
|
|
186
|
+
"GroupImpl.finalize: no GroupImpl service was found in the layer's context.",
|
|
144
187
|
),
|
|
145
188
|
),
|
|
146
189
|
),
|
|
@@ -150,6 +193,16 @@ export const finalize = (
|
|
|
150
193
|
Effect.gen(function* () {
|
|
151
194
|
const registry = yield* Registry.Registry;
|
|
152
195
|
const items = yield* Ref.get(registry);
|
|
196
|
+
const missing = RegistryItems.missingRequired(
|
|
197
|
+
items,
|
|
198
|
+
required,
|
|
199
|
+
groupPath,
|
|
200
|
+
);
|
|
201
|
+
if (missing.length > 0) {
|
|
202
|
+
return yield* Effect.die(
|
|
203
|
+
new MissingRequiredMiddlewareError({ missing }),
|
|
204
|
+
);
|
|
205
|
+
}
|
|
153
206
|
return {
|
|
154
207
|
[TypeId]: TypeId,
|
|
155
208
|
finalizationStatus: "Finalized" as const,
|
package/src/GroupRegistry.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as RegisteredFunction from "./RegisteredFunction.js";
|
|
1
2
|
import * as Effect from "effect/Effect";
|
|
2
3
|
import type * as Layer from "effect/Layer";
|
|
3
4
|
import * as Ref from "effect/Ref";
|
|
@@ -9,9 +10,12 @@ import * as MiddlewareRegistryItem from "./MiddlewareRegistryItem.js";
|
|
|
9
10
|
export const make = (
|
|
10
11
|
groupLayer: Layer.Layer<GroupImpl.GroupImpl<"Finalized">>,
|
|
11
12
|
) => {
|
|
12
|
-
const registryItems = Effect.gen(function* () {
|
|
13
|
+
const { registryItems, observability } = Effect.gen(function* () {
|
|
13
14
|
const registry = yield* Registry.Registry;
|
|
14
|
-
return
|
|
15
|
+
return {
|
|
16
|
+
registryItems: yield* Ref.get(registry),
|
|
17
|
+
observability: yield* RegisteredFunction.Observability,
|
|
18
|
+
};
|
|
15
19
|
}).pipe(
|
|
16
20
|
Effect.provide(groupLayer),
|
|
17
21
|
Effect.provideService(
|
|
@@ -21,7 +25,7 @@ export const make = (
|
|
|
21
25
|
Effect.runSync,
|
|
22
26
|
);
|
|
23
27
|
|
|
24
|
-
return partitionRegistryItems(registryItems);
|
|
28
|
+
return { ...partitionRegistryItems(registryItems), observability };
|
|
25
29
|
};
|
|
26
30
|
|
|
27
31
|
const partitionRegistryItems = (registryItems: RegistryItems.RegistryItems) => {
|
package/src/Handler.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ActionCall } from "@confect/core/ActionCall";
|
|
1
2
|
import type { FunctionSpec, RuntimeAndFunctionType } from "@confect/core";
|
|
2
3
|
import type * as FunctionProvenance from "@confect/core/FunctionProvenance";
|
|
3
4
|
import type * as NodeServices from "@effect/platform-node/NodeServices";
|
|
@@ -94,6 +95,7 @@ export type MutationServices<
|
|
|
94
95
|
> =
|
|
95
96
|
| DatabaseReader.DatabaseReader<DatabaseSchema_>
|
|
96
97
|
| DatabaseWriter.DatabaseWriter<DatabaseSchema_>
|
|
98
|
+
| Auth.MutationAuth
|
|
97
99
|
| (DatabaseSchema_["target"]["kind"] extends "component" ? never : Auth.Auth)
|
|
98
100
|
| Scheduler.ForScope<DatabaseSchema.Scope<DatabaseSchema_>>
|
|
99
101
|
| StorageReader.ForScope<DatabaseSchema.Scope<DatabaseSchema_>>
|
|
@@ -114,9 +116,7 @@ export type MutationServices<
|
|
|
114
116
|
/**
|
|
115
117
|
* Shared by both action runtimes.
|
|
116
118
|
*/
|
|
117
|
-
|
|
118
|
-
DatabaseSchema_ extends DatabaseSchema.AnyWithProps,
|
|
119
|
-
> =
|
|
119
|
+
type RawActionServices<DatabaseSchema_ extends DatabaseSchema.AnyWithProps> =
|
|
120
120
|
| Scheduler.ForScope<DatabaseSchema.Scope<DatabaseSchema_>>
|
|
121
121
|
| (DatabaseSchema_["target"]["kind"] extends "component" ? never : Auth.Auth)
|
|
122
122
|
| StorageReader.ForScope<DatabaseSchema.Scope<DatabaseSchema_>>
|
|
@@ -134,6 +134,22 @@ export type ActionServices<
|
|
|
134
134
|
DatabaseSchema.Scope<DatabaseSchema_>
|
|
135
135
|
>;
|
|
136
136
|
|
|
137
|
+
export type ActionServices<S extends DatabaseSchema.AnyWithProps, R = never> = [
|
|
138
|
+
Extract<R, ActionCall>,
|
|
139
|
+
] extends [never]
|
|
140
|
+
? RawActionServices<S>
|
|
141
|
+
: Exclude<
|
|
142
|
+
RawActionServices<S>,
|
|
143
|
+
| Scheduler.ForScope<DatabaseSchema.Scope<S>>
|
|
144
|
+
| QueryRunner.QueryRunner
|
|
145
|
+
| MutationRunner.MutationRunner
|
|
146
|
+
| ActionRunner.ActionRunner
|
|
147
|
+
| ActionCtx.ActionCtx<
|
|
148
|
+
DataModel.ToConvex<DataModel.FromSchema<S>>,
|
|
149
|
+
DatabaseSchema.Scope<S>
|
|
150
|
+
>
|
|
151
|
+
>;
|
|
152
|
+
|
|
137
153
|
export type ConfectProvenanceQuery<
|
|
138
154
|
DatabaseSchema_ extends DatabaseSchema.AnyWithProps,
|
|
139
155
|
FunctionSpec_ extends
|
|
@@ -153,7 +169,7 @@ export type ConvexRuntimeAction<
|
|
|
153
169
|
FunctionSpec_ extends
|
|
154
170
|
FunctionSpec.AnyWithPropsWithFunctionType<RuntimeAndFunctionType.AnyAction>,
|
|
155
171
|
R = never,
|
|
156
|
-
> = Base<FunctionSpec_, ActionServices<DatabaseSchema_> | R>;
|
|
172
|
+
> = Base<FunctionSpec_, ActionServices<DatabaseSchema_, R> | R>;
|
|
157
173
|
|
|
158
174
|
export type NodeRuntimeAction<
|
|
159
175
|
DatabaseSchema_ extends DatabaseSchema.AnyWithProps,
|
|
@@ -162,7 +178,7 @@ export type NodeRuntimeAction<
|
|
|
162
178
|
R = never,
|
|
163
179
|
> = Base<
|
|
164
180
|
FunctionSpec_,
|
|
165
|
-
ActionServices<DatabaseSchema_> | NodeServices.NodeServices | R
|
|
181
|
+
ActionServices<DatabaseSchema_, R> | NodeServices.NodeServices | R
|
|
166
182
|
>;
|
|
167
183
|
|
|
168
184
|
type Base<FunctionSpec_ extends FunctionSpec.AnyWithProps, R> = (
|
package/src/HttpRouter.ts
CHANGED
|
@@ -24,6 +24,8 @@ import type * as HttpApiGroup from "effect/http-api/HttpApiGroup";
|
|
|
24
24
|
import type * as Etag from "effect/http/Etag";
|
|
25
25
|
import type * as HttpPlatform from "effect/http/HttpPlatform";
|
|
26
26
|
import * as HttpRouter from "effect/http/HttpRouter";
|
|
27
|
+
import * as HttpMiddleware from "effect/http/HttpMiddleware";
|
|
28
|
+
import type * as HttpServerRequest from "effect/http/HttpServerRequest";
|
|
27
29
|
import * as HttpServer from "effect/http/HttpServer";
|
|
28
30
|
import type * as ActionRunner from "./ActionRunner.js";
|
|
29
31
|
import type * as Auth from "./Auth.js";
|
|
@@ -33,6 +35,8 @@ import type * as DatabaseSchema from "./DatabaseSchema.js";
|
|
|
33
35
|
import type * as DataModel from "./DataModel.js";
|
|
34
36
|
import type * as Handler from "./Handler.js";
|
|
35
37
|
import * as ConvexConfigProvider from "./ConvexConfigProvider.js";
|
|
38
|
+
import * as ObservabilityRuntime from "./internal/observability.js";
|
|
39
|
+
import { withOptions } from "./internal/httpOptions.js";
|
|
36
40
|
import * as ConvexLogger from "./ConvexLogger.js";
|
|
37
41
|
import type * as MutationRunner from "./MutationRunner.js";
|
|
38
42
|
import type * as QueryRunner from "./QueryRunner.js";
|
|
@@ -116,9 +120,17 @@ export type Routes<Services_ = Services> = Layer.Layer<
|
|
|
116
120
|
*/
|
|
117
121
|
export const make = <Id extends string, Groups extends HttpApiGroup.Constraint>(
|
|
118
122
|
routes: Routes,
|
|
119
|
-
options?: {
|
|
123
|
+
options?: {
|
|
124
|
+
readonly observability?: Layer.Layer<never> | undefined;
|
|
125
|
+
readonly api?: HttpApi.HttpApi<Id, Groups> | undefined;
|
|
126
|
+
},
|
|
120
127
|
): ConvexHttpRouter =>
|
|
121
|
-
makeWithLayer(
|
|
128
|
+
makeWithLayer(
|
|
129
|
+
routes,
|
|
130
|
+
RegisteredFunction.baseActionLayer,
|
|
131
|
+
options?.api,
|
|
132
|
+
options?.observability,
|
|
133
|
+
);
|
|
122
134
|
|
|
123
135
|
/**
|
|
124
136
|
* Derive HTTP capabilities and ID scopes from the generated database schema.
|
|
@@ -131,7 +143,10 @@ export const forSchema =
|
|
|
131
143
|
) =>
|
|
132
144
|
<Id extends string, Groups extends HttpApiGroup.Constraint>(
|
|
133
145
|
routes: Routes<Handler.ActionServices<DatabaseSchema_>>,
|
|
134
|
-
options?: {
|
|
146
|
+
options?: {
|
|
147
|
+
readonly observability?: Layer.Layer<never> | undefined;
|
|
148
|
+
readonly api?: HttpApi.HttpApi<Id, Groups> | undefined;
|
|
149
|
+
},
|
|
135
150
|
): ConvexHttpRouter =>
|
|
136
151
|
makeWithLayer<
|
|
137
152
|
Handler.ActionServices<DatabaseSchema_>,
|
|
@@ -148,6 +163,8 @@ export const forSchema =
|
|
|
148
163
|
),
|
|
149
164
|
(ctx) => RegisteredFunction.actionLayer(schema, ctx),
|
|
150
165
|
options?.api,
|
|
166
|
+
options?.observability,
|
|
167
|
+
schema.target.kind === "component",
|
|
151
168
|
);
|
|
152
169
|
|
|
153
170
|
// Convex uses a component-relative path to select its HTTP action but passes
|
|
@@ -175,15 +192,49 @@ const makeWithLayer = <
|
|
|
175
192
|
routes: Routes<Services_>,
|
|
176
193
|
layer: (ctx: GenericActionCtx<DataModel_>) => Layer.Layer<Services_>,
|
|
177
194
|
api: HttpApi.HttpApi<Id, Groups> | undefined,
|
|
195
|
+
observability?: Layer.Layer<never>,
|
|
196
|
+
siteRelative = false,
|
|
178
197
|
): ConvexHttpRouter => {
|
|
179
198
|
applyMonkeyPatches();
|
|
180
199
|
|
|
200
|
+
const spanName = Layer.effect(
|
|
201
|
+
HttpMiddleware.SpanNameGenerator,
|
|
202
|
+
Effect.gen(function* () {
|
|
203
|
+
const prefix = siteRelative
|
|
204
|
+
? (yield* Config.schema(
|
|
205
|
+
Schema.URLFromString,
|
|
206
|
+
"CONVEX_SITE_URL",
|
|
207
|
+
)).pathname.replace(/\/$/, "")
|
|
208
|
+
: "";
|
|
209
|
+
return (request: HttpServerRequest.HttpServerRequest) =>
|
|
210
|
+
convexHttpRouter.lookup(
|
|
211
|
+
new URL(request.originalUrl).pathname.slice(prefix.length) || "/",
|
|
212
|
+
request.method as (typeof ROUTABLE_HTTP_METHODS)[number],
|
|
213
|
+
)?.[2] ?? "/*";
|
|
214
|
+
}).pipe(Effect.orDie),
|
|
215
|
+
);
|
|
216
|
+
|
|
181
217
|
// Provided (not just merged) so that route layers' own construction—e.g.
|
|
182
218
|
// a `Layer.unwrap` reading `Config`—resolves configuration through the
|
|
183
219
|
// Convex-aware provider; merged so that request fibers—endpoint handlers
|
|
184
220
|
// and middleware—do too.
|
|
185
|
-
|
|
186
|
-
|
|
221
|
+
// Collected now: toWebHandler builds AppLayer before the reflection below.
|
|
222
|
+
const endpoints = new Set<string>();
|
|
223
|
+
if (api !== undefined)
|
|
224
|
+
HttpApi.reflect(api, {
|
|
225
|
+
onGroup: () => {},
|
|
226
|
+
onEndpoint: ({ endpoint }) => {
|
|
227
|
+
endpoints.add(
|
|
228
|
+
`${endpoint.method} ${apiRouterPath(endpoint.path as FindMyWay.PathInput, endpoint.params)}`,
|
|
229
|
+
);
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
const AppLayer = withOptions(routes, endpoints).pipe(
|
|
233
|
+
Layer.provideMerge(
|
|
234
|
+
observability === undefined
|
|
235
|
+
? ConvexLogger.layer
|
|
236
|
+
: Layer.merge(ObservabilityRuntime.memoized(observability), spanName),
|
|
237
|
+
),
|
|
187
238
|
Layer.provideMerge(ConvexConfigProvider.layer),
|
|
188
239
|
Layer.provide(HttpServer.layerServices),
|
|
189
240
|
);
|
|
@@ -193,6 +244,17 @@ const makeWithLayer = <
|
|
|
193
244
|
// exposes as a per-request context argument.
|
|
194
245
|
const { handler } = HttpRouter.toWebHandler(AppLayer, {
|
|
195
246
|
disableLogger: true,
|
|
247
|
+
/* oxlint-disable effecttsgo/any-unknown-in-error-context -- Keep Effect's erased HTTP boundary errors intact. */
|
|
248
|
+
...(observability === undefined
|
|
249
|
+
? {}
|
|
250
|
+
: {
|
|
251
|
+
middleware: (effect) =>
|
|
252
|
+
Effect.andThen(
|
|
253
|
+
Effect.annotateCurrentSpan("kind", "httpAction"),
|
|
254
|
+
effect,
|
|
255
|
+
),
|
|
256
|
+
}),
|
|
257
|
+
/* oxlint-enable effecttsgo/any-unknown-in-error-context */
|
|
196
258
|
});
|
|
197
259
|
|
|
198
260
|
const httpAction = httpActionGeneric((ctx, request): Promise<Response> => {
|
|
@@ -386,3 +448,5 @@ const applyMonkeyPatches = () => {
|
|
|
386
448
|
});
|
|
387
449
|
}
|
|
388
450
|
};
|
|
451
|
+
|
|
452
|
+
export { withBodyLimit } from "./internal/httpBody.js";
|