@dxos/effect 0.8.1 → 0.8.2-main.10c050d
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/lib/browser/index.mjs +70 -68
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +22 -20
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +70 -68
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/ast.d.ts +19 -19
- package/dist/types/src/ast.d.ts.map +1 -1
- package/dist/types/src/jsonPath.d.ts +3 -3
- package/dist/types/src/jsonPath.d.ts.map +1 -1
- package/dist/types/src/url.d.ts +4 -4
- package/dist/types/src/url.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/src/ast.test.ts +39 -42
- package/src/ast.ts +84 -70
- package/src/jsonPath.ts +7 -5
- package/src/url.test.ts +8 -8
- package/src/url.ts +7 -7
package/src/ast.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Option, pipe, SchemaAST
|
|
5
|
+
import { Option, pipe, SchemaAST, Schema } from 'effect';
|
|
6
6
|
|
|
7
7
|
import { invariant } from '@dxos/invariant';
|
|
8
8
|
import { isNonNullable } from '@dxos/util';
|
|
@@ -13,7 +13,7 @@ import { type JsonPath, type JsonProp } from './jsonPath';
|
|
|
13
13
|
// Refs
|
|
14
14
|
// https://effect.website/docs/schema/introduction
|
|
15
15
|
// https://www.npmjs.com/package/@effect/schema
|
|
16
|
-
// https://effect-ts.github.io/effect/schema/
|
|
16
|
+
// https://effect-ts.github.io/effect/schema/SchemaAST.ts.html
|
|
17
17
|
//
|
|
18
18
|
|
|
19
19
|
export type SimpleType = 'object' | 'string' | 'number' | 'boolean' | 'enum' | 'literal';
|
|
@@ -21,31 +21,36 @@ export type SimpleType = 'object' | 'string' | 'number' | 'boolean' | 'enum' | '
|
|
|
21
21
|
/**
|
|
22
22
|
* Get the base type; e.g., traverse through refinements.
|
|
23
23
|
*/
|
|
24
|
-
export const getSimpleType = (node:
|
|
25
|
-
if (
|
|
24
|
+
export const getSimpleType = (node: SchemaAST.AST): SimpleType | undefined => {
|
|
25
|
+
if (
|
|
26
|
+
SchemaAST.isDeclaration(node) ||
|
|
27
|
+
SchemaAST.isObjectKeyword(node) ||
|
|
28
|
+
SchemaAST.isTypeLiteral(node) ||
|
|
29
|
+
isDiscriminatedUnion(node)
|
|
30
|
+
) {
|
|
26
31
|
return 'object';
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
if (
|
|
34
|
+
if (SchemaAST.isStringKeyword(node)) {
|
|
30
35
|
return 'string';
|
|
31
36
|
}
|
|
32
|
-
if (
|
|
37
|
+
if (SchemaAST.isNumberKeyword(node)) {
|
|
33
38
|
return 'number';
|
|
34
39
|
}
|
|
35
|
-
if (
|
|
40
|
+
if (SchemaAST.isBooleanKeyword(node)) {
|
|
36
41
|
return 'boolean';
|
|
37
42
|
}
|
|
38
43
|
|
|
39
|
-
if (
|
|
44
|
+
if (SchemaAST.isEnums(node)) {
|
|
40
45
|
return 'enum';
|
|
41
46
|
}
|
|
42
47
|
|
|
43
|
-
if (
|
|
48
|
+
if (SchemaAST.isLiteral(node)) {
|
|
44
49
|
return 'literal';
|
|
45
50
|
}
|
|
46
51
|
};
|
|
47
52
|
|
|
48
|
-
export const isSimpleType = (node:
|
|
53
|
+
export const isSimpleType = (node: SchemaAST.AST): boolean => !!getSimpleType(node);
|
|
49
54
|
|
|
50
55
|
export namespace SimpleType {
|
|
51
56
|
/**
|
|
@@ -91,9 +96,9 @@ export enum VisitResult {
|
|
|
91
96
|
|
|
92
97
|
export type Path = (string | number)[];
|
|
93
98
|
|
|
94
|
-
export type TestFn = (node:
|
|
99
|
+
export type TestFn = (node: SchemaAST.AST, path: Path, depth: number) => VisitResult | boolean | undefined;
|
|
95
100
|
|
|
96
|
-
export type VisitorFn = (node:
|
|
101
|
+
export type VisitorFn = (node: SchemaAST.AST, path: Path, depth: number) => void;
|
|
97
102
|
|
|
98
103
|
const defaultTest: TestFn = isSimpleType;
|
|
99
104
|
|
|
@@ -104,9 +109,9 @@ const defaultTest: TestFn = isSimpleType;
|
|
|
104
109
|
* - https://github.com/syntax-tree/unist-util-is?tab=readme-ov-file#test
|
|
105
110
|
*/
|
|
106
111
|
export const visit: {
|
|
107
|
-
(node:
|
|
108
|
-
(node:
|
|
109
|
-
} = (node:
|
|
112
|
+
(node: SchemaAST.AST, visitor: VisitorFn): void;
|
|
113
|
+
(node: SchemaAST.AST, test: TestFn, visitor: VisitorFn): void;
|
|
114
|
+
} = (node: SchemaAST.AST, testOrVisitor: TestFn | VisitorFn, visitor?: VisitorFn): void => {
|
|
110
115
|
if (!visitor) {
|
|
111
116
|
visitNode(node, defaultTest, testOrVisitor);
|
|
112
117
|
} else {
|
|
@@ -115,7 +120,7 @@ export const visit: {
|
|
|
115
120
|
};
|
|
116
121
|
|
|
117
122
|
const visitNode = (
|
|
118
|
-
node:
|
|
123
|
+
node: SchemaAST.AST,
|
|
119
124
|
test: TestFn | undefined,
|
|
120
125
|
visitor: VisitorFn,
|
|
121
126
|
path: Path = [],
|
|
@@ -139,8 +144,8 @@ const visitNode = (
|
|
|
139
144
|
}
|
|
140
145
|
|
|
141
146
|
// Object.
|
|
142
|
-
if (
|
|
143
|
-
for (const prop of
|
|
147
|
+
if (SchemaAST.isTypeLiteral(node)) {
|
|
148
|
+
for (const prop of SchemaAST.getPropertySignatures(node)) {
|
|
144
149
|
const currentPath = [...path, prop.name.toString()];
|
|
145
150
|
const result = visitNode(prop.type, test, visitor, currentPath, depth + 1);
|
|
146
151
|
if (result === VisitResult.EXIT) {
|
|
@@ -150,7 +155,7 @@ const visitNode = (
|
|
|
150
155
|
}
|
|
151
156
|
|
|
152
157
|
// Array.
|
|
153
|
-
else if (
|
|
158
|
+
else if (SchemaAST.isTupleType(node)) {
|
|
154
159
|
for (const [i, element] of node.elements.entries()) {
|
|
155
160
|
const currentPath = [...path, i];
|
|
156
161
|
const result = visitNode(element.type, test, visitor, currentPath, depth);
|
|
@@ -161,7 +166,7 @@ const visitNode = (
|
|
|
161
166
|
}
|
|
162
167
|
|
|
163
168
|
// Branching union (e.g., optional, discriminated unions).
|
|
164
|
-
else if (
|
|
169
|
+
else if (SchemaAST.isUnion(node)) {
|
|
165
170
|
for (const type of node.types) {
|
|
166
171
|
const result = visitNode(type, test, visitor, path, depth);
|
|
167
172
|
if (result === VisitResult.EXIT) {
|
|
@@ -171,7 +176,7 @@ const visitNode = (
|
|
|
171
176
|
}
|
|
172
177
|
|
|
173
178
|
// Refinement.
|
|
174
|
-
else if (
|
|
179
|
+
else if (SchemaAST.isRefinement(node)) {
|
|
175
180
|
const result = visitNode(node.from, test, visitor, path, depth);
|
|
176
181
|
if (result === VisitResult.EXIT) {
|
|
177
182
|
return result;
|
|
@@ -185,14 +190,14 @@ const visitNode = (
|
|
|
185
190
|
* Recursively descend into AST to find first node that passes the test.
|
|
186
191
|
*/
|
|
187
192
|
// TODO(burdon): Rewrite using visitNode?
|
|
188
|
-
export const findNode = (node:
|
|
193
|
+
export const findNode = (node: SchemaAST.AST, test: (node: SchemaAST.AST) => boolean): SchemaAST.AST | undefined => {
|
|
189
194
|
if (test(node)) {
|
|
190
195
|
return node;
|
|
191
196
|
}
|
|
192
197
|
|
|
193
198
|
// Object.
|
|
194
|
-
else if (
|
|
195
|
-
for (const prop of
|
|
199
|
+
else if (SchemaAST.isTypeLiteral(node)) {
|
|
200
|
+
for (const prop of SchemaAST.getPropertySignatures(node)) {
|
|
196
201
|
const child = findNode(prop.type, test);
|
|
197
202
|
if (child) {
|
|
198
203
|
return child;
|
|
@@ -201,7 +206,7 @@ export const findNode = (node: AST.AST, test: (node: AST.AST) => boolean): AST.A
|
|
|
201
206
|
}
|
|
202
207
|
|
|
203
208
|
// Tuple.
|
|
204
|
-
else if (
|
|
209
|
+
else if (SchemaAST.isTupleType(node)) {
|
|
205
210
|
for (const [_, element] of node.elements.entries()) {
|
|
206
211
|
const child = findNode(element.type, test);
|
|
207
212
|
if (child) {
|
|
@@ -211,7 +216,7 @@ export const findNode = (node: AST.AST, test: (node: AST.AST) => boolean): AST.A
|
|
|
211
216
|
}
|
|
212
217
|
|
|
213
218
|
// Branching union (e.g., optional, discriminated unions).
|
|
214
|
-
else if (
|
|
219
|
+
else if (SchemaAST.isUnion(node)) {
|
|
215
220
|
if (isOption(node)) {
|
|
216
221
|
for (const type of node.types) {
|
|
217
222
|
const child = findNode(type, test);
|
|
@@ -223,7 +228,7 @@ export const findNode = (node: AST.AST, test: (node: AST.AST) => boolean): AST.A
|
|
|
223
228
|
}
|
|
224
229
|
|
|
225
230
|
// Refinement.
|
|
226
|
-
else if (
|
|
231
|
+
else if (SchemaAST.isRefinement(node)) {
|
|
227
232
|
return findNode(node.from, test);
|
|
228
233
|
}
|
|
229
234
|
};
|
|
@@ -231,12 +236,15 @@ export const findNode = (node: AST.AST, test: (node: AST.AST) => boolean): AST.A
|
|
|
231
236
|
/**
|
|
232
237
|
* Get the AST node for the given property (dot-path).
|
|
233
238
|
*/
|
|
234
|
-
export const findProperty = (
|
|
235
|
-
|
|
239
|
+
export const findProperty = (
|
|
240
|
+
schema: Schema.Schema.AnyNoContext,
|
|
241
|
+
path: JsonPath | JsonProp,
|
|
242
|
+
): SchemaAST.AST | undefined => {
|
|
243
|
+
const getProp = (node: SchemaAST.AST, path: JsonProp[]): SchemaAST.AST | undefined => {
|
|
236
244
|
const [name, ...rest] = path;
|
|
237
|
-
const typeNode = findNode(node,
|
|
245
|
+
const typeNode = findNode(node, SchemaAST.isTypeLiteral);
|
|
238
246
|
invariant(typeNode);
|
|
239
|
-
for (const prop of
|
|
247
|
+
for (const prop of SchemaAST.getPropertySignatures(typeNode)) {
|
|
240
248
|
if (prop.name === name) {
|
|
241
249
|
if (rest.length) {
|
|
242
250
|
return getProp(prop.type, rest);
|
|
@@ -254,11 +262,11 @@ export const findProperty = (schema: S.Schema.AnyNoContext, path: JsonPath | Jso
|
|
|
254
262
|
// Annotations
|
|
255
263
|
//
|
|
256
264
|
|
|
257
|
-
const defaultAnnotations: Record<string,
|
|
258
|
-
['ObjectKeyword' as const]:
|
|
259
|
-
['StringKeyword' as const]:
|
|
260
|
-
['NumberKeyword' as const]:
|
|
261
|
-
['BooleanKeyword' as const]:
|
|
265
|
+
const defaultAnnotations: Record<string, SchemaAST.Annotated> = {
|
|
266
|
+
['ObjectKeyword' as const]: SchemaAST.objectKeyword,
|
|
267
|
+
['StringKeyword' as const]: SchemaAST.stringKeyword,
|
|
268
|
+
['NumberKeyword' as const]: SchemaAST.numberKeyword,
|
|
269
|
+
['BooleanKeyword' as const]: SchemaAST.booleanKeyword,
|
|
262
270
|
};
|
|
263
271
|
|
|
264
272
|
/**
|
|
@@ -268,10 +276,10 @@ const defaultAnnotations: Record<string, AST.Annotated> = {
|
|
|
268
276
|
*/
|
|
269
277
|
export const getAnnotation =
|
|
270
278
|
<T>(annotationId: symbol, noDefault = true) =>
|
|
271
|
-
(node:
|
|
279
|
+
(node: SchemaAST.AST): T | undefined => {
|
|
272
280
|
// Title fallback seems to be the identifier.
|
|
273
|
-
const id = pipe(
|
|
274
|
-
const value = pipe(
|
|
281
|
+
const id = pipe(SchemaAST.getIdentifierAnnotation(node), Option.getOrUndefined);
|
|
282
|
+
const value = pipe(SchemaAST.getAnnotation<T>(annotationId)(node), Option.getOrUndefined);
|
|
275
283
|
if (noDefault && (value === defaultAnnotations[node._tag]?.annotations[annotationId] || value === id)) {
|
|
276
284
|
return undefined;
|
|
277
285
|
}
|
|
@@ -284,16 +292,16 @@ export const getAnnotation =
|
|
|
284
292
|
* Optionally skips default annotations for basic types (e.g., 'a string').
|
|
285
293
|
*/
|
|
286
294
|
// TODO(burdon): Convert to effect pattern (i.e., return operator like getAnnotation).
|
|
287
|
-
export const findAnnotation = <T>(node:
|
|
295
|
+
export const findAnnotation = <T>(node: SchemaAST.AST, annotationId: symbol, noDefault = true): T | undefined => {
|
|
288
296
|
const getAnnotationById = getAnnotation(annotationId, noDefault);
|
|
289
297
|
|
|
290
|
-
const getBaseAnnotation = (node:
|
|
298
|
+
const getBaseAnnotation = (node: SchemaAST.AST): T | undefined => {
|
|
291
299
|
const value = getAnnotationById(node);
|
|
292
300
|
if (value !== undefined) {
|
|
293
301
|
return value as T;
|
|
294
302
|
}
|
|
295
303
|
|
|
296
|
-
if (
|
|
304
|
+
if (SchemaAST.isUnion(node)) {
|
|
297
305
|
if (isOption(node)) {
|
|
298
306
|
return getAnnotationById(node.types[0]) as T;
|
|
299
307
|
}
|
|
@@ -308,40 +316,40 @@ export const findAnnotation = <T>(node: AST.AST, annotationId: symbol, noDefault
|
|
|
308
316
|
//
|
|
309
317
|
|
|
310
318
|
/**
|
|
311
|
-
* Effect
|
|
319
|
+
* Effect Schema.optional creates a union type with undefined as the second type.
|
|
312
320
|
*/
|
|
313
|
-
export const isOption = (node:
|
|
314
|
-
return
|
|
321
|
+
export const isOption = (node: SchemaAST.AST): boolean => {
|
|
322
|
+
return SchemaAST.isUnion(node) && node.types.length === 2 && SchemaAST.isUndefinedKeyword(node.types[1]);
|
|
315
323
|
};
|
|
316
324
|
|
|
317
325
|
/**
|
|
318
326
|
* Determines if the node is a union of literal types.
|
|
319
327
|
*/
|
|
320
|
-
export const isLiteralUnion = (node:
|
|
321
|
-
return
|
|
328
|
+
export const isLiteralUnion = (node: SchemaAST.AST): boolean => {
|
|
329
|
+
return SchemaAST.isUnion(node) && node.types.every(SchemaAST.isLiteral);
|
|
322
330
|
};
|
|
323
331
|
|
|
324
332
|
/**
|
|
325
333
|
* Determines if the node is a discriminated union.
|
|
326
334
|
*/
|
|
327
|
-
export const isDiscriminatedUnion = (node:
|
|
328
|
-
return
|
|
335
|
+
export const isDiscriminatedUnion = (node: SchemaAST.AST): boolean => {
|
|
336
|
+
return SchemaAST.isUnion(node) && !!getDiscriminatingProps(node)?.length;
|
|
329
337
|
};
|
|
330
338
|
|
|
331
339
|
/**
|
|
332
340
|
* Get the discriminating properties for the given union type.
|
|
333
341
|
*/
|
|
334
|
-
export const getDiscriminatingProps = (node:
|
|
335
|
-
invariant(
|
|
342
|
+
export const getDiscriminatingProps = (node: SchemaAST.AST): string[] | undefined => {
|
|
343
|
+
invariant(SchemaAST.isUnion(node));
|
|
336
344
|
if (isOption(node)) {
|
|
337
345
|
return;
|
|
338
346
|
}
|
|
339
347
|
|
|
340
348
|
// Get common literals across all types.
|
|
341
349
|
return node.types.reduce<string[]>((shared, type) => {
|
|
342
|
-
const props =
|
|
350
|
+
const props = SchemaAST.getPropertySignatures(type)
|
|
343
351
|
// TODO(burdon): Should check each literal is unique.
|
|
344
|
-
.filter((p) =>
|
|
352
|
+
.filter((p) => SchemaAST.isLiteral(p.type))
|
|
345
353
|
.map((p) => p.name.toString());
|
|
346
354
|
|
|
347
355
|
// Return common literals.
|
|
@@ -352,8 +360,11 @@ export const getDiscriminatingProps = (node: AST.AST): string[] | undefined => {
|
|
|
352
360
|
/**
|
|
353
361
|
* Get the discriminated type for the given value.
|
|
354
362
|
*/
|
|
355
|
-
export const getDiscriminatedType = (
|
|
356
|
-
|
|
363
|
+
export const getDiscriminatedType = (
|
|
364
|
+
node: SchemaAST.AST,
|
|
365
|
+
value: Record<string, any> = {},
|
|
366
|
+
): SchemaAST.AST | undefined => {
|
|
367
|
+
invariant(SchemaAST.isUnion(node));
|
|
357
368
|
invariant(value);
|
|
358
369
|
const props = getDiscriminatingProps(node);
|
|
359
370
|
if (!props?.length) {
|
|
@@ -362,10 +373,10 @@ export const getDiscriminatedType = (node: AST.AST, value: Record<string, any> =
|
|
|
362
373
|
|
|
363
374
|
// Match provided values.
|
|
364
375
|
for (const type of node.types) {
|
|
365
|
-
const match =
|
|
376
|
+
const match = SchemaAST.getPropertySignatures(type)
|
|
366
377
|
.filter((prop) => props?.includes(prop.name.toString()))
|
|
367
378
|
.every((prop) => {
|
|
368
|
-
invariant(
|
|
379
|
+
invariant(SchemaAST.isLiteral(prop.type));
|
|
369
380
|
return prop.type.literal === value[prop.name.toString()];
|
|
370
381
|
});
|
|
371
382
|
|
|
@@ -382,33 +393,36 @@ export const getDiscriminatedType = (node: AST.AST, value: Record<string, any> =
|
|
|
382
393
|
.map((prop) => {
|
|
383
394
|
const literals = node.types
|
|
384
395
|
.map((type) => {
|
|
385
|
-
const literal =
|
|
386
|
-
invariant(
|
|
396
|
+
const literal = SchemaAST.getPropertySignatures(type).find((p) => p.name.toString() === prop)!;
|
|
397
|
+
invariant(SchemaAST.isLiteral(literal.type));
|
|
387
398
|
return literal.type.literal;
|
|
388
399
|
})
|
|
389
400
|
.filter(isNonNullable);
|
|
390
401
|
|
|
391
|
-
return literals.length ? [prop,
|
|
402
|
+
return literals.length ? [prop, Schema.Literal(...literals)] : undefined;
|
|
392
403
|
})
|
|
393
404
|
.filter(isNonNullable),
|
|
394
405
|
);
|
|
395
406
|
|
|
396
|
-
const schema =
|
|
407
|
+
const schema = Schema.Struct(fields);
|
|
397
408
|
return schema.ast;
|
|
398
409
|
};
|
|
399
410
|
|
|
400
411
|
/**
|
|
401
412
|
* Maps AST nodes.
|
|
402
|
-
* The user is responsible for recursively calling {@link mapAst} on the
|
|
413
|
+
* The user is responsible for recursively calling {@link mapAst} on the SchemaAST.
|
|
403
414
|
* NOTE: Will evaluate suspended ASTs.
|
|
404
415
|
*/
|
|
405
|
-
export const mapAst = (
|
|
416
|
+
export const mapAst = (
|
|
417
|
+
ast: SchemaAST.AST,
|
|
418
|
+
f: (ast: SchemaAST.AST, key: keyof any | undefined) => SchemaAST.AST,
|
|
419
|
+
): SchemaAST.AST => {
|
|
406
420
|
switch (ast._tag) {
|
|
407
421
|
case 'TypeLiteral': {
|
|
408
|
-
return new
|
|
422
|
+
return new SchemaAST.TypeLiteral(
|
|
409
423
|
ast.propertySignatures.map(
|
|
410
424
|
(prop) =>
|
|
411
|
-
new
|
|
425
|
+
new SchemaAST.PropertySignature(
|
|
412
426
|
prop.name,
|
|
413
427
|
f(prop.type, prop.name),
|
|
414
428
|
prop.isOptional,
|
|
@@ -420,19 +434,19 @@ export const mapAst = (ast: AST.AST, f: (ast: AST.AST, key: keyof any | undefine
|
|
|
420
434
|
);
|
|
421
435
|
}
|
|
422
436
|
case 'Union': {
|
|
423
|
-
return
|
|
437
|
+
return SchemaAST.Union.make(ast.types.map(f), ast.annotations);
|
|
424
438
|
}
|
|
425
439
|
case 'TupleType': {
|
|
426
|
-
return new
|
|
427
|
-
ast.elements.map((t, index) => new
|
|
428
|
-
ast.rest.map((t) => new
|
|
440
|
+
return new SchemaAST.TupleType(
|
|
441
|
+
ast.elements.map((t, index) => new SchemaAST.OptionalType(f(t.type, index), t.isOptional, t.annotations)),
|
|
442
|
+
ast.rest.map((t) => new SchemaAST.Type(f(t.type, undefined), t.annotations)),
|
|
429
443
|
ast.isReadonly,
|
|
430
444
|
ast.annotations,
|
|
431
445
|
);
|
|
432
446
|
}
|
|
433
447
|
case 'Suspend': {
|
|
434
448
|
const newAst = f(ast.f(), undefined);
|
|
435
|
-
return new
|
|
449
|
+
return new SchemaAST.Suspend(() => newAst, ast.annotations);
|
|
436
450
|
}
|
|
437
451
|
default: {
|
|
438
452
|
// TODO(dmaretskyi): Support more nodes.
|
package/src/jsonPath.ts
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Schema
|
|
6
|
-
import { isSome } from 'effect/Option';
|
|
5
|
+
import { Schema, Option } from 'effect';
|
|
7
6
|
import { JSONPath } from 'jsonpath-plus';
|
|
8
7
|
|
|
9
8
|
import { invariant } from '@dxos/invariant';
|
|
@@ -17,11 +16,14 @@ const PROP_REGEX = /\w+/;
|
|
|
17
16
|
/**
|
|
18
17
|
* https://www.ietf.org/archive/id/draft-goessner-dispatch-jsonpath-00.html
|
|
19
18
|
*/
|
|
20
|
-
export const JsonPath =
|
|
21
|
-
|
|
19
|
+
export const JsonPath = Schema.String.pipe(Schema.pattern(PATH_REGEX)).annotations({
|
|
20
|
+
title: 'JSON path',
|
|
21
|
+
description: 'JSON path to a property',
|
|
22
|
+
}) as any as Schema.Schema<JsonPath>;
|
|
23
|
+
export const JsonProp = Schema.NonEmptyString.pipe(Schema.pattern(PROP_REGEX)) as any as Schema.Schema<JsonProp>;
|
|
22
24
|
|
|
23
25
|
export const isJsonPath = (value: unknown): value is JsonPath => {
|
|
24
|
-
return isSome(
|
|
26
|
+
return Option.isSome(Schema.validateOption(JsonPath)(value));
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
/**
|
package/src/url.test.ts
CHANGED
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { Schema
|
|
5
|
+
import { Schema } from 'effect';
|
|
6
6
|
import { describe, expect, test } from 'vitest';
|
|
7
7
|
|
|
8
8
|
import { ParamKeyAnnotation, UrlParser } from './url';
|
|
9
9
|
|
|
10
|
-
const Invitation =
|
|
11
|
-
accessToken:
|
|
12
|
-
deviceInvitationCode:
|
|
13
|
-
spaceInvitationCode:
|
|
14
|
-
experimental:
|
|
15
|
-
testing:
|
|
16
|
-
timeout:
|
|
10
|
+
const Invitation = Schema.Struct({
|
|
11
|
+
accessToken: Schema.String,
|
|
12
|
+
deviceInvitationCode: Schema.String.pipe(ParamKeyAnnotation({ key: 'deviceInvitationCode' })),
|
|
13
|
+
spaceInvitationCode: Schema.String,
|
|
14
|
+
experimental: Schema.Boolean,
|
|
15
|
+
testing: Schema.Boolean,
|
|
16
|
+
timeout: Schema.Number,
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
describe('Params', () => {
|
package/src/url.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { SchemaAST
|
|
5
|
+
import { SchemaAST, type Schema, Option, pipe } from 'effect';
|
|
6
6
|
|
|
7
7
|
import { decamelize } from '@dxos/util';
|
|
8
8
|
|
|
@@ -10,12 +10,12 @@ const ParamKeyAnnotationId = Symbol.for('@dxos/schema/annotation/ParamKey');
|
|
|
10
10
|
|
|
11
11
|
type ParamKeyAnnotationValue = { key: string };
|
|
12
12
|
|
|
13
|
-
export const getParamKeyAnnotation: (annotated:
|
|
14
|
-
|
|
13
|
+
export const getParamKeyAnnotation: (annotated: SchemaAST.Annotated) => Option.Option<ParamKeyAnnotationValue> =
|
|
14
|
+
SchemaAST.getAnnotation<ParamKeyAnnotationValue>(ParamKeyAnnotationId);
|
|
15
15
|
|
|
16
16
|
export const ParamKeyAnnotation =
|
|
17
17
|
(value: ParamKeyAnnotationValue) =>
|
|
18
|
-
<S extends
|
|
18
|
+
<S extends Schema.Annotable.All>(self: S): Schema.Annotable.Self<S> =>
|
|
19
19
|
self.annotations({ [ParamKeyAnnotationId]: value });
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -23,7 +23,7 @@ export const ParamKeyAnnotation =
|
|
|
23
23
|
* Supports custom key serialization.
|
|
24
24
|
*/
|
|
25
25
|
export class UrlParser<T extends Record<string, any>> {
|
|
26
|
-
constructor(private readonly _schema:
|
|
26
|
+
constructor(private readonly _schema: Schema.Struct<T>) {}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Parse URL params.
|
|
@@ -37,9 +37,9 @@ export class UrlParser<T extends Record<string, any>> {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
if (value != null) {
|
|
40
|
-
if (
|
|
40
|
+
if (SchemaAST.isNumberKeyword(type.ast)) {
|
|
41
41
|
params[key] = parseInt(value);
|
|
42
|
-
} else if (
|
|
42
|
+
} else if (SchemaAST.isBooleanKeyword(type.ast)) {
|
|
43
43
|
params[key] = value === 'true' || value === '1';
|
|
44
44
|
} else {
|
|
45
45
|
params[key] = value;
|