@colyseus/schema 5.0.21 → 5.0.23
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/build/codegen/cli.cjs +448 -201
- package/build/codegen/cli.cjs.map +1 -1
- package/build/codegen/languages/swift.d.ts +23 -0
- package/build/index.cjs.map +1 -1
- package/build/index.mjs.map +1 -1
- package/build/types/HelperTypes.d.ts +14 -2
- package/build/types/builder.d.ts +5 -1
- package/package.json +1 -1
- package/src/codegen/api.ts +2 -1
- package/src/codegen/languages/swift.ts +270 -0
- package/src/types/HelperTypes.ts +16 -0
- package/src/types/builder.ts +8 -1
|
@@ -8,7 +8,7 @@ import type { SetSchema } from "./custom/SetSchema.js";
|
|
|
8
8
|
import type { StreamSchema } from "./custom/StreamSchema.js";
|
|
9
9
|
import type { FieldBuilder } from "./builder.js";
|
|
10
10
|
export type Constructor<T = {}> = new (...args: any[]) => T;
|
|
11
|
-
type PrimitiveStringToType<T> = T extends "string" ? string : T extends "number" | "int8" | "uint8" | "int16" | "uint16" | "int32" | "uint32" | "int64" | "uint64" | "float32" | "float64" ? number : T extends "boolean" ? boolean : T;
|
|
11
|
+
type PrimitiveStringToType<T> = T extends "string" ? string : T extends "number" | "int8" | "uint8" | "int16" | "uint16" | "int32" | "uint32" | "int64" | "uint64" | "float32" | "float64" ? number : T extends "bigint64" | "biguint64" ? bigint : T extends "boolean" ? boolean : T;
|
|
12
12
|
/**
|
|
13
13
|
* What the decoder callbacks accept as "a collection": the public shape, which
|
|
14
14
|
* a plain array satisfies too — `@type([X]) items: X[]` is a common way to
|
|
@@ -23,7 +23,7 @@ export interface Collection<K = any, V = any, IT = V> extends CollectionLike<K,
|
|
|
23
23
|
/** See {@link $resyncPrune} — every collection kind must declare its resync-sweep semantics. */
|
|
24
24
|
[$resyncPrune](visited: Set<number | string>, prune: (value: V, identity: number | string) => void, keep: (value: V) => void): void;
|
|
25
25
|
}
|
|
26
|
-
export type InferValueType<T> = T extends FieldBuilder<infer V> ? V : T extends "string" ? string : T extends "number" ? number : T extends "int8" ? number : T extends "uint8" ? number : T extends "int16" ? number : T extends "uint16" ? number : T extends "int32" ? number : T extends "uint32" ? number : T extends "int64" ? number : T extends "uint64" ? number : T extends "float32" ? number : T extends "float64" ? number : T extends "boolean" ? boolean : T extends {
|
|
26
|
+
export type InferValueType<T> = T extends FieldBuilder<infer V> ? V : T extends "string" ? string : T extends "number" ? number : T extends "int8" ? number : T extends "uint8" ? number : T extends "int16" ? number : T extends "uint16" ? number : T extends "int32" ? number : T extends "uint32" ? number : T extends "int64" ? number : T extends "uint64" ? number : T extends "float32" ? number : T extends "float64" ? number : T extends "bigint64" ? bigint : T extends "biguint64" ? bigint : T extends "boolean" ? boolean : T extends {
|
|
27
27
|
type: infer ChildType extends PrimitiveType;
|
|
28
28
|
} ? InferValueType<ChildType> : T extends {
|
|
29
29
|
type: infer ChildType extends Constructor;
|
|
@@ -76,6 +76,18 @@ export type InferValueType<T> = T extends FieldBuilder<infer V> ? V : T extends
|
|
|
76
76
|
} ? StreamSchema<InstanceType<ChildType>> : T extends {
|
|
77
77
|
stream: infer ChildType;
|
|
78
78
|
} ? StreamSchema<ChildType> : T extends Constructor ? InstanceType<T> : T extends Record<string | number, string | number> ? T[keyof T] : T extends PrimitiveType ? T : never;
|
|
79
|
+
/**
|
|
80
|
+
* Codecs that can carry a `T` — {@link InferValueType} run backwards, derived
|
|
81
|
+
* from it so the two can't drift. Constrains the element refinement in
|
|
82
|
+
* `t.array<Mark>("uint8")`; `never` (an uncallable overload) when no codec
|
|
83
|
+
* decodes into `T`.
|
|
84
|
+
*
|
|
85
|
+
* `[T] extends [...]` is deliberate: a distributive check would let a mixed
|
|
86
|
+
* union like `string | number` match on either half.
|
|
87
|
+
*/
|
|
88
|
+
export type CodecFor<T> = {
|
|
89
|
+
[K in RawPrimitiveType]: [T] extends [InferValueType<K>] ? K : never;
|
|
90
|
+
}[RawPrimitiveType];
|
|
79
91
|
type IsOptionalBuilderKey<T, K extends keyof T> = T[K] extends FieldBuilder<unknown, boolean, infer O extends boolean> ? O : false;
|
|
80
92
|
type OptionalBuilderKeys<T> = {
|
|
81
93
|
[K in keyof T]-?: IsOptionalBuilderKey<T, K> extends true ? K : never;
|
package/build/types/builder.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { CollectionSchema } from "./custom/CollectionSchema.js";
|
|
|
5
5
|
import type { StreamSchema } from "./custom/StreamSchema.js";
|
|
6
6
|
import type { Schema } from "../Schema.js";
|
|
7
7
|
import type { DefinitionType, RawPrimitiveType } from "../annotations.js";
|
|
8
|
-
import type { InferValueType, Constructor } from "./HelperTypes.js";
|
|
8
|
+
import type { InferValueType, Constructor, CodecFor } from "./HelperTypes.js";
|
|
9
9
|
import { $builder } from "./symbols.js";
|
|
10
10
|
import { type QuantizeOptions } from "./quantize.js";
|
|
11
11
|
/**
|
|
@@ -217,18 +217,22 @@ export type ChildType = RawPrimitiveType | Constructor<Schema>;
|
|
|
217
217
|
interface ArrayFactory {
|
|
218
218
|
<C extends Constructor<Schema>>(child: C): FieldBuilder<ArraySchema<InstanceType<C>>, true, false>;
|
|
219
219
|
<P extends RawPrimitiveType>(child: P): FieldBuilder<ArraySchema<InferValueType<P>>, true, false>;
|
|
220
|
+
<T>(child: CodecFor<T>): FieldBuilder<ArraySchema<T>, true, false>;
|
|
220
221
|
}
|
|
221
222
|
interface MapFactory {
|
|
222
223
|
<C extends Constructor<Schema>>(child: C): FieldBuilder<MapSchema<InstanceType<C>>, true, false>;
|
|
223
224
|
<P extends RawPrimitiveType>(child: P): FieldBuilder<MapSchema<InferValueType<P>>, true, false>;
|
|
225
|
+
<T>(child: CodecFor<T>): FieldBuilder<MapSchema<T>, true, false>;
|
|
224
226
|
}
|
|
225
227
|
interface SetFactory {
|
|
226
228
|
<C extends Constructor<Schema>>(child: C): FieldBuilder<SetSchema<InstanceType<C>>, true, false>;
|
|
227
229
|
<P extends RawPrimitiveType>(child: P): FieldBuilder<SetSchema<InferValueType<P>>, true, false>;
|
|
230
|
+
<T>(child: CodecFor<T>): FieldBuilder<SetSchema<T>, true, false>;
|
|
228
231
|
}
|
|
229
232
|
interface CollectionFactory {
|
|
230
233
|
<C extends Constructor<Schema>>(child: C): FieldBuilder<CollectionSchema<InstanceType<C>>, true, false>;
|
|
231
234
|
<P extends RawPrimitiveType>(child: P): FieldBuilder<CollectionSchema<InferValueType<P>>, true, false>;
|
|
235
|
+
<T>(child: CodecFor<T>): FieldBuilder<CollectionSchema<T>, true, false>;
|
|
232
236
|
}
|
|
233
237
|
interface StreamFactory {
|
|
234
238
|
<C extends Constructor<Schema>>(child: C): FieldBuilder<StreamSchema<InstanceType<C>>, true, false>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colyseus/schema",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.23",
|
|
4
4
|
"description": "Automatic state replication for multiplayer games. Mutate plain objects, and every client gets a live, typed mirror through delta encoding.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/codegen/api.ts
CHANGED
|
@@ -15,8 +15,9 @@ import * as lua from "./languages/lua.js";
|
|
|
15
15
|
import * as c from "./languages/c.js";
|
|
16
16
|
import * as gdscript from "./languages/gdscript.js";
|
|
17
17
|
import * as dart from "./languages/dart.js";
|
|
18
|
+
import * as swift from "./languages/swift.js";
|
|
18
19
|
|
|
19
|
-
export const generators: Record<string, any> = { csharp, cpp, haxe, ts, js, java, lua, c, gdscript, dart, };
|
|
20
|
+
export const generators: Record<string, any> = { csharp, cpp, haxe, ts, js, java, lua, c, gdscript, dart, swift, };
|
|
20
21
|
|
|
21
22
|
export interface GenerateOptions {
|
|
22
23
|
files: string[],
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Class,
|
|
3
|
+
Property,
|
|
4
|
+
File,
|
|
5
|
+
getCommentHeader,
|
|
6
|
+
Interface,
|
|
7
|
+
Enum,
|
|
8
|
+
Context,
|
|
9
|
+
} from "../types.js";
|
|
10
|
+
import { GenerateOptions } from "../api.js";
|
|
11
|
+
|
|
12
|
+
export const name = "Swift";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Swift types for interface (plain message) properties. Schema getters do not
|
|
16
|
+
* use this table: the `Colyseus` package reads every numeric field as `Double`
|
|
17
|
+
* through `SchemaView`, so all numeric schema types collapse there.
|
|
18
|
+
*/
|
|
19
|
+
const typeMaps: { [key: string]: string } = {
|
|
20
|
+
"string": "String",
|
|
21
|
+
"number": "Double",
|
|
22
|
+
"boolean": "Bool",
|
|
23
|
+
"int8": "Int",
|
|
24
|
+
"uint8": "Int",
|
|
25
|
+
"int16": "Int",
|
|
26
|
+
"uint16": "Int",
|
|
27
|
+
"int32": "Int",
|
|
28
|
+
"uint32": "Int",
|
|
29
|
+
"int64": "Int",
|
|
30
|
+
"uint64": "Int",
|
|
31
|
+
"float32": "Double",
|
|
32
|
+
"float64": "Double",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const enumNames = new Set<string>();
|
|
36
|
+
|
|
37
|
+
const COMMON_IMPORTS = `import Colyseus`;
|
|
38
|
+
|
|
39
|
+
const distinct = (value: string, index: number, self: string[]) =>
|
|
40
|
+
self.indexOf(value) === index;
|
|
41
|
+
|
|
42
|
+
const isSchemaType = (childType: string) =>
|
|
43
|
+
childType !== undefined && /^[A-Z]/.test(childType) && !enumNames.has(childType);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Swift Code Generator
|
|
47
|
+
*
|
|
48
|
+
* Emits typed façades over the `Colyseus` package's runtime: one `SchemaRef`
|
|
49
|
+
* subclass per schema, whose properties read through the shared handle on the
|
|
50
|
+
* instance the core decoded. Nothing is copied and nothing is stored, so a
|
|
51
|
+
* generated class stays correct as patches arrive.
|
|
52
|
+
*
|
|
53
|
+
* Collection properties return `MapSchema<T>` / `ArraySchema<T>`, which carry
|
|
54
|
+
* the field they came from — that is what `callbacks.onAdd(state.players, …)`
|
|
55
|
+
* registers against.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Generate individual files for each class/interface/enum
|
|
60
|
+
*/
|
|
61
|
+
export function generate(context: Context, options: GenerateOptions): File[] {
|
|
62
|
+
context.enums.forEach((structure) => enumNames.add(structure.name));
|
|
63
|
+
|
|
64
|
+
return [
|
|
65
|
+
...context.classes.map(klass => ({
|
|
66
|
+
name: `${klass.name}.swift`,
|
|
67
|
+
content: generateFile(generateClassBody(klass, context.classes, !!options.namespace), options)
|
|
68
|
+
})),
|
|
69
|
+
...context.interfaces.map(structure => ({
|
|
70
|
+
name: `${structure.name}.swift`,
|
|
71
|
+
content: generateFile(generateInterfaceBody(structure), options),
|
|
72
|
+
})),
|
|
73
|
+
...context.enums.filter(structure => structure.name !== 'OPERATION').map((structure) => ({
|
|
74
|
+
name: `${structure.name}.swift`,
|
|
75
|
+
content: generateFile(generateEnumBody(structure), options),
|
|
76
|
+
})),
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Generate a single bundled file containing all classes, interfaces, and enums
|
|
82
|
+
*/
|
|
83
|
+
export function renderBundle(context: Context, options: GenerateOptions): File {
|
|
84
|
+
const fileName = options.namespace ? `${options.namespace}.swift` : "Schema.swift";
|
|
85
|
+
|
|
86
|
+
context.enums.forEach((structure) => enumNames.add(structure.name));
|
|
87
|
+
|
|
88
|
+
const bodies = [
|
|
89
|
+
...context.classes.map(klass => generateClassBody(klass, context.classes, !!options.namespace)),
|
|
90
|
+
...context.interfaces.map(iface => generateInterfaceBody(iface)),
|
|
91
|
+
...context.enums
|
|
92
|
+
.filter(structure => structure.name !== 'OPERATION')
|
|
93
|
+
.map(e => generateEnumBody(e)),
|
|
94
|
+
].join("\n\n");
|
|
95
|
+
|
|
96
|
+
return { name: fileName, content: generateFile(bodies, options) };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Swift has no namespaces, so one stands in as a caseless enum. Declarations
|
|
101
|
+
* go inside it through an extension, which works the same whether they are
|
|
102
|
+
* bundled into one file or split across many.
|
|
103
|
+
*/
|
|
104
|
+
function generateFile(body: string, options: GenerateOptions): string {
|
|
105
|
+
const header = `${getCommentHeader()}
|
|
106
|
+
|
|
107
|
+
${COMMON_IMPORTS}
|
|
108
|
+
`;
|
|
109
|
+
|
|
110
|
+
if (!options.namespace) {
|
|
111
|
+
return `${header}
|
|
112
|
+
${body}
|
|
113
|
+
`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return `${header}
|
|
117
|
+
public enum ${options.namespace} {}
|
|
118
|
+
|
|
119
|
+
extension ${options.namespace} {
|
|
120
|
+
${indent(body)}
|
|
121
|
+
}
|
|
122
|
+
`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function indent(text: string): string {
|
|
126
|
+
return text
|
|
127
|
+
.split("\n")
|
|
128
|
+
.map(line => (line.length > 0 ? ` ${line}` : line))
|
|
129
|
+
.join("\n");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function generateClassBody(klass: Class, allClasses: Class[], namespaced: boolean): string {
|
|
133
|
+
// A class nobody extends is final. One that is extended stays open so a
|
|
134
|
+
// consumer in another module can subclass it — except inside a namespace,
|
|
135
|
+
// where `open` conflicts with the extension's own access level and the
|
|
136
|
+
// subclass is generated alongside it anyway.
|
|
137
|
+
const isExtended = allClasses.some(other => other.extends === klass.name);
|
|
138
|
+
const modifier = isExtended ? (namespaced ? "public" : "open") : "public final";
|
|
139
|
+
const parent = (klass.extends === "Schema") ? "SchemaRef" : klass.extends;
|
|
140
|
+
|
|
141
|
+
const properties = klass.properties
|
|
142
|
+
.map(prop => generateProperty(prop))
|
|
143
|
+
.filter(Boolean)
|
|
144
|
+
.join("\n");
|
|
145
|
+
|
|
146
|
+
// Swift does not carry an `@unchecked Sendable` conformance across module
|
|
147
|
+
// boundaries, so every subclass has to restate it. What it asserts is the
|
|
148
|
+
// SDK's own contract: decoded state is read where it is pumped.
|
|
149
|
+
return `${modifier} class ${klass.name}: ${parent}, @unchecked Sendable {
|
|
150
|
+
${properties}
|
|
151
|
+
}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The Swift type a scalar schema field reads as, or undefined when the field
|
|
156
|
+
* can only be read dynamically (enum-typed and unknown types).
|
|
157
|
+
*/
|
|
158
|
+
function scalarSwiftType(type: string): string | undefined {
|
|
159
|
+
if (type === "string") { return "String"; }
|
|
160
|
+
if (type === "boolean") { return "Bool"; }
|
|
161
|
+
if (typeMaps[type] === "Double" || typeMaps[type] === "Int" || type === "quantized" || type === "number") {
|
|
162
|
+
return "Double";
|
|
163
|
+
}
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function generateProperty(prop: Property): string {
|
|
168
|
+
const deprecation = (prop.deprecated)
|
|
169
|
+
? ` @available(*, deprecated, message: "field '${prop.name}' is deprecated.")\n`
|
|
170
|
+
: '';
|
|
171
|
+
|
|
172
|
+
const escaped = escapeName(prop.name);
|
|
173
|
+
let body: string;
|
|
174
|
+
|
|
175
|
+
if (prop.childType && isSchemaType(prop.childType)) {
|
|
176
|
+
if (prop.type === "ref") {
|
|
177
|
+
body = ` public var ${escaped}: ${prop.childType}? { refOf("${prop.name}") }`;
|
|
178
|
+
} else if (prop.type === "map") {
|
|
179
|
+
body = ` public var ${escaped}: MapSchema<${prop.childType}> { mapOf("${prop.name}") }`;
|
|
180
|
+
} else {
|
|
181
|
+
body = ` public var ${escaped}: ArraySchema<${prop.childType}> { arrayOf("${prop.name}") }`;
|
|
182
|
+
}
|
|
183
|
+
} else if (prop.childType) {
|
|
184
|
+
// A collection of primitives. Everything numeric reads as Double, the
|
|
185
|
+
// same collapse the scalar getters make.
|
|
186
|
+
const child = typeMaps[prop.childType] === "String" ? "String" : "Double";
|
|
187
|
+
if (prop.type === "map") {
|
|
188
|
+
body = ` public var ${escaped}: MapSchema<${child}> { mapOf("${prop.name}") }`;
|
|
189
|
+
} else if (prop.type === "array") {
|
|
190
|
+
body = ` public var ${escaped}: ArraySchema<${child}> { arrayOf("${prop.name}") }`;
|
|
191
|
+
} else {
|
|
192
|
+
// A "ref" with a primitive child has no typed shape to offer.
|
|
193
|
+
body = ` public var ${escaped}: Double { view["${prop.name}"] }`;
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
const swiftType = scalarSwiftType(prop.type);
|
|
197
|
+
if (swiftType === "String") {
|
|
198
|
+
body = ` public var ${escaped}: String { view.string("${prop.name}") ?? "" }`;
|
|
199
|
+
} else if (swiftType === "Bool") {
|
|
200
|
+
body = ` public var ${escaped}: Bool { view.bool("${prop.name}") }`;
|
|
201
|
+
} else if (swiftType === "Double") {
|
|
202
|
+
body = ` public var ${escaped}: Double { view["${prop.name}"] }`;
|
|
203
|
+
} else {
|
|
204
|
+
// Enum-typed or unknown: read as the number the wire carries.
|
|
205
|
+
body = ` public var ${escaped}: Double { view["${prop.name}"] }`;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return deprecation + body;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Message payloads are plain structs rather than façades: they arrive as
|
|
214
|
+
* msgpack, not as decoded schema state.
|
|
215
|
+
*/
|
|
216
|
+
function generateInterfaceBody(struct: Interface): string {
|
|
217
|
+
const fields = struct.properties
|
|
218
|
+
.map(prop => ` public var ${escapeName(prop.name)}: ${getInterfaceType(prop)}?`)
|
|
219
|
+
.join("\n");
|
|
220
|
+
|
|
221
|
+
return `public struct ${struct.name}: Codable {
|
|
222
|
+
${fields}
|
|
223
|
+
|
|
224
|
+
public init() {}
|
|
225
|
+
}`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function getInterfaceType(prop: Property): string {
|
|
229
|
+
if (prop.type === "array") {
|
|
230
|
+
return `[${typeMaps[prop.childType] ?? prop.childType ?? "Double"}]`;
|
|
231
|
+
}
|
|
232
|
+
return typeMaps[prop.type] ?? prop.type ?? "Double";
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* A namespace of constants rather than a Swift enum: Colyseus enums may carry
|
|
237
|
+
* string or floating-point values, and a Swift enum's raw type has to be one
|
|
238
|
+
* or the other.
|
|
239
|
+
*/
|
|
240
|
+
function generateEnumBody(_enum: Enum): string {
|
|
241
|
+
const members = _enum.properties
|
|
242
|
+
.map((prop, i) => {
|
|
243
|
+
let value: string;
|
|
244
|
+
if (prop.type) {
|
|
245
|
+
value = isNaN(Number(prop.type)) ? `"${prop.type}"` : `${Number(prop.type)}`;
|
|
246
|
+
} else {
|
|
247
|
+
value = `${i}`;
|
|
248
|
+
}
|
|
249
|
+
return ` public static let ${escapeName(prop.name)} = ${value}`;
|
|
250
|
+
})
|
|
251
|
+
.join("\n");
|
|
252
|
+
|
|
253
|
+
return `public enum ${_enum.name} {
|
|
254
|
+
${members}
|
|
255
|
+
}`;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Field names come from the server schema and may collide with a keyword. */
|
|
259
|
+
const SWIFT_KEYWORDS = new Set([
|
|
260
|
+
"associatedtype", "class", "deinit", "enum", "extension", "fileprivate", "func", "import",
|
|
261
|
+
"init", "inout", "internal", "let", "open", "operator", "private", "precedencegroup",
|
|
262
|
+
"protocol", "public", "rethrows", "static", "struct", "subscript", "typealias", "var",
|
|
263
|
+
"break", "case", "catch", "continue", "default", "defer", "do", "else", "fallthrough",
|
|
264
|
+
"for", "guard", "if", "in", "repeat", "return", "throw", "switch", "where", "while",
|
|
265
|
+
"Any", "as", "await", "false", "is", "nil", "self", "Self", "super", "throws", "true", "try",
|
|
266
|
+
]);
|
|
267
|
+
|
|
268
|
+
function escapeName(name: string): string {
|
|
269
|
+
return SWIFT_KEYWORDS.has(name) ? `\`${name}\`` : name;
|
|
270
|
+
}
|
package/src/types/HelperTypes.ts
CHANGED
|
@@ -14,6 +14,7 @@ export type Constructor<T = {}> = new (...args: any[]) => T;
|
|
|
14
14
|
type PrimitiveStringToType<T> =
|
|
15
15
|
T extends "string" ? string
|
|
16
16
|
: T extends "number" | "int8" | "uint8" | "int16" | "uint16" | "int32" | "uint32" | "int64" | "uint64" | "float32" | "float64" ? number
|
|
17
|
+
: T extends "bigint64" | "biguint64" ? bigint
|
|
17
18
|
: T extends "boolean" ? boolean
|
|
18
19
|
: T;
|
|
19
20
|
|
|
@@ -53,6 +54,8 @@ export type InferValueType<T> =
|
|
|
53
54
|
: T extends "uint64" ? number
|
|
54
55
|
: T extends "float32" ? number
|
|
55
56
|
: T extends "float64" ? number
|
|
57
|
+
: T extends "bigint64" ? bigint
|
|
58
|
+
: T extends "biguint64" ? bigint
|
|
56
59
|
: T extends "boolean" ? boolean
|
|
57
60
|
|
|
58
61
|
// Handle { type: ... } patterns
|
|
@@ -95,6 +98,19 @@ export type InferValueType<T> =
|
|
|
95
98
|
|
|
96
99
|
: never;
|
|
97
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Codecs that can carry a `T` — {@link InferValueType} run backwards, derived
|
|
103
|
+
* from it so the two can't drift. Constrains the element refinement in
|
|
104
|
+
* `t.array<Mark>("uint8")`; `never` (an uncallable overload) when no codec
|
|
105
|
+
* decodes into `T`.
|
|
106
|
+
*
|
|
107
|
+
* `[T] extends [...]` is deliberate: a distributive check would let a mixed
|
|
108
|
+
* union like `string | number` match on either half.
|
|
109
|
+
*/
|
|
110
|
+
export type CodecFor<T> = {
|
|
111
|
+
[K in RawPrimitiveType]: [T] extends [InferValueType<K>] ? K : never
|
|
112
|
+
}[RawPrimitiveType];
|
|
113
|
+
|
|
98
114
|
// Keys whose builder carries the `.optional()` brand. Reads the brand rather
|
|
99
115
|
// than `undefined extends V`: the latter is true for EVERY V when the consumer
|
|
100
116
|
// compiles with `strictNullChecks: false`, flipping all fields optional.
|
package/src/types/builder.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { CollectionSchema } from "./custom/CollectionSchema.js";
|
|
|
5
5
|
import type { StreamSchema } from "./custom/StreamSchema.js";
|
|
6
6
|
import type { Schema } from "../Schema.js";
|
|
7
7
|
import type { DefinitionType, RawPrimitiveType } from "../annotations.js";
|
|
8
|
-
import type { InferValueType, Constructor } from "./HelperTypes.js";
|
|
8
|
+
import type { InferValueType, Constructor, CodecFor } from "./HelperTypes.js";
|
|
9
9
|
import { $builder } from "./symbols.js";
|
|
10
10
|
import { ARRAY_STREAM_NOT_SUPPORTED } from "../encoder/streaming.js";
|
|
11
11
|
import { resolveQuantize, type QuantizeOptions } from "./quantize.js";
|
|
@@ -337,21 +337,28 @@ function resolveChild(child: ChildType): DefinitionType {
|
|
|
337
337
|
// overloads narrow the return type for Schema/primitive children.
|
|
338
338
|
// All collection factories tag `HasDefault = true` because schema() auto-
|
|
339
339
|
// instantiates an empty collection when no explicit default is given.
|
|
340
|
+
//
|
|
341
|
+
// The third overload refines the ELEMENT type (`t.map<V>` — the value, never
|
|
342
|
+
// the key), with the same type-level-only caveat as `PrimitiveFactory` above.
|
|
340
343
|
interface ArrayFactory {
|
|
341
344
|
<C extends Constructor<Schema>>(child: C): FieldBuilder<ArraySchema<InstanceType<C>>, true, false>;
|
|
342
345
|
<P extends RawPrimitiveType>(child: P): FieldBuilder<ArraySchema<InferValueType<P>>, true, false>;
|
|
346
|
+
<T>(child: CodecFor<T>): FieldBuilder<ArraySchema<T>, true, false>;
|
|
343
347
|
}
|
|
344
348
|
interface MapFactory {
|
|
345
349
|
<C extends Constructor<Schema>>(child: C): FieldBuilder<MapSchema<InstanceType<C>>, true, false>;
|
|
346
350
|
<P extends RawPrimitiveType>(child: P): FieldBuilder<MapSchema<InferValueType<P>>, true, false>;
|
|
351
|
+
<T>(child: CodecFor<T>): FieldBuilder<MapSchema<T>, true, false>;
|
|
347
352
|
}
|
|
348
353
|
interface SetFactory {
|
|
349
354
|
<C extends Constructor<Schema>>(child: C): FieldBuilder<SetSchema<InstanceType<C>>, true, false>;
|
|
350
355
|
<P extends RawPrimitiveType>(child: P): FieldBuilder<SetSchema<InferValueType<P>>, true, false>;
|
|
356
|
+
<T>(child: CodecFor<T>): FieldBuilder<SetSchema<T>, true, false>;
|
|
351
357
|
}
|
|
352
358
|
interface CollectionFactory {
|
|
353
359
|
<C extends Constructor<Schema>>(child: C): FieldBuilder<CollectionSchema<InstanceType<C>>, true, false>;
|
|
354
360
|
<P extends RawPrimitiveType>(child: P): FieldBuilder<CollectionSchema<InferValueType<P>>, true, false>;
|
|
361
|
+
<T>(child: CodecFor<T>): FieldBuilder<CollectionSchema<T>, true, false>;
|
|
355
362
|
}
|
|
356
363
|
// t.stream(Entity) — priority-batched collection of Schema instances.
|
|
357
364
|
// Element type is restricted to Schema subclasses (no primitives) because
|