shapecraft 1.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.
- package/CLAUDE.md +227 -0
- package/README.md +22 -0
- package/apps/cli/node_modules/.bin/prettier +21 -0
- package/apps/cli/node_modules/.bin/tsc +21 -0
- package/apps/cli/node_modules/.bin/tsserver +21 -0
- package/apps/cli/node_modules/.bin/tsx +21 -0
- package/apps/cli/node_modules/.bin/vitest +21 -0
- package/apps/cli/package.json +47 -0
- package/apps/cli/src/index.ts +98 -0
- package/apps/cli/tsconfig.cjs.json +10 -0
- package/apps/cli/tsconfig.esm.json +10 -0
- package/apps/cli/tsconfig.json +22 -0
- package/package.json +16 -0
- package/packages/core/node_modules/.bin/prettier +21 -0
- package/packages/core/node_modules/.bin/tsc +21 -0
- package/packages/core/node_modules/.bin/tsserver +21 -0
- package/packages/core/node_modules/.bin/tsx +21 -0
- package/packages/core/node_modules/.bin/vitest +21 -0
- package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/core/package.json +44 -0
- package/packages/core/src/common/array.test.ts +19 -0
- package/packages/core/src/common/array.ts +15 -0
- package/packages/core/src/common/index.ts +5 -0
- package/packages/core/src/common/is.ts +23 -0
- package/packages/core/src/common/object.ts +35 -0
- package/packages/core/src/common/phantom.ts +1 -0
- package/packages/core/src/common/result.ts +43 -0
- package/packages/core/src/common/string.ts +28 -0
- package/packages/core/src/common/types.ts +34 -0
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/shape/annotate.ts +139 -0
- package/packages/core/src/shape/annotation.ts +47 -0
- package/packages/core/src/shape/base.ts +71 -0
- package/packages/core/src/shape/builder.test.ts +728 -0
- package/packages/core/src/shape/builder.ts +475 -0
- package/packages/core/src/shape/error.ts +4 -0
- package/packages/core/src/shape/index.ts +3 -0
- package/packages/core/src/shape/number.ts +118 -0
- package/packages/core/src/shape/shape.test.ts +792 -0
- package/packages/core/src/shape/shape.ts +377 -0
- package/packages/core/src/shape/tags.ts +14 -0
- package/packages/core/src/shape/transforms/index.ts +3 -0
- package/packages/core/src/shape/transforms/json-schema/index.ts +2 -0
- package/packages/core/src/shape/transforms/json-schema/transform.test.ts +850 -0
- package/packages/core/src/shape/transforms/json-schema/transform.ts +882 -0
- package/packages/core/src/shape/transforms/json-schema/types.ts +132 -0
- package/packages/core/src/shape/transforms/sql/dialects/dialect.ts +89 -0
- package/packages/core/src/shape/transforms/sql/dialects/index.ts +14 -0
- package/packages/core/src/shape/transforms/sql/dialects/postgres.ts +392 -0
- package/packages/core/src/shape/transforms/sql/dialects/sqlite.ts +333 -0
- package/packages/core/src/shape/transforms/sql/from-sql.test.ts +704 -0
- package/packages/core/src/shape/transforms/sql/from-sql.ts +210 -0
- package/packages/core/src/shape/transforms/sql/index.ts +3 -0
- package/packages/core/src/shape/transforms/sql/options.ts +6 -0
- package/packages/core/src/shape/transforms/sql/parser/check-decoder.ts +457 -0
- package/packages/core/src/shape/transforms/sql/parser/create-domain.ts +105 -0
- package/packages/core/src/shape/transforms/sql/parser/create-table.ts +809 -0
- package/packages/core/src/shape/transforms/sql/parser/create-type.ts +91 -0
- package/packages/core/src/shape/transforms/sql/parser/cursor.ts +179 -0
- package/packages/core/src/shape/transforms/sql/parser/default-decoder.ts +129 -0
- package/packages/core/src/shape/transforms/sql/parser/lexer.ts +289 -0
- package/packages/core/src/shape/transforms/sql/parser/pg-types.ts +247 -0
- package/packages/core/src/shape/transforms/sql/parser/sqlite-types.ts +103 -0
- package/packages/core/src/shape/transforms/sql/parser/statements.ts +127 -0
- package/packages/core/src/shape/transforms/sql/parser/type-spec.ts +159 -0
- package/packages/core/src/shape/transforms/sql/transform.sqlite.test.ts +448 -0
- package/packages/core/src/shape/transforms/sql/transform.test.ts +880 -0
- package/packages/core/src/shape/transforms/sql/transform.ts +295 -0
- package/packages/core/src/shape/transforms/typescript/index.ts +1 -0
- package/packages/core/src/shape/transforms/typescript/transform.ts +211 -0
- package/packages/core/src/shape/tuple.test.ts +171 -0
- package/packages/core/src/shape/validate.ts +413 -0
- package/packages/core/tsconfig.cjs.json +11 -0
- package/packages/core/tsconfig.esm.json +10 -0
- package/packages/core/tsconfig.json +23 -0
- package/packages/samples/node_modules/.bin/prettier +21 -0
- package/packages/samples/node_modules/.bin/tsc +21 -0
- package/packages/samples/node_modules/.bin/tsserver +21 -0
- package/packages/samples/node_modules/.bin/tsx +21 -0
- package/packages/samples/node_modules/.bin/vitest +21 -0
- package/packages/samples/package.json +47 -0
- package/packages/samples/src/blog.ts +49 -0
- package/packages/samples/src/config.ts +50 -0
- package/packages/samples/src/ecommerce.ts +65 -0
- package/packages/samples/src/embeddings.ts +43 -0
- package/packages/samples/src/events.ts +52 -0
- package/packages/samples/src/geometry.ts +62 -0
- package/packages/samples/src/index.ts +9 -0
- package/packages/samples/src/relational.ts +17 -0
- package/packages/samples/src/tuples.ts +67 -0
- package/packages/samples/src/user.ts +9 -0
- package/packages/samples/tsconfig.cjs.json +11 -0
- package/packages/samples/tsconfig.esm.json +10 -0
- package/packages/samples/tsconfig.json +23 -0
- package/pnpm-workspace.yaml +3 -0
- package/test-data/json-schema/address.json +35 -0
- package/test-data/json-schema/array-of-things.json +36 -0
- package/test-data/json-schema/basic.json +21 -0
- package/test-data/json-schema/blog-post.json +29 -0
- package/test-data/json-schema/calendar.json +48 -0
- package/test-data/json-schema/complex-object-with-nested-properties.json +41 -0
- package/test-data/json-schema/ecommerce-complex.json +344 -0
- package/test-data/json-schema/ecommerce-system.json +27 -0
- package/test-data/json-schema/enumerated-values.json +11 -0
- package/test-data/json-schema/fstab-entry.json +92 -0
- package/test-data/json-schema/geographical-location.json +20 -0
- package/test-data/json-schema/health-record.json +41 -0
- package/test-data/json-schema/job-posting.json +33 -0
- package/test-data/json-schema/movie.json +35 -0
- package/test-data/json-schema/regular-expression-pattern.json +12 -0
- package/test-data/json-schema/user-profile.json +33 -0
- package/test-data/sql/ecommerce.sql +641 -0
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
import { R } from "../common";
|
|
2
|
+
import { remapObjectValues } from "../common/object";
|
|
3
|
+
import { Arrayish, Tuple } from "../common/types";
|
|
4
|
+
import { ValidationError } from "./error";
|
|
5
|
+
import { annotate, shapes } from "./shape";
|
|
6
|
+
import {
|
|
7
|
+
JSONSchema,
|
|
8
|
+
SQLOptions,
|
|
9
|
+
toJSONSchema,
|
|
10
|
+
toSQL,
|
|
11
|
+
toTypescript,
|
|
12
|
+
} from "./transforms";
|
|
13
|
+
|
|
14
|
+
export interface ShapeBuilderBase<out S extends shapes.Shape> {
|
|
15
|
+
type: S["type"];
|
|
16
|
+
shape: S;
|
|
17
|
+
|
|
18
|
+
is(input: unknown): input is shapes.InferShapeOut<S>;
|
|
19
|
+
parse(input: unknown): R.Result<shapes.InferShapeOut<S>, ValidationError[]>;
|
|
20
|
+
optional(): ShapeBuilder<shapes.Annotated<S, { optional: true }>>;
|
|
21
|
+
unique(): ShapeBuilder<shapes.Annotated<S, { unique: true }>>;
|
|
22
|
+
primary(): ShapeBuilder<shapes.Annotated<S, { primary: true }>>;
|
|
23
|
+
default<Value extends shapes.InferShapeOut<S>>(
|
|
24
|
+
value: Value,
|
|
25
|
+
): ShapeBuilder<shapes.Annotated<S, { default: Value }>>;
|
|
26
|
+
title<Text extends string>(
|
|
27
|
+
text: Text,
|
|
28
|
+
): ShapeBuilder<shapes.Annotated<S, { title: Text }>>;
|
|
29
|
+
describe<Text extends string>(
|
|
30
|
+
text: Text,
|
|
31
|
+
): ShapeBuilder<shapes.Annotated<S, { description: Text }>>;
|
|
32
|
+
comment<Text extends string>(
|
|
33
|
+
text: Text,
|
|
34
|
+
): ShapeBuilder<shapes.Annotated<S, { comment: Text }>>;
|
|
35
|
+
autoIncrement(): ShapeBuilder<shapes.Annotated<S, { autoIncrement: true }>>;
|
|
36
|
+
foreign<Table extends string, Column extends string>(
|
|
37
|
+
table: Table,
|
|
38
|
+
column: Column,
|
|
39
|
+
): ShapeBuilder<
|
|
40
|
+
shapes.Annotated<S, { foreign: { table: Table; column: Column } }>
|
|
41
|
+
>;
|
|
42
|
+
references<
|
|
43
|
+
Target extends ShapeBuilder<
|
|
44
|
+
shapes.Annotated<shapes.ShapeMapping, { title: string }>
|
|
45
|
+
>,
|
|
46
|
+
Field extends Extract<
|
|
47
|
+
keyof {
|
|
48
|
+
[P in keyof shapes.InputOfShape<Target["shape"]> as shapes.InputOfShape<
|
|
49
|
+
Target["shape"]
|
|
50
|
+
>[P]["type"] extends S["type"] ?
|
|
51
|
+
P
|
|
52
|
+
: never]: P;
|
|
53
|
+
},
|
|
54
|
+
string
|
|
55
|
+
>,
|
|
56
|
+
>(
|
|
57
|
+
target: Target,
|
|
58
|
+
field: Field,
|
|
59
|
+
): ShapeBuilder<
|
|
60
|
+
shapes.Annotated<
|
|
61
|
+
S,
|
|
62
|
+
{
|
|
63
|
+
foreign: {
|
|
64
|
+
table: Target["shape"]["anno"]["title"];
|
|
65
|
+
column: Extract<Field, string>;
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
>
|
|
69
|
+
>;
|
|
70
|
+
meta<Meta extends Record<string, any>>(
|
|
71
|
+
meta: Meta,
|
|
72
|
+
): ShapeBuilder<shapes.Annotated<S, { meta: S["anno"]["meta"] & Meta }>>;
|
|
73
|
+
|
|
74
|
+
////////////////
|
|
75
|
+
|
|
76
|
+
toJSONSchema(): JSONSchema;
|
|
77
|
+
toSQL(opts?: SQLOptions): R.Result<string, string[]>;
|
|
78
|
+
toTypescript(): string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface ShapeBuilderWithLength<
|
|
82
|
+
out S extends shapes.ShapeWithLength = shapes.ShapeWithLength,
|
|
83
|
+
> extends ShapeBuilderBase<S> {
|
|
84
|
+
min<N extends number>(n: N): ShapeBuilder<shapes.Annotated<S, { min: N }>>;
|
|
85
|
+
max<N extends number>(n: N): ShapeBuilder<shapes.Annotated<S, { max: N }>>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ShapeBuilderForString<
|
|
89
|
+
out S extends shapes.ShapeString = shapes.ShapeString,
|
|
90
|
+
> extends ShapeBuilderWithLength<S> {
|
|
91
|
+
regex<P extends string | RegExp>(
|
|
92
|
+
pattern: P,
|
|
93
|
+
): ShapeBuilder<shapes.Annotated<S, { pattern: P }>>;
|
|
94
|
+
format<F extends string>(
|
|
95
|
+
format: F,
|
|
96
|
+
): ShapeBuilder<shapes.Annotated<S, { format: F }>>;
|
|
97
|
+
uuid(
|
|
98
|
+
version?: number | undefined,
|
|
99
|
+
): ShapeBuilder<shapes.Annotated<S, { pattern: RegExp }>>;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ShapeBuilderForNumber<
|
|
103
|
+
out S extends shapes.ShapeNumber = shapes.ShapeNumber,
|
|
104
|
+
> extends ShapeBuilderWithLength<S> {}
|
|
105
|
+
|
|
106
|
+
export interface ShapeBuilderForArray<
|
|
107
|
+
out S extends shapes.ShapeArray = shapes.ShapeArray,
|
|
108
|
+
> extends ShapeBuilderWithLength<S> {
|
|
109
|
+
uniqueItems(): ShapeBuilder<shapes.Annotated<S, { uniqueItems: true }>>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface ShapeBuilderForMapping<
|
|
113
|
+
out S extends shapes.ShapeMapping = shapes.ShapeMapping,
|
|
114
|
+
> extends ShapeBuilderBase<S> {
|
|
115
|
+
unique(): ShapeBuilder<shapes.Annotated<S, { unique: true }>>;
|
|
116
|
+
unique<
|
|
117
|
+
Cols extends ReadonlyArray<Extract<keyof shapes.InputOfShape<S>, string>>,
|
|
118
|
+
>(
|
|
119
|
+
columns: Cols,
|
|
120
|
+
name?: string,
|
|
121
|
+
): ShapeBuilder<S>;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export type ShapeBuilder<S extends { type: string }> =
|
|
125
|
+
[S["type"]] extends ["string"] ?
|
|
126
|
+
ShapeBuilderForString<Extract<S, shapes.ShapeString>>
|
|
127
|
+
: [S["type"]] extends ["number"] ?
|
|
128
|
+
ShapeBuilderForNumber<Extract<S, shapes.ShapeNumber>>
|
|
129
|
+
: [S["type"]] extends ["array"] ?
|
|
130
|
+
ShapeBuilderForArray<Extract<S, shapes.ShapeArray>>
|
|
131
|
+
: [S["type"]] extends ["mapping"] ?
|
|
132
|
+
ShapeBuilderForMapping<Extract<S, shapes.ShapeMapping>>
|
|
133
|
+
: [S] extends [shapes.Shape] ? ShapeBuilderBase<S>
|
|
134
|
+
: never;
|
|
135
|
+
|
|
136
|
+
export type AnyShapeBuilder =
|
|
137
|
+
| ShapeBuilderBase<shapes.Shape>
|
|
138
|
+
| ShapeBuilderForString<shapes.ShapeString>
|
|
139
|
+
| ShapeBuilderForNumber<shapes.ShapeNumber>
|
|
140
|
+
| ShapeBuilderForArray<shapes.ShapeArray>
|
|
141
|
+
| ShapeBuilderForMapping<shapes.ShapeMapping>;
|
|
142
|
+
|
|
143
|
+
export function createBuilderBase<const S extends shapes.Shape>(
|
|
144
|
+
shape: S,
|
|
145
|
+
): ShapeBuilderBase<S> {
|
|
146
|
+
return {
|
|
147
|
+
type: shape.type,
|
|
148
|
+
shape: shape,
|
|
149
|
+
is: (input: unknown) => shapes.is(shape, input),
|
|
150
|
+
parse: (input: unknown) => shapes.parse(shape, input),
|
|
151
|
+
optional: () => createBuilder(annotate.optional(shape)),
|
|
152
|
+
unique: () => createBuilder(annotate.unique(shape)),
|
|
153
|
+
primary: () => createBuilder(annotate.primary(shape)),
|
|
154
|
+
default: (value) => createBuilder(annotate.defaulted(shape, value)),
|
|
155
|
+
title: (text) => createBuilder(annotate.titled(shape, text)),
|
|
156
|
+
describe: (text) => createBuilder(annotate.described(shape, text)),
|
|
157
|
+
comment: (text) => createBuilder(annotate.commented(shape, text)),
|
|
158
|
+
autoIncrement: () => createBuilder(annotate.autoIncremented(shape)),
|
|
159
|
+
foreign: <Table extends string, Column extends string>(
|
|
160
|
+
table: Table,
|
|
161
|
+
column: Column,
|
|
162
|
+
) => createBuilder(annotate.foreign(shape, table, column)),
|
|
163
|
+
references: <
|
|
164
|
+
Target extends ShapeBuilder<
|
|
165
|
+
shapes.Annotated<shapes.ShapeMapping, { title: string }>
|
|
166
|
+
>,
|
|
167
|
+
Field extends Extract<
|
|
168
|
+
keyof {
|
|
169
|
+
[P in keyof shapes.InputOfShape<
|
|
170
|
+
Target["shape"]
|
|
171
|
+
> as shapes.InputOfShape<Target["shape"]>[P]["type"] extends (
|
|
172
|
+
S["type"]
|
|
173
|
+
) ?
|
|
174
|
+
P
|
|
175
|
+
: never]: P;
|
|
176
|
+
},
|
|
177
|
+
string
|
|
178
|
+
>,
|
|
179
|
+
>(
|
|
180
|
+
target: Target,
|
|
181
|
+
field: Field,
|
|
182
|
+
) =>
|
|
183
|
+
createBuilder(
|
|
184
|
+
annotate.foreign(shape, target.shape.anno.title, field),
|
|
185
|
+
) as ShapeBuilder<
|
|
186
|
+
shapes.Annotated<
|
|
187
|
+
S,
|
|
188
|
+
{
|
|
189
|
+
foreign: {
|
|
190
|
+
table: Target["shape"]["anno"]["title"];
|
|
191
|
+
column: Extract<Field, string>;
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
>
|
|
195
|
+
>,
|
|
196
|
+
meta: (meta) => createBuilder(annotate.meta(shape, meta)),
|
|
197
|
+
|
|
198
|
+
//////
|
|
199
|
+
toJSONSchema: () => toJSONSchema(shape),
|
|
200
|
+
toSQL: (opts?: SQLOptions) => toSQL(shape, opts),
|
|
201
|
+
toTypescript: () => toTypescript(shape),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function createBuilderWithLength<const S extends shapes.ShapeWithLength>(
|
|
206
|
+
shape: S,
|
|
207
|
+
): ShapeBuilderWithLength<S> {
|
|
208
|
+
return {
|
|
209
|
+
...createBuilderBase(shape),
|
|
210
|
+
min: <N extends number>(n: N) => createBuilder(annotate.min(shape, n)),
|
|
211
|
+
max: <N extends number>(n: N) => createBuilder(annotate.max(shape, n)),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function createBuilderForString<const S extends shapes.ShapeString>(
|
|
216
|
+
shape: S,
|
|
217
|
+
): ShapeBuilderForString<S> {
|
|
218
|
+
return {
|
|
219
|
+
...createBuilderWithLength(shape),
|
|
220
|
+
regex: <P extends string | RegExp>(pattern: P) =>
|
|
221
|
+
createBuilder(annotate.as(shape, { pattern: pattern })),
|
|
222
|
+
format: <F extends string>(format: F) =>
|
|
223
|
+
createBuilder(annotate.as(shape, { format: format })),
|
|
224
|
+
uuid: (version?: number | undefined) =>
|
|
225
|
+
createBuilder(annotate.uuid(shape, version)),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function createBuilderForNumber<const S extends shapes.ShapeNumber>(
|
|
230
|
+
shape: S,
|
|
231
|
+
): ShapeBuilderForNumber<S> {
|
|
232
|
+
return {
|
|
233
|
+
...createBuilderWithLength(shape),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function createBuilderForArray<const S extends shapes.ShapeArray>(
|
|
238
|
+
shape: S,
|
|
239
|
+
): ShapeBuilderForArray<S> {
|
|
240
|
+
return {
|
|
241
|
+
...createBuilderWithLength(shape),
|
|
242
|
+
uniqueItems: () => createBuilder(annotate.uniqueItems(shape)),
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function createBuilderForMapping<const S extends shapes.ShapeMapping>(
|
|
247
|
+
shape: S,
|
|
248
|
+
): ShapeBuilderForMapping<S> {
|
|
249
|
+
const uniqueFn = ((
|
|
250
|
+
columns?: ReadonlyArray<string>,
|
|
251
|
+
name?: string,
|
|
252
|
+
): AnyShapeBuilder => {
|
|
253
|
+
if (columns === undefined) {
|
|
254
|
+
return createBuilder(annotate.unique(shape)) as AnyShapeBuilder;
|
|
255
|
+
}
|
|
256
|
+
const existing = shape.anno.uniqueConstraints ?? [];
|
|
257
|
+
const entry: { columns: string[]; name?: string } = {
|
|
258
|
+
columns: [...columns],
|
|
259
|
+
};
|
|
260
|
+
if (name !== undefined) entry.name = name;
|
|
261
|
+
return createBuilder(
|
|
262
|
+
annotate.as(shape, {
|
|
263
|
+
uniqueConstraints: [...existing, entry],
|
|
264
|
+
}),
|
|
265
|
+
) as AnyShapeBuilder;
|
|
266
|
+
}) as ShapeBuilderForMapping<S>["unique"];
|
|
267
|
+
return {
|
|
268
|
+
...createBuilderBase(shape),
|
|
269
|
+
unique: uniqueFn,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function createBuilder<const S extends shapes.Shape>(
|
|
274
|
+
shape: S,
|
|
275
|
+
): ShapeBuilder<S>;
|
|
276
|
+
export function createBuilder<const S extends shapes.ShapeString>(
|
|
277
|
+
shape: S,
|
|
278
|
+
): ShapeBuilderForString<S>;
|
|
279
|
+
export function createBuilder<const S extends shapes.ShapeNumber>(
|
|
280
|
+
shape: S,
|
|
281
|
+
): ShapeBuilderForNumber<S>;
|
|
282
|
+
export function createBuilder<const S extends shapes.ShapeArray>(
|
|
283
|
+
shape: S,
|
|
284
|
+
): ShapeBuilderForArray<S>;
|
|
285
|
+
export function createBuilder<const S extends shapes.ShapeMapping>(
|
|
286
|
+
shape: S,
|
|
287
|
+
): ShapeBuilderForMapping<S>;
|
|
288
|
+
export function createBuilder(shape: shapes.Shape): AnyShapeBuilder;
|
|
289
|
+
export function createBuilder(shape: shapes.Shape): AnyShapeBuilder {
|
|
290
|
+
switch (shape.type) {
|
|
291
|
+
case "string":
|
|
292
|
+
return createBuilderForString(shape);
|
|
293
|
+
case "number":
|
|
294
|
+
return createBuilderForNumber(shape);
|
|
295
|
+
case "array":
|
|
296
|
+
return createBuilderForArray(shape);
|
|
297
|
+
case "mapping":
|
|
298
|
+
return createBuilderForMapping(shape as shapes.ShapeMapping);
|
|
299
|
+
default:
|
|
300
|
+
return createBuilderBase(shape);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/*
|
|
305
|
+
Allows for more ergonomic shape definitions like:
|
|
306
|
+
```
|
|
307
|
+
const myShape = B.mapping({
|
|
308
|
+
foo: B.string().optional()
|
|
309
|
+
})
|
|
310
|
+
```
|
|
311
|
+
*/
|
|
312
|
+
export namespace B {
|
|
313
|
+
// ---------- primitive scalars
|
|
314
|
+
export const string = (): ShapeBuilderForString<shapes.ShapeString> =>
|
|
315
|
+
createBuilderForString(shapes.string());
|
|
316
|
+
export const boolean = (): ShapeBuilderBase<shapes.ShapeBoolean> =>
|
|
317
|
+
createBuilderBase(shapes.boolean());
|
|
318
|
+
export const notDefined = (): ShapeBuilderBase<shapes.ShapeUndefined> =>
|
|
319
|
+
createBuilderBase(shapes.notDefined());
|
|
320
|
+
export const date = (): ShapeBuilderBase<shapes.ShapeDate> =>
|
|
321
|
+
createBuilderBase(shapes.date());
|
|
322
|
+
export const nil = (): ShapeBuilderBase<shapes.ShapeNil> =>
|
|
323
|
+
createBuilderBase(shapes.nil());
|
|
324
|
+
export const unknown = (): ShapeBuilderBase<shapes.ShapeUnknown> =>
|
|
325
|
+
createBuilderBase(shapes.unknown());
|
|
326
|
+
export const binary = (): ShapeBuilderBase<shapes.ShapeBinary> =>
|
|
327
|
+
createBuilderBase(shapes.binary());
|
|
328
|
+
|
|
329
|
+
// ---------- numeric tags
|
|
330
|
+
export const number = (): ShapeBuilderForNumber<
|
|
331
|
+
shapes.ShapeNumber<null, "number">
|
|
332
|
+
> => createBuilderForNumber(shapes.number());
|
|
333
|
+
export const int = (): ShapeBuilderForNumber<
|
|
334
|
+
shapes.ShapeNumber<null, "int">
|
|
335
|
+
> => createBuilderForNumber(shapes.int());
|
|
336
|
+
export const int8 = (): ShapeBuilderForNumber<
|
|
337
|
+
shapes.ShapeNumber<null, "int8">
|
|
338
|
+
> => createBuilderForNumber(shapes.int8());
|
|
339
|
+
export const uint8 = (): ShapeBuilderForNumber<
|
|
340
|
+
shapes.ShapeNumber<null, "uint8">
|
|
341
|
+
> => createBuilderForNumber(shapes.uint8());
|
|
342
|
+
export const int16 = (): ShapeBuilderForNumber<
|
|
343
|
+
shapes.ShapeNumber<null, "int16">
|
|
344
|
+
> => createBuilderForNumber(shapes.int16());
|
|
345
|
+
export const uint16 = (): ShapeBuilderForNumber<
|
|
346
|
+
shapes.ShapeNumber<null, "uint16">
|
|
347
|
+
> => createBuilderForNumber(shapes.uint16());
|
|
348
|
+
export const int32 = (): ShapeBuilderForNumber<
|
|
349
|
+
shapes.ShapeNumber<null, "int32">
|
|
350
|
+
> => createBuilderForNumber(shapes.int32());
|
|
351
|
+
export const uint32 = (): ShapeBuilderForNumber<
|
|
352
|
+
shapes.ShapeNumber<null, "uint32">
|
|
353
|
+
> => createBuilderForNumber(shapes.uint32());
|
|
354
|
+
export const int64 = (): ShapeBuilderForNumber<
|
|
355
|
+
shapes.ShapeNumber<null, "int64">
|
|
356
|
+
> => createBuilderForNumber(shapes.int64());
|
|
357
|
+
export const uint64 = (): ShapeBuilderForNumber<
|
|
358
|
+
shapes.ShapeNumber<null, "uint64">
|
|
359
|
+
> => createBuilderForNumber(shapes.uint64());
|
|
360
|
+
export const float = (): ShapeBuilderForNumber<
|
|
361
|
+
shapes.ShapeNumber<null, "float">
|
|
362
|
+
> => createBuilderForNumber(shapes.float());
|
|
363
|
+
export const float32 = (): ShapeBuilderForNumber<
|
|
364
|
+
shapes.ShapeNumber<null, "float32">
|
|
365
|
+
> => createBuilderForNumber(shapes.float32());
|
|
366
|
+
export const float64 = (): ShapeBuilderForNumber<
|
|
367
|
+
shapes.ShapeNumber<null, "float64">
|
|
368
|
+
> => createBuilderForNumber(shapes.float64());
|
|
369
|
+
|
|
370
|
+
// ---------- literal
|
|
371
|
+
export function literal<const V extends string>(
|
|
372
|
+
value: V,
|
|
373
|
+
): ShapeBuilderForString<shapes.MakeLiteral<shapes.ShapeString<V>>>;
|
|
374
|
+
export function literal<const V extends number>(
|
|
375
|
+
value: V,
|
|
376
|
+
): ShapeBuilderForNumber<shapes.MakeLiteral<shapes.ShapeNumber<V>>>;
|
|
377
|
+
export function literal<const V extends boolean>(
|
|
378
|
+
value: V,
|
|
379
|
+
): ShapeBuilderBase<shapes.MakeLiteral<shapes.ShapeBoolean<V>>>;
|
|
380
|
+
export function literal<const V extends string | number | boolean>(
|
|
381
|
+
value: V,
|
|
382
|
+
): AnyShapeBuilder {
|
|
383
|
+
return createBuilder(shapes.literal(value));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ---------- containers
|
|
387
|
+
export const array = <const Builder extends { shape: shapes.Shape }>(
|
|
388
|
+
builder: Builder,
|
|
389
|
+
): ShapeBuilderForArray<shapes.ShapeArray<Builder["shape"]>> =>
|
|
390
|
+
createBuilderForArray(shapes.array(builder.shape));
|
|
391
|
+
|
|
392
|
+
export const mapping = <
|
|
393
|
+
const Rec extends Record<string, { shape: shapes.Shape }>,
|
|
394
|
+
>(
|
|
395
|
+
rec: Rec,
|
|
396
|
+
): ShapeBuilderForMapping<
|
|
397
|
+
shapes.ShapeMapping<{ -readonly [P in keyof Rec]: Rec[P]["shape"] }>
|
|
398
|
+
> => {
|
|
399
|
+
return createBuilderForMapping(
|
|
400
|
+
shapes.mapping(remapObjectValues(rec, ([_k, r]) => r.shape)),
|
|
401
|
+
);
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
export const record = <
|
|
405
|
+
const K extends { shape: shapes.ShapeKeyable },
|
|
406
|
+
const V extends { shape: shapes.Shape },
|
|
407
|
+
>(
|
|
408
|
+
key: K,
|
|
409
|
+
value: V,
|
|
410
|
+
): ShapeBuilderBase<shapes.ShapeRecord<[K["shape"], V["shape"]]>> =>
|
|
411
|
+
createBuilderBase(shapes.record(key.shape, value.shape));
|
|
412
|
+
|
|
413
|
+
export const range = <const Item extends { shape: shapes.Shape }>(
|
|
414
|
+
item: Item,
|
|
415
|
+
): ShapeBuilderBase<shapes.ShapeRange<Item["shape"]>> =>
|
|
416
|
+
createBuilderBase(shapes.range(item.shape));
|
|
417
|
+
|
|
418
|
+
export function vector<const Dims extends number>(
|
|
419
|
+
dims: Dims,
|
|
420
|
+
): ShapeBuilderBase<shapes.ShapeVector<Dims, shapes.ShapeNumber>>;
|
|
421
|
+
export function vector<
|
|
422
|
+
const Dims extends number,
|
|
423
|
+
const Format extends { shape: shapes.ShapeNumber },
|
|
424
|
+
>(
|
|
425
|
+
dims: Dims,
|
|
426
|
+
format: Format,
|
|
427
|
+
): ShapeBuilderBase<shapes.ShapeVector<Dims, Format["shape"]>>;
|
|
428
|
+
export function vector(
|
|
429
|
+
dims: number,
|
|
430
|
+
format?: { shape: shapes.ShapeNumber },
|
|
431
|
+
): ShapeBuilderBase<shapes.ShapeVector> {
|
|
432
|
+
return createBuilderBase(
|
|
433
|
+
format ? shapes.vector(dims, format.shape) : shapes.vector(dims),
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export function union<const Of extends readonly { shape: shapes.Shape }[]>(
|
|
438
|
+
...of: Of
|
|
439
|
+
): ShapeBuilderBase<
|
|
440
|
+
shapes.ShapeUnion<{ -readonly [K in keyof Of]: Of[K]["shape"] }>
|
|
441
|
+
>;
|
|
442
|
+
export function union(
|
|
443
|
+
...of: Arrayish<AnyShapeBuilder>
|
|
444
|
+
): ShapeBuilderBase<shapes.ShapeUnion> {
|
|
445
|
+
return createBuilderBase(shapes.union(...of.flat().map((x) => x.shape)));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export function tuple<const Of extends readonly { shape: shapes.Shape }[]>(
|
|
449
|
+
...of: Of
|
|
450
|
+
): ShapeBuilderBase<
|
|
451
|
+
shapes.ShapeTuple<{ -readonly [K in keyof Of]: Of[K]["shape"] }>
|
|
452
|
+
>;
|
|
453
|
+
export function tuple<const Of extends readonly { shape: shapes.Shape }[]>(
|
|
454
|
+
of: Of,
|
|
455
|
+
): ShapeBuilderBase<
|
|
456
|
+
shapes.ShapeTuple<{ -readonly [K in keyof Of]: Of[K]["shape"] }>
|
|
457
|
+
>;
|
|
458
|
+
export function tuple(
|
|
459
|
+
...of: Arrayish<AnyShapeBuilder>
|
|
460
|
+
): ShapeBuilderBase<shapes.ShapeTuple> {
|
|
461
|
+
return createBuilderBase(shapes.tuple(...of.flat().map((x) => x.shape)));
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export const module = <
|
|
465
|
+
const Rec extends Record<string, { shape: shapes.ShapeMapping }>,
|
|
466
|
+
>(
|
|
467
|
+
rec: Rec,
|
|
468
|
+
): ShapeBuilderBase<
|
|
469
|
+
shapes.ShapeModule<{ -readonly [P in keyof Rec]: Rec[P]["shape"] }>
|
|
470
|
+
> => {
|
|
471
|
+
return createBuilderBase(
|
|
472
|
+
shapes.module(remapObjectValues(rec, ([_k, r]) => r.shape)),
|
|
473
|
+
);
|
|
474
|
+
};
|
|
475
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { FloatTag, IntTag, NumberTag } from "./tags";
|
|
2
|
+
|
|
3
|
+
const INT8_MIN = -128;
|
|
4
|
+
const INT8_MAX = 127;
|
|
5
|
+
const UINT8_MAX = 255;
|
|
6
|
+
const INT16_MIN = -32_768;
|
|
7
|
+
const INT16_MAX = 32_767;
|
|
8
|
+
const UINT16_MAX = 65_535;
|
|
9
|
+
const INT32_MIN = -2_147_483_648;
|
|
10
|
+
const INT32_MAX = 2_147_483_647;
|
|
11
|
+
const UINT32_MAX = 4_294_967_295;
|
|
12
|
+
const INT64_MIN = -(2 ** 63);
|
|
13
|
+
const INT64_MAX = 2 ** 63 - 1;
|
|
14
|
+
const UINT64_MAX = 2 ** 64 - 1;
|
|
15
|
+
|
|
16
|
+
const FLOAT32_MAX = 3.4028234663852886e38;
|
|
17
|
+
|
|
18
|
+
const fitsFloat32 = (x: number): boolean => {
|
|
19
|
+
if (!Number.isFinite(x)) return true;
|
|
20
|
+
if (Math.abs(x) > FLOAT32_MAX) return false;
|
|
21
|
+
return Math.fround(x) === x;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const inferNumberTag = (x: number): NumberTag => {
|
|
25
|
+
if (!Number.isFinite(x)) {
|
|
26
|
+
return "float32";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (Number.isInteger(x)) {
|
|
30
|
+
if (x >= 0) {
|
|
31
|
+
if (x <= INT8_MAX) return "uint8";
|
|
32
|
+
if (x <= UINT8_MAX) return "uint8";
|
|
33
|
+
if (x <= INT16_MAX) return "uint16";
|
|
34
|
+
if (x <= UINT16_MAX) return "uint16";
|
|
35
|
+
if (x <= INT32_MAX) return "uint32";
|
|
36
|
+
if (x <= UINT32_MAX) return "uint32";
|
|
37
|
+
if (x <= INT64_MAX) return "uint64";
|
|
38
|
+
if (x <= UINT64_MAX) return "uint64";
|
|
39
|
+
return "int";
|
|
40
|
+
} else {
|
|
41
|
+
if (x >= INT8_MIN) return "int8";
|
|
42
|
+
if (x >= INT16_MIN) return "int16";
|
|
43
|
+
if (x >= INT32_MIN) return "int32";
|
|
44
|
+
if (x >= INT64_MIN) return "int64";
|
|
45
|
+
return "int";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Non-integer floats
|
|
50
|
+
if (fitsFloat32(x)) return "float32";
|
|
51
|
+
return "float64";
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const isFloatTag = (x: NumberTag): x is FloatTag =>
|
|
55
|
+
x === "float" || x === "float32" || x === "float64";
|
|
56
|
+
|
|
57
|
+
export const isIntTag = (x: NumberTag): x is IntTag => !isFloatTag(x);
|
|
58
|
+
|
|
59
|
+
export const isUnsignedTag = (x: NumberTag): boolean => x.startsWith("uint");
|
|
60
|
+
|
|
61
|
+
const INT_RANK: Record<IntTag, number> = {
|
|
62
|
+
uint8: 1,
|
|
63
|
+
int8: 2,
|
|
64
|
+
uint16: 3,
|
|
65
|
+
int16: 4,
|
|
66
|
+
uint32: 5,
|
|
67
|
+
int32: 6,
|
|
68
|
+
uint64: 7,
|
|
69
|
+
int64: 8,
|
|
70
|
+
int: 9,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const FLOAT_RANK: Record<FloatTag, number> = {
|
|
74
|
+
float32: 1,
|
|
75
|
+
float64: 2,
|
|
76
|
+
float: 3,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const rankOf = (x: NumberTag): number =>
|
|
80
|
+
isIntTag(x) ? INT_RANK[x]
|
|
81
|
+
: isFloatTag(x) ? FLOAT_RANK[x]
|
|
82
|
+
: -1;
|
|
83
|
+
|
|
84
|
+
const merge = (a: NumberTag, b: NumberTag): NumberTag => {
|
|
85
|
+
if (a === b) return a;
|
|
86
|
+
if (a === "number" || b === "number") return "number";
|
|
87
|
+
|
|
88
|
+
if (isIntTag(a) && isIntTag(b)) {
|
|
89
|
+
return rankOf(a) > rankOf(b) ? a : b;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (isFloatTag(a) && isFloatTag(b)) {
|
|
93
|
+
return rankOf(a) > rankOf(b) ? a : b;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return "float64";
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export const inferNumberTagFromSamples = (samples: number[]): NumberTag => {
|
|
100
|
+
if (samples.length <= 0) return "number";
|
|
101
|
+
const tags = samples.map((s) => inferNumberTag(s));
|
|
102
|
+
const uniqueTags = Array.from(new Set(tags).values());
|
|
103
|
+
if (uniqueTags.length === 1) return uniqueTags[0]!;
|
|
104
|
+
return uniqueTags.reduce(merge);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export const numberTagCanBeCastedTo = (
|
|
108
|
+
tag: NumberTag,
|
|
109
|
+
to: NumberTag,
|
|
110
|
+
): boolean => {
|
|
111
|
+
if (tag === to || to === "number") return true;
|
|
112
|
+
|
|
113
|
+
if ((isIntTag(tag) && isIntTag(to)) || (isFloatTag(tag) && isFloatTag(to))) {
|
|
114
|
+
return rankOf(tag) <= rankOf(to);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return false;
|
|
118
|
+
};
|