@gaganref/confect-server 10.3.1-fork.8 → 10.3.1-fork.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/dist/ActionCall.d.ts +30 -0
  2. package/dist/ActionCall.d.ts.map +1 -0
  3. package/dist/ActionCall.js +41 -0
  4. package/dist/ActionCall.js.map +1 -0
  5. package/dist/ActionRunner.d.ts +1 -1
  6. package/dist/ActionRunner.d.ts.map +1 -1
  7. package/dist/ActionRunner.js +3 -3
  8. package/dist/ActionRunner.js.map +1 -1
  9. package/dist/AiGatewayDecisionClient.js +1 -1
  10. package/dist/AiGatewayLanguageClient.js +1 -1
  11. package/dist/Auth.d.ts +31 -2
  12. package/dist/Auth.d.ts.map +1 -1
  13. package/dist/Auth.js +23 -3
  14. package/dist/Auth.js.map +1 -1
  15. package/dist/Callback.d.ts +1 -1
  16. package/dist/Callback.d.ts.map +1 -1
  17. package/dist/Callback.js +6 -6
  18. package/dist/Callback.js.map +1 -1
  19. package/dist/ConvexConfigProvider.js +2 -2
  20. package/dist/ConvexFunctionDefinitions.d.ts.map +1 -1
  21. package/dist/ConvexFunctionDefinitions.js +4 -4
  22. package/dist/ConvexFunctionDefinitions.js.map +1 -1
  23. package/dist/ConvexLogger.js +1 -1
  24. package/dist/CronJobs.d.ts.map +1 -1
  25. package/dist/CronJobs.js +7 -6
  26. package/dist/CronJobs.js.map +1 -1
  27. package/dist/DatabaseSchema.js +2 -2
  28. package/dist/DatabaseWriter.d.ts +36 -2
  29. package/dist/DatabaseWriter.d.ts.map +1 -1
  30. package/dist/DatabaseWriter.js +55 -8
  31. package/dist/DatabaseWriter.js.map +1 -1
  32. package/dist/ExecutionMetadata.js +1 -1
  33. package/dist/FunctionImpl.js +2 -2
  34. package/dist/FunctionRegistryItem.d.ts +1 -0
  35. package/dist/FunctionRegistryItem.d.ts.map +1 -1
  36. package/dist/FunctionRegistryItem.js +2 -1
  37. package/dist/FunctionRegistryItem.js.map +1 -1
  38. package/dist/GroupImpl.d.ts +21 -2
  39. package/dist/GroupImpl.d.ts.map +1 -1
  40. package/dist/GroupImpl.js +21 -22
  41. package/dist/GroupImpl.js.map +1 -1
  42. package/dist/GroupRegistry.d.ts +1 -0
  43. package/dist/GroupRegistry.d.ts.map +1 -1
  44. package/dist/GroupRegistry.js +10 -3
  45. package/dist/GroupRegistry.js.map +1 -1
  46. package/dist/Handler.d.ts +8 -4
  47. package/dist/Handler.d.ts.map +1 -1
  48. package/dist/Handler.js.map +1 -1
  49. package/dist/HttpRouter.d.ts +3 -0
  50. package/dist/HttpRouter.d.ts.map +1 -1
  51. package/dist/HttpRouter.js +29 -10
  52. package/dist/HttpRouter.js.map +1 -1
  53. package/dist/MiddlewareImpl.js +1 -1
  54. package/dist/MountInit.d.ts +31 -0
  55. package/dist/MountInit.d.ts.map +1 -0
  56. package/dist/MountInit.js +49 -0
  57. package/dist/MountInit.js.map +1 -0
  58. package/dist/MutationRunner.d.ts +1 -1
  59. package/dist/MutationRunner.d.ts.map +1 -1
  60. package/dist/MutationRunner.js +3 -3
  61. package/dist/MutationRunner.js.map +1 -1
  62. package/dist/MutationTransactionContext.d.ts.map +1 -1
  63. package/dist/MutationTransactionContext.js +4 -3
  64. package/dist/MutationTransactionContext.js.map +1 -1
  65. package/dist/OfficialComponent.d.ts +60 -0
  66. package/dist/OfficialComponent.d.ts.map +1 -0
  67. package/dist/OfficialComponent.js +72 -0
  68. package/dist/OfficialComponent.js.map +1 -0
  69. package/dist/OrderedQuery.d.ts +4 -5
  70. package/dist/OrderedQuery.d.ts.map +1 -1
  71. package/dist/OrderedQuery.js.map +1 -1
  72. package/dist/QueryInitializer.js +2 -2
  73. package/dist/QueryRunner.js +2 -2
  74. package/dist/QueryStream.d.ts +120 -12
  75. package/dist/QueryStream.d.ts.map +1 -1
  76. package/dist/QueryStream.js +143 -41
  77. package/dist/QueryStream.js.map +1 -1
  78. package/dist/QueryStreamCursor.js +1 -1
  79. package/dist/QueryStreamIndexPrefix.js +1 -1
  80. package/dist/QueryStreamIndexRange.js +2 -2
  81. package/dist/QueryStreamKey.js +1 -1
  82. package/dist/QueryStreamKeyBounds.js +2 -2
  83. package/dist/QueryStreamKeyLayout.js +2 -2
  84. package/dist/QueryStreamKeyValues.js +2 -2
  85. package/dist/QueryStreamPagination.js +1 -1
  86. package/dist/QueryStreamReadBudget.d.ts +3 -2
  87. package/dist/QueryStreamReadBudget.d.ts.map +1 -1
  88. package/dist/QueryStreamReadBudget.js +8 -5
  89. package/dist/QueryStreamReadBudget.js.map +1 -1
  90. package/dist/QueryTransactionContext.js +2 -2
  91. package/dist/RegisteredConvexFunction.d.ts +10 -5
  92. package/dist/RegisteredConvexFunction.d.ts.map +1 -1
  93. package/dist/RegisteredConvexFunction.js +45 -29
  94. package/dist/RegisteredConvexFunction.js.map +1 -1
  95. package/dist/RegisteredFunction.d.ts +39 -22
  96. package/dist/RegisteredFunction.d.ts.map +1 -1
  97. package/dist/RegisteredFunction.js +155 -42
  98. package/dist/RegisteredFunction.js.map +1 -1
  99. package/dist/RegisteredNodeFunction.d.ts +1 -1
  100. package/dist/RegisteredNodeFunction.d.ts.map +1 -1
  101. package/dist/RegisteredNodeFunction.js +10 -6
  102. package/dist/RegisteredNodeFunction.js.map +1 -1
  103. package/dist/RegistryItems.d.ts +7 -0
  104. package/dist/RegistryItems.d.ts.map +1 -1
  105. package/dist/RegistryItems.js +8 -3
  106. package/dist/RegistryItems.js.map +1 -1
  107. package/dist/RequestMetadata.js +1 -1
  108. package/dist/Scheduler.d.ts +1 -1
  109. package/dist/Scheduler.d.ts.map +1 -1
  110. package/dist/Scheduler.js +14 -13
  111. package/dist/Scheduler.js.map +1 -1
  112. package/dist/Storage.js +1 -1
  113. package/dist/StorageActionWriter.js +1 -1
  114. package/dist/StorageReader.js +1 -1
  115. package/dist/StorageWriter.js +1 -1
  116. package/dist/Table.d.ts +6 -6
  117. package/dist/Table.d.ts.map +1 -1
  118. package/dist/TableInfo.d.ts +1 -1
  119. package/dist/TableInfo.d.ts.map +1 -1
  120. package/dist/TableInfo.js.map +1 -1
  121. package/dist/TransactionMetadata.js +1 -1
  122. package/dist/VectorSearch.js +1 -1
  123. package/dist/index.d.ts +3 -0
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +21 -18
  126. package/dist/internal/AiGatewayDecisionClient.js +1 -1
  127. package/dist/internal/AiGatewayLanguageClient.js +1 -1
  128. package/dist/internal/AiGatewayServiceToken.js +1 -1
  129. package/dist/internal/DocumentPatch.d.ts +16 -0
  130. package/dist/internal/DocumentPatch.d.ts.map +1 -0
  131. package/dist/internal/DocumentPatch.js +36 -0
  132. package/dist/internal/DocumentPatch.js.map +1 -0
  133. package/dist/internal/httpBody.d.ts +13 -0
  134. package/dist/internal/httpBody.d.ts.map +1 -0
  135. package/dist/internal/httpBody.js +47 -0
  136. package/dist/internal/httpBody.js.map +1 -0
  137. package/dist/internal/httpOptions.d.ts +4 -0
  138. package/dist/internal/httpOptions.d.ts.map +1 -0
  139. package/dist/internal/httpOptions.js +83 -0
  140. package/dist/internal/httpOptions.js.map +1 -0
  141. package/dist/internal/observability.d.ts +5 -0
  142. package/dist/internal/observability.d.ts.map +1 -0
  143. package/dist/internal/observability.js +21 -0
  144. package/dist/internal/observability.js.map +1 -0
  145. package/dist/internal/reservedArgument.d.ts +8 -0
  146. package/dist/internal/reservedArgument.d.ts.map +1 -0
  147. package/dist/internal/reservedArgument.js +30 -0
  148. package/dist/internal/reservedArgument.js.map +1 -0
  149. package/package.json +5 -5
  150. package/src/ActionCall.ts +83 -0
  151. package/src/ActionRunner.ts +3 -2
  152. package/src/Auth.ts +49 -1
  153. package/src/Callback.ts +3 -2
  154. package/src/ConvexFunctionDefinitions.ts +2 -1
  155. package/src/CronJobs.ts +2 -1
  156. package/src/DatabaseWriter.ts +183 -25
  157. package/src/FunctionRegistryItem.ts +4 -0
  158. package/src/GroupImpl.ts +61 -8
  159. package/src/GroupRegistry.ts +7 -3
  160. package/src/Handler.ts +21 -5
  161. package/src/HttpRouter.ts +69 -5
  162. package/src/MountInit.ts +80 -0
  163. package/src/MutationRunner.ts +3 -2
  164. package/src/MutationTransactionContext.ts +2 -1
  165. package/src/OfficialComponent.ts +168 -0
  166. package/src/OrderedQuery.ts +4 -5
  167. package/src/QueryStream.ts +391 -61
  168. package/src/QueryStreamReadBudget.ts +19 -6
  169. package/src/RegisteredConvexFunction.ts +35 -2
  170. package/src/RegisteredFunction.ts +251 -45
  171. package/src/RegisteredNodeFunction.ts +11 -1
  172. package/src/RegistryItems.ts +31 -0
  173. package/src/Scheduler.ts +17 -19
  174. package/src/TableInfo.ts +2 -1
  175. package/src/index.ts +3 -0
  176. package/src/internal/DocumentPatch.ts +87 -0
  177. package/src/internal/httpBody.ts +72 -0
  178. package/src/internal/httpOptions.ts +154 -0
  179. package/src/internal/observability.ts +27 -0
  180. package/src/internal/reservedArgument.ts +56 -0
@@ -10,11 +10,14 @@ 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
 
@@ -97,11 +100,54 @@ export type DatabaseWriterTag<
97
100
  DatabaseWriterService<DatabaseSchema_, Docs>
98
101
  >;
99
102
 
103
+ /**
104
+ * Decoded documents surrounding one successful write.
105
+ */
106
+ export type Change<S extends DatabaseSchema.AnyWithProps> = {
107
+ [Name in DataModel.TableNames<DataModel.FromSchema<S>>]: {
108
+ readonly tableName: Name;
109
+ } & (
110
+ | {
111
+ readonly operation: "insert";
112
+ readonly oldDoc: null;
113
+ readonly newDoc: DocumentByName_<DataModel.FromSchema<S>, Name>;
114
+ }
115
+ | {
116
+ readonly operation: "update";
117
+ readonly oldDoc: DocumentByName_<DataModel.FromSchema<S>, Name>;
118
+ readonly newDoc: DocumentByName_<DataModel.FromSchema<S>, Name>;
119
+ }
120
+ | {
121
+ readonly operation: "delete";
122
+ readonly oldDoc: DocumentByName_<DataModel.FromSchema<S>, Name>;
123
+ readonly newDoc: null;
124
+ }
125
+ );
126
+ }[DataModel.TableNames<DataModel.FromSchema<S>>];
127
+
128
+ /**
129
+ * Runs after the write, in its transaction. A defect aborts the mutation.
130
+ *
131
+ * Only writes through this writer are observed. Raw `ctx.db`, `MutationCtx`
132
+ * writes and a second `DatabaseWriter.make` bypass the hook, so a host that
133
+ * relies on it keeps raw writers out of its functions. Component writers and
134
+ * nested `runMutation` calls each need their own hook installation.
135
+ *
136
+ * Concurrent writes to one document (for example under `Effect.all`) can
137
+ * interleave the read-write-read steps. Await them sequentially when the hook
138
+ * needs exact per-write snapshots. The writer takes no lock, because a hook
139
+ * that writes through the same writer would deadlock under one.
140
+ */
141
+ export type Hook<S extends DatabaseSchema.AnyWithProps> = (
142
+ change: Change<S>,
143
+ ) => Effect.Effect<void>;
144
+
100
145
  export const make = <DatabaseSchema_ extends DatabaseSchema.AnyWithProps>(
101
146
  databaseSchema: DatabaseSchema_,
102
147
  convexDatabaseWriter: GenericDatabaseWriter<
103
148
  DataModel.ToConvex<DataModel.FromSchema<DatabaseSchema_>>
104
149
  >,
150
+ hook?: Hook<DatabaseSchema_>,
105
151
  ): DatabaseWriterService<DatabaseSchema_> => {
106
152
  type DataModel_ = DataModel.FromSchema<DatabaseSchema_>;
107
153
 
@@ -131,38 +177,108 @@ export const make = <DatabaseSchema_ extends DatabaseSchema.AnyWithProps>(
131
177
  return id as unknown as DataModel.Id<DataModel_, TableName>;
132
178
  });
133
179
 
180
+ // A codec whose encoding spans fields cannot encode a patch on its own, so
181
+ // the patch is merged into the decoded document and the result replaces it.
182
+ const replacePatched = (
183
+ id: DataModel.Id<DataModel_, TableName>,
184
+ patchedValues: PatchValue<
185
+ Document.WithoutSystemFields<DocumentByName_<DataModel_, TableName>>
186
+ >,
187
+ ) =>
188
+ Effect.gen(function* () {
189
+ const originalDecodedDoc = yield* QueryInitializer.getById<
190
+ Table.AnyWithProps,
191
+ TableName
192
+ >(
193
+ convexDatabaseWriter as any,
194
+ tableDef,
195
+ )(id as unknown as GenericId<TableName>);
196
+
197
+ const updatedEncodedDoc = yield* pipe(
198
+ patchedValues,
199
+ Record.reduce(originalDecodedDoc, (acc, value, key) =>
200
+ value === undefined
201
+ ? Record.remove(acc, key)
202
+ : Record.set(acc, key, value),
203
+ ),
204
+ Document.encode(tableDef),
205
+ );
206
+
207
+ yield* Effect.promise(() =>
208
+ convexDatabaseWriter.replace(
209
+ id as unknown as GenericId<TableName>,
210
+ updatedEncodedDoc as Expand<
211
+ BetterOmit<
212
+ DocumentByName<DataModel.ToConvex<DataModel_>, TableName>,
213
+ "_creationTime" | "_id"
214
+ >
215
+ >,
216
+ ),
217
+ );
218
+ });
219
+
134
220
  const patch = Effect.fn("DatabaseWriter.patch")(function* (
135
221
  id: DataModel.Id<DataModel_, TableName>,
136
222
  patchedValues: PatchValue<
137
223
  Document.WithoutSystemFields<DocumentByName_<DataModel_, TableName>>
138
224
  >,
139
225
  ) {
140
- const originalDecodedDoc = yield* QueryInitializer.getById<
141
- Table.AnyWithProps,
142
- TableName
143
- >(
144
- convexDatabaseWriter as any,
145
- tableDef,
146
- )(id as unknown as GenericId<TableName>);
226
+ const encodePatch = DocumentPatch.encoder(tableDef);
227
+ if (encodePatch === undefined) {
228
+ return yield* replacePatched(id, patchedValues);
229
+ }
147
230
 
148
- const updatedEncodedDoc = yield* pipe(
149
- patchedValues,
150
- Record.reduce(originalDecodedDoc, (acc, value, key) =>
151
- value === undefined
152
- ? Record.remove(acc, key)
153
- : Record.set(acc, key, value),
231
+ const storedDoc = yield* pipe(
232
+ Effect.promise(() =>
233
+ convexDatabaseWriter.get(id as unknown as GenericId<TableName>),
234
+ ),
235
+ Effect.andThen((value) =>
236
+ Effect.fromResult(
237
+ Result.fromNullishOr(
238
+ value,
239
+ () =>
240
+ new QueryInitializer.GetByIdFailure({
241
+ tableName,
242
+ id,
243
+ }),
244
+ ),
245
+ ),
246
+ ),
247
+ );
248
+ // The patch must encode and the patched document must decode. When
249
+ // either fails, the stored document decides whose fault that is.
250
+ const encodedPatch = yield* pipe(
251
+ encodePatch(patchedValues),
252
+ Effect.mapError(({ message }) => message),
253
+ Effect.tap((delta) =>
254
+ pipe(
255
+ { ...storedDoc, ...delta },
256
+ Record.filter(Predicate.isNotUndefined),
257
+ Document.decode(tableDef),
258
+ Effect.mapError(({ parseError }) => parseError),
259
+ ),
260
+ ),
261
+ Effect.catch((parseError) =>
262
+ pipe(
263
+ Document.decode(storedDoc, tableDef),
264
+ Effect.andThen(
265
+ Effect.fail(
266
+ new Document.DocumentEncodeError({
267
+ tableName,
268
+ id,
269
+ parseError,
270
+ }),
271
+ ),
272
+ ),
273
+ ),
154
274
  ),
155
- Document.encode(tableDef),
156
275
  );
157
276
 
158
277
  yield* Effect.promise(() =>
159
- convexDatabaseWriter.replace(
278
+ convexDatabaseWriter.patch(
160
279
  id as unknown as GenericId<TableName>,
161
- updatedEncodedDoc as Expand<
162
- BetterOmit<
163
- DocumentByName<DataModel.ToConvex<DataModel_>, TableName>,
164
- "_creationTime" | "_id"
165
- >
280
+ encodedPatch as Partial<
281
+ DocumentByName<DataModel.ToConvex<DataModel_>, TableName>
166
282
  >,
167
283
  ),
168
284
  );
@@ -194,11 +310,52 @@ export const make = <DatabaseSchema_ extends DatabaseSchema.AnyWithProps>(
194
310
  convexDatabaseWriter.delete(id as unknown as GenericId<TableName>),
195
311
  );
196
312
 
313
+ const accessor = { insert, patch, replace, delete: delete_ };
314
+ if (hook === undefined) return accessor;
315
+
316
+ const read = (id: DataModel.Id<DataModel_, TableName>) =>
317
+ QueryInitializer.getById<Table.AnyWithProps, TableName>(
318
+ convexDatabaseWriter as any,
319
+ tableDef,
320
+ )(id as unknown as GenericId<TableName>);
321
+ const notify = (
322
+ operation: "insert" | "update" | "delete",
323
+ oldDoc: unknown,
324
+ newDoc: unknown,
325
+ ) =>
326
+ hook({ tableName, operation, oldDoc, newDoc } as Change<DatabaseSchema_>);
327
+ const update =
328
+ <A extends ReadonlyArray<unknown>, E, BeforeError>(
329
+ before: (
330
+ id: DataModel.Id<DataModel_, TableName>,
331
+ ) => Effect.Effect<unknown, BeforeError>,
332
+ write: (
333
+ id: DataModel.Id<DataModel_, TableName>,
334
+ ...args: A
335
+ ) => Effect.Effect<void, E>,
336
+ ) =>
337
+ (id: DataModel.Id<DataModel_, TableName>, ...args: A) =>
338
+ Effect.gen(function* () {
339
+ const oldDoc = yield* before(id);
340
+ yield* write(id, ...args);
341
+ yield* notify("update", oldDoc, yield* read(id).pipe(Effect.orDie));
342
+ });
343
+
197
344
  return {
198
- insert,
199
- patch,
200
- replace,
201
- delete: delete_,
345
+ insert: (document: Parameters<typeof insert>[0]) =>
346
+ Effect.gen(function* () {
347
+ const id = yield* insert(document);
348
+ yield* notify("insert", null, yield* read(id).pipe(Effect.orDie));
349
+ return id;
350
+ }),
351
+ patch: update(read, patch),
352
+ replace: update((id) => read(id).pipe(Effect.orDie), replace),
353
+ delete: (id: DataModel.Id<DataModel_, TableName>) =>
354
+ Effect.gen(function* () {
355
+ const oldDoc = yield* read(id).pipe(Effect.orDie);
356
+ yield* delete_(id);
357
+ yield* notify("delete", oldDoc, null);
358
+ }),
202
359
  };
203
360
  };
204
361
 
@@ -225,8 +382,9 @@ export const layer = <DatabaseSchema_ extends DatabaseSchema.AnyWithProps>(
225
382
  convexDatabaseWriter: GenericDatabaseWriter<
226
383
  DataModel.ToConvex<DataModel.FromSchema<DatabaseSchema_>>
227
384
  >,
385
+ hook?: Hook<DatabaseSchema_>,
228
386
  ) =>
229
387
  Layer.succeed(
230
388
  DatabaseWriter<DatabaseSchema_>(),
231
- make(databaseSchema, convexDatabaseWriter),
389
+ make(databaseSchema, convexDatabaseWriter, hook),
232
390
  );
@@ -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 findUnfinalizedGroupImpl = <S>(
115
- context: Context.Context<S>,
116
- ): Option.Option<AnyUnfinalized> =>
117
- Array.findFirst(context.mapUnsafe.values(), isUnfinalizedGroupImpl);
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 const finalize = (
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
- findUnfinalizedGroupImpl(context).pipe(
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 Unfinalized GroupImpl service was found in the layer's context.",
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,
@@ -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 yield* Ref.get(registry);
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
- export type ActionServices<
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?: { readonly api?: HttpApi.HttpApi<Id, Groups> | undefined },
123
+ options?: {
124
+ readonly observability?: Layer.Layer<never> | undefined;
125
+ readonly api?: HttpApi.HttpApi<Id, Groups> | undefined;
126
+ },
120
127
  ): ConvexHttpRouter =>
121
- makeWithLayer(routes, RegisteredFunction.baseActionLayer, options?.api);
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?: { readonly api?: HttpApi.HttpApi<Id, Groups> | undefined },
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
- const AppLayer = routes.pipe(
186
- Layer.provideMerge(ConvexLogger.layer),
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";