@workerstack/database 0.22.0 → 0.23.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/README.md +95 -30
- package/docs/meta-contract.md +1 -1
- package/package.json +3 -2
- package/src/cli/args.ts +4 -2
- package/src/cli/codegen.ts +2 -2
- package/src/cli/comb-config.ts +8 -3
- package/src/cli/file-path.ts +25 -0
- package/src/cli/migrate-apply.ts +5 -21
- package/src/cli/migrate-d1.ts +5 -1
- package/src/cli/migrate-json.ts +48 -0
- package/src/cli/migrate.ts +26 -4
- package/src/formats/bcp47.ts +12 -0
- package/src/formats/catalog.ts +27 -4
- package/src/index.ts +1 -0
- package/src/migrate/core/apply.ts +63 -22
- package/src/migrate/core/atlas-apply-runs.ts +15 -26
- package/src/migrate/core/cleanup.ts +22 -10
- package/src/migrate/core/d1-engine-child.mjs +27 -11
- package/src/migrate/core/d1-history.ts +23 -26
- package/src/migrate/core/d1-wrangler-error.ts +24 -1
- package/src/migrate/core/pg-replay-commit.ts +41 -38
- package/src/migrate/core/pg-replay-drops.ts +17 -32
- package/src/migrate/core/pg-replay-hidden.ts +75 -0
- package/src/migrate/core/pg-replay-roles.ts +2 -4
- package/src/migrate/core/pg-replay.ts +21 -19
- package/src/migrate/core/pg-server-snapshot.ts +4 -24
- package/src/migrate/core/proof-readers.ts +80 -0
- package/src/migrate/core/proof-run.ts +19 -5
- package/src/migrate/core/proof-scope.ts +27 -14
- package/src/migrate/core/sql-code.ts +36 -5
- package/src/migrate/core/sql-scan.ts +7 -1
- package/src/migrate/core/sqlite-keywords.ts +70 -0
- package/src/migrate/core/stopped.ts +70 -0
- package/src/migrate/core/trigger-write.d.mts +7 -0
- package/src/migrate/core/trigger-write.mjs +42 -0
- package/src/query/cursor-mint.ts +11 -18
- package/src/query/cursor-refusals.ts +22 -11
- package/src/query/descriptions.ts +4 -9
- package/src/query/filter-parse.ts +80 -22
- package/src/query/filter-validate.ts +10 -13
- package/src/query/filter.ts +40 -18
- package/src/query/list-extras.ts +7 -10
- package/src/query/list-schema.ts +0 -2
- package/src/query/merged.ts +3 -2
- package/src/query/pagination.ts +30 -6
- package/src/query/rounded-key.ts +5 -8
- package/src/query/schema-params.ts +12 -5
- package/src/query/select-validate.ts +7 -4
- package/src/query/sqlite/cursor-fit.ts +10 -2
- package/src/query/trusted.ts +7 -2
- package/types/cli/args.d.ts.map +1 -1
- package/types/cli/comb-config.d.ts +5 -2
- package/types/cli/comb-config.d.ts.map +1 -1
- package/types/cli/file-path.d.ts +11 -0
- package/types/cli/file-path.d.ts.map +1 -0
- package/types/cli/migrate-apply.d.ts +1 -0
- package/types/cli/migrate-apply.d.ts.map +1 -1
- package/types/cli/migrate-d1.d.ts.map +1 -1
- package/types/cli/migrate-json.d.ts +4 -0
- package/types/cli/migrate-json.d.ts.map +1 -0
- package/types/cli/migrate.d.ts.map +1 -1
- package/types/formats/bcp47.d.ts +9 -0
- package/types/formats/bcp47.d.ts.map +1 -0
- package/types/formats/catalog.d.ts.map +1 -1
- package/types/index.d.ts +1 -0
- package/types/index.d.ts.map +1 -1
- package/types/migrate/core/apply.d.ts +3 -2
- package/types/migrate/core/apply.d.ts.map +1 -1
- package/types/migrate/core/atlas-apply-runs.d.ts +6 -0
- package/types/migrate/core/atlas-apply-runs.d.ts.map +1 -1
- package/types/migrate/core/cleanup.d.ts +16 -4
- package/types/migrate/core/cleanup.d.ts.map +1 -1
- package/types/migrate/core/d1-history.d.ts.map +1 -1
- package/types/migrate/core/d1-wrangler-error.d.ts +11 -0
- package/types/migrate/core/d1-wrangler-error.d.ts.map +1 -1
- package/types/migrate/core/pg-replay-commit.d.ts +14 -9
- package/types/migrate/core/pg-replay-commit.d.ts.map +1 -1
- package/types/migrate/core/pg-replay-drops.d.ts +7 -12
- package/types/migrate/core/pg-replay-drops.d.ts.map +1 -1
- package/types/migrate/core/pg-replay-hidden.d.ts +36 -0
- package/types/migrate/core/pg-replay-hidden.d.ts.map +1 -0
- package/types/migrate/core/pg-replay-roles.d.ts +2 -4
- package/types/migrate/core/pg-replay-roles.d.ts.map +1 -1
- package/types/migrate/core/pg-replay.d.ts.map +1 -1
- package/types/migrate/core/pg-server-snapshot.d.ts +4 -11
- package/types/migrate/core/pg-server-snapshot.d.ts.map +1 -1
- package/types/migrate/core/proof-readers.d.ts +19 -0
- package/types/migrate/core/proof-readers.d.ts.map +1 -0
- package/types/migrate/core/proof-run.d.ts.map +1 -1
- package/types/migrate/core/proof-scope.d.ts +15 -7
- package/types/migrate/core/proof-scope.d.ts.map +1 -1
- package/types/migrate/core/sql-code.d.ts +7 -3
- package/types/migrate/core/sql-code.d.ts.map +1 -1
- package/types/migrate/core/sql-scan.d.ts.map +1 -1
- package/types/migrate/core/sqlite-keywords.d.ts +11 -0
- package/types/migrate/core/sqlite-keywords.d.ts.map +1 -0
- package/types/migrate/core/stopped.d.ts +23 -0
- package/types/migrate/core/stopped.d.ts.map +1 -0
- package/types/query/cursor-mint.d.ts.map +1 -1
- package/types/query/cursor-refusals.d.ts +17 -1
- package/types/query/cursor-refusals.d.ts.map +1 -1
- package/types/query/descriptions.d.ts +1 -1
- package/types/query/descriptions.d.ts.map +1 -1
- package/types/query/filter-parse.d.ts +15 -4
- package/types/query/filter-parse.d.ts.map +1 -1
- package/types/query/filter-validate.d.ts +1 -1
- package/types/query/filter-validate.d.ts.map +1 -1
- package/types/query/filter.d.ts +9 -1
- package/types/query/filter.d.ts.map +1 -1
- package/types/query/list-extras.d.ts.map +1 -1
- package/types/query/list-schema.d.ts.map +1 -1
- package/types/query/merged.d.ts.map +1 -1
- package/types/query/pagination.d.ts +13 -1
- package/types/query/pagination.d.ts.map +1 -1
- package/types/query/rounded-key.d.ts +2 -11
- package/types/query/rounded-key.d.ts.map +1 -1
- package/types/query/schema-params.d.ts.map +1 -1
- package/types/query/select-validate.d.ts.map +1 -1
- package/types/query/sqlite/cursor-fit.d.ts.map +1 -1
- package/types/query/trusted.d.ts +1 -1
- package/types/query/trusted.d.ts.map +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* - `field.op.value`: one condition; `,` joins conditions with AND
|
|
6
6
|
* - `or(...)`, `and(...)`: groups, nestable
|
|
7
|
-
* - `in.(a,b)`, `nin.(a,b)`: lists
|
|
7
|
+
* - `in.(a,b)`, `nin.(a,b)`: lists; `in.()` is the one empty list, and a comma beside no member is malformed
|
|
8
8
|
* - `"..."`: a quoted value may hold `,` `(` `)` and `.`; `\"` and `\\` escape inside it
|
|
9
9
|
* - `@name`: a computed field
|
|
10
10
|
*/
|
|
@@ -31,34 +31,66 @@ const FILTER_OPERATORS: readonly [FilterOperator, ...FilterOperator[]] = [
|
|
|
31
31
|
/** An operator and the `.` after it; longest first, so `neq.` is not read as `ne` then `q.` */
|
|
32
32
|
const OPERATOR_PATTERN = /^(contains|ilike|like|neq|gte|lte|nin|eq|ne|gt|lt|in|is)\./
|
|
33
33
|
|
|
34
|
+
/** What a refused filter is told: this alone when no more is known, else this and the reason */
|
|
35
|
+
const SYNTAX = "Invalid filter syntax"
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The deepest nesting the parser reads, whatever cap a caller sets: it descends one call per group,
|
|
39
|
+
* so a bound keeps a deep filter a refusal instead of a stack overflow on every runtime comb runs on
|
|
40
|
+
*/
|
|
41
|
+
const FILTER_MAX_DEPTH = 256
|
|
42
|
+
|
|
34
43
|
class FilterParser {
|
|
35
44
|
private pos = 0
|
|
36
|
-
|
|
45
|
+
/** Why the input is untrustworthy, the first reason met; null while every term parsed */
|
|
46
|
+
private failure: string | null = null
|
|
37
47
|
private readonly input: string
|
|
48
|
+
/** Whitespace the caller's string held before the input: positions are told in the caller's string */
|
|
49
|
+
private readonly offset: number
|
|
50
|
+
/** The deepest group the input may open, the top level being 0 */
|
|
51
|
+
private readonly maxDepth: number
|
|
38
52
|
|
|
39
|
-
constructor(input: string) {
|
|
53
|
+
constructor(input: string, offset: number, maxDepth: number) {
|
|
40
54
|
this.input = input
|
|
55
|
+
this.offset = offset
|
|
56
|
+
this.maxDepth = Math.min(maxDepth, FILTER_MAX_DEPTH)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Why any term could not be parsed, or null: the input is not trustworthy while this is set. */
|
|
60
|
+
get error(): string | null {
|
|
61
|
+
return this.failure
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Mark the input malformed; the first reason stays */
|
|
65
|
+
private fail(reason: string = SYNTAX): void {
|
|
66
|
+
this.failure ??= reason
|
|
41
67
|
}
|
|
42
68
|
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
|
|
69
|
+
/** Mark a comma beside no member, told by where the member should stand */
|
|
70
|
+
private failEmptyMember(at: number): void {
|
|
71
|
+
this.fail(`${SYNTAX}: empty member at position ${at + this.offset}`)
|
|
46
72
|
}
|
|
47
73
|
|
|
48
74
|
parse(): FilterAST {
|
|
49
|
-
const root = this.parseGroup("and")
|
|
75
|
+
const root = this.parseGroup("and", 0)
|
|
50
76
|
/* a stray ")" stops the top-level group early; anything left is unparsed */
|
|
51
|
-
if (this.pos < this.input.length) this.
|
|
77
|
+
if (this.pos < this.input.length) this.fail()
|
|
52
78
|
return { root }
|
|
53
79
|
}
|
|
54
80
|
|
|
55
|
-
private parseGroup(logic: "and" | "or"): FilterGroup {
|
|
81
|
+
private parseGroup(logic: "and" | "or", depth: number): FilterGroup {
|
|
56
82
|
const group: FilterGroup = { conditions: [], logic, subgroups: [] }
|
|
57
83
|
while (this.skipWhitespace() < this.input.length && this.peek() !== ")") {
|
|
58
84
|
const nested = this.matchKeyword("or(") ? "or" : this.matchKeyword("and(") ? "and" : null
|
|
85
|
+
if (nested && depth === this.maxDepth) {
|
|
86
|
+
/* the first group past the cap: the input is refused here, before the descent could overflow */
|
|
87
|
+
this.fail(`Filter nesting too deep at position ${this.pos + this.offset} (max ${this.maxDepth})`)
|
|
88
|
+
this.pos = this.input.length
|
|
89
|
+
break
|
|
90
|
+
}
|
|
59
91
|
if (nested) {
|
|
60
92
|
this.pos += nested.length + 1
|
|
61
|
-
group.subgroups.push(this.parseGroup(nested))
|
|
93
|
+
group.subgroups.push(this.parseGroup(nested, depth + 1))
|
|
62
94
|
this.expect(")")
|
|
63
95
|
} else {
|
|
64
96
|
this.parseTerm(group)
|
|
@@ -66,18 +98,26 @@ class FilterParser {
|
|
|
66
98
|
this.skipWhitespace()
|
|
67
99
|
if (this.peek() !== ",") break
|
|
68
100
|
this.pos++
|
|
101
|
+
/* a comma owes a member: a group that ends on one holds an empty member */
|
|
102
|
+
if (this.skipWhitespace() >= this.input.length || this.peek() === ")") {
|
|
103
|
+
this.failEmptyMember(this.pos)
|
|
104
|
+
break
|
|
105
|
+
}
|
|
69
106
|
}
|
|
70
107
|
return group
|
|
71
108
|
}
|
|
72
109
|
|
|
73
110
|
/** One condition into `group`; a term it cannot read is skipped and marks the input malformed */
|
|
74
111
|
private parseTerm(group: FilterGroup): void {
|
|
112
|
+
const start = this.pos
|
|
75
113
|
const condition = this.parseCondition()
|
|
76
114
|
if (condition) {
|
|
77
115
|
group.conditions.push(condition)
|
|
78
116
|
return
|
|
79
117
|
}
|
|
80
|
-
|
|
118
|
+
/* a term that starts at its comma is an empty member, beside another comma or leading the group */
|
|
119
|
+
if (this.input[start] === ",") this.failEmptyMember(start)
|
|
120
|
+
else this.fail()
|
|
81
121
|
while (this.pos < this.input.length && this.peek() !== "," && this.peek() !== ")") this.pos++
|
|
82
122
|
}
|
|
83
123
|
|
|
@@ -113,14 +153,21 @@ class FilterParser {
|
|
|
113
153
|
const values: string[] = []
|
|
114
154
|
while (this.skipWhitespace() < this.input.length && this.peek() !== ")") {
|
|
115
155
|
const quoted = this.peek() === '"'
|
|
156
|
+
const at = this.pos
|
|
116
157
|
const value = this.readValue()
|
|
158
|
+
/* a bare member with nothing in it stands beside a comma; only a quoted one may be empty */
|
|
117
159
|
if (quoted || value !== "") values.push(value)
|
|
160
|
+
else this.failEmptyMember(at)
|
|
118
161
|
/* a quoted member ends at its quote: only a comma or the list's end may follow */
|
|
119
162
|
if (quoted && this.skipWhitespace() < this.input.length && this.peek() !== "," && this.peek() !== ")") {
|
|
120
|
-
this.
|
|
163
|
+
this.fail()
|
|
121
164
|
return values
|
|
122
165
|
}
|
|
123
|
-
if (this.peek() === ",")
|
|
166
|
+
if (this.peek() === ",") {
|
|
167
|
+
this.pos++
|
|
168
|
+
/* a comma owes a member: `in.(x,)` holds an empty one, `in.()` is the only empty list */
|
|
169
|
+
if (this.skipWhitespace() >= this.input.length || this.peek() === ")") this.failEmptyMember(this.pos)
|
|
170
|
+
}
|
|
124
171
|
}
|
|
125
172
|
this.expect(")")
|
|
126
173
|
return values
|
|
@@ -135,7 +182,7 @@ class FilterParser {
|
|
|
135
182
|
const start = this.pos
|
|
136
183
|
while (this.pos < this.input.length && this.peek() !== "," && this.peek() !== ")") this.pos++
|
|
137
184
|
const value = this.input.slice(start, this.pos).trim()
|
|
138
|
-
if (value.includes("(")) this.
|
|
185
|
+
if (value.includes("(")) this.fail()
|
|
139
186
|
return value
|
|
140
187
|
}
|
|
141
188
|
|
|
@@ -152,7 +199,7 @@ class FilterParser {
|
|
|
152
199
|
value += this.peek()
|
|
153
200
|
}
|
|
154
201
|
/* an unterminated quote */
|
|
155
|
-
this.
|
|
202
|
+
this.fail()
|
|
156
203
|
return value
|
|
157
204
|
}
|
|
158
205
|
|
|
@@ -167,7 +214,7 @@ class FilterParser {
|
|
|
167
214
|
/** A group that is never closed leaves the input unbalanced: reject it. */
|
|
168
215
|
private expect(char: string): void {
|
|
169
216
|
if (this.peek() === char) this.pos++
|
|
170
|
-
else this.
|
|
217
|
+
else this.fail()
|
|
171
218
|
}
|
|
172
219
|
|
|
173
220
|
/** Step past whitespace; returns the new position */
|
|
@@ -178,15 +225,26 @@ class FilterParser {
|
|
|
178
225
|
}
|
|
179
226
|
|
|
180
227
|
/**
|
|
181
|
-
* The AST of a filter string, or
|
|
228
|
+
* The AST of a non-blank filter string, or why it cannot be read, in words for a caller to show: a
|
|
182
229
|
* filter the parser does not fully understand is refused, never reduced to "no filter", which
|
|
183
|
-
* would widen the result to the whole table on a typo.
|
|
230
|
+
* would widen the result to the whole table on a typo. A group nested past `maxDepth`, or past
|
|
231
|
+
* FILTER_MAX_DEPTH whatever `maxDepth` is, is refused where it opens.
|
|
232
|
+
*/
|
|
233
|
+
function readFilter(input: string, maxDepth: number): { ast: FilterAST } | { error: string } {
|
|
234
|
+
const parser = new FilterParser(input.trim(), input.length - input.trimStart().length, maxDepth)
|
|
235
|
+
const ast = parser.parse()
|
|
236
|
+
return parser.error === null ? { ast } : { error: parser.error }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* The AST of a filter string, or null when there is none or any part of it cannot be read, or it
|
|
241
|
+
* nests past FILTER_MAX_DEPTH: a filter the parser does not fully understand is refused, never
|
|
242
|
+
* reduced to "no filter", which would widen the result to the whole table on a typo.
|
|
184
243
|
*/
|
|
185
244
|
function parseFilter(input: string | null | undefined): FilterAST | null {
|
|
186
245
|
if (!input || input.trim() === "") return null
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
return parser.hadError ? null : ast
|
|
246
|
+
const read = readFilter(input, FILTER_MAX_DEPTH)
|
|
247
|
+
return "ast" in read ? read.ast : null
|
|
190
248
|
}
|
|
191
249
|
|
|
192
|
-
export { FILTER_OPERATORS, parseFilter }
|
|
250
|
+
export { FILTER_OPERATORS, parseFilter, readFilter }
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* every value typed as its field declares, so SQL generation binds numbers as numbers and
|
|
4
4
|
* booleans as booleans, and computed resolvers receive the declared type.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { readFilter } from "./filter-parse.ts"
|
|
7
7
|
import { type CheckedField, typedValue } from "./filter-typed.ts"
|
|
8
8
|
import { patternOf } from "./like.ts"
|
|
9
9
|
import type { FieldType, FilterAST, FilterCondition, FilterGroup, FilterOperator } from "./types.ts"
|
|
@@ -66,7 +66,7 @@ type FilterValidationResult =
|
|
|
66
66
|
type FilterLimits = {
|
|
67
67
|
/** Conditions in the whole filter, every group counted */
|
|
68
68
|
maxConditions: number
|
|
69
|
-
/** Nested or()/and() groups below the top level */
|
|
69
|
+
/** Nested or()/and() groups below the top level; the parser reads no more than 256, whatever this is */
|
|
70
70
|
maxDepth: number
|
|
71
71
|
/** Members of one in/nin list */
|
|
72
72
|
maxInValues: number
|
|
@@ -168,17 +168,16 @@ function checkCondition(condition: FilterCondition, check: Check): FilterConditi
|
|
|
168
168
|
return { ...condition, value: typed.value }
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
/** The group with every condition checked and typed, and how many conditions and
|
|
172
|
-
type Shape = { conditions: number;
|
|
171
|
+
/** The group with every condition checked and typed, and how many conditions and values it holds */
|
|
172
|
+
type Shape = { conditions: number; group: FilterGroup; values: number }
|
|
173
173
|
|
|
174
|
-
function checkGroup(group: FilterGroup, check: Check
|
|
175
|
-
const subgroups = group.subgroups.map((subgroup) => checkGroup(subgroup, check
|
|
174
|
+
function checkGroup(group: FilterGroup, check: Check): Shape {
|
|
175
|
+
const subgroups = group.subgroups.map((subgroup) => checkGroup(subgroup, check))
|
|
176
176
|
/* counted on the typed conditions: is.true binds its boolean, is.null nothing */
|
|
177
177
|
const conditions = group.conditions.map((condition) => checkCondition(condition, check))
|
|
178
178
|
const values = conditions.reduce((total, condition) => total + boundBy(condition), 0)
|
|
179
179
|
return {
|
|
180
180
|
conditions: subgroups.reduce((total, sub) => total + sub.conditions, group.conditions.length),
|
|
181
|
-
depth: Math.max(depth, ...subgroups.map((sub) => sub.depth)),
|
|
182
181
|
values: subgroups.reduce((total, sub) => total + sub.values, values),
|
|
183
182
|
group: {
|
|
184
183
|
conditions,
|
|
@@ -201,20 +200,18 @@ function validateFilter(
|
|
|
201
200
|
const fields = checkedFields(allowedFields)
|
|
202
201
|
const caps = limitsOver("validateFilter", FILTER_LIMITS, limits)
|
|
203
202
|
if (!filterString || filterString.trim() === "") return null
|
|
204
|
-
const
|
|
205
|
-
if (
|
|
203
|
+
const read = readFilter(filterString, caps.maxDepth)
|
|
204
|
+
if ("error" in read) return { errors: [read.error], valid: false }
|
|
205
|
+
const ast = read.ast
|
|
206
206
|
|
|
207
207
|
const check: Check = { errors: [], fields, limits: caps }
|
|
208
|
-
const shape = checkGroup(ast.root, check
|
|
208
|
+
const shape = checkGroup(ast.root, check)
|
|
209
209
|
if (shape.conditions > check.limits.maxConditions) {
|
|
210
210
|
check.errors.push(`Too many filter conditions: ${shape.conditions} (max ${check.limits.maxConditions})`)
|
|
211
211
|
}
|
|
212
212
|
if (shape.values > check.limits.maxValues) {
|
|
213
213
|
check.errors.push(`Filter binds too many values: ${shape.values} (max ${check.limits.maxValues})`)
|
|
214
214
|
}
|
|
215
|
-
if (shape.depth > check.limits.maxDepth) {
|
|
216
|
-
check.errors.push(`Filter nesting too deep: ${shape.depth} (max ${check.limits.maxDepth})`)
|
|
217
|
-
}
|
|
218
215
|
return check.errors.length > 0 ? { errors: check.errors, valid: false } : { ast: { root: shape.group }, valid: true }
|
|
219
216
|
}
|
|
220
217
|
|
package/src/query/filter.ts
CHANGED
|
@@ -16,6 +16,43 @@ import {
|
|
|
16
16
|
} from "./filter-validate.ts"
|
|
17
17
|
import type { SortDirection, SortField } from "./types.ts"
|
|
18
18
|
|
|
19
|
+
/** The direction suffix an `order` entry may end in, before any nulls suffix, and what it reads as */
|
|
20
|
+
const SORT_DIRECTIONS = { asc: "asc", desc: "desc" } as const satisfies Record<string, SortDirection>
|
|
21
|
+
/** The nulls suffix an `order` entry may end in, and what it reads as */
|
|
22
|
+
const SORT_NULLS = { nullsfirst: "first", nullslast: "last" } as const
|
|
23
|
+
|
|
24
|
+
/** The value `suffixes` reads the last of `tokens` as, popping it, or undefined when it is none of them */
|
|
25
|
+
function suffixOf<T extends string>(tokens: string[], suffixes: Readonly<Record<string, T>>): T | undefined {
|
|
26
|
+
for (const [suffix, value] of Object.entries(suffixes))
|
|
27
|
+
if (tokens.at(-1) === suffix) {
|
|
28
|
+
tokens.pop()
|
|
29
|
+
return value
|
|
30
|
+
}
|
|
31
|
+
return undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function escapeRegExp(value: string): string {
|
|
35
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A regular expression taking any one of `names`, each literally */
|
|
39
|
+
function anyOf(names: readonly string[]): string {
|
|
40
|
+
return `(?:${names.map(escapeRegExp).join("|")})`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A regular expression taking every `order` parseOrder reads over the fields in `sort`, up to
|
|
45
|
+
* `maxSortKeys` entries: the empty value, which reads as the default order, and each entry the field
|
|
46
|
+
* and the suffixes orderEntries reads off it. A field named twice is refused, so no order holds more
|
|
47
|
+
* entries than `sort` has fields; which field repeats needs a lookahead, which RE2 lacks, so the
|
|
48
|
+
* pattern leaves that to the description
|
|
49
|
+
*/
|
|
50
|
+
function orderPattern(sort: readonly string[], maxSortKeys: number): string {
|
|
51
|
+
const entry = `${anyOf(sort)}(?:\\.${anyOf(Object.keys(SORT_DIRECTIONS))})?(?:\\.${anyOf(Object.keys(SORT_NULLS))})?`
|
|
52
|
+
const keys = Math.min(maxSortKeys, sort.length)
|
|
53
|
+
return `^(?:${entry}(?:,${entry}){0,${Math.max(0, keys - 1)}})?$`
|
|
54
|
+
}
|
|
55
|
+
|
|
19
56
|
/**
|
|
20
57
|
* Parse PostgREST-style `order` string into SortField array.
|
|
21
58
|
*
|
|
@@ -67,24 +104,8 @@ function orderEntries(input: string | null | undefined): Array<SortField | strin
|
|
|
67
104
|
|
|
68
105
|
const tokens = trimmed.split(".")
|
|
69
106
|
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
if (last === "nullsfirst") {
|
|
73
|
-
nulls = "first"
|
|
74
|
-
tokens.pop()
|
|
75
|
-
} else if (last === "nullslast") {
|
|
76
|
-
nulls = "last"
|
|
77
|
-
tokens.pop()
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
let direction: SortDirection = "asc"
|
|
81
|
-
const newLast = tokens[tokens.length - 1]
|
|
82
|
-
if (newLast === "asc") {
|
|
83
|
-
tokens.pop()
|
|
84
|
-
} else if (newLast === "desc") {
|
|
85
|
-
direction = "desc"
|
|
86
|
-
tokens.pop()
|
|
87
|
-
}
|
|
107
|
+
const nulls = suffixOf(tokens, SORT_NULLS)
|
|
108
|
+
const direction: SortDirection = suffixOf(tokens, SORT_DIRECTIONS) ?? "asc"
|
|
88
109
|
|
|
89
110
|
const field = tokens.join(".")
|
|
90
111
|
if (!field || part !== trimmed) {
|
|
@@ -108,6 +129,7 @@ export {
|
|
|
108
129
|
parseFilterNumber,
|
|
109
130
|
namedTwice,
|
|
110
131
|
orderEntries,
|
|
132
|
+
orderPattern,
|
|
111
133
|
parseOrder,
|
|
112
134
|
validateFilter,
|
|
113
135
|
type FilterField,
|
package/src/query/list-extras.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
unprintedNumber,
|
|
17
17
|
} from "./held-keys.ts"
|
|
18
18
|
import type { SortField } from "./types.ts"
|
|
19
|
+
import { UnmintableRow } from "./cursor-refusals.ts"
|
|
19
20
|
|
|
20
21
|
/** The column every built list selects its tiebreak as, which the next cursor's id is minted from */
|
|
21
22
|
const COMB_ID = "_combId" as const
|
|
@@ -140,19 +141,15 @@ const FLOAT_MARK = "!f"
|
|
|
140
141
|
* which the read places nowhere a cursor resumes after
|
|
141
142
|
*/
|
|
142
143
|
const storesUnread = (field: string, stored: string): CombError =>
|
|
143
|
-
new
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
status: "internal_server_error",
|
|
147
|
-
})
|
|
144
|
+
new UnmintableRow(
|
|
145
|
+
`${field} stores a ${stored.toUpperCase()} value its column's drizzle mode reads as another, which no cursor of a list that binds the key as read resumes after: store what the mode reads back (a timestamp's epoch number, a boolean's 0 or 1, a finite number), or sort by a column of the list's own table`,
|
|
146
|
+
)
|
|
148
147
|
|
|
149
148
|
/** The 500 for a SQLite number key no text SQLite prints of reads back as it, which no cursor binds exactly */
|
|
150
149
|
const printsUnread = (field: string): CombError =>
|
|
151
|
-
new
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
status: "internal_server_error",
|
|
155
|
-
})
|
|
150
|
+
new UnmintableRow(
|
|
151
|
+
`${field} holds a REAL no text SQLite prints of it reads back as (D1 misprints some past about 1e100 and below 1e-100), which no cursor resumes after: store a number of fewer significant digits, or sort by another column`,
|
|
152
|
+
)
|
|
156
153
|
|
|
157
154
|
/** The 500 for a Postgres float key read under extra_float_digits 0 or below: its read is rounded */
|
|
158
155
|
const printsRounded = (field: string): CombError =>
|
package/src/query/list-schema.ts
CHANGED
|
@@ -24,7 +24,6 @@ import {
|
|
|
24
24
|
selectQueryFieldFor,
|
|
25
25
|
sortDirectionEnum,
|
|
26
26
|
} from "./schema-params.ts"
|
|
27
|
-
import { pageTooDeep } from "./trusted.ts"
|
|
28
27
|
import type { ListQueryConfig, ListQueryLimits, ListQueryOutput, OutputStableShape } from "./schema-types.ts"
|
|
29
28
|
import { validateSelectWithConfig } from "./select-validate.ts"
|
|
30
29
|
import type { FieldType } from "./types.ts"
|
|
@@ -178,7 +177,6 @@ function createListQuerySchema<
|
|
|
178
177
|
|
|
179
178
|
const { maxLimit = PAGINATION_DEFAULTS.maxLimit, maxPage = PAGINATION_DEFAULTS.maxPage } = config.pagination ?? {}
|
|
180
179
|
const piped = schema.transform((data, ctx): ExtendedOutput => {
|
|
181
|
-
if (data.page !== undefined && data.page > maxPage) throw pageTooDeep(data.page, maxPage)
|
|
182
180
|
/* Optional params exist on `data` only when advertised above. */
|
|
183
181
|
const optional = data as {
|
|
184
182
|
filter?: string | undefined
|
package/src/query/merged.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
COMB_ID,
|
|
19
19
|
type ListPagination,
|
|
20
20
|
type ListRow,
|
|
21
|
+
nextCursorOf,
|
|
21
22
|
type PaginationResponse,
|
|
22
23
|
totalOf,
|
|
23
24
|
} from "./pagination.ts"
|
|
@@ -238,8 +239,8 @@ function paginateMerged<T extends object>(
|
|
|
238
239
|
const window = rows.slice(start, start + limit + 1)
|
|
239
240
|
const hasMore = full || window.length > limit
|
|
240
241
|
const page = hasMore ? window.slice(0, limit) : window
|
|
241
|
-
/*
|
|
242
|
-
const last = page
|
|
242
|
+
/* the rows a cursor may be minted from are fitted; the last one's is sealed */
|
|
243
|
+
const last = nextCursorOf(page, { byCursor: cursor !== undefined, hasMore }, (row) => pagination.fit(row))
|
|
243
244
|
const meta: PaginationResponse = {
|
|
244
245
|
count: countOf(rowsPerShard, cursor),
|
|
245
246
|
hasMore,
|
package/src/query/pagination.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { paginationIn, refuseGoneAnchor, refuseOverRead, refuseUnwired } from ".
|
|
|
16
16
|
import type { paginationResponseSchema } from "./schema.ts"
|
|
17
17
|
import type { ListQueryInput, ParsedFields, SortField } from "./types.ts"
|
|
18
18
|
import type * as z from "zod"
|
|
19
|
+
import { UnmintableRow } from "./cursor-refusals.ts"
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* The name of one column a builder selects for `paginate` to read, which no row it returns keeps: the
|
|
@@ -187,6 +188,28 @@ function fitFrom(
|
|
|
187
188
|
})
|
|
188
189
|
}
|
|
189
190
|
|
|
191
|
+
/**
|
|
192
|
+
* What seals a served page's next cursor, or undefined for none, `check` measuring a row's cursor. On
|
|
193
|
+
* a page by cursor it runs on every row: its client walks the list one cursor at a time, so a row no
|
|
194
|
+
* cursor resumes after is refused on the page that serves it, whatever the page size. On a page by
|
|
195
|
+
* number it runs only on the last row of a page with more, the one the cursor is minted from, as
|
|
196
|
+
* 0.20.1 did; a row whose own value no cursor holds (UnmintableRow) mints none, and the page serves it,
|
|
197
|
+
* its `hasMore` still saying a next page by number exists. A refusal of the list's wiring throws on both
|
|
198
|
+
*/
|
|
199
|
+
function nextCursorOf<T>(
|
|
200
|
+
page: readonly T[],
|
|
201
|
+
{ byCursor, hasMore }: { byCursor: boolean; hasMore: boolean },
|
|
202
|
+
check: (row: T) => (() => string) | undefined,
|
|
203
|
+
): (() => string) | undefined {
|
|
204
|
+
if (byCursor) return page.map(check).at(-1)
|
|
205
|
+
try {
|
|
206
|
+
return (hasMore ? page.slice(-1) : []).map(check).at(-1)
|
|
207
|
+
} catch (error) {
|
|
208
|
+
if (error instanceof UnmintableRow) return undefined
|
|
209
|
+
throw error
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
190
213
|
/**
|
|
191
214
|
* The page of `items`, read with `built`, and its pagination: count, whether there is more, and
|
|
192
215
|
* the next cursor, sealed under the builder's options; none for a list no cursor can resume
|
|
@@ -207,12 +230,12 @@ function paginate<T extends object>(
|
|
|
207
230
|
refuseGoneAnchor(items)
|
|
208
231
|
const hasMore = items.length > pagination.limit
|
|
209
232
|
const sliced = hasMore ? items.slice(0, pagination.limit) : items
|
|
210
|
-
/* a key no cursor bound as read places is refused
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
233
|
+
/* a key no cursor bound as read places is refused as a mint is, checked from the fat row, under the
|
|
234
|
+
sort the builder ordered by, so the next request's signature matches; the last one's is sealed */
|
|
235
|
+
const last = nextCursorOf(sliced, { byCursor: pagination.cursor !== undefined, hasMore }, (item) => {
|
|
236
|
+
refuseUnheld(item as Record<string, unknown>, pagination.parsedSort)
|
|
237
|
+
return pagination.cursorable ? pagination.fit(item) : undefined
|
|
238
|
+
})
|
|
216
239
|
const meta: PaginationResponse = {
|
|
217
240
|
count: countOf(items, pagination),
|
|
218
241
|
hasMore,
|
|
@@ -229,6 +252,7 @@ function paginate<T extends object>(
|
|
|
229
252
|
export {
|
|
230
253
|
applySelect,
|
|
231
254
|
COMB_ID,
|
|
255
|
+
nextCursorOf,
|
|
232
256
|
type ListExtra,
|
|
233
257
|
type ListPagination,
|
|
234
258
|
type ListRow,
|
package/src/query/rounded-key.ts
CHANGED
|
@@ -7,10 +7,9 @@
|
|
|
7
7
|
* selected does the value alone. A class is trusted only when it names a double, so one comb cannot
|
|
8
8
|
* place, such as a type no family covers, is refused as an integer is.
|
|
9
9
|
*/
|
|
10
|
-
import { CombError } from "../error.ts"
|
|
11
|
-
import { combErrorKeys } from "../types.ts"
|
|
12
10
|
import { storedClasses } from "./list-extras.ts"
|
|
13
11
|
import type { SortField } from "./types.ts"
|
|
12
|
+
import { UnmintableRow } from "./cursor-refusals.ts"
|
|
14
13
|
|
|
15
14
|
// lookup-table: why a list cannot page an integer sort key the driver rounded, by the list
|
|
16
15
|
const ROUNDED: Readonly<Record<"derived" | "merged" | "unread", string>> = {
|
|
@@ -45,7 +44,7 @@ function roundedKey(
|
|
|
45
44
|
row: Record<string, unknown>,
|
|
46
45
|
sort: readonly SortField[],
|
|
47
46
|
list: "derived" | "merged" | { unread: (field: string) => boolean },
|
|
48
|
-
):
|
|
47
|
+
): UnmintableRow | undefined {
|
|
49
48
|
const [kind, judged] = typeof list === "string" ? [list, () => true] : (["unread", list.unread] as const)
|
|
50
49
|
const stored = storedClasses(row)
|
|
51
50
|
const rounded = sort.find(({ field }, i) => {
|
|
@@ -60,13 +59,11 @@ function roundedKey(
|
|
|
60
59
|
})
|
|
61
60
|
if (rounded === undefined) return undefined
|
|
62
61
|
const value = row[rounded.field] as number
|
|
63
|
-
return new
|
|
64
|
-
|
|
62
|
+
return new UnmintableRow(
|
|
63
|
+
Number.isInteger(value)
|
|
65
64
|
? `${rounded.field} ${BigInt(value)} is past ±(2^53 − 1), where the driver rounded it: ${ROUNDED[kind]}`
|
|
66
65
|
: `${rounded.field} ${value} is a fraction under a type no driver reads exactly as a JS number, so the driver may have rounded it: ${FRACTION[kind]}: sort by a Postgres numeric, whose text comb carries, read the key as a string, or sort by a double precision or real`,
|
|
67
|
-
|
|
68
|
-
status: "internal_server_error",
|
|
69
|
-
})
|
|
66
|
+
)
|
|
70
67
|
}
|
|
71
68
|
|
|
72
69
|
export { roundedKey }
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import { FILTER_OPERATORS } from "./filter-parse.ts"
|
|
15
15
|
import { PAGINATION_DEFAULTS } from "./schema-limits.ts"
|
|
16
16
|
import type { FieldsConfig, PaginationConfig } from "./schema-types.ts"
|
|
17
|
-
import { invalidPage } from "./trusted.ts"
|
|
17
|
+
import { invalidPage, pageTooDeep } from "./trusted.ts"
|
|
18
18
|
import type { FieldSelection, FilterAST, FilterGroup, ParsedFields } from "./types.ts"
|
|
19
19
|
|
|
20
20
|
/** The `select` param for an endpoint of these fields, its text and examples theirs */
|
|
@@ -88,10 +88,17 @@ function fromDigits(raw: unknown, max: number): unknown {
|
|
|
88
88
|
return DECIMAL_DIGITS.test(raw) && !padded ? Number(raw) : Number.NaN
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* `raw` as a page, a whole number from 1 to `maxPage`: past it, whatever its size, is `page_too_deep`,
|
|
93
|
+
* decided here on the digits before any number the schema could refuse exists; anything else is
|
|
94
|
+
* invalid_page, as the builders answer it. A digit string past 1e308 reads as Infinity: it is whole
|
|
95
|
+
*/
|
|
92
96
|
function pageFrom(raw: unknown, maxPage: number): unknown {
|
|
93
97
|
const page = fromDigits(raw, maxPage)
|
|
94
|
-
|
|
98
|
+
const isDigits = typeof raw === "string"
|
|
99
|
+
if (typeof page !== "number" || page < 1 || !(Number.isInteger(page) || (isDigits && page === Infinity)))
|
|
100
|
+
throw invalidPage(raw)
|
|
101
|
+
if (page > maxPage) throw pageTooDeep(isDigits ? raw.replace(/^0+/, "") : page, maxPage)
|
|
95
102
|
return page
|
|
96
103
|
}
|
|
97
104
|
|
|
@@ -147,8 +154,8 @@ function limitSchemaFor(pagination: PaginationConfig | undefined): z.ZodDefault<
|
|
|
147
154
|
/**
|
|
148
155
|
* The `page` param: decimal digits or a number, a whole number from 1. Neither bound is a field
|
|
149
156
|
* error, which would give one condition two contracts: a page that is no whole number from 1 is
|
|
150
|
-
* `invalid_page`, and maxPage
|
|
151
|
-
*
|
|
157
|
+
* `invalid_page`, and one past maxPage is `page_too_deep` at any size, each as an unbounded list
|
|
158
|
+
* answers it
|
|
152
159
|
*/
|
|
153
160
|
function pageSchema(maxPage: number): z.ZodOptional<CountParam> {
|
|
154
161
|
const page = z
|
|
@@ -13,10 +13,13 @@ type Report = (message: string) => void
|
|
|
13
13
|
/** A relation by name, and the config that declares its fields */
|
|
14
14
|
type Relation = { fieldsConfig: FieldsConfig; name: string }
|
|
15
15
|
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Report every name in `scalars` that `allowed` lacks; computed `@` fields are the handler's, and `*`
|
|
18
|
+
* is no name: it expands to `allowed` after this check, so a name beside it is held to `allowed` too
|
|
19
|
+
*/
|
|
17
20
|
function checkScalars(scalars: readonly string[], allowed: ReadonlySet<string>, report: Report): void {
|
|
18
21
|
for (const field of scalars) {
|
|
19
|
-
if (!allowed.has(field) && !field.startsWith("@")) report(field)
|
|
22
|
+
if (field !== "*" && !allowed.has(field) && !field.startsWith("@")) report(field)
|
|
20
23
|
}
|
|
21
24
|
}
|
|
22
25
|
|
|
@@ -30,9 +33,9 @@ function checkRelation(selection: FieldSelection, { fieldsConfig, name }: Relati
|
|
|
30
33
|
|
|
31
34
|
const relationFields = fieldsConfig.relationFields ?? {}
|
|
32
35
|
const allowed = new Set(Object.hasOwn(relationFields, name) ? relationFields[name] : [])
|
|
36
|
+
checkScalars(selection.scalars, allowed, (field) => report(`Unknown ${name} field: ${field}`))
|
|
33
37
|
/* expand nested wildcard to all allowed fields */
|
|
34
38
|
if (selection.scalars.includes("*")) selection.scalars = [...allowed]
|
|
35
|
-
checkScalars(selection.scalars, allowed, (field) => report(`Unknown ${name} field: ${field}`))
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
/** The parsed selection, checked against `fieldsConfig`; null when there is none or it does not parse */
|
|
@@ -54,9 +57,9 @@ function validateSelectWithConfig(
|
|
|
54
57
|
return null
|
|
55
58
|
}
|
|
56
59
|
|
|
60
|
+
checkScalars(parsed.root.scalars, new Set(fieldsConfig.scalars), (field) => report(`Unknown field: ${field}`))
|
|
57
61
|
/* Handle * wildcard at top level — expand to all scalars */
|
|
58
62
|
if (parsed.root.scalars.includes("*")) parsed.root.scalars = [...fieldsConfig.scalars]
|
|
59
|
-
checkScalars(parsed.root.scalars, new Set(fieldsConfig.scalars), (field) => report(`Unknown field: ${field}`))
|
|
60
63
|
|
|
61
64
|
const relations = new Set(fieldsConfig.relations ?? [])
|
|
62
65
|
/* parseSelect stores a selection for every embed, never null */
|
|
@@ -141,6 +141,14 @@ const NUMERIC_TEXT = /^(?:-?\d+(?:\.\d+)?|NaN|-?Infinity)$/
|
|
|
141
141
|
const carriesOpaque = (column: SQLWrapper | undefined): boolean =>
|
|
142
142
|
is(column, Column) && column.dataType === "custom" && !is(column, SQLiteColumn)
|
|
143
143
|
|
|
144
|
+
/**
|
|
145
|
+
* `cast` as an operand of no affinity, as a bound parameter is: a CAST has its type's affinity, which
|
|
146
|
+
* SQLite applies to a column of none or BLOB's before it compares, so `k > cast(? as real)` read a
|
|
147
|
+
* stored TEXT '10' as 10 and paged it out of the column's own order. The unary `+` drops the affinity
|
|
148
|
+
* and keeps the index search
|
|
149
|
+
*/
|
|
150
|
+
const affinityFree = (cast: SQL): SQL => sql`+${cast}`
|
|
151
|
+
|
|
144
152
|
/**
|
|
145
153
|
* A cursor value as its key binds it. Stored text the list carried (`carried`, the cursor's flag)
|
|
146
154
|
* binds as itself, past the column's mapper: cast to numeric on a key that reads rounded, and on a
|
|
@@ -150,12 +158,12 @@ const carriesOpaque = (column: SQLWrapper | undefined): boolean =>
|
|
|
150
158
|
*/
|
|
151
159
|
function boundCarried(column: SQLWrapper | undefined, value: unknown, carried: boolean): unknown {
|
|
152
160
|
/* one SQLite parse of the text the list checked reads back as the key, on every driver */
|
|
153
|
-
if (value instanceof RealText) return sql`cast(${value.text} as real)`
|
|
161
|
+
if (value instanceof RealText) return affinityFree(sql`cast(${value.text} as real)`)
|
|
154
162
|
if (carriedText(column, value, { asRead: false, carried })) return sql`${value}`
|
|
155
163
|
/* a mode's mapper binds the value as another (a boolean's as 0 or 1); D1 binds no bigint, so one past 2^53 goes as its digits */
|
|
156
164
|
if (carriedStored(column, value, { asRead: false, carried }))
|
|
157
165
|
return typeof value === "bigint" && !Number.isSafeInteger(Number(value))
|
|
158
|
-
? sql`cast(${String(value)} as integer)`
|
|
166
|
+
? affinityFree(sql`cast(${String(value)} as integer)`)
|
|
159
167
|
: sql`${bindable(value)}`
|
|
160
168
|
if (!carried || typeof value !== "string" || !NUMERIC_TEXT.test(value)) return value
|
|
161
169
|
if (readsRounded(column)) return sql`${value}::numeric`
|
package/src/query/trusted.ts
CHANGED
|
@@ -84,10 +84,15 @@ function invalidPage(page: unknown): CombError {
|
|
|
84
84
|
})
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/** The most digits of a page past `maxPage` its message names */
|
|
88
|
+
const PAGE_SHOWN = 24
|
|
89
|
+
|
|
87
90
|
/** The 400 for a page past `maxPage`, from a list schema or from a list no schema bounded */
|
|
88
|
-
function pageTooDeep(page: number, maxPage: number): CombError {
|
|
91
|
+
function pageTooDeep(page: number | string, maxPage: number): CombError {
|
|
92
|
+
/* a page of any size reaches here as digits, so the message keeps the front of it, not all */
|
|
93
|
+
const shown = String(page)
|
|
89
94
|
return new CombError({
|
|
90
|
-
cause: `page ${
|
|
95
|
+
cause: `page ${shown.length > PAGE_SHOWN ? `${shown.slice(0, PAGE_SHOWN)}…` : shown} is past ${maxPage}; page further by cursor`,
|
|
91
96
|
errorKey: combErrorKeys.PAGE_TOO_DEEP,
|
|
92
97
|
status: "bad_request",
|
|
93
98
|
})
|
package/types/cli/args.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,OAAO,GAChB,SAAS,GACT,eAAe,GACf,kBAAkB,GAClB,eAAe,GACf,cAAc,GACd,cAAc,GACd,gBAAgB,CAAA;AAEnB,MAAM,MAAM,UAAU,GAAG;IACxB,kFAAkF;IAClF,KAAK,CAAC,EAAE,IAAI,GAAG,SAAS,CAAA;IACxB,gCAAgC;IAChC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAA;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,KAAK,MAAM,EAAE,CAAA;IACnB,KAAK,CAAC,EAAE,IAAI,MAAM,EAAE,GAAG,SAAS,CAAA;IAChC,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC1B,CAAA;AAED,eAAO,MAAM,OAAO,EAAE,SAAS,UAAU,
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,OAAO,GAChB,SAAS,GACT,eAAe,GACf,kBAAkB,GAClB,eAAe,GACf,cAAc,GACd,cAAc,GACd,gBAAgB,CAAA;AAEnB,MAAM,MAAM,UAAU,GAAG;IACxB,kFAAkF;IAClF,KAAK,CAAC,EAAE,IAAI,GAAG,SAAS,CAAA;IACxB,gCAAgC;IAChC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAA;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,KAAK,MAAM,EAAE,CAAA;IACnB,KAAK,CAAC,EAAE,IAAI,MAAM,EAAE,GAAG,SAAS,CAAA;IAChC,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC1B,CAAA;AAED,eAAO,MAAM,OAAO,EAAE,SAAS,UAAU,EA0DxC,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACxB,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAA;CACtC,CAAA;AA6CD,iGAAiG;AACjG,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,GAAG,UAAU,CAsB/E;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAE1E;AAqBD,0GAA0G;AAC1G,wBAAgB,WAAW,IAAI,MAAM,EAAE,CAMtC;AAmBD,8FAA8F;AAC9F,wBAAgB,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,UAAU,GAAG,MAAM,CAIpE;AAED,6DAA6D;AAC7D,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAGpD"}
|