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