vue-context-storage 0.1.9 → 0.1.11
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/dist/index.d.ts +414 -14
- package/dist/index.js +93 -71
- package/package.json +73 -83
- package/dist/collection.d.cts +0 -22
- package/dist/collection.d.ts +0 -22
- package/dist/handlers/query/helpers.d.cts +0 -45
- package/dist/handlers/query/helpers.d.ts +0 -45
- package/dist/handlers/query/index.d.cts +0 -31
- package/dist/handlers/query/index.d.ts +0 -31
- package/dist/handlers/query/transform-helpers.d.cts +0 -123
- package/dist/handlers/query/transform-helpers.d.ts +0 -123
- package/dist/handlers/query/types.d.cts +0 -103
- package/dist/handlers/query/types.d.ts +0 -103
- package/dist/handlers.d.cts +0 -15
- package/dist/handlers.d.ts +0 -15
- package/dist/index.cjs +0 -571
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -14
- package/dist/index.js.map +0 -1
- package/dist/injectionSymbols.d.cts +0 -7
- package/dist/injectionSymbols.d.ts +0 -7
- package/dist/symbols.d.cts +0 -4
- package/dist/symbols.d.ts +0 -4
- package/src/collection.ts +0 -71
- package/src/components/ContextStorage.vue +0 -23
- package/src/components/ContextStorageActivator.vue +0 -20
- package/src/components/ContextStorageCollection.vue +0 -92
- package/src/components/ContextStorageProvider.vue +0 -38
- package/src/components.ts +0 -5
- package/src/handlers/query/helpers.ts +0 -134
- package/src/handlers/query/index.ts +0 -355
- package/src/handlers/query/transform-helpers.ts +0 -309
- package/src/handlers/query/types.ts +0 -125
- package/src/handlers.ts +0 -18
- package/src/index.ts +0 -44
- package/src/injectionSymbols.ts +0 -15
- package/src/plugin.ts +0 -16
- package/src/shims-vue.d.ts +0 -5
- package/src/symbols.ts +0 -4
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
import { ContextStorageHandlerConstructor } from '../../handlers.ts'
|
|
2
|
-
import { deserializeParams, serializeParams } from './helpers.ts'
|
|
3
|
-
import { contextStorageQueryHandler } from '../../symbols.ts'
|
|
4
|
-
import { cloneDeep, isEqual, merge, pick } from 'lodash'
|
|
5
|
-
import { getCurrentInstance, inject, MaybeRefOrGetter, onBeforeUnmount, toValue, watch } from 'vue'
|
|
6
|
-
import { LocationQuery, useRoute, useRouter } from 'vue-router'
|
|
7
|
-
import {
|
|
8
|
-
ContextStorageQueryRegisteredItem,
|
|
9
|
-
IContextStorageQueryHandler,
|
|
10
|
-
QueryHandlerBaseOptions,
|
|
11
|
-
RegisterQueryHandlerBaseOptions,
|
|
12
|
-
RegisterQueryHandlerOptions,
|
|
13
|
-
} from './types.ts'
|
|
14
|
-
|
|
15
|
-
export function useContextStorageQueryHandler<T extends Record<string, unknown>>(
|
|
16
|
-
data: MaybeRefOrGetter<T>,
|
|
17
|
-
options?: RegisterQueryHandlerBaseOptions<T>,
|
|
18
|
-
): void {
|
|
19
|
-
const handler = inject<InstanceType<typeof ContextStorageQueryHandler>>(
|
|
20
|
-
contextStorageQueryHandler,
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
if (!handler) {
|
|
24
|
-
throw new Error('[ContextStorage] ContextStorageQueryHandler is not provided')
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const currentInstance = getCurrentInstance()
|
|
28
|
-
const uid = currentInstance?.uid || 0
|
|
29
|
-
|
|
30
|
-
const causer = new Error().stack?.split('\n')[2]?.trimStart() || 'unknown'
|
|
31
|
-
|
|
32
|
-
const stop = handler.register(data, { causer, uid, ...options })
|
|
33
|
-
onBeforeUnmount(() => {
|
|
34
|
-
stop()
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function sortQueryByReference(query: LocationQuery, ...references: LocationQuery[]): LocationQuery {
|
|
39
|
-
const sorted: LocationQuery = {}
|
|
40
|
-
|
|
41
|
-
const referenceKeys = new Set<string>()
|
|
42
|
-
|
|
43
|
-
references.forEach((reference) => {
|
|
44
|
-
Object.keys(reference).forEach((key) => {
|
|
45
|
-
referenceKeys.add(key)
|
|
46
|
-
})
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
referenceKeys.forEach((key) => {
|
|
50
|
-
if (key in query && !(key in sorted)) {
|
|
51
|
-
sorted[key] = query[key]
|
|
52
|
-
}
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
Object.keys(query).forEach((key) => {
|
|
56
|
-
if (!(key in sorted)) {
|
|
57
|
-
sorted[key] = query[key]
|
|
58
|
-
}
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
return sorted
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export class ContextStorageQueryHandler implements IContextStorageQueryHandler {
|
|
65
|
-
private enabled = false
|
|
66
|
-
private registered: ContextStorageQueryRegisteredItem<any>[] = []
|
|
67
|
-
private currentQuery: LocationQuery | undefined = undefined
|
|
68
|
-
private readonly route: ReturnType<typeof useRoute>
|
|
69
|
-
private router: ReturnType<typeof useRouter>
|
|
70
|
-
private initialState?: Record<string, unknown>
|
|
71
|
-
private hasAnyRegistered = false
|
|
72
|
-
private preventSyncRegisteredToQueryByAfterEachRoute = false
|
|
73
|
-
private preventAfterEachRouteCallsWhileCallingRouter = false
|
|
74
|
-
|
|
75
|
-
static customQueryHandlerOptions: QueryHandlerBaseOptions = {}
|
|
76
|
-
|
|
77
|
-
private readonly options: Required<QueryHandlerBaseOptions> = {
|
|
78
|
-
mode: 'replace',
|
|
79
|
-
emptyPlaceholder: '_',
|
|
80
|
-
mergeOnlyExistingKeysWithoutTransform: true,
|
|
81
|
-
preserveUnusedKeys: false,
|
|
82
|
-
preserveEmptyState: false,
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// noinspection JSUnusedGlobalSymbols
|
|
86
|
-
static configure(options: QueryHandlerBaseOptions): ContextStorageHandlerConstructor {
|
|
87
|
-
ContextStorageQueryHandler.customQueryHandlerOptions = options
|
|
88
|
-
|
|
89
|
-
return ContextStorageQueryHandler
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
constructor() {
|
|
93
|
-
this.route = useRoute()
|
|
94
|
-
this.router = useRouter()
|
|
95
|
-
|
|
96
|
-
this.options = {
|
|
97
|
-
...this.options,
|
|
98
|
-
...ContextStorageQueryHandler.customQueryHandlerOptions,
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const stopAfterEach = this.router.afterEach(() => {
|
|
102
|
-
this.afterEachRoute()
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
onBeforeUnmount(() => {
|
|
106
|
-
stopAfterEach()
|
|
107
|
-
})
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
getInjectionKey(): typeof contextStorageQueryHandler {
|
|
111
|
-
return contextStorageQueryHandler
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
setInitialState(state: Record<string, unknown> | undefined): void {
|
|
115
|
-
this.initialState = state
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
static getInitialStateResolver(): () => LocationQuery {
|
|
119
|
-
const route = useRoute()
|
|
120
|
-
|
|
121
|
-
return () => route.query
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
setEnabled(state: boolean, initial: boolean): void {
|
|
125
|
-
const prevState = this.enabled
|
|
126
|
-
this.enabled = state
|
|
127
|
-
|
|
128
|
-
if (this.hasAnyRegistered) {
|
|
129
|
-
if (initial) {
|
|
130
|
-
this.syncInitialStateToRegistered()
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if ((state && !prevState) || !initial) {
|
|
134
|
-
this.syncRegisteredToQuery()
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
async syncRegisteredToQuery(): Promise<void> {
|
|
140
|
-
if (!this.enabled) {
|
|
141
|
-
return
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (this.preventSyncRegisteredToQueryByAfterEachRoute) {
|
|
145
|
-
return
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const { newQuery, newQueryRaw } = this.#buildQueryFromRegistered()
|
|
149
|
-
|
|
150
|
-
this.currentQuery = newQueryRaw
|
|
151
|
-
|
|
152
|
-
if (isEqual(newQuery, this.route.query)) {
|
|
153
|
-
return
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
this.preventAfterEachRouteCallsWhileCallingRouter = true
|
|
157
|
-
try {
|
|
158
|
-
if (this.options.mode === 'replace') {
|
|
159
|
-
await this.router.replace({ ...this.route, query: newQuery })
|
|
160
|
-
} else {
|
|
161
|
-
await this.router.push({ ...this.route, query: newQuery })
|
|
162
|
-
}
|
|
163
|
-
} catch (e) {
|
|
164
|
-
console.error('[ContextStorage] Got error while routing', e)
|
|
165
|
-
}
|
|
166
|
-
this.preventAfterEachRouteCallsWhileCallingRouter = false
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
afterEachRoute(): void {
|
|
170
|
-
if (!this.enabled) {
|
|
171
|
-
return
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (this.preventAfterEachRouteCallsWhileCallingRouter) {
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
this.setInitialState(this.route.query)
|
|
179
|
-
|
|
180
|
-
this.preventSyncRegisteredToQueryByAfterEachRoute = true
|
|
181
|
-
queueMicrotask(() => {
|
|
182
|
-
this.preventSyncRegisteredToQueryByAfterEachRoute = false
|
|
183
|
-
|
|
184
|
-
this.syncInitialStateToRegistered()
|
|
185
|
-
this.syncRegisteredToQuery()
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
setTimeout(() => {
|
|
189
|
-
this.syncInitialStateToRegistered()
|
|
190
|
-
this.syncRegisteredToQuery()
|
|
191
|
-
})
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
syncInitialStateToRegisteredItem<T extends Record<string, unknown>>(
|
|
195
|
-
item: ContextStorageQueryRegisteredItem<T>,
|
|
196
|
-
): void {
|
|
197
|
-
if (this.initialState === undefined) {
|
|
198
|
-
return
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
let deserialized = deserializeParams(this.initialState)
|
|
202
|
-
|
|
203
|
-
const {
|
|
204
|
-
prefix,
|
|
205
|
-
mergeOnlyExistingKeysWithoutTransform = this.options.mergeOnlyExistingKeysWithoutTransform,
|
|
206
|
-
} = item.options || {}
|
|
207
|
-
|
|
208
|
-
if (typeof prefix === 'string' && prefix.length > 0) {
|
|
209
|
-
deserialized = deserialized[prefix]
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (deserialized === undefined) {
|
|
213
|
-
return
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const itemData = toValue(item.data)
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* null can be if query parameter only has a name without a value sign
|
|
220
|
-
*/
|
|
221
|
-
if (deserialized !== null) {
|
|
222
|
-
const deserializedKeys = Object.keys(deserialized)
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* If the data is empty, return the initial value.
|
|
226
|
-
*
|
|
227
|
-
* This can happen when directly navigating to a route, for example through a menu item.
|
|
228
|
-
*/
|
|
229
|
-
if (!deserializedKeys.length) {
|
|
230
|
-
merge(itemData, item.initialData)
|
|
231
|
-
return
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
if (deserializedKeys.length === 1 && deserialized[this.options.emptyPlaceholder] === null) {
|
|
235
|
-
delete deserialized[this.options.emptyPlaceholder]
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (item.options?.transform) {
|
|
240
|
-
deserialized = item.options.transform(deserialized, item.initialData)
|
|
241
|
-
} else {
|
|
242
|
-
if (mergeOnlyExistingKeysWithoutTransform) {
|
|
243
|
-
deserialized = pick(deserialized, Object.keys(item.initialData))
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (isEqual(itemData, deserialized)) {
|
|
248
|
-
return
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
merge(itemData, deserialized)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
syncInitialStateToRegistered(): void {
|
|
255
|
-
this.registered.forEach((item) => this.syncInitialStateToRegisteredItem(item))
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
register<T extends Record<string, unknown>>(
|
|
259
|
-
data: MaybeRefOrGetter<T>,
|
|
260
|
-
options: RegisterQueryHandlerOptions<T>,
|
|
261
|
-
): () => void {
|
|
262
|
-
this.hasAnyRegistered = true
|
|
263
|
-
|
|
264
|
-
const watchHandle = watch(data, () => this.syncRegisteredToQuery(), {
|
|
265
|
-
deep: true,
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
const item: ContextStorageQueryRegisteredItem<T> = {
|
|
269
|
-
data,
|
|
270
|
-
initialData: cloneDeep(toValue(data)) as T,
|
|
271
|
-
options,
|
|
272
|
-
watchHandle,
|
|
273
|
-
}
|
|
274
|
-
this.registered.push(item)
|
|
275
|
-
|
|
276
|
-
const syncCallback = (): void => {
|
|
277
|
-
this.syncInitialStateToRegisteredItem(item)
|
|
278
|
-
this.syncRegisteredToQuery()
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (this.preventAfterEachRouteCallsWhileCallingRouter) {
|
|
282
|
-
/**
|
|
283
|
-
* Macrotask solves syncing issues when syncRegisteredToQuery called after HMR
|
|
284
|
-
*/
|
|
285
|
-
setTimeout(syncCallback)
|
|
286
|
-
} else {
|
|
287
|
-
queueMicrotask(syncCallback)
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
return (): void => {
|
|
291
|
-
this.registered.splice(this.registered.indexOf(item), 1)
|
|
292
|
-
this.syncRegisteredToQuery()
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
#buildQueryFromRegistered(): { newQuery: LocationQuery; newQueryRaw: LocationQuery } {
|
|
297
|
-
const newQueryRaw: LocationQuery = {}
|
|
298
|
-
|
|
299
|
-
this.registered.forEach((item) => {
|
|
300
|
-
const { prefix, preserveEmptyState = this.options.preserveEmptyState } = item.options || {}
|
|
301
|
-
const patch = serializeParams(toValue(item.data), {
|
|
302
|
-
prefix,
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
const patchKeys = Object.keys(patch)
|
|
306
|
-
|
|
307
|
-
// If there are key intersections between the query and the patch, a warning is issued.
|
|
308
|
-
// Patches should not overwrite each other, otherwise, upon reload, an incorrect value will be restored.
|
|
309
|
-
patchKeys.forEach((key) => {
|
|
310
|
-
if (newQueryRaw.hasOwnProperty(key)) {
|
|
311
|
-
console.warn(
|
|
312
|
-
`[ContextStorage] Key ${key} is already present, overriding ` +
|
|
313
|
-
(item.options?.causer || ''),
|
|
314
|
-
)
|
|
315
|
-
}
|
|
316
|
-
})
|
|
317
|
-
|
|
318
|
-
if (!patchKeys.length && preserveEmptyState) {
|
|
319
|
-
patch[prefix || this.options.emptyPlaceholder] = null
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
Object.assign(newQueryRaw, patch)
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
let newQuery = { ...newQueryRaw }
|
|
326
|
-
|
|
327
|
-
/*
|
|
328
|
-
* It will not delete from the query the keys that are not used in the patch.
|
|
329
|
-
*
|
|
330
|
-
* It will only work if the registered item has a transform, otherwise without
|
|
331
|
-
* it - all keys are dumped into item.data during the initial fill from initialState
|
|
332
|
-
*/
|
|
333
|
-
if (this.options.preserveUnusedKeys) {
|
|
334
|
-
newQuery = { ...this.route.query, ...newQuery }
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if (this.currentQuery !== undefined) {
|
|
338
|
-
//Perform a diff of keys between currentQuery and newQueryRaw, and remove the keys that are in currentQuery but not in newQueryRaw.
|
|
339
|
-
//This is necessary to ensure that the query string does not contain keys that are no longer used.
|
|
340
|
-
Object.keys(this.currentQuery).forEach((key) => {
|
|
341
|
-
if (!newQueryRaw.hasOwnProperty(key)) {
|
|
342
|
-
delete newQuery[key]
|
|
343
|
-
}
|
|
344
|
-
})
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (Object.keys(newQuery).length > 1 && newQuery[this.options.emptyPlaceholder] === null) {
|
|
348
|
-
delete newQuery[this.options.emptyPlaceholder]
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
newQuery = sortQueryByReference(newQuery, newQueryRaw)
|
|
352
|
-
|
|
353
|
-
return { newQuery, newQueryRaw }
|
|
354
|
-
}
|
|
355
|
-
}
|
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
import { QueryValue } from './types.ts'
|
|
2
|
-
|
|
3
|
-
interface AsNumberOptions {
|
|
4
|
-
nullable?: boolean
|
|
5
|
-
missable?: boolean
|
|
6
|
-
fallbackValue?: number
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function asNumber(value: QueryValue | number | undefined): number
|
|
10
|
-
export function asNumber(
|
|
11
|
-
value: QueryValue | number | undefined,
|
|
12
|
-
options: { nullable: true; missable: true; fallbackValue?: number },
|
|
13
|
-
): number | null | undefined
|
|
14
|
-
export function asNumber(
|
|
15
|
-
value: QueryValue | number | undefined,
|
|
16
|
-
options: { nullable: true; missable?: false; fallbackValue?: number },
|
|
17
|
-
): number | null
|
|
18
|
-
export function asNumber(
|
|
19
|
-
value: QueryValue | number | undefined,
|
|
20
|
-
options: { nullable?: false; missable: true; fallbackValue?: number },
|
|
21
|
-
): number | undefined
|
|
22
|
-
export function asNumber(
|
|
23
|
-
value: QueryValue | number | undefined,
|
|
24
|
-
options: { nullable?: false; missable?: false; fallbackValue?: number },
|
|
25
|
-
): number
|
|
26
|
-
export function asNumber(
|
|
27
|
-
value: QueryValue | number | undefined,
|
|
28
|
-
options?: AsNumberOptions,
|
|
29
|
-
): number | null | undefined {
|
|
30
|
-
const {
|
|
31
|
-
nullable = false,
|
|
32
|
-
missable = false,
|
|
33
|
-
fallbackValue = missable ? undefined : nullable ? null : 0,
|
|
34
|
-
} = options || {}
|
|
35
|
-
|
|
36
|
-
if (value === null && nullable) {
|
|
37
|
-
return null
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (value === undefined && missable) {
|
|
41
|
-
return undefined
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
value = Number(value)
|
|
45
|
-
|
|
46
|
-
return isNaN(value) ? fallbackValue : value
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface AsStringOptions<T extends readonly string[] = string[]> {
|
|
50
|
-
nullable?: boolean
|
|
51
|
-
missable?: boolean
|
|
52
|
-
fallbackValue?: T extends readonly string[] ? T[number] : string
|
|
53
|
-
allowedValues?: T
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function asString(value: QueryValue | undefined): string
|
|
57
|
-
export function asString<T extends readonly string[]>(
|
|
58
|
-
value: QueryValue | undefined,
|
|
59
|
-
options: {
|
|
60
|
-
nullable: true
|
|
61
|
-
missable: true
|
|
62
|
-
fallbackValue?: T[number]
|
|
63
|
-
allowedValues: T
|
|
64
|
-
},
|
|
65
|
-
): T[number] | null | undefined
|
|
66
|
-
export function asString<T extends readonly string[]>(
|
|
67
|
-
value: QueryValue | undefined,
|
|
68
|
-
options: {
|
|
69
|
-
nullable: true
|
|
70
|
-
missable?: false
|
|
71
|
-
fallbackValue?: T[number]
|
|
72
|
-
allowedValues: T
|
|
73
|
-
},
|
|
74
|
-
): T[number] | null
|
|
75
|
-
export function asString<T extends readonly string[]>(
|
|
76
|
-
value: QueryValue | undefined,
|
|
77
|
-
options: {
|
|
78
|
-
nullable?: false
|
|
79
|
-
missable: true
|
|
80
|
-
fallbackValue?: T[number]
|
|
81
|
-
allowedValues: T
|
|
82
|
-
},
|
|
83
|
-
): T[number] | undefined
|
|
84
|
-
export function asString<T extends readonly string[]>(
|
|
85
|
-
value: QueryValue | undefined,
|
|
86
|
-
options: {
|
|
87
|
-
nullable?: false
|
|
88
|
-
missable?: false
|
|
89
|
-
fallbackValue?: T[number]
|
|
90
|
-
allowedValues: T
|
|
91
|
-
},
|
|
92
|
-
): T[number]
|
|
93
|
-
export function asString(
|
|
94
|
-
value: QueryValue | undefined,
|
|
95
|
-
options: { nullable: true; missable: true; fallbackValue?: string },
|
|
96
|
-
): string | null | undefined
|
|
97
|
-
export function asString(
|
|
98
|
-
value: QueryValue | undefined,
|
|
99
|
-
options: { nullable: true; missable?: false; fallbackValue?: string },
|
|
100
|
-
): string | null
|
|
101
|
-
export function asString(
|
|
102
|
-
value: QueryValue | undefined,
|
|
103
|
-
options: { nullable?: false; missable: true; fallbackValue?: string },
|
|
104
|
-
): string | undefined
|
|
105
|
-
export function asString(
|
|
106
|
-
value: QueryValue | undefined,
|
|
107
|
-
options: { nullable?: false; missable?: false; fallbackValue?: string },
|
|
108
|
-
): string
|
|
109
|
-
export function asString(
|
|
110
|
-
value: QueryValue | undefined,
|
|
111
|
-
options?: AsStringOptions,
|
|
112
|
-
): QueryValue | undefined {
|
|
113
|
-
const {
|
|
114
|
-
nullable = false,
|
|
115
|
-
missable = false,
|
|
116
|
-
fallbackValue = missable ? undefined : nullable ? null : '',
|
|
117
|
-
allowedValues,
|
|
118
|
-
} = options || {}
|
|
119
|
-
|
|
120
|
-
if (value === null && nullable) {
|
|
121
|
-
return null
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (value === undefined && missable) {
|
|
125
|
-
return undefined
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const stringValue = value ?? fallbackValue
|
|
129
|
-
|
|
130
|
-
if (allowedValues && typeof stringValue === 'string' && !allowedValues.includes(stringValue)) {
|
|
131
|
-
return fallbackValue
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return stringValue
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
interface AsNumberArrayOptions {
|
|
138
|
-
nullable?: boolean
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export function asNumberArray(value: QueryValue | undefined): number[]
|
|
142
|
-
export function asNumberArray(
|
|
143
|
-
value: QueryValue | undefined,
|
|
144
|
-
options: { nullable: true },
|
|
145
|
-
): number[] | null
|
|
146
|
-
export function asNumberArray(
|
|
147
|
-
value: QueryValue | undefined,
|
|
148
|
-
options: { nullable?: false },
|
|
149
|
-
): number[]
|
|
150
|
-
export function asNumberArray(
|
|
151
|
-
value: QueryValue | undefined,
|
|
152
|
-
options?: AsNumberArrayOptions,
|
|
153
|
-
): number[] | null {
|
|
154
|
-
const { nullable = false } = options || {}
|
|
155
|
-
|
|
156
|
-
if (value === null && nullable) {
|
|
157
|
-
return null
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (value === undefined) {
|
|
161
|
-
return nullable ? null : []
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
let arrayValue: (string | null)[]
|
|
165
|
-
|
|
166
|
-
if (Array.isArray(value)) {
|
|
167
|
-
arrayValue = value
|
|
168
|
-
} else if (typeof value === 'string') {
|
|
169
|
-
arrayValue = [value]
|
|
170
|
-
} else {
|
|
171
|
-
arrayValue = []
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return arrayValue.map((item) => {
|
|
175
|
-
if (item === null) {
|
|
176
|
-
return 0
|
|
177
|
-
}
|
|
178
|
-
const num = Number(item)
|
|
179
|
-
return isNaN(num) ? 0 : num
|
|
180
|
-
})
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
interface AsArrayOptions<T> {
|
|
184
|
-
nullable?: boolean
|
|
185
|
-
missable?: boolean
|
|
186
|
-
transform?: (value: QueryValue) => T
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export function asArray<T>(value: QueryValue | undefined): T[]
|
|
190
|
-
export function asArray<T>(
|
|
191
|
-
value: QueryValue | undefined,
|
|
192
|
-
options: { nullable: true; missable: true; transform?: (value: QueryValue) => T },
|
|
193
|
-
): T[] | null | undefined
|
|
194
|
-
export function asArray<T>(
|
|
195
|
-
value: QueryValue | undefined,
|
|
196
|
-
options: { nullable: true; missable?: false; transform?: (value: QueryValue) => T },
|
|
197
|
-
): T[] | null
|
|
198
|
-
export function asArray<T>(
|
|
199
|
-
value: QueryValue | undefined,
|
|
200
|
-
options: { nullable?: false; missable: true; transform?: (value: QueryValue) => T },
|
|
201
|
-
): T[] | undefined
|
|
202
|
-
export function asArray<T>(
|
|
203
|
-
value: QueryValue | undefined,
|
|
204
|
-
options: { nullable?: false; missable?: false; transform?: (value: QueryValue) => T },
|
|
205
|
-
): T[]
|
|
206
|
-
export function asArray<T>(
|
|
207
|
-
value: QueryValue | undefined,
|
|
208
|
-
options?: AsArrayOptions<T>,
|
|
209
|
-
): T[] | null | undefined {
|
|
210
|
-
const { nullable = false, missable = false, transform } = options || {}
|
|
211
|
-
|
|
212
|
-
if (value === null && nullable) {
|
|
213
|
-
return null
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (value === undefined && missable) {
|
|
217
|
-
return undefined
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (value === undefined) {
|
|
221
|
-
return nullable ? null : []
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
let arrayValue: QueryValue[]
|
|
225
|
-
|
|
226
|
-
if (Array.isArray(value)) {
|
|
227
|
-
arrayValue = value
|
|
228
|
-
} else {
|
|
229
|
-
arrayValue = [value]
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (transform) {
|
|
233
|
-
return arrayValue.map((item) => transform(item))
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return arrayValue as T[]
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
interface AsBooleanOptions {
|
|
240
|
-
nullable?: boolean
|
|
241
|
-
missable?: boolean
|
|
242
|
-
fallbackValue?: boolean
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
export function asBoolean(value: QueryValue | undefined): boolean
|
|
246
|
-
export function asBoolean(
|
|
247
|
-
value: QueryValue | undefined,
|
|
248
|
-
options: { nullable: true; missable: true; fallbackValue?: boolean },
|
|
249
|
-
): boolean | null | undefined
|
|
250
|
-
export function asBoolean(
|
|
251
|
-
value: QueryValue | undefined,
|
|
252
|
-
options: { nullable: true; missable?: false; fallbackValue?: boolean },
|
|
253
|
-
): boolean | null
|
|
254
|
-
export function asBoolean(
|
|
255
|
-
value: QueryValue | undefined,
|
|
256
|
-
options: { nullable?: false; missable: true; fallbackValue?: boolean },
|
|
257
|
-
): boolean | undefined
|
|
258
|
-
export function asBoolean(
|
|
259
|
-
value: QueryValue | undefined,
|
|
260
|
-
options: { nullable?: false; missable?: false; fallbackValue?: boolean },
|
|
261
|
-
): boolean
|
|
262
|
-
export function asBoolean(
|
|
263
|
-
value: QueryValue | undefined,
|
|
264
|
-
options?: AsBooleanOptions,
|
|
265
|
-
): boolean | null | undefined {
|
|
266
|
-
const {
|
|
267
|
-
nullable = false,
|
|
268
|
-
missable = false,
|
|
269
|
-
fallbackValue = missable ? undefined : nullable ? null : false,
|
|
270
|
-
} = options || {}
|
|
271
|
-
|
|
272
|
-
if (value === null && nullable) {
|
|
273
|
-
return null
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (value === undefined && missable) {
|
|
277
|
-
return undefined
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (value === undefined || value === null) {
|
|
281
|
-
return fallbackValue
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (typeof value === 'string') {
|
|
285
|
-
const lowerValue = value.toLowerCase()
|
|
286
|
-
if (lowerValue === 'true' || lowerValue === '1') {
|
|
287
|
-
return true
|
|
288
|
-
}
|
|
289
|
-
if (lowerValue === 'false' || lowerValue === '0') {
|
|
290
|
-
return false
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return fallbackValue
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
export const transform: {
|
|
298
|
-
asString: typeof asString
|
|
299
|
-
asNumber: typeof asNumber
|
|
300
|
-
asArray: typeof asArray
|
|
301
|
-
asNumberArray: typeof asNumberArray
|
|
302
|
-
asBoolean: typeof asBoolean
|
|
303
|
-
} = {
|
|
304
|
-
asString,
|
|
305
|
-
asNumber,
|
|
306
|
-
asArray,
|
|
307
|
-
asNumberArray,
|
|
308
|
-
asBoolean,
|
|
309
|
-
}
|