@confect/server 6.0.0 → 8.0.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +157 -1
  2. package/dist/ActionRunner.d.ts +3 -5
  3. package/dist/ActionRunner.d.ts.map +1 -1
  4. package/dist/ActionRunner.js.map +1 -1
  5. package/dist/DatabaseReader.d.ts +1 -1
  6. package/dist/DatabaseSchema.d.ts +5 -10
  7. package/dist/DatabaseSchema.d.ts.map +1 -1
  8. package/dist/DatabaseSchema.js +5 -6
  9. package/dist/DatabaseSchema.js.map +1 -1
  10. package/dist/Document.d.ts.map +1 -1
  11. package/dist/Document.js +35 -23
  12. package/dist/Document.js.map +1 -1
  13. package/dist/Handler.d.ts +1 -1
  14. package/dist/Handler.d.ts.map +1 -1
  15. package/dist/Handler.js.map +1 -1
  16. package/dist/MutationRunner.d.ts +5 -16
  17. package/dist/MutationRunner.d.ts.map +1 -1
  18. package/dist/MutationRunner.js +2 -12
  19. package/dist/MutationRunner.js.map +1 -1
  20. package/dist/QueryRunner.d.ts +3 -5
  21. package/dist/QueryRunner.d.ts.map +1 -1
  22. package/dist/QueryRunner.js.map +1 -1
  23. package/dist/RegisteredConvexFunction.d.ts +6 -6
  24. package/dist/RegisteredConvexFunction.d.ts.map +1 -1
  25. package/dist/RegisteredConvexFunction.js +36 -15
  26. package/dist/RegisteredConvexFunction.js.map +1 -1
  27. package/dist/RegisteredFunction.d.ts +24 -5
  28. package/dist/RegisteredFunction.d.ts.map +1 -1
  29. package/dist/RegisteredFunction.js +34 -5
  30. package/dist/RegisteredFunction.js.map +1 -1
  31. package/dist/RegisteredNodeFunction.js +3 -1
  32. package/dist/RegisteredNodeFunction.js.map +1 -1
  33. package/dist/SchemaToValidator.d.ts +8 -8
  34. package/dist/StorageActionWriter.d.ts +1 -1
  35. package/package.json +22 -18
  36. package/src/ActionRunner.ts +5 -1
  37. package/src/DatabaseSchema.ts +7 -10
  38. package/src/Document.ts +90 -58
  39. package/src/Handler.ts +5 -1
  40. package/src/MutationRunner.ts +6 -16
  41. package/src/QueryRunner.ts +5 -1
  42. package/src/RegisteredConvexFunction.ts +111 -94
  43. package/src/RegisteredFunction.ts +67 -22
  44. package/src/RegisteredNodeFunction.ts +4 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@confect/server",
3
- "version": "6.0.0",
3
+ "version": "8.0.0",
4
4
  "description": "Backend bindings to the Convex platform",
5
5
  "repository": {
6
6
  "type": "git",
@@ -44,38 +44,38 @@
44
44
  "license": "ISC",
45
45
  "devDependencies": {
46
46
  "@ark/attest": "0.56.0",
47
- "@effect/cluster": "0.56.4",
48
- "@effect/experimental": "0.58.0",
49
- "@effect/platform": "0.94.5",
50
- "@effect/rpc": "0.73.2",
51
- "@effect/sql": "0.49.0",
52
- "@effect/vitest": "0.27.0",
53
- "@effect/workflow": "0.16.0",
47
+ "@effect/cluster": "0.58.2",
48
+ "@effect/experimental": "0.60.0",
49
+ "@effect/platform": "0.96.1",
50
+ "@effect/rpc": "0.75.1",
51
+ "@effect/sql": "0.51.1",
52
+ "@effect/vitest": "0.29.0",
53
+ "@effect/workflow": "0.18.1",
54
54
  "@eslint/js": "10.0.1",
55
55
  "@swc/jest": "0.2.39",
56
56
  "@types/node": "25.3.3",
57
57
  "@vitest/coverage-v8": "3.2.4",
58
- "convex-test": "0.0.41",
58
+ "convex-test": "0.0.50",
59
59
  "dotenv": "17.3.1",
60
- "effect": "3.19.19",
60
+ "effect": "3.21.2",
61
61
  "eslint": "10.0.2",
62
62
  "prettier": "3.8.1",
63
63
  "tsdown": "0.20.3",
64
- "tsx": "^4.21.0",
64
+ "tsx": "4.21.0",
65
65
  "typescript": "5.9.3",
66
66
  "typescript-eslint": "8.56.1",
67
67
  "vite": "7.3.1",
68
68
  "vite-tsconfig-paths": "6.1.1",
69
69
  "vitest": "3.2.4",
70
- "@confect/cli": "6.0.0",
71
- "@confect/test": "6.0.0"
70
+ "@confect/cli": "8.0.0",
71
+ "@confect/test": "8.0.0"
72
72
  },
73
73
  "peerDependencies": {
74
- "@effect/platform": "^0.94.5",
75
- "@effect/platform-node": "^0.104.1",
74
+ "@effect/platform": "^0.96.1",
75
+ "@effect/platform-node": "^0.106.0",
76
76
  "convex": "^1.30.0",
77
- "effect": "^3.19.16",
78
- "@confect/core": "6.0.0"
77
+ "effect": "^3.21.2",
78
+ "@confect/core": "^8.0.0"
79
79
  },
80
80
  "engines": {
81
81
  "node": ">=22",
@@ -88,11 +88,15 @@
88
88
  "build": "tsdown --config-loader unrun",
89
89
  "dev": "tsdown --watch",
90
90
  "test": "vitest run",
91
+ "test:mock-backend": "vitest run --config vitest.mock-backend.config.ts",
92
+ "test:local-backend": "vitest run --config vitest.local-backend.config.ts",
93
+ "codegen:mock-backend": "cd test/mock-backend/fixtures && pnpm confect codegen && CONVEX_AGENT_MODE=anonymous pnpm convex dev --once --typecheck=disable --tail-logs=disable",
94
+ "codegen:local-backend": "cd test/local-backend/fixtures && pnpm confect codegen && CONVEX_AGENT_MODE=anonymous pnpm convex dev --once --typecheck=disable --tail-logs=disable",
91
95
  "typecheck": "tsc --noEmit --project tsconfig.json",
92
96
  "fix": "prettier --write . && eslint --fix . --max-warnings=0",
93
97
  "format": "prettier --check .",
94
98
  "lint": "eslint . --max-warnings=0",
95
- "bench": "tsx test/SchemaToValidator.bench.ts",
99
+ "bench": "tsx test/SchemaToValidator.bench.ts && tsx test/Document.bench.ts",
96
100
  "clean": "rm -rf dist coverage node_modules"
97
101
  }
98
102
  }
@@ -1,5 +1,6 @@
1
1
  import * as Ref from "@confect/core/Ref";
2
2
  import { type GenericActionCtx } from "convex/server";
3
+ import type { ParseResult, Effect } from "effect";
3
4
  import { Context, Layer } from "effect";
4
5
 
5
6
  const make =
@@ -7,7 +8,10 @@ const make =
7
8
  <Action extends Ref.AnyAction>(
8
9
  action: Action,
9
10
  ...args: Ref.OptionalArgs<Action>
10
- ) =>
11
+ ): Effect.Effect<
12
+ Ref.Returns<Action>,
13
+ Ref.Error<Action> | ParseResult.ParseError
14
+ > =>
11
15
  Ref.runWithCodec(
12
16
  action,
13
17
  (args[0] ?? {}) as Ref.Args<Action>,
@@ -3,14 +3,15 @@ import {
3
3
  defineSchema as defineConvexSchema,
4
4
  type SchemaDefinition,
5
5
  } from "convex/server";
6
- import { Array, pipe, Predicate, Record } from "effect";
6
+ import { Array, pipe, Record } from "effect";
7
7
  import * as Table from "./Table";
8
+ import { TypeId } from "@confect/core/DatabaseSchema";
8
9
 
9
- export const TypeId = "@confect/server/DatabaseSchema";
10
- export type TypeId = typeof TypeId;
11
-
12
- export const isSchema = (u: unknown): u is Any =>
13
- Predicate.hasProperty(u, TypeId);
10
+ export {
11
+ type Any,
12
+ isDatabaseSchema,
13
+ TypeId,
14
+ } from "@confect/core/DatabaseSchema";
14
15
 
15
16
  /**
16
17
  * A schema definition tracks the schema and its Convex schema definition.
@@ -31,10 +32,6 @@ export interface DatabaseSchema<Tables_ extends Table.AnyWithProps = never> {
31
32
  ): DatabaseSchema<Tables_ | TableDef>;
32
33
  }
33
34
 
34
- export interface Any {
35
- readonly [TypeId]: TypeId;
36
- }
37
-
38
35
  export interface AnyWithProps {
39
36
  readonly [TypeId]: TypeId;
40
37
  readonly tables: Record<string, Table.AnyWithProps>;
package/src/Document.ts CHANGED
@@ -1,6 +1,6 @@
1
+ import * as SystemFields from "@confect/core/SystemFields";
1
2
  import { Effect, Function, ParseResult, pipe, Schema } from "effect";
2
3
  import type { ReadonlyRecord } from "effect/Record";
3
- import * as SystemFields from "@confect/core/SystemFields";
4
4
  import type * as DataModel from "./DataModel";
5
5
  import type { ReadonlyValue } from "./SchemaToValidator";
6
6
  import type * as TableInfo from "./TableInfo";
@@ -10,6 +10,37 @@ export type WithoutSystemFields<Doc> = Omit<Doc, "_creationTime" | "_id">;
10
10
  export type Any = any;
11
11
  export type AnyEncoded = ReadonlyRecord<string, ReadonlyValue>;
12
12
 
13
+ type Decode = (doc: unknown) => Effect.Effect<unknown, ParseResult.ParseError>;
14
+
15
+ const decoderCache = new WeakMap<
16
+ Schema.Schema.AnyNoContext,
17
+ Map<string, Decode>
18
+ >();
19
+
20
+ const getDecoder = (
21
+ tableName: string,
22
+ tableSchema: Schema.Schema.AnyNoContext,
23
+ ): Decode => {
24
+ const byTable =
25
+ decoderCache.get(tableSchema) ??
26
+ (() => {
27
+ const map = new Map<string, Decode>();
28
+ decoderCache.set(tableSchema, map);
29
+ return map;
30
+ })();
31
+
32
+ return (
33
+ byTable.get(tableName) ??
34
+ (() => {
35
+ const decoder = Schema.decode(
36
+ SystemFields.extendWithSystemFields(tableName, tableSchema),
37
+ ) as Decode;
38
+ byTable.set(tableName, decoder);
39
+ return decoder;
40
+ })()
41
+ );
42
+ };
43
+
13
44
  export const decode = Function.dual<
14
45
  <
15
46
  DataModel_ extends DataModel.AnyWithProps,
@@ -53,36 +84,43 @@ export const decode = Function.dual<
53
84
  DataModel.TableInfoWithName_<DataModel_, TableName>["document"],
54
85
  DocumentDecodeError
55
86
  > =>
56
- Effect.gen(function* () {
57
- const TableSchemaWithSystemFields = SystemFields.extendWithSystemFields(
58
- tableName,
59
- tableSchema,
60
- );
61
-
62
- const encodedDoc =
63
- self as (typeof TableSchemaWithSystemFields)["Encoded"];
64
-
65
- const decodedDoc = yield* pipe(
66
- encodedDoc,
67
- Schema.decode(TableSchemaWithSystemFields),
68
- Effect.catchTag("ParseError", (parseError) =>
69
- Effect.gen(function* () {
70
- const formattedParseError =
71
- yield* ParseResult.TreeFormatter.formatError(parseError);
72
-
73
- return yield* new DocumentDecodeError({
74
- tableName,
75
- id: encodedDoc._id,
76
- parseError: formattedParseError,
77
- });
78
- }),
79
- ),
80
- );
81
-
82
- return decodedDoc;
83
- }),
87
+ pipe(
88
+ self,
89
+ getDecoder(tableName, tableSchema),
90
+ Effect.catchIf(ParseResult.isParseError, (parseError) =>
91
+ Effect.gen(function* () {
92
+ const formattedParseError =
93
+ yield* ParseResult.TreeFormatter.formatError(parseError);
94
+
95
+ return yield* new DocumentDecodeError({
96
+ tableName,
97
+ id: self._id,
98
+ parseError: formattedParseError,
99
+ });
100
+ }),
101
+ ),
102
+ Effect.map(
103
+ (decodedDoc) =>
104
+ decodedDoc as DataModel.TableInfoWithName_<
105
+ DataModel_,
106
+ TableName
107
+ >["document"],
108
+ ),
109
+ ),
84
110
  );
85
111
 
112
+ type Encode = (doc: unknown) => Effect.Effect<unknown, ParseResult.ParseError>;
113
+
114
+ const encoderCache = new WeakMap<Schema.Schema.AnyNoContext, Encode>();
115
+
116
+ const getEncoder = (tableSchema: Schema.Schema.AnyNoContext): Encode =>
117
+ encoderCache.get(tableSchema) ??
118
+ (() => {
119
+ const encoder = Schema.encode(tableSchema) as Encode;
120
+ encoderCache.set(tableSchema, encoder);
121
+ return encoder;
122
+ })();
123
+
86
124
  export const encode = Function.dual<
87
125
  <
88
126
  DataModel_ extends DataModel.AnyWithProps,
@@ -126,35 +164,29 @@ export const encode = Function.dual<
126
164
  DataModel.TableInfoWithName_<DataModel_, TableName>["encodedDocument"],
127
165
  DocumentEncodeError
128
166
  > =>
129
- Effect.gen(function* () {
130
- type TableSchemaWithSystemFields = SystemFields.ExtendWithSystemFields<
131
- TableName,
132
- TableInfo.TableSchema<
133
- DataModel.TableInfoWithName_<DataModel_, TableName>
134
- >
135
- >;
136
-
137
- const decodedDoc = self as TableSchemaWithSystemFields["Type"];
138
-
139
- const encodedDoc = yield* pipe(
140
- decodedDoc,
141
- Schema.encode(tableSchema),
142
- Effect.catchTag("ParseError", (parseError) =>
143
- Effect.gen(function* () {
144
- const formattedParseError =
145
- yield* ParseResult.TreeFormatter.formatError(parseError);
146
-
147
- return yield* new DocumentEncodeError({
148
- tableName,
149
- id: decodedDoc._id,
150
- parseError: formattedParseError,
151
- });
152
- }),
153
- ),
154
- );
155
-
156
- return encodedDoc;
157
- }),
167
+ pipe(
168
+ self,
169
+ getEncoder(tableSchema),
170
+ Effect.catchIf(ParseResult.isParseError, (parseError) =>
171
+ Effect.gen(function* () {
172
+ const formattedParseError =
173
+ yield* ParseResult.TreeFormatter.formatError(parseError);
174
+
175
+ return yield* new DocumentEncodeError({
176
+ tableName,
177
+ id: self._id,
178
+ parseError: formattedParseError,
179
+ });
180
+ }),
181
+ ),
182
+ Effect.map(
183
+ (encodedDoc) =>
184
+ encodedDoc as DataModel.TableInfoWithName_<
185
+ DataModel_,
186
+ TableName
187
+ >["encodedDocument"],
188
+ ),
189
+ ),
158
190
  );
159
191
 
160
192
  export class DocumentDecodeError extends Schema.TaggedError<DocumentDecodeError>()(
package/src/Handler.ts CHANGED
@@ -128,7 +128,11 @@ export type NodeRuntimeAction<
128
128
 
129
129
  type Base<FunctionSpec_ extends FunctionSpec.AnyWithProps, R> = (
130
130
  args: FunctionSpec.Args<FunctionSpec_>,
131
- ) => Effect.Effect<FunctionSpec.Returns<FunctionSpec_>, never, R>;
131
+ ) => Effect.Effect<
132
+ FunctionSpec.Returns<FunctionSpec_>,
133
+ FunctionSpec.Error<FunctionSpec_>,
134
+ R
135
+ >;
132
136
 
133
137
  export type Any = AnyConfectProvenance | AnyConvexProvenance;
134
138
 
@@ -1,13 +1,17 @@
1
1
  import * as Ref from "@confect/core/Ref";
2
2
  import { type GenericMutationCtx } from "convex/server";
3
- import { Context, Layer, Schema } from "effect";
3
+ import type { ParseResult, Effect } from "effect";
4
+ import { Context, Layer } from "effect";
4
5
 
5
6
  const make =
6
7
  (runMutation: GenericMutationCtx<any>["runMutation"]) =>
7
8
  <Mutation extends Ref.AnyMutation>(
8
9
  mutation: Mutation,
9
10
  ...args: Ref.OptionalArgs<Mutation>
10
- ) =>
11
+ ): Effect.Effect<
12
+ Ref.Returns<Mutation>,
13
+ Ref.Error<Mutation> | ParseResult.ParseError
14
+ > =>
11
15
  Ref.runWithCodec(
12
16
  mutation,
13
17
  (args[0] ?? {}) as Ref.Args<Mutation>,
@@ -22,17 +26,3 @@ export type MutationRunner = typeof MutationRunner.Identifier;
22
26
 
23
27
  export const layer = (runMutation: GenericMutationCtx<any>["runMutation"]) =>
24
28
  Layer.succeed(MutationRunner, make(runMutation));
25
-
26
- export class MutationRollback extends Schema.TaggedError<MutationRollback>()(
27
- "MutationRollback",
28
- {
29
- mutationName: Schema.String,
30
- error: Schema.Unknown,
31
- },
32
- ) {
33
- /* v8 ignore start */
34
- override get message(): string {
35
- return `Mutation ${this.mutationName} failed and was rolled back.\n\n${this.error}`;
36
- }
37
- /* v8 ignore stop */
38
- }
@@ -1,5 +1,6 @@
1
1
  import * as Ref from "@confect/core/Ref";
2
2
  import { type GenericQueryCtx } from "convex/server";
3
+ import type { ParseResult, Effect } from "effect";
3
4
  import { Context, Layer } from "effect";
4
5
 
5
6
  const make =
@@ -7,7 +8,10 @@ const make =
7
8
  <Query extends Ref.AnyQuery>(
8
9
  query: Query,
9
10
  ...args: Ref.OptionalArgs<Query>
10
- ) =>
11
+ ): Effect.Effect<
12
+ Ref.Returns<Query>,
13
+ Ref.Error<Query> | ParseResult.ParseError
14
+ > =>
11
15
  Ref.runWithCodec(
12
16
  query,
13
17
  (args[0] ?? {}) as Ref.Args<Query>,