effect-qb 0.12.3
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/README.md +1294 -0
- package/dist/mysql.js +57575 -0
- package/dist/postgres.js +6303 -0
- package/package.json +42 -0
- package/src/internal/aggregation-validation.ts +57 -0
- package/src/internal/case-analysis.ts +50 -0
- package/src/internal/coercion-analysis.ts +30 -0
- package/src/internal/coercion-errors.ts +29 -0
- package/src/internal/coercion-kind.ts +32 -0
- package/src/internal/coercion-normalize.ts +7 -0
- package/src/internal/coercion-rules.ts +25 -0
- package/src/internal/column-state.ts +453 -0
- package/src/internal/column.ts +417 -0
- package/src/internal/datatypes/define.ts +44 -0
- package/src/internal/datatypes/lookup.ts +280 -0
- package/src/internal/datatypes/shape.ts +72 -0
- package/src/internal/derived-table.ts +149 -0
- package/src/internal/dialect.ts +30 -0
- package/src/internal/executor.ts +390 -0
- package/src/internal/expression-ast.ts +349 -0
- package/src/internal/expression.ts +325 -0
- package/src/internal/grouping-key.ts +82 -0
- package/src/internal/json/ast.ts +63 -0
- package/src/internal/json/errors.ts +13 -0
- package/src/internal/json/path.ts +227 -0
- package/src/internal/json/shape.ts +1 -0
- package/src/internal/json/types.ts +386 -0
- package/src/internal/mysql-dialect.ts +39 -0
- package/src/internal/mysql-renderer.ts +37 -0
- package/src/internal/plan.ts +64 -0
- package/src/internal/postgres-dialect.ts +34 -0
- package/src/internal/postgres-renderer.ts +40 -0
- package/src/internal/predicate-analysis.ts +71 -0
- package/src/internal/predicate-atom.ts +43 -0
- package/src/internal/predicate-branches.ts +40 -0
- package/src/internal/predicate-context.ts +279 -0
- package/src/internal/predicate-formula.ts +100 -0
- package/src/internal/predicate-key.ts +28 -0
- package/src/internal/predicate-nnf.ts +12 -0
- package/src/internal/predicate-normalize.ts +202 -0
- package/src/internal/projection-alias.ts +15 -0
- package/src/internal/projections.ts +101 -0
- package/src/internal/query-ast.ts +297 -0
- package/src/internal/query-factory.ts +6757 -0
- package/src/internal/query-requirements.ts +40 -0
- package/src/internal/query.ts +1590 -0
- package/src/internal/renderer.ts +102 -0
- package/src/internal/runtime-normalize.ts +344 -0
- package/src/internal/runtime-schema.ts +428 -0
- package/src/internal/runtime-value.ts +85 -0
- package/src/internal/schema-derivation.ts +131 -0
- package/src/internal/sql-expression-renderer.ts +1353 -0
- package/src/internal/table-options.ts +225 -0
- package/src/internal/table.ts +674 -0
- package/src/mysql/column.ts +30 -0
- package/src/mysql/datatypes/index.ts +6 -0
- package/src/mysql/datatypes/spec.ts +180 -0
- package/src/mysql/errors/catalog.ts +51662 -0
- package/src/mysql/errors/fields.ts +21 -0
- package/src/mysql/errors/index.ts +18 -0
- package/src/mysql/errors/normalize.ts +232 -0
- package/src/mysql/errors/requirements.ts +73 -0
- package/src/mysql/executor.ts +134 -0
- package/src/mysql/query.ts +189 -0
- package/src/mysql/renderer.ts +19 -0
- package/src/mysql/table.ts +157 -0
- package/src/mysql.ts +18 -0
- package/src/postgres/column.ts +20 -0
- package/src/postgres/datatypes/index.ts +8 -0
- package/src/postgres/datatypes/spec.ts +264 -0
- package/src/postgres/errors/catalog.ts +452 -0
- package/src/postgres/errors/fields.ts +48 -0
- package/src/postgres/errors/index.ts +4 -0
- package/src/postgres/errors/normalize.ts +209 -0
- package/src/postgres/errors/requirements.ts +65 -0
- package/src/postgres/errors/types.ts +38 -0
- package/src/postgres/executor.ts +131 -0
- package/src/postgres/query.ts +189 -0
- package/src/postgres/renderer.ts +29 -0
- package/src/postgres/table.ts +157 -0
- package/src/postgres.ts +18 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as Expression from "./expression.js"
|
|
2
|
+
import * as ExpressionAst from "./expression-ast.js"
|
|
3
|
+
|
|
4
|
+
const literalGroupingKey = (value: unknown): string => {
|
|
5
|
+
if (value instanceof Date) {
|
|
6
|
+
return `date:${value.toISOString()}`
|
|
7
|
+
}
|
|
8
|
+
if (value === null) {
|
|
9
|
+
return "null"
|
|
10
|
+
}
|
|
11
|
+
switch (typeof value) {
|
|
12
|
+
case "string":
|
|
13
|
+
return `string:${JSON.stringify(value)}`
|
|
14
|
+
case "number":
|
|
15
|
+
return `number:${value}`
|
|
16
|
+
case "boolean":
|
|
17
|
+
return `boolean:${value}`
|
|
18
|
+
default:
|
|
19
|
+
return `literal:${JSON.stringify(value)}`
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const groupingKeyOfExpression = (expression: Expression.Any): string => {
|
|
24
|
+
const ast = (expression as Expression.Any & {
|
|
25
|
+
readonly [ExpressionAst.TypeId]: ExpressionAst.Any
|
|
26
|
+
})[ExpressionAst.TypeId]
|
|
27
|
+
switch (ast.kind) {
|
|
28
|
+
case "column":
|
|
29
|
+
return `column:${ast.tableName}.${ast.columnName}`
|
|
30
|
+
case "literal":
|
|
31
|
+
return `literal:${literalGroupingKey(ast.value)}`
|
|
32
|
+
case "cast":
|
|
33
|
+
return `cast(${groupingKeyOfExpression(ast.value)} as ${ast.target.dialect}:${ast.target.kind})`
|
|
34
|
+
case "isNull":
|
|
35
|
+
case "isNotNull":
|
|
36
|
+
case "not":
|
|
37
|
+
case "upper":
|
|
38
|
+
case "lower":
|
|
39
|
+
case "count":
|
|
40
|
+
case "max":
|
|
41
|
+
case "min":
|
|
42
|
+
return `${ast.kind}(${groupingKeyOfExpression(ast.value)})`
|
|
43
|
+
case "eq":
|
|
44
|
+
case "neq":
|
|
45
|
+
case "lt":
|
|
46
|
+
case "lte":
|
|
47
|
+
case "gt":
|
|
48
|
+
case "gte":
|
|
49
|
+
case "like":
|
|
50
|
+
case "ilike":
|
|
51
|
+
case "isDistinctFrom":
|
|
52
|
+
case "isNotDistinctFrom":
|
|
53
|
+
return `${ast.kind}(${groupingKeyOfExpression(ast.left)},${groupingKeyOfExpression(ast.right)})`
|
|
54
|
+
case "and":
|
|
55
|
+
case "or":
|
|
56
|
+
case "coalesce":
|
|
57
|
+
case "concat":
|
|
58
|
+
case "in":
|
|
59
|
+
case "notIn":
|
|
60
|
+
case "between":
|
|
61
|
+
return `${ast.kind}(${ast.values.map(groupingKeyOfExpression).join(",")})`
|
|
62
|
+
case "case":
|
|
63
|
+
return `case(${ast.branches.map((branch: ExpressionAst.CaseBranchNode) =>
|
|
64
|
+
`when:${groupingKeyOfExpression(branch.when)}=>${groupingKeyOfExpression(branch.then)}`).join("|")};else:${groupingKeyOfExpression(ast.else)})`
|
|
65
|
+
default:
|
|
66
|
+
throw new Error("Unsupported expression for grouping key generation")
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const dedupeGroupedExpressions = <Values extends readonly Expression.Any[]>(
|
|
71
|
+
values: Values
|
|
72
|
+
): Values => {
|
|
73
|
+
const seen = new Set<string>()
|
|
74
|
+
return values.filter((value) => {
|
|
75
|
+
const key = groupingKeyOfExpression(value)
|
|
76
|
+
if (seen.has(key)) {
|
|
77
|
+
return false
|
|
78
|
+
}
|
|
79
|
+
seen.add(key)
|
|
80
|
+
return true
|
|
81
|
+
}) as unknown as Values
|
|
82
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type * as Expression from ".././expression.js"
|
|
2
|
+
import type * as JsonPath from "./path.js"
|
|
3
|
+
|
|
4
|
+
export type JsonKind =
|
|
5
|
+
| "jsonGet"
|
|
6
|
+
| "jsonPath"
|
|
7
|
+
| "jsonAccess"
|
|
8
|
+
| "jsonTraverse"
|
|
9
|
+
| "jsonGetText"
|
|
10
|
+
| "jsonPathText"
|
|
11
|
+
| "jsonAccessText"
|
|
12
|
+
| "jsonTraverseText"
|
|
13
|
+
| "jsonHasKey"
|
|
14
|
+
| "jsonKeyExists"
|
|
15
|
+
| "jsonHasAnyKeys"
|
|
16
|
+
| "jsonHasAllKeys"
|
|
17
|
+
| "jsonConcat"
|
|
18
|
+
| "jsonMerge"
|
|
19
|
+
| "jsonDelete"
|
|
20
|
+
| "jsonDeletePath"
|
|
21
|
+
| "jsonRemove"
|
|
22
|
+
| "jsonSet"
|
|
23
|
+
| "jsonInsert"
|
|
24
|
+
| "jsonPathExists"
|
|
25
|
+
| "jsonPathMatch"
|
|
26
|
+
| "jsonBuildObject"
|
|
27
|
+
| "jsonBuildArray"
|
|
28
|
+
| "jsonToJson"
|
|
29
|
+
| "jsonToJsonb"
|
|
30
|
+
| "jsonTypeOf"
|
|
31
|
+
| "jsonLength"
|
|
32
|
+
| "jsonKeys"
|
|
33
|
+
| "jsonStripNulls"
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Broad JSON AST node accepted by the renderer.
|
|
37
|
+
*
|
|
38
|
+
* The JSON subsystem is intentionally shaped as a small grammar rather than a
|
|
39
|
+
* collection of bespoke node interfaces. The renderer uses the `kind` plus the
|
|
40
|
+
* common field names below to lower the nodes into dialect SQL.
|
|
41
|
+
*/
|
|
42
|
+
export interface JsonNode<
|
|
43
|
+
Kind extends JsonKind = JsonKind
|
|
44
|
+
> {
|
|
45
|
+
readonly kind: Kind
|
|
46
|
+
readonly value?: Expression.Any
|
|
47
|
+
readonly base?: Expression.Any
|
|
48
|
+
readonly left?: Expression.Any
|
|
49
|
+
readonly right?: Expression.Any
|
|
50
|
+
readonly path?: JsonPath.Path<any> | readonly any[]
|
|
51
|
+
readonly segments?: readonly any[]
|
|
52
|
+
readonly keys?: readonly string[]
|
|
53
|
+
readonly query?: string | Expression.Any
|
|
54
|
+
readonly newValue?: Expression.Any
|
|
55
|
+
readonly insert?: Expression.Any
|
|
56
|
+
readonly createMissing?: boolean
|
|
57
|
+
readonly insertAfter?: boolean
|
|
58
|
+
readonly entries?: readonly {
|
|
59
|
+
readonly key: string
|
|
60
|
+
readonly value: Expression.Any
|
|
61
|
+
}[]
|
|
62
|
+
readonly values?: readonly Expression.Any[]
|
|
63
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type JsonPathUsageError<
|
|
2
|
+
Operation extends string,
|
|
3
|
+
Root,
|
|
4
|
+
Path,
|
|
5
|
+
Reason extends string
|
|
6
|
+
> = {
|
|
7
|
+
readonly __effect_qb_error__: "effect-qb: invalid json path usage"
|
|
8
|
+
readonly __effect_qb_json_operation__: Operation
|
|
9
|
+
readonly __effect_qb_json_reason__: Reason
|
|
10
|
+
readonly __effect_qb_json_root__: Root
|
|
11
|
+
readonly __effect_qb_json_path__: Path
|
|
12
|
+
readonly __effect_qb_hint__: "Use key(...) on objects, index(...) on arrays, and prefer exact key/index paths when you want precise output typing"
|
|
13
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import type * as Expression from ".././expression.js"
|
|
2
|
+
import type { JsonPathUsageError } from "./errors.js"
|
|
3
|
+
import type {
|
|
4
|
+
JsonBuildArray as JsonBuildArrayResult,
|
|
5
|
+
JsonBuildObject as JsonBuildObjectResult,
|
|
6
|
+
JsonConcatResult,
|
|
7
|
+
JsonDeleteAtPath,
|
|
8
|
+
JsonInsertAtPath,
|
|
9
|
+
JsonLiteralInput,
|
|
10
|
+
JsonStripNullsResult as JsonStripNullsResultValue,
|
|
11
|
+
JsonValueAtPath,
|
|
12
|
+
JsonSetAtPath
|
|
13
|
+
} from "./types.js"
|
|
14
|
+
import type { JsonKeysResult as JsonKeysResultValue, JsonLengthResult as JsonLengthResultValue, JsonTextResult as JsonTextResultValue, JsonTypeName as JsonTypeNameValue } from "./types.js"
|
|
15
|
+
|
|
16
|
+
export const SegmentTypeId: unique symbol = Symbol.for("effect-qb/JsonPathSegment")
|
|
17
|
+
|
|
18
|
+
export type SegmentTypeId = typeof SegmentTypeId
|
|
19
|
+
|
|
20
|
+
export const TypeId: unique symbol = Symbol.for("effect-qb/JsonPath")
|
|
21
|
+
|
|
22
|
+
export type TypeId = typeof TypeId
|
|
23
|
+
|
|
24
|
+
type SegmentState<Kind extends string> = {
|
|
25
|
+
readonly kind: Kind
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface KeySegment<Key extends string = string> {
|
|
29
|
+
readonly [SegmentTypeId]: SegmentState<"key">
|
|
30
|
+
readonly kind: "key"
|
|
31
|
+
readonly key: Key
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface IndexSegment<Index extends number = number> {
|
|
35
|
+
readonly [SegmentTypeId]: SegmentState<"index">
|
|
36
|
+
readonly kind: "index"
|
|
37
|
+
readonly index: Index
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface WildcardSegment {
|
|
41
|
+
readonly [SegmentTypeId]: SegmentState<"wildcard">
|
|
42
|
+
readonly kind: "wildcard"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface SliceSegment<
|
|
46
|
+
Start extends number | undefined = number | undefined,
|
|
47
|
+
End extends number | undefined = number | undefined
|
|
48
|
+
> {
|
|
49
|
+
readonly [SegmentTypeId]: SegmentState<"slice">
|
|
50
|
+
readonly kind: "slice"
|
|
51
|
+
readonly start: Start
|
|
52
|
+
readonly end: End
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface DescendSegment {
|
|
56
|
+
readonly [SegmentTypeId]: SegmentState<"descend">
|
|
57
|
+
readonly kind: "descend"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type CanonicalSegment =
|
|
61
|
+
| KeySegment
|
|
62
|
+
| IndexSegment
|
|
63
|
+
| WildcardSegment
|
|
64
|
+
| SliceSegment
|
|
65
|
+
| DescendSegment
|
|
66
|
+
|
|
67
|
+
export type AnySegment = any
|
|
68
|
+
|
|
69
|
+
export type ExactSegment = KeySegment | IndexSegment
|
|
70
|
+
|
|
71
|
+
type PathState<Segments extends readonly CanonicalSegment[]> = {
|
|
72
|
+
readonly segments: Segments
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface Path<Segments extends readonly CanonicalSegment[] = readonly CanonicalSegment[]> {
|
|
76
|
+
readonly [TypeId]: PathState<Segments>
|
|
77
|
+
readonly segments: Segments
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type JsonPath<Segments extends readonly CanonicalSegment[] = readonly CanonicalSegment[]> = Path<Segments>
|
|
81
|
+
|
|
82
|
+
export type JsonPathSegments = readonly CanonicalSegment[]
|
|
83
|
+
|
|
84
|
+
export type JsonPrimitive = JsonLiteralInput
|
|
85
|
+
|
|
86
|
+
export type JsonLiteral = JsonLiteralInput
|
|
87
|
+
|
|
88
|
+
export type JsonInput = Expression.Any | JsonLiteral
|
|
89
|
+
|
|
90
|
+
const makeSegment = <Segment extends CanonicalSegment>(segment: Segment): Segment => segment
|
|
91
|
+
|
|
92
|
+
export const key = <Key extends string>(value: Key): KeySegment<Key> =>
|
|
93
|
+
makeSegment({
|
|
94
|
+
[SegmentTypeId]: {
|
|
95
|
+
kind: "key"
|
|
96
|
+
},
|
|
97
|
+
kind: "key",
|
|
98
|
+
key: value
|
|
99
|
+
} as KeySegment<Key>)
|
|
100
|
+
|
|
101
|
+
export const index = <Index extends number>(value: Index): IndexSegment<Index> =>
|
|
102
|
+
makeSegment({
|
|
103
|
+
[SegmentTypeId]: {
|
|
104
|
+
kind: "index"
|
|
105
|
+
},
|
|
106
|
+
kind: "index",
|
|
107
|
+
index: value
|
|
108
|
+
} as IndexSegment<Index>)
|
|
109
|
+
|
|
110
|
+
export const wildcard = (): WildcardSegment =>
|
|
111
|
+
makeSegment({
|
|
112
|
+
[SegmentTypeId]: {
|
|
113
|
+
kind: "wildcard"
|
|
114
|
+
},
|
|
115
|
+
kind: "wildcard"
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
export const slice = <
|
|
119
|
+
Start extends number | undefined = undefined,
|
|
120
|
+
End extends number | undefined = undefined
|
|
121
|
+
>(
|
|
122
|
+
start?: Start,
|
|
123
|
+
end?: End
|
|
124
|
+
): SliceSegment<Start, End> =>
|
|
125
|
+
makeSegment({
|
|
126
|
+
[SegmentTypeId]: {
|
|
127
|
+
kind: "slice"
|
|
128
|
+
},
|
|
129
|
+
kind: "slice",
|
|
130
|
+
start: start as Start,
|
|
131
|
+
end: end as End
|
|
132
|
+
} as SliceSegment<Start, End>)
|
|
133
|
+
|
|
134
|
+
export const descend = (): DescendSegment =>
|
|
135
|
+
makeSegment({
|
|
136
|
+
[SegmentTypeId]: {
|
|
137
|
+
kind: "descend"
|
|
138
|
+
},
|
|
139
|
+
kind: "descend"
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
export const path = <Segments extends readonly CanonicalSegment[]>(
|
|
143
|
+
...segments: Segments
|
|
144
|
+
): Path<Segments> => ({
|
|
145
|
+
[TypeId]: {
|
|
146
|
+
segments
|
|
147
|
+
},
|
|
148
|
+
segments
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
export const makeJsonPath = path
|
|
152
|
+
|
|
153
|
+
export type SegmentsOf<Value extends Path<any>> = Value[typeof TypeId]["segments"]
|
|
154
|
+
|
|
155
|
+
export type IsExactSegment<Segment extends CanonicalSegment> = Segment extends ExactSegment ? true : false
|
|
156
|
+
|
|
157
|
+
export type IsExactPath<PathValue extends Path<any>> =
|
|
158
|
+
SegmentsOf<PathValue> extends readonly [infer Head extends CanonicalSegment, ...infer Tail extends readonly CanonicalSegment[]]
|
|
159
|
+
? IsExactSegment<Head> extends true
|
|
160
|
+
? Tail extends readonly []
|
|
161
|
+
? true
|
|
162
|
+
: IsExactPath<Path<Tail>>
|
|
163
|
+
: false
|
|
164
|
+
: true
|
|
165
|
+
|
|
166
|
+
export type JsonPathValue<
|
|
167
|
+
Root,
|
|
168
|
+
PathValue extends Path<any>,
|
|
169
|
+
Operation extends string = "json.get"
|
|
170
|
+
> = JsonValueAtPath<Root, PathValue, Operation>
|
|
171
|
+
|
|
172
|
+
export type JsonPathOutput<
|
|
173
|
+
Root,
|
|
174
|
+
PathValue extends Path<any>,
|
|
175
|
+
Operation extends string = "json.get"
|
|
176
|
+
> = JsonValueAtPath<Root, PathValue, Operation>
|
|
177
|
+
|
|
178
|
+
export type JsonPathCompatible<
|
|
179
|
+
Root,
|
|
180
|
+
PathValue extends Path<any>,
|
|
181
|
+
Operation extends string = "json.get"
|
|
182
|
+
> = JsonValueAtPath<Root, PathValue, Operation> extends JsonPathUsageError<any, any, any, any>
|
|
183
|
+
? JsonValueAtPath<Root, PathValue, Operation>
|
|
184
|
+
: PathValue
|
|
185
|
+
|
|
186
|
+
export type JsonPathUpdate<
|
|
187
|
+
Root,
|
|
188
|
+
PathValue extends Path<any>,
|
|
189
|
+
Next,
|
|
190
|
+
Operation extends string = "json.set"
|
|
191
|
+
> = JsonSetAtPath<Root, PathValue, Next, Operation>
|
|
192
|
+
|
|
193
|
+
export type JsonPathDelete<
|
|
194
|
+
Root,
|
|
195
|
+
PathValue extends Path<any>,
|
|
196
|
+
Operation extends string = "json.delete"
|
|
197
|
+
> = JsonDeleteAtPath<Root, PathValue, Operation>
|
|
198
|
+
|
|
199
|
+
export type JsonConcat<
|
|
200
|
+
Left,
|
|
201
|
+
Right
|
|
202
|
+
> = JsonConcatResult<Left, Right>
|
|
203
|
+
|
|
204
|
+
export type JsonBuildObject<
|
|
205
|
+
Shape extends Record<string, JsonInput>
|
|
206
|
+
> = JsonBuildObjectResult<Shape>
|
|
207
|
+
|
|
208
|
+
export type JsonBuildArray<
|
|
209
|
+
Values extends readonly JsonInput[]
|
|
210
|
+
> = JsonBuildArrayResult<Values>
|
|
211
|
+
|
|
212
|
+
export type JsonTextResult<Value> = JsonTextResultValue<Value>
|
|
213
|
+
|
|
214
|
+
export type JsonTypeName<Value> = JsonTypeNameValue<Value>
|
|
215
|
+
|
|
216
|
+
export type JsonLengthResult<Value> = JsonLengthResultValue<Value>
|
|
217
|
+
|
|
218
|
+
export type JsonKeysResult<Value> = JsonKeysResultValue<Value>
|
|
219
|
+
|
|
220
|
+
export type JsonStripNullsResult<Value> = JsonStripNullsResultValue<Value>
|
|
221
|
+
|
|
222
|
+
export type JsonValueOfInput<Input> =
|
|
223
|
+
Input extends Expression.Any
|
|
224
|
+
? Expression.RuntimeOf<Input>
|
|
225
|
+
: Input extends JsonLiteral
|
|
226
|
+
? Input
|
|
227
|
+
: never
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type JsonShape = Record<string, unknown> | readonly unknown[] | string | number | boolean | null
|