@confect/core 9.0.0-next.5 → 9.0.0-next.6
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/CHANGELOG.md +150 -0
- package/dist/FunctionProvenance.d.ts +17 -6
- package/dist/FunctionProvenance.d.ts.map +1 -1
- package/dist/FunctionProvenance.js +24 -5
- package/dist/FunctionProvenance.js.map +1 -1
- package/dist/FunctionSpec.d.ts +24 -24
- package/dist/FunctionSpec.js +1 -1
- package/dist/FunctionSpec.js.map +1 -1
- package/dist/GroupSpec.js +1 -1
- package/dist/GroupSpec.js.map +1 -1
- package/dist/Identifier.d.ts +19 -0
- package/dist/Identifier.d.ts.map +1 -0
- package/dist/{internal/utils.js → Identifier.js} +26 -3
- package/dist/Identifier.js.map +1 -0
- package/dist/Lazy.d.ts +27 -0
- package/dist/Lazy.d.ts.map +1 -0
- package/dist/Lazy.js +44 -0
- package/dist/Lazy.js.map +1 -0
- package/dist/Ref.d.ts.map +1 -1
- package/dist/Ref.js +4 -4
- package/dist/Ref.js.map +1 -1
- package/dist/Spec.d.ts +3 -22
- package/dist/Spec.d.ts.map +1 -1
- package/dist/Spec.js +11 -45
- package/dist/Spec.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/package.json +41 -42
- package/src/FunctionProvenance.ts +31 -9
- package/src/FunctionSpec.ts +4 -4
- package/src/GroupSpec.ts +1 -1
- package/src/{internal/utils.ts → Identifier.ts} +34 -0
- package/src/Lazy.ts +40 -0
- package/src/Ref.ts +4 -5
- package/src/Spec.ts +6 -84
- package/src/index.ts +2 -0
- package/dist/internal/utils.d.ts +0 -5
- package/dist/internal/utils.d.ts.map +0 -1
- package/dist/internal/utils.js.map +0 -1
|
@@ -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
|
+
};
|
package/src/Ref.ts
CHANGED
|
@@ -199,8 +199,7 @@ export const hasErrorSchema = (ref: Any): boolean =>
|
|
|
199
199
|
Match.value(ref.functionSpec.functionProvenance).pipe(
|
|
200
200
|
Match.tag(
|
|
201
201
|
"Confect",
|
|
202
|
-
(confectFunctionProvenance) =>
|
|
203
|
-
confectFunctionProvenance.error !== undefined,
|
|
202
|
+
(confectFunctionProvenance) => "error" in confectFunctionProvenance,
|
|
204
203
|
),
|
|
205
204
|
Match.tag("Convex", () => false),
|
|
206
205
|
Match.exhaustive,
|
|
@@ -296,7 +295,7 @@ export const decodeError = <Ref_ extends Any>(
|
|
|
296
295
|
): Effect.Effect<Option.Option<Error<Ref_>>, ParseResult.ParseError> =>
|
|
297
296
|
Match.value(ref.functionSpec.functionProvenance).pipe(
|
|
298
297
|
Match.tag("Confect", (confectFunctionProvenance) =>
|
|
299
|
-
|
|
298
|
+
"error" in confectFunctionProvenance
|
|
300
299
|
? Effect.map(
|
|
301
300
|
Schema.decode(confectFunctionProvenance.error)(encodedError),
|
|
302
301
|
Option.some,
|
|
@@ -317,7 +316,7 @@ export const decodeErrorSync = <Ref_ extends Any>(
|
|
|
317
316
|
): Option.Option<Error<Ref_>> =>
|
|
318
317
|
Match.value(ref.functionSpec.functionProvenance).pipe(
|
|
319
318
|
Match.tag("Confect", (confectFunctionProvenance) =>
|
|
320
|
-
|
|
319
|
+
"error" in confectFunctionProvenance
|
|
321
320
|
? Option.some(
|
|
322
321
|
Schema.decodeSync(confectFunctionProvenance.error)(
|
|
323
322
|
encodedError,
|
|
@@ -336,7 +335,7 @@ export const maybeDecodeErrorSync = <Ref_ extends Any>(
|
|
|
336
335
|
isConvexError(error)
|
|
337
336
|
? Match.value(ref.functionSpec.functionProvenance).pipe(
|
|
338
337
|
Match.tag("Confect", (confectFunctionProvenance) =>
|
|
339
|
-
|
|
338
|
+
"error" in confectFunctionProvenance
|
|
340
339
|
? Schema.decodeSync(confectFunctionProvenance.error)(error.data)
|
|
341
340
|
: error,
|
|
342
341
|
),
|
package/src/Spec.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { Array, Option, Predicate, Record
|
|
1
|
+
import { Array, Option, Predicate, Record } from "effect";
|
|
2
2
|
import * as GroupSpec from "./GroupSpec";
|
|
3
3
|
import type * as RuntimeAndFunctionType from "./RuntimeAndFunctionType";
|
|
4
|
-
import { validateConfectFunctionIdentifier } from "./internal/utils";
|
|
5
4
|
|
|
6
5
|
export const TypeId = "@confect/core/Spec";
|
|
7
6
|
export type TypeId = typeof TypeId;
|
|
@@ -33,13 +32,6 @@ export interface Spec<
|
|
|
33
32
|
GroupName
|
|
34
33
|
>;
|
|
35
34
|
};
|
|
36
|
-
/**
|
|
37
|
-
* Mapping from an imported leaf `GroupSpec` reference to its full dot-path
|
|
38
|
-
* within this spec tree. Populated by codegen-emitted `_generated/spec.ts`
|
|
39
|
-
* via {@link Spec#addPath}; consumed by `FunctionImpl.make` /
|
|
40
|
-
* `GroupImpl.make` to resolve a spec's location without walking the tree.
|
|
41
|
-
*/
|
|
42
|
-
readonly paths: ReadonlyMap<GroupSpec.AnyWithProps, string>;
|
|
43
35
|
|
|
44
36
|
add<Group extends GroupSpec.AnyWithPropsWithRuntime<Runtime>>(
|
|
45
37
|
group: Group,
|
|
@@ -52,18 +44,6 @@ export interface Spec<
|
|
|
52
44
|
name: Name,
|
|
53
45
|
group: Group,
|
|
54
46
|
): Spec<Runtime, Groups_ | GroupSpec.NamedAt<Group, Name>>;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Register the imported leaf `group` at `path` within this spec's path
|
|
58
|
-
* mapping. Returns a new `Spec` with one additional entry. The tree shape
|
|
59
|
-
* (`groups`) is unaffected — registration and tree assembly are
|
|
60
|
-
* independent steps, both performed by codegen in `_generated/spec.ts`.
|
|
61
|
-
*
|
|
62
|
-
* Re-registering the same group with the same path is a no-op (cheap
|
|
63
|
-
* defense for codegen watch-mode re-imports). Re-registering with a
|
|
64
|
-
* different path throws.
|
|
65
|
-
*/
|
|
66
|
-
addPath(group: GroupSpec.AnyWithProps, path: string): Spec<Runtime, Groups_>;
|
|
67
47
|
}
|
|
68
48
|
|
|
69
49
|
export interface Any {
|
|
@@ -82,25 +62,6 @@ export interface AnyWithPropsWithRuntime<
|
|
|
82
62
|
export type Groups<Spec_ extends AnyWithProps> =
|
|
83
63
|
Spec_["groups"][keyof Spec_["groups"]];
|
|
84
64
|
|
|
85
|
-
const validatePath = (path: string): void => {
|
|
86
|
-
if (path.length === 0) {
|
|
87
|
-
throw new Error(
|
|
88
|
-
"Expected a non-empty Confect group path, but received an empty string.",
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const segments = String.split(path, ".");
|
|
93
|
-
|
|
94
|
-
for (const segment of segments) {
|
|
95
|
-
if (segment.length === 0) {
|
|
96
|
-
throw new Error(
|
|
97
|
-
`Expected a Confect group path made of dot-separated identifier segments, but received: "${path}".`,
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
validateConfectFunctionIdentifier(segment);
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
|
|
104
65
|
const Proto = {
|
|
105
66
|
[TypeId]: TypeId,
|
|
106
67
|
|
|
@@ -108,7 +69,6 @@ const Proto = {
|
|
|
108
69
|
return makeProto({
|
|
109
70
|
runtime: this.runtime,
|
|
110
71
|
groups: Record.set(this.groups, group.name, group),
|
|
111
|
-
paths: this.paths,
|
|
112
72
|
});
|
|
113
73
|
},
|
|
114
74
|
|
|
@@ -120,30 +80,6 @@ const Proto = {
|
|
|
120
80
|
return makeProto({
|
|
121
81
|
runtime: this.runtime,
|
|
122
82
|
groups: Record.set(this.groups, name, GroupSpec.withName(name, group)),
|
|
123
|
-
paths: this.paths,
|
|
124
|
-
});
|
|
125
|
-
},
|
|
126
|
-
|
|
127
|
-
addPath(this: AnyWithProps, group: GroupSpec.AnyWithProps, path: string) {
|
|
128
|
-
validatePath(path);
|
|
129
|
-
|
|
130
|
-
const existing = this.paths.get(group);
|
|
131
|
-
if (existing !== undefined) {
|
|
132
|
-
if (existing === path) {
|
|
133
|
-
return this;
|
|
134
|
-
}
|
|
135
|
-
throw new Error(
|
|
136
|
-
`Spec.addPath: the provided GroupSpec is already registered at "${existing}", but was re-registered at "${path}". Each GroupSpec must have at most one path.`,
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const nextPaths = new Map(this.paths);
|
|
141
|
-
nextPaths.set(group, path);
|
|
142
|
-
|
|
143
|
-
return makeProto({
|
|
144
|
-
runtime: this.runtime,
|
|
145
|
-
groups: this.groups,
|
|
146
|
-
paths: nextPaths,
|
|
147
83
|
});
|
|
148
84
|
},
|
|
149
85
|
};
|
|
@@ -154,32 +90,26 @@ const makeProto = <
|
|
|
154
90
|
>({
|
|
155
91
|
runtime,
|
|
156
92
|
groups,
|
|
157
|
-
paths,
|
|
158
93
|
}: {
|
|
159
94
|
runtime: Runtime;
|
|
160
95
|
groups: Record.ReadonlyRecord<string, Groups_>;
|
|
161
|
-
paths: ReadonlyMap<GroupSpec.AnyWithProps, string>;
|
|
162
96
|
}): Spec<Runtime, Groups_> =>
|
|
163
97
|
Object.assign(Object.create(Proto), {
|
|
164
98
|
runtime,
|
|
165
99
|
groups,
|
|
166
|
-
paths,
|
|
167
100
|
});
|
|
168
101
|
|
|
169
|
-
const emptyPaths = (): ReadonlyMap<GroupSpec.AnyWithProps, string> => new Map();
|
|
170
|
-
|
|
171
102
|
export const make = (): Spec<"Convex"> =>
|
|
172
|
-
makeProto({ runtime: "Convex", groups: {}
|
|
103
|
+
makeProto({ runtime: "Convex", groups: {} });
|
|
173
104
|
|
|
174
105
|
export const makeNode = (): Spec<"Node"> =>
|
|
175
|
-
makeProto({ runtime: "Node", groups: {}
|
|
106
|
+
makeProto({ runtime: "Node", groups: {} });
|
|
176
107
|
|
|
177
108
|
/**
|
|
178
|
-
* Merges a Convex spec with an optional Node spec
|
|
109
|
+
* Merges a Convex spec with an optional Node spec into a single assembled
|
|
110
|
+
* spec (used by codegen to build `Refs.make` and to enumerate function paths).
|
|
179
111
|
* When `nodeSpec` is provided, its groups are merged under a "node" namespace,
|
|
180
|
-
* mirroring the structure used by `Refs.make`.
|
|
181
|
-
* entries are re-prefixed with `"node."` so they continue to identify the
|
|
182
|
-
* same leaves at their new positions in the merged tree.
|
|
112
|
+
* mirroring the structure used by `Refs.make`.
|
|
183
113
|
*/
|
|
184
114
|
export const merge = <
|
|
185
115
|
ConvexSpec extends AnyWithPropsWithRuntime<"Convex">,
|
|
@@ -202,16 +132,8 @@ export const merge = <
|
|
|
202
132
|
}),
|
|
203
133
|
);
|
|
204
134
|
|
|
205
|
-
const paths = new Map(convexSpec.paths);
|
|
206
|
-
if (nodeSpec !== undefined) {
|
|
207
|
-
for (const [group, path] of nodeSpec.paths) {
|
|
208
|
-
paths.set(group, `node.${path}`);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
135
|
return Object.assign(Object.create(Proto), {
|
|
213
136
|
runtime: "Convex" as const,
|
|
214
137
|
groups,
|
|
215
|
-
paths,
|
|
216
138
|
}) as AnyWithProps;
|
|
217
139
|
};
|
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";
|
package/dist/internal/utils.d.ts
DELETED
|
@@ -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"}
|