@confect/core 9.0.0-next.0 → 9.0.0-next.10

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 (81) hide show
  1. package/CHANGELOG.md +268 -4
  2. package/dist/FunctionProvenance.d.ts +101 -95
  3. package/dist/FunctionProvenance.d.ts.map +1 -1
  4. package/dist/FunctionProvenance.js +25 -6
  5. package/dist/FunctionProvenance.js.map +1 -1
  6. package/dist/FunctionSpec.d.ts +178 -218
  7. package/dist/FunctionSpec.d.ts.map +1 -1
  8. package/dist/FunctionSpec.js +2 -2
  9. package/dist/FunctionSpec.js.map +1 -1
  10. package/dist/GenericId.d.ts +7 -12
  11. package/dist/GenericId.d.ts.map +1 -1
  12. package/dist/GenericId.js +2 -1
  13. package/dist/GenericId.js.map +1 -1
  14. package/dist/GroupPath.d.ts +10 -16
  15. package/dist/GroupPath.d.ts.map +1 -1
  16. package/dist/GroupSpec.d.ts +38 -37
  17. package/dist/GroupSpec.d.ts.map +1 -1
  18. package/dist/GroupSpec.js +9 -3
  19. package/dist/GroupSpec.js.map +1 -1
  20. package/dist/Identifier.d.ts +14 -0
  21. package/dist/Identifier.d.ts.map +1 -0
  22. package/dist/{internal/utils.js → Identifier.js} +26 -3
  23. package/dist/Identifier.js.map +1 -0
  24. package/dist/Lazy.d.ts +22 -0
  25. package/dist/Lazy.d.ts.map +1 -0
  26. package/dist/Lazy.js +44 -0
  27. package/dist/Lazy.js.map +1 -0
  28. package/dist/PaginationResult.d.ts +11 -18
  29. package/dist/PaginationResult.d.ts.map +1 -1
  30. package/dist/PaginationResult.js +1 -1
  31. package/dist/PaginationResult.js.map +1 -1
  32. package/dist/Ref.d.ts +55 -51
  33. package/dist/Ref.d.ts.map +1 -1
  34. package/dist/Ref.js +17 -6
  35. package/dist/Ref.js.map +1 -1
  36. package/dist/Refs.d.ts +21 -21
  37. package/dist/Refs.d.ts.map +1 -1
  38. package/dist/Refs.js +5 -10
  39. package/dist/Refs.js.map +1 -1
  40. package/dist/Registry.d.ts +7 -11
  41. package/dist/Registry.d.ts.map +1 -1
  42. package/dist/Registry.js +2 -1
  43. package/dist/Registry.js.map +1 -1
  44. package/dist/RuntimeAndFunctionType.d.ts +36 -41
  45. package/dist/RuntimeAndFunctionType.d.ts.map +1 -1
  46. package/dist/Spec.d.ts +23 -32
  47. package/dist/Spec.d.ts.map +1 -1
  48. package/dist/Spec.js +9 -48
  49. package/dist/Spec.js.map +1 -1
  50. package/dist/SystemFields.d.ts +10 -16
  51. package/dist/SystemFields.d.ts.map +1 -1
  52. package/dist/SystemFields.js +1 -1
  53. package/dist/SystemFields.js.map +1 -1
  54. package/dist/Types.d.ts +27 -27
  55. package/dist/Types.d.ts.map +1 -1
  56. package/dist/UserIdentity.d.ts +56 -63
  57. package/dist/UserIdentity.d.ts.map +1 -1
  58. package/dist/UserIdentity.js +1 -1
  59. package/dist/UserIdentity.js.map +1 -1
  60. package/dist/index.d.ts +17 -15
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +3 -1
  63. package/dist/tsconfig.src.tsbuildinfo +1 -0
  64. package/package.json +37 -46
  65. package/src/FunctionProvenance.ts +32 -10
  66. package/src/FunctionSpec.ts +5 -5
  67. package/src/GenericId.ts +3 -1
  68. package/src/GroupSpec.ts +16 -11
  69. package/src/{internal/utils.ts → Identifier.ts} +34 -0
  70. package/src/Lazy.ts +40 -0
  71. package/src/PaginationResult.ts +1 -1
  72. package/src/Ref.ts +23 -8
  73. package/src/Refs.ts +13 -44
  74. package/src/Registry.ts +2 -1
  75. package/src/Spec.ts +18 -81
  76. package/src/SystemFields.ts +1 -1
  77. package/src/UserIdentity.ts +1 -1
  78. package/src/index.ts +2 -0
  79. package/dist/internal/utils.d.ts +0 -5
  80. package/dist/internal/utils.d.ts.map +0 -1
  81. package/dist/internal/utils.js.map +0 -1
@@ -1,6 +1,7 @@
1
1
  import type { DefaultFunctionArgs } from "convex/server";
2
2
  import type { Schema } from "effect";
3
- import { Data } from "effect";
3
+ import * as Data from "effect/Data";
4
+ import * as Lazy from "./Lazy";
4
5
 
5
6
  export type FunctionProvenance = Data.TaggedEnum<{
6
7
  Confect: {
@@ -43,20 +44,41 @@ export interface AnyConvex extends Convex<DefaultFunctionArgs, any> {}
43
44
 
44
45
  export const FunctionProvenance = Data.taggedEnum<FunctionProvenance>();
45
46
 
47
+ /**
48
+ * Build a `Confect` provenance from lazy schema thunks. `args`, `returns`,
49
+ * and `error` are exposed as sync lazy memoised getters (via {@link Lazy.defineProperty})
50
+ * that only evaluate their thunk on first access, mirroring how `Table`
51
+ * defers `Fields`/`Doc`/`tableDefinition`. This keeps importing the assembled
52
+ * `_generated/spec.ts` cheap — no `Schema.Struct(...)` / `Schema.Array(...)`
53
+ * work runs at module load; it is deferred to the first invocation that
54
+ * actually compiles validators or runs a codec.
55
+ *
56
+ * The object is built by hand rather than through `FunctionProvenance.Confect`
57
+ * because the `Data` constructor copies its input with `Object.assign`, which
58
+ * would force the getters at construction time and defeat the laziness.
59
+ * `error` is only installed when an `errorThunk` is provided, so its absence
60
+ * is observable via `"error" in provenance` without forcing anything; nothing
61
+ * relies on `Data`'s structural `Equal`/`Hash` for provenance values.
62
+ */
46
63
  export const Confect = <
47
64
  Args extends Schema.Schema.AnyNoContext,
48
65
  Returns extends Schema.Schema.AnyNoContext,
49
66
  Error extends Schema.Schema.AnyNoContext = never,
50
67
  >(
51
- args: Args,
52
- returns: Returns,
53
- error?: Error,
54
- ) =>
55
- FunctionProvenance.Confect({
56
- args,
57
- returns,
58
- ...(error !== undefined ? { error } : {}),
59
- });
68
+ args: () => Args,
69
+ returns: () => Returns,
70
+ error?: () => Error,
71
+ ): Confect<Args, Returns, Error> => {
72
+ const provenance = { _tag: "Confect" as const };
73
+
74
+ Lazy.defineProperty(provenance, "args", args);
75
+ Lazy.defineProperty(provenance, "returns", returns);
76
+ if (error !== undefined) {
77
+ Lazy.defineProperty(provenance, "error", error);
78
+ }
79
+
80
+ return provenance as Confect<Args, Returns, Error>;
81
+ };
60
82
 
61
83
  export const Convex = <_Args extends DefaultFunctionArgs, _Returns>() =>
62
84
  FunctionProvenance.Convex(
@@ -6,9 +6,9 @@ import type {
6
6
  RegisteredQuery,
7
7
  } from "convex/server";
8
8
  import type { Schema } from "effect";
9
- import { Predicate } from "effect";
9
+ import * as Predicate from "effect/Predicate";
10
10
  import * as FunctionProvenance from "./FunctionProvenance";
11
- import { validateConfectFunctionIdentifier } from "./internal/utils";
11
+ import { validateConfectFunctionIdentifier } from "./Identifier";
12
12
  import * as RuntimeAndFunctionType from "./RuntimeAndFunctionType";
13
13
 
14
14
  export const TypeId = "@confect/core/FunctionSpec";
@@ -225,9 +225,9 @@ const make =
225
225
  error,
226
226
  }: {
227
227
  name: Name_;
228
- args: Args_;
229
- returns: Returns_;
230
- error?: Error_;
228
+ args: () => Args_;
229
+ returns: () => Returns_;
230
+ error?: () => Error_;
231
231
  }): FunctionSpec<
232
232
  RuntimeAndFunctionType_,
233
233
  FunctionVisibility_,
package/src/GenericId.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import type { GenericId as ConvexGenericId } from "convex/values";
2
- import { type Option, Schema, SchemaAST } from "effect";
2
+ import type { Option } from "effect";
3
+ import * as Schema from "effect/Schema";
4
+ import * as SchemaAST from "effect/SchemaAST";
3
5
 
4
6
  const ConvexId = Symbol.for("ConvexId");
5
7
 
package/src/GroupSpec.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { Predicate, Record } from "effect";
1
+ import * as Predicate from "effect/Predicate";
2
+ import * as Record from "effect/Record";
2
3
  import type * as FunctionSpec from "./FunctionSpec";
3
4
  import type * as RuntimeAndFunctionType from "./RuntimeAndFunctionType";
4
- import { validateConfectFunctionIdentifier } from "./internal/utils";
5
+ import { validateConfectFunctionIdentifier } from "./Identifier";
5
6
 
6
7
  export const TypeId = "@confect/core/GroupSpec";
7
8
  export type TypeId = typeof TypeId;
@@ -13,7 +14,11 @@ export interface GroupSpec<
13
14
  Runtime extends RuntimeAndFunctionType.Runtime,
14
15
  Name_ extends string,
15
16
  Functions_ extends FunctionSpec.AnyWithPropsWithRuntime<Runtime> = never,
16
- Groups_ extends AnyWithPropsWithRuntime<Runtime> = never,
17
+ // Subgroups may be of any runtime, independent of this group's own runtime: a
18
+ // group is only a namespace for its children, which are otherwise-independent
19
+ // modules. Functions, by contrast, stay homogeneous (a Node group only accepts
20
+ // Node actions) — `addFunction` keeps the `<Runtime>` bound below.
21
+ Groups_ extends AnyWithProps = never,
17
22
  > {
18
23
  readonly [TypeId]: TypeId;
19
24
  readonly runtime: Runtime;
@@ -31,14 +36,11 @@ export interface GroupSpec<
31
36
  function_: Function,
32
37
  ): GroupSpec<Runtime, Name_, Functions_ | Function, Groups_>;
33
38
 
34
- addGroup<Group extends AnyWithPropsWithRuntime<Runtime>>(
39
+ addGroup<Group extends AnyWithProps>(
35
40
  group: Group,
36
41
  ): GroupSpec<Runtime, Name_, Functions_, Groups_ | Group>;
37
42
 
38
- addGroupAt<
39
- const AtName extends string,
40
- Group extends AnyWithPropsWithRuntime<Runtime>,
41
- >(
43
+ addGroupAt<const AtName extends string, Group extends AnyWithProps>(
42
44
  name: AtName,
43
45
  group: Group,
44
46
  ): GroupSpec<Runtime, Name_, Functions_, Groups_ | NamedAt<Group, AtName>>;
@@ -209,7 +211,10 @@ export const withName = <const Name_ extends string>(
209
211
  return group_;
210
212
  }
211
213
 
212
- // Keep object identity so impls can pass the same GroupSpec instance
213
- // imported from a sibling `.spec.ts` into `resolveGroupPath`.
214
- return Object.assign(group_, { name }) as AnyWithProps;
214
+ return makeProto({
215
+ runtime: group_.runtime,
216
+ name,
217
+ functions: group_.functions,
218
+ groups: group_.groups,
219
+ });
215
220
  };
@@ -56,6 +56,12 @@ const RESERVED_CONVEX_FILE_NAMES = new Set(["schema", "http", "crons"]);
56
56
 
57
57
  const jsIdentifierRegex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
58
58
 
59
+ // Stricter than `jsIdentifierRegex`: tables cannot start with `_` (Convex
60
+ // reserves leading underscores for system tables) or `$` (Convex's table
61
+ // naming grammar does not accept it). Letters/digits/underscore only,
62
+ // letter-leading.
63
+ const tableNameRegex = /^[a-zA-Z][a-zA-Z0-9_]*$/;
64
+
59
65
  const isReservedJsIdentifier = (identifier: string) =>
60
66
  RESERVED_JS_IDENTIFIERS.has(identifier);
61
67
 
@@ -65,6 +71,9 @@ const isReservedConvexFileName = (fileName: string) =>
65
71
  const matchesJsIdentifierPattern = (identifier: string) =>
66
72
  jsIdentifierRegex.test(identifier);
67
73
 
74
+ const matchesTableNamePattern = (identifier: string) =>
75
+ tableNameRegex.test(identifier);
76
+
68
77
  export const validateConfectFunctionIdentifier = (identifier: string) => {
69
78
  if (!matchesJsIdentifierPattern(identifier)) {
70
79
  throw new Error(
@@ -84,3 +93,28 @@ export const validateConfectFunctionIdentifier = (identifier: string) => {
84
93
  );
85
94
  }
86
95
  };
96
+
97
+ /**
98
+ * Validate that `identifier` is suitable as a Convex table name (and, equivalently,
99
+ * as a `confect/tables/<identifier>.ts` filename).
100
+ *
101
+ * Rules:
102
+ * - Must match `/^[A-Za-z][A-Za-z0-9_]*$/` — letter-leading, alphanumeric plus
103
+ * underscore. No `$` (not a valid Convex table name character); no leading
104
+ * `_` (Convex reserves `_<name>` for its system tables).
105
+ * - Must not be a reserved JavaScript identifier, so the name can also be used
106
+ * as a binding name in generated code without escaping.
107
+ */
108
+ export const validateConfectTableIdentifier = (identifier: string) => {
109
+ if (!matchesTableNamePattern(identifier)) {
110
+ throw new Error(
111
+ `Expected a valid Confect table identifier, but received: "${identifier}". Valid table identifiers must start with a letter and can only contain letters, numbers, and underscores. Leading underscores are reserved for Convex system tables.`,
112
+ );
113
+ }
114
+
115
+ if (isReservedJsIdentifier(identifier)) {
116
+ throw new Error(
117
+ `Expected a valid Confect table identifier, but received: "${identifier}". "${identifier}" is a reserved JavaScript identifier.`,
118
+ );
119
+ }
120
+ };
package/src/Lazy.ts ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Install a lazy memoised property on `target`. The first access runs
3
+ * `compute()` and replaces the getter with a plain, non-writable data
4
+ * property whose value is the computed result. Subsequent accesses hit
5
+ * the V8 fast path for own data properties — no function call, identical
6
+ * returned reference — so first and second-and-subsequent accesses are
7
+ * observably indistinguishable.
8
+ *
9
+ * The replacement property is `enumerable: true` so the lazy property
10
+ * still participates in `Object.keys` / `JSON.stringify` after it
11
+ * materialises, matching the shape of a plain data property. The property
12
+ * is also `enumerable` before materialising, so presence checks
13
+ * (`"key" in target`, `Object.hasOwn(target, key)`) observe it without
14
+ * forcing the computation.
15
+ *
16
+ * This is the single shared implementation consumed across packages (e.g.
17
+ * `@confect/core`'s lazy `FunctionSpec` schemas and `@confect/server`'s lazy
18
+ * `Table` `Fields` / `Doc` / `tableDefinition`), so there is no chance of the
19
+ * two drifting apart.
20
+ */
21
+ export const defineProperty = <T extends object, K extends PropertyKey>(
22
+ target: T,
23
+ key: K,
24
+ compute: () => unknown,
25
+ ): void => {
26
+ Object.defineProperty(target, key, {
27
+ configurable: true,
28
+ enumerable: true,
29
+ get(this: T) {
30
+ const value = compute();
31
+ Object.defineProperty(this, key, {
32
+ value,
33
+ writable: false,
34
+ enumerable: true,
35
+ configurable: false,
36
+ });
37
+ return value;
38
+ },
39
+ });
40
+ };
@@ -1,4 +1,4 @@
1
- import { Schema } from "effect";
1
+ import * as Schema from "effect/Schema";
2
2
 
3
3
  export const PaginationResult = <Doc extends Schema.Schema.AnyNoContext>(
4
4
  Doc: Doc,
package/src/Ref.ts CHANGED
@@ -6,7 +6,10 @@ import { makeFunctionReference } from "convex/server";
6
6
  import type { Value } from "convex/values";
7
7
  import { ConvexError } from "convex/values";
8
8
  import type { ParseResult } from "effect";
9
- import { Effect, Match, Option, Schema } from "effect";
9
+ import * as Effect from "effect/Effect";
10
+ import * as Match from "effect/Match";
11
+ import * as Option from "effect/Option";
12
+ import * as Schema from "effect/Schema";
10
13
  import type * as FunctionSpec from "./FunctionSpec";
11
14
  import type * as RuntimeAndFunctionType from "./RuntimeAndFunctionType";
12
15
 
@@ -190,17 +193,29 @@ export const make = <FunctionSpec_ extends FunctionSpec.AnyWithProps>(
190
193
  export const getConvexFunctionName = (ref: Any): string =>
191
194
  `${ref.functionNamespace}:${ref.functionSpec.name}`;
192
195
 
196
+ const functionReferenceCache = new Map<string, FunctionReference<Any>>();
197
+
193
198
  export const getFunctionReference = <Ref_ extends Any>(
194
199
  ref: Ref_,
195
- ): FunctionReference<Ref_> =>
196
- makeFunctionReference(getConvexFunctionName(ref)) as FunctionReference<Ref_>;
200
+ ): FunctionReference<Ref_> => {
201
+ const functionName = getConvexFunctionName(ref);
202
+
203
+ const cached = functionReferenceCache.get(functionName);
204
+ if (cached !== undefined) {
205
+ return cached as FunctionReference<Ref_>;
206
+ }
207
+
208
+ const functionReference = makeFunctionReference(functionName);
209
+ functionReferenceCache.set(functionName, functionReference);
210
+
211
+ return functionReference as FunctionReference<Ref_>;
212
+ };
197
213
 
198
214
  export const hasErrorSchema = (ref: Any): boolean =>
199
215
  Match.value(ref.functionSpec.functionProvenance).pipe(
200
216
  Match.tag(
201
217
  "Confect",
202
- (confectFunctionProvenance) =>
203
- confectFunctionProvenance.error !== undefined,
218
+ (confectFunctionProvenance) => "error" in confectFunctionProvenance,
204
219
  ),
205
220
  Match.tag("Convex", () => false),
206
221
  Match.exhaustive,
@@ -296,7 +311,7 @@ export const decodeError = <Ref_ extends Any>(
296
311
  ): Effect.Effect<Option.Option<Error<Ref_>>, ParseResult.ParseError> =>
297
312
  Match.value(ref.functionSpec.functionProvenance).pipe(
298
313
  Match.tag("Confect", (confectFunctionProvenance) =>
299
- confectFunctionProvenance.error !== undefined
314
+ "error" in confectFunctionProvenance
300
315
  ? Effect.map(
301
316
  Schema.decode(confectFunctionProvenance.error)(encodedError),
302
317
  Option.some,
@@ -317,7 +332,7 @@ export const decodeErrorSync = <Ref_ extends Any>(
317
332
  ): Option.Option<Error<Ref_>> =>
318
333
  Match.value(ref.functionSpec.functionProvenance).pipe(
319
334
  Match.tag("Confect", (confectFunctionProvenance) =>
320
- confectFunctionProvenance.error !== undefined
335
+ "error" in confectFunctionProvenance
321
336
  ? Option.some(
322
337
  Schema.decodeSync(confectFunctionProvenance.error)(
323
338
  encodedError,
@@ -336,7 +351,7 @@ export const maybeDecodeErrorSync = <Ref_ extends Any>(
336
351
  isConvexError(error)
337
352
  ? Match.value(ref.functionSpec.functionProvenance).pipe(
338
353
  Match.tag("Confect", (confectFunctionProvenance) =>
339
- confectFunctionProvenance.error !== undefined
354
+ "error" in confectFunctionProvenance
340
355
  ? Schema.decodeSync(confectFunctionProvenance.error)(error.data)
341
356
  : error,
342
357
  ),
package/src/Refs.ts CHANGED
@@ -1,30 +1,16 @@
1
1
  import type { Types } from "effect";
2
- import { Array, Option, pipe, Record } from "effect";
2
+ import { pipe } from "effect/Function";
3
+ import * as Option from "effect/Option";
4
+ import * as Record from "effect/Record";
3
5
  import type * as FunctionSpec from "./FunctionSpec";
4
- import * as GroupSpec from "./GroupSpec";
6
+ import type * as GroupSpec from "./GroupSpec";
5
7
  import * as Ref from "./Ref";
6
8
  import type * as Spec from "./Spec";
7
9
 
8
10
  export type Refs<
9
- ConvexSpec extends Spec.AnyWithPropsWithRuntime<"Convex">,
10
- NodeSpec extends Spec.AnyWithPropsWithRuntime<"Node"> = never,
11
+ Spec_ extends Spec.AnyWithProps,
11
12
  Predicate extends Ref.Any = Ref.Any,
12
- > = Types.Simplify<
13
- OmitEmpty<
14
- Helper<
15
- | Spec.Groups<ConvexSpec>
16
- | (NodeSpec extends never
17
- ? never
18
- : GroupSpec.GroupSpec<
19
- "Node",
20
- "node",
21
- never,
22
- NodeSpec["groups"][keyof NodeSpec["groups"]]
23
- >),
24
- Predicate
25
- >
26
- >
27
- >;
13
+ > = Types.Simplify<OmitEmpty<Helper<Spec.Groups<Spec_>, Predicate>>>;
28
14
 
29
15
  type GroupRefs<
30
16
  Group extends GroupSpec.AnyWithProps,
@@ -86,33 +72,16 @@ type Any =
86
72
  }
87
73
  | Ref.Any;
88
74
 
89
- export const make = <
90
- ConvexSpec extends Spec.AnyWithPropsWithRuntime<"Convex">,
91
- NodeSpec extends Spec.AnyWithPropsWithRuntime<"Node"> = never,
92
- >(
93
- convexSpec: ConvexSpec,
94
- nodeSpec?: NodeSpec,
75
+ export const make = <Spec_ extends Spec.AnyWithProps>(
76
+ spec: Spec_,
95
77
  ): {
96
- public: Refs<ConvexSpec, NodeSpec, Ref.AnyPublic>;
97
- internal: Refs<ConvexSpec, NodeSpec, Ref.AnyInternal>;
78
+ public: Refs<Spec_, Ref.AnyPublic>;
79
+ internal: Refs<Spec_, Ref.AnyInternal>;
98
80
  } => {
99
- const groups = Option.fromNullable(nodeSpec).pipe(
100
- Option.map((nodeSpec_) =>
101
- Array.reduce(
102
- Record.toEntries(nodeSpec_.groups),
103
- GroupSpec.makeNodeAt("node"),
104
- (nodeGroupSpec, [name, group]) => nodeGroupSpec.addGroupAt(name, group),
105
- ),
106
- ),
107
- Option.match({
108
- onNone: () => convexSpec.groups,
109
- onSome: (nodeGroup) => ({ ...convexSpec.groups, node: nodeGroup }),
110
- }),
111
- );
112
- const refs = makeHelper(groups);
81
+ const refs = makeHelper(spec.groups);
113
82
  return {
114
- public: refs as Refs<ConvexSpec, NodeSpec, Ref.AnyPublic>,
115
- internal: refs as Refs<ConvexSpec, NodeSpec, Ref.AnyInternal>,
83
+ public: refs as Refs<Spec_, Ref.AnyPublic>,
84
+ internal: refs as Refs<Spec_, Ref.AnyInternal>,
116
85
  };
117
86
  };
118
87
 
package/src/Registry.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { Context, Ref } from "effect";
1
+ import * as Context from "effect/Context";
2
+ import * as Ref from "effect/Ref";
2
3
 
3
4
  /**
4
5
  * Recursive tree that mirrors a `Spec`'s group structure. Leaves are the
package/src/Spec.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { Array, Option, Predicate, Record } from "effect";
1
+ import * as Predicate from "effect/Predicate";
2
+ import * as Record from "effect/Record";
2
3
  import * as GroupSpec from "./GroupSpec";
3
- import type * as RuntimeAndFunctionType from "./RuntimeAndFunctionType";
4
4
 
5
5
  export const TypeId = "@confect/core/Spec";
6
6
  export type TypeId = typeof TypeId;
@@ -8,24 +8,15 @@ export type TypeId = typeof TypeId;
8
8
  export const isSpec = (u: unknown): u is AnyWithProps =>
9
9
  Predicate.hasProperty(u, TypeId);
10
10
 
11
- export const isConvexSpec = (
12
- u: unknown,
13
- ): u is AnyWithPropsWithRuntime<"Convex"> =>
14
- Predicate.hasProperty(u, TypeId) &&
15
- Predicate.hasProperty(u, "runtime") &&
16
- u.runtime === "Convex";
17
-
18
- export const isNodeSpec = (u: unknown): u is AnyWithPropsWithRuntime<"Node"> =>
19
- Predicate.hasProperty(u, TypeId) &&
20
- Predicate.hasProperty(u, "runtime") &&
21
- u.runtime === "Node";
22
-
23
- export interface Spec<
24
- Runtime extends RuntimeAndFunctionType.Runtime,
25
- Groups_ extends GroupSpec.AnyWithPropsWithRuntime<Runtime> = never,
26
- > {
11
+ /**
12
+ * A Confect spec: a flat container of function groups. Groups may be of any
13
+ * runtime — a group built with `GroupSpec.makeNode()` (a Node action group) sits
14
+ * alongside `GroupSpec.make()` groups in the same namespace. The runtime of a
15
+ * group lives on the group itself (`GroupSpec.runtime`) and on each function's
16
+ * `RuntimeAndFunctionType`; the spec does not carry a runtime of its own.
17
+ */
18
+ export interface Spec<Groups_ extends GroupSpec.AnyWithProps = never> {
27
19
  readonly [TypeId]: TypeId;
28
- readonly runtime: Runtime;
29
20
  readonly groups: {
30
21
  [GroupName in GroupSpec.Name<Groups_>]: GroupSpec.WithName<
31
22
  Groups_,
@@ -33,31 +24,21 @@ export interface Spec<
33
24
  >;
34
25
  };
35
26
 
36
- add<Group extends GroupSpec.AnyWithPropsWithRuntime<Runtime>>(
27
+ add<Group extends GroupSpec.AnyWithProps>(
37
28
  group: Group,
38
- ): Spec<Runtime, Groups_ | Group>;
29
+ ): Spec<Groups_ | Group>;
39
30
 
40
- addAt<
41
- const Name extends string,
42
- Group extends GroupSpec.AnyWithPropsWithRuntime<Runtime>,
43
- >(
31
+ addAt<const Name extends string, Group extends GroupSpec.AnyWithProps>(
44
32
  name: Name,
45
33
  group: Group,
46
- ): Spec<Runtime, Groups_ | GroupSpec.NamedAt<Group, Name>>;
34
+ ): Spec<Groups_ | GroupSpec.NamedAt<Group, Name>>;
47
35
  }
48
36
 
49
37
  export interface Any {
50
38
  readonly [TypeId]: TypeId;
51
39
  }
52
40
 
53
- export interface AnyWithProps extends Spec<
54
- RuntimeAndFunctionType.Runtime,
55
- GroupSpec.AnyWithProps
56
- > {}
57
-
58
- export interface AnyWithPropsWithRuntime<
59
- Runtime extends RuntimeAndFunctionType.Runtime,
60
- > extends Spec<Runtime, GroupSpec.AnyWithPropsWithRuntime<Runtime>> {}
41
+ export interface AnyWithProps extends Spec<GroupSpec.AnyWithProps> {}
61
42
 
62
43
  export type Groups<Spec_ extends AnyWithProps> =
63
44
  Spec_["groups"][keyof Spec_["groups"]];
@@ -67,7 +48,6 @@ const Proto = {
67
48
 
68
49
  add<Group extends GroupSpec.AnyWithProps>(this: AnyWithProps, group: Group) {
69
50
  return makeProto({
70
- runtime: this.runtime,
71
51
  groups: Record.set(this.groups, group.name, group),
72
52
  });
73
53
  },
@@ -78,61 +58,18 @@ const Proto = {
78
58
  group: Group,
79
59
  ) {
80
60
  return makeProto({
81
- runtime: this.runtime,
82
61
  groups: Record.set(this.groups, name, GroupSpec.withName(name, group)),
83
62
  });
84
63
  },
85
64
  };
86
65
 
87
- const makeProto = <
88
- Runtime extends RuntimeAndFunctionType.Runtime,
89
- Groups_ extends GroupSpec.AnyWithPropsWithRuntime<Runtime>,
90
- >({
91
- runtime,
66
+ const makeProto = <Groups_ extends GroupSpec.AnyWithProps>({
92
67
  groups,
93
68
  }: {
94
- runtime: Runtime;
95
69
  groups: Record.ReadonlyRecord<string, Groups_>;
96
- }): Spec<Runtime, Groups_> =>
70
+ }): Spec<Groups_> =>
97
71
  Object.assign(Object.create(Proto), {
98
- runtime,
99
72
  groups,
100
73
  });
101
74
 
102
- export const make = (): Spec<"Convex"> =>
103
- makeProto({ runtime: "Convex", groups: {} });
104
-
105
- export const makeNode = (): Spec<"Node"> =>
106
- makeProto({ runtime: "Node", groups: {} });
107
-
108
- /**
109
- * Merges a Convex spec with an optional Node spec for use with `Api.make`.
110
- * When `nodeSpec` is provided, its groups are merged under a "node" namespace,
111
- * mirroring the structure used by `Refs.make`.
112
- */
113
- export const merge = <
114
- ConvexSpec extends AnyWithPropsWithRuntime<"Convex">,
115
- NodeSpec extends AnyWithPropsWithRuntime<"Node">,
116
- >(
117
- convexSpec: ConvexSpec,
118
- nodeSpec?: NodeSpec,
119
- ): AnyWithProps => {
120
- const groups = Option.fromNullable(nodeSpec).pipe(
121
- Option.map((nodeSpec_) =>
122
- Array.reduce(
123
- Record.toEntries(nodeSpec_.groups),
124
- GroupSpec.makeNodeAt("node"),
125
- (nodeGroupSpec, [name, group]) => nodeGroupSpec.addGroupAt(name, group),
126
- ),
127
- ),
128
- Option.match({
129
- onNone: () => convexSpec.groups,
130
- onSome: (nodeGroup) => ({ ...convexSpec.groups, node: nodeGroup }),
131
- }),
132
- );
133
-
134
- return Object.assign(Object.create(Proto), {
135
- runtime: "Convex" as const,
136
- groups,
137
- }) as AnyWithProps;
138
- };
75
+ export const make = (): Spec => makeProto({ groups: {} });
@@ -3,7 +3,7 @@ import type {
3
3
  IdField,
4
4
  SystemFields as NonIdSystemFields,
5
5
  } from "convex/server";
6
- import { Schema } from "effect";
6
+ import * as Schema from "effect/Schema";
7
7
  import * as GenericId from "./GenericId";
8
8
 
9
9
  type SystemFieldsSchema<TableName extends string> = Schema.Struct<{
@@ -1,4 +1,4 @@
1
- import { Schema } from "effect";
1
+ import * as Schema from "effect/Schema";
2
2
 
3
3
  export const UserIdentity = <CustomClaimsFields extends Schema.Struct.Fields>(
4
4
  customClaimsFields: CustomClaimsFields,
package/src/index.ts CHANGED
@@ -3,6 +3,8 @@ export * as FunctionSpec from "./FunctionSpec";
3
3
  export * as GenericId from "./GenericId";
4
4
  export * as GroupPath from "./GroupPath";
5
5
  export * as GroupSpec from "./GroupSpec";
6
+ export * as Identifier from "./Identifier";
7
+ export * as Lazy from "./Lazy";
6
8
  export * as PaginationResult from "./PaginationResult";
7
9
  export * as Ref from "./Ref";
8
10
  export * as Refs from "./Refs";
@@ -1,5 +0,0 @@
1
- //#region src/internal/utils.d.ts
2
- declare const validateConfectFunctionIdentifier: (identifier: string) => void;
3
- //#endregion
4
- export { validateConfectFunctionIdentifier };
5
- //# sourceMappingURL=utils.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils.d.ts","names":[],"sources":["../../src/internal/utils.ts"],"mappings":";cAmEa,iCAAA,GAAqC,UAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils.js","names":[],"sources":["../../src/internal/utils.ts"],"sourcesContent":["const RESERVED_JS_IDENTIFIERS = new Set([\n // Reserved keywords\n \"break\",\n \"case\",\n \"catch\",\n \"class\",\n \"const\",\n \"continue\",\n \"debugger\",\n \"default\",\n \"delete\",\n \"do\",\n \"else\",\n \"export\",\n \"extends\",\n \"finally\",\n \"for\",\n \"function\",\n \"if\",\n \"import\",\n \"in\",\n \"instanceof\",\n \"new\",\n \"return\",\n \"super\",\n \"switch\",\n \"this\",\n \"throw\",\n \"try\",\n \"typeof\",\n \"var\",\n \"void\",\n \"while\",\n \"with\",\n \"yield\",\n // Future reserved keywords\n \"await\",\n \"enum\",\n \"implements\",\n \"interface\",\n \"let\",\n \"package\",\n \"private\",\n \"protected\",\n \"public\",\n \"static\",\n // Literal values that cannot be reassigned\n \"null\",\n \"true\",\n \"false\",\n // Global objects that shouldn't be shadowed\n \"undefined\",\n]);\n\nconst RESERVED_CONVEX_FILE_NAMES = new Set([\"schema\", \"http\", \"crons\"]);\n\nconst jsIdentifierRegex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;\n\nconst isReservedJsIdentifier = (identifier: string) =>\n RESERVED_JS_IDENTIFIERS.has(identifier);\n\nconst isReservedConvexFileName = (fileName: string) =>\n RESERVED_CONVEX_FILE_NAMES.has(fileName);\n\nconst matchesJsIdentifierPattern = (identifier: string) =>\n jsIdentifierRegex.test(identifier);\n\nexport const validateConfectFunctionIdentifier = (identifier: string) => {\n if (!matchesJsIdentifierPattern(identifier)) {\n throw new Error(\n `Expected a valid Confect function identifier, but received: \"${identifier}\". Valid identifiers must start with a letter, underscore, or dollar sign, and can only contain letters, numbers, underscores, or dollar signs.`,\n );\n }\n\n if (isReservedJsIdentifier(identifier)) {\n throw new Error(\n `Expected a valid Confect function identifier, but received: \"${identifier}\". \"${identifier}\" is a reserved JavaScript identifier.`,\n );\n }\n\n if (isReservedConvexFileName(identifier)) {\n throw new Error(\n `Expected a valid Confect function identifier, but received: \"${identifier}\". \"${identifier}\" is a reserved Convex file name.`,\n );\n }\n};\n"],"mappings":";AAAA,MAAM,0BAA0B,IAAI,IAAI;CAEtC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CAEA;CACD,CAAC;AAEF,MAAM,6BAA6B,IAAI,IAAI;CAAC;CAAU;CAAQ;CAAQ,CAAC;AAEvE,MAAM,oBAAoB;AAE1B,MAAM,0BAA0B,eAC9B,wBAAwB,IAAI,WAAW;AAEzC,MAAM,4BAA4B,aAChC,2BAA2B,IAAI,SAAS;AAE1C,MAAM,8BAA8B,eAClC,kBAAkB,KAAK,WAAW;AAEpC,MAAa,qCAAqC,eAAuB;AACvE,KAAI,CAAC,2BAA2B,WAAW,CACzC,OAAM,IAAI,MACR,gEAAgE,WAAW,iJAC5E;AAGH,KAAI,uBAAuB,WAAW,CACpC,OAAM,IAAI,MACR,gEAAgE,WAAW,MAAM,WAAW,wCAC7F;AAGH,KAAI,yBAAyB,WAAW,CACtC,OAAM,IAAI,MACR,gEAAgE,WAAW,MAAM,WAAW,mCAC7F"}