@dxos/effect 0.7.5-main.9d2a38b → 0.7.5-main.e9bb01b
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 +63 -12
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +66 -13
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +63 -12
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/ast.d.ts +1 -12
- package/dist/types/src/ast.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/jsonPath.d.ts +45 -0
- package/dist/types/src/jsonPath.d.ts.map +1 -0
- package/dist/types/src/jsonPath.test.d.ts +2 -0
- package/dist/types/src/jsonPath.test.d.ts.map +1 -0
- package/package.json +5 -5
- package/src/ast.test.ts +1 -24
- package/src/ast.ts +2 -12
- package/src/index.ts +1 -0
- package/src/jsonPath.test.ts +96 -0
- package/src/jsonPath.ts +85 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Schema as S } from '@effect/schema';
|
|
2
|
+
export type JsonProp = string & {
|
|
3
|
+
__JsonPath: true;
|
|
4
|
+
__JsonProp: true;
|
|
5
|
+
};
|
|
6
|
+
export type JsonPath = string & {
|
|
7
|
+
__JsonPath: true;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* https://www.ietf.org/archive/id/draft-goessner-dispatch-jsonpath-00.html
|
|
11
|
+
*/
|
|
12
|
+
export declare const JsonPath: S.Schema<JsonPath>;
|
|
13
|
+
export declare const JsonProp: S.Schema<JsonProp>;
|
|
14
|
+
export declare const isJsonPath: (value: unknown) => value is JsonPath;
|
|
15
|
+
/**
|
|
16
|
+
* Creates a JsonPath from an array of path segments.
|
|
17
|
+
*
|
|
18
|
+
* Currently supports:
|
|
19
|
+
* - Simple property access (e.g., 'foo.bar')
|
|
20
|
+
* - Array indexing with non-negative integers (e.g., 'foo[0]')
|
|
21
|
+
* - Identifiers starting with letters, underscore, or $ (e.g., '$foo', '_bar')
|
|
22
|
+
* - Dot notation for nested properties (e.g., 'foo.bar.baz')
|
|
23
|
+
*
|
|
24
|
+
* Does not support (yet?).
|
|
25
|
+
* - Recursive descent (..)
|
|
26
|
+
* - Wildcards (*)
|
|
27
|
+
* - Array slicing
|
|
28
|
+
* - Filters
|
|
29
|
+
* - Negative indices
|
|
30
|
+
*
|
|
31
|
+
* @param path Array of string or number segments
|
|
32
|
+
* @returns Valid JsonPath or undefined if invalid
|
|
33
|
+
*/
|
|
34
|
+
export declare const createJsonPath: (path: (string | number)[]) => JsonPath;
|
|
35
|
+
/**
|
|
36
|
+
* Converts Effect validation path format (e.g. "addresses.[0].zip")
|
|
37
|
+
* to JsonPath format (e.g. "addresses[0].zip")
|
|
38
|
+
*/
|
|
39
|
+
export declare const fromEffectValidationPath: (effectPath: string) => JsonPath;
|
|
40
|
+
/**
|
|
41
|
+
* Splits a JsonPath into its constituent parts.
|
|
42
|
+
* Handles property access and array indexing.
|
|
43
|
+
*/
|
|
44
|
+
export declare const splitJsonPath: (path: JsonPath) => string[];
|
|
45
|
+
//# sourceMappingURL=jsonPath.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonPath.d.ts","sourceRoot":"","sources":["../../../src/jsonPath.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,gBAAgB,CAAC;AAK7C,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,UAAU,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,IAAI,CAAA;CAAE,CAAC;AACvE,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,UAAU,EAAE,IAAI,CAAA;CAAE,CAAC;AAKrD;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAkD,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC1F,eAAO,MAAM,QAAQ,EAA0D,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAElG,eAAO,MAAM,UAAU,UAAW,OAAO,KAAG,KAAK,IAAI,QAEpD,CAAC;AAEF;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,cAAc,SAAU,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,KAAG,QAa1D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,eAAgB,MAAM,KAAG,QAK7D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,aAAa,SAAU,QAAQ,KAAG,MAAM,EAUpD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonPath.test.d.ts","sourceRoot":"","sources":["../../../src/jsonPath.test.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/effect",
|
|
3
|
-
"version": "0.7.5-main.
|
|
3
|
+
"version": "0.7.5-main.e9bb01b",
|
|
4
4
|
"description": "Effect utils.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
"src"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@dxos/invariant": "0.7.5-main.
|
|
26
|
-
"@dxos/
|
|
27
|
-
"@dxos/
|
|
25
|
+
"@dxos/invariant": "0.7.5-main.e9bb01b",
|
|
26
|
+
"@dxos/util": "0.7.5-main.e9bb01b",
|
|
27
|
+
"@dxos/node-std": "0.7.5-main.e9bb01b"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@effect/schema": "^0.75.5",
|
|
31
31
|
"effect": "^3.12.1",
|
|
32
|
-
"@dxos/log": "0.7.5-main.
|
|
32
|
+
"@dxos/log": "0.7.5-main.e9bb01b"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"@effect/schema": "^0.75.5",
|
package/src/ast.test.ts
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { AST, Schema as S } from '@effect/schema';
|
|
6
|
-
import { isNone, isSome } from 'effect/Option';
|
|
7
6
|
import { describe, test } from 'vitest';
|
|
8
7
|
|
|
9
8
|
import { invariant } from '@dxos/invariant';
|
|
@@ -19,9 +18,8 @@ import {
|
|
|
19
18
|
isOption,
|
|
20
19
|
isSimpleType,
|
|
21
20
|
visit,
|
|
22
|
-
JsonPath,
|
|
23
|
-
type JsonProp,
|
|
24
21
|
} from './ast';
|
|
22
|
+
import { type JsonPath, type JsonProp } from './jsonPath';
|
|
25
23
|
|
|
26
24
|
const ZipCode = S.String.pipe(
|
|
27
25
|
S.pattern(/^\d{5}$/, {
|
|
@@ -183,25 +181,4 @@ describe('AST', () => {
|
|
|
183
181
|
);
|
|
184
182
|
}
|
|
185
183
|
});
|
|
186
|
-
|
|
187
|
-
// TODO(ZaymonFC): Update this when we settle on the right indexing syntax for arrays.
|
|
188
|
-
test('json path validation', ({ expect }) => {
|
|
189
|
-
const validatePath = S.validateOption(JsonPath);
|
|
190
|
-
|
|
191
|
-
// Valid paths.
|
|
192
|
-
expect(isSome(validatePath('foo'))).toBe(true);
|
|
193
|
-
expect(isSome(validatePath('foo.bar'))).toBe(true);
|
|
194
|
-
expect(isSome(validatePath('foo.bar.baz'))).toBe(true);
|
|
195
|
-
expect(isSome(validatePath('foo[1].bar'))).toBe(true);
|
|
196
|
-
expect(isSome(validatePath('_foo.$bar'))).toBe(true);
|
|
197
|
-
|
|
198
|
-
// Invalid paths.
|
|
199
|
-
expect(isNone(validatePath(''))).toBe(true);
|
|
200
|
-
expect(isNone(validatePath('.'))).toBe(true);
|
|
201
|
-
expect(isNone(validatePath('foo.'))).toBe(true);
|
|
202
|
-
expect(isNone(validatePath('foo..bar'))).toBe(true);
|
|
203
|
-
expect(isNone(validatePath('foo.#bar'))).toBe(true);
|
|
204
|
-
expect(isNone(validatePath('[1].bar'))).toBe(true);
|
|
205
|
-
expect(isNone(validatePath('test.[1].bar[1]'))).toBe(true);
|
|
206
|
-
});
|
|
207
184
|
});
|
package/src/ast.ts
CHANGED
|
@@ -8,6 +8,8 @@ import { Option, pipe } from 'effect';
|
|
|
8
8
|
import { invariant } from '@dxos/invariant';
|
|
9
9
|
import { nonNullable } from '@dxos/util';
|
|
10
10
|
|
|
11
|
+
import { type JsonPath, type JsonProp } from './jsonPath';
|
|
12
|
+
|
|
11
13
|
//
|
|
12
14
|
// Refs
|
|
13
15
|
// https://effect.website/docs/schema/introduction
|
|
@@ -76,18 +78,6 @@ export namespace SimpleType {
|
|
|
76
78
|
// Branded types
|
|
77
79
|
//
|
|
78
80
|
|
|
79
|
-
export type JsonProp = string & { __JsonPath: true; __JsonProp: true };
|
|
80
|
-
export type JsonPath = string & { __JsonPath: true };
|
|
81
|
-
|
|
82
|
-
const PATH_REGEX = /^[a-zA-Z_$][\w$]*(?:\.[a-zA-Z_$][\w$]*|\[\d+\])*$/;
|
|
83
|
-
const PROP_REGEX = /\w+/;
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* https://www.ietf.org/archive/id/draft-goessner-dispatch-jsonpath-00.html
|
|
87
|
-
*/
|
|
88
|
-
export const JsonPath = S.NonEmptyString.pipe(S.pattern(PATH_REGEX)) as any as S.Schema<JsonPath>;
|
|
89
|
-
export const JsonProp = S.NonEmptyString.pipe(S.pattern(PROP_REGEX)) as any as S.Schema<JsonProp>;
|
|
90
|
-
|
|
91
81
|
export enum VisitResult {
|
|
92
82
|
CONTINUE = 0,
|
|
93
83
|
/**
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from 'vitest';
|
|
6
|
+
|
|
7
|
+
import { createJsonPath, isJsonPath, type JsonPath, splitJsonPath } from './jsonPath';
|
|
8
|
+
|
|
9
|
+
describe('createJsonPath', () => {
|
|
10
|
+
test('supported path subset', () => {
|
|
11
|
+
// Simple property access.
|
|
12
|
+
expect(createJsonPath(['foo'])).toBe('foo');
|
|
13
|
+
expect(createJsonPath(['foo', 'bar'])).toBe('foo.bar');
|
|
14
|
+
expect(createJsonPath(['names', 1, 'bar'])).toBe('names[1].bar');
|
|
15
|
+
expect(createJsonPath(['names', 1])).toBe('names[1]');
|
|
16
|
+
expect(createJsonPath(['names', 1, 'names'])).toBe('names[1].names');
|
|
17
|
+
|
|
18
|
+
// Array indexing.
|
|
19
|
+
expect(createJsonPath(['foo', 0, 'bar'])).toBe('foo[0].bar');
|
|
20
|
+
expect(createJsonPath(['items', 1])).toBe('items[1]');
|
|
21
|
+
|
|
22
|
+
// $ is valid in identifiers.
|
|
23
|
+
expect(createJsonPath(['$foo', '$bar'])).toBe('$foo.$bar');
|
|
24
|
+
expect(createJsonPath([])).toBe('');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('invalid paths', () => {
|
|
28
|
+
expect(() => createJsonPath(['123foo'])).toThrow(); // Can't start with number.
|
|
29
|
+
expect(() => createJsonPath(['foo', -1, 'bar'])).toThrow(); // No negative indices.
|
|
30
|
+
expect(() => createJsonPath(['foo', 1.5, 'bar'])).toThrow(); // No float indices.
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('path splitting', () => {
|
|
34
|
+
const cases = [
|
|
35
|
+
['foo.bar[0].baz', ['foo', 'bar', '0', 'baz']],
|
|
36
|
+
['users[1].name', ['users', '1', 'name']],
|
|
37
|
+
['data[0][1]', ['data', '0', '1']],
|
|
38
|
+
['simple.path', ['simple', 'path']],
|
|
39
|
+
['root', ['root']],
|
|
40
|
+
] as const;
|
|
41
|
+
|
|
42
|
+
cases.forEach(([input, expected]) => {
|
|
43
|
+
expect(splitJsonPath(input as JsonPath)).toEqual(expected);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('path splitting - extended cases', () => {
|
|
48
|
+
const cases = [
|
|
49
|
+
// Multiple consecutive array indices.
|
|
50
|
+
['matrix[0][1][2]', ['matrix', '0', '1', '2']],
|
|
51
|
+
// Properties with underscores and $.
|
|
52
|
+
['$_foo.bar_baz', ['$_foo', 'bar_baz']],
|
|
53
|
+
// Deep nesting.
|
|
54
|
+
['very.deep.nested[0].property.path[5]', ['very', 'deep', 'nested', '0', 'property', 'path', '5']],
|
|
55
|
+
// Single character properties.
|
|
56
|
+
['a[0].b.c', ['a', '0', 'b', 'c']],
|
|
57
|
+
// Properties containing numbers.
|
|
58
|
+
['prop123.item456[7]', ['prop123', 'item456', '7']],
|
|
59
|
+
] as const;
|
|
60
|
+
|
|
61
|
+
cases.forEach(([input, expected]) => {
|
|
62
|
+
expect(splitJsonPath(input as JsonPath)).toEqual(expected);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('invalid path formats', () => {
|
|
67
|
+
// These should return empty array or handle gracefully.
|
|
68
|
+
const invalidPaths = ['', '.', '[', ']', 'foo[].bar', 'foo[a].bar', 'foo[-1].bar'] as const;
|
|
69
|
+
|
|
70
|
+
invalidPaths.forEach((path) => {
|
|
71
|
+
expect(splitJsonPath(path as JsonPath)).toEqual([]);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('isJsonPath validation', () => {
|
|
76
|
+
// Valid paths.
|
|
77
|
+
expect(isJsonPath('')).toBe(true);
|
|
78
|
+
expect(isJsonPath('foo')).toBe(true);
|
|
79
|
+
expect(isJsonPath('foo.bar')).toBe(true);
|
|
80
|
+
expect(isJsonPath('foo[0].bar')).toBe(true);
|
|
81
|
+
expect(isJsonPath('items[1]')).toBe(true);
|
|
82
|
+
expect(isJsonPath('$foo.$bar')).toBe(true);
|
|
83
|
+
expect(isJsonPath('matrix[0][1][2]')).toBe(true);
|
|
84
|
+
expect(isJsonPath('deep.nested[0].path')).toBe(true);
|
|
85
|
+
|
|
86
|
+
// Invalid paths.
|
|
87
|
+
expect(isJsonPath('items[0]name')).toBe(false); // Missing dot
|
|
88
|
+
expect(isJsonPath('123foo')).toBe(false); // Starts with number
|
|
89
|
+
expect(isJsonPath('foo[].bar')).toBe(false); // Empty brackets
|
|
90
|
+
expect(isJsonPath('foo[-1]')).toBe(false); // Negative index
|
|
91
|
+
expect(isJsonPath('foo[a]')).toBe(false); // Non-numeric index
|
|
92
|
+
expect(isJsonPath('.foo')).toBe(false); // Starts with dot
|
|
93
|
+
expect(isJsonPath('foo.')).toBe(false); // Ends with dot
|
|
94
|
+
expect(isJsonPath('[0]foo')).toBe(false); // Starts with bracket
|
|
95
|
+
});
|
|
96
|
+
});
|
package/src/jsonPath.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Schema as S } from '@effect/schema';
|
|
6
|
+
import { isSome } from 'effect/Option';
|
|
7
|
+
|
|
8
|
+
import { invariant } from '@dxos/invariant';
|
|
9
|
+
|
|
10
|
+
export type JsonProp = string & { __JsonPath: true; __JsonProp: true };
|
|
11
|
+
export type JsonPath = string & { __JsonPath: true };
|
|
12
|
+
|
|
13
|
+
const PATH_REGEX = /^($|[a-zA-Z_$][\w$]*(?:\.[a-zA-Z_$][\w$]*|\[\d+\](?:\.)?)*$)/;
|
|
14
|
+
const PROP_REGEX = /\w+/;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* https://www.ietf.org/archive/id/draft-goessner-dispatch-jsonpath-00.html
|
|
18
|
+
*/
|
|
19
|
+
export const JsonPath = S.String.pipe(S.pattern(PATH_REGEX)) as any as S.Schema<JsonPath>;
|
|
20
|
+
export const JsonProp = S.NonEmptyString.pipe(S.pattern(PROP_REGEX)) as any as S.Schema<JsonProp>;
|
|
21
|
+
|
|
22
|
+
export const isJsonPath = (value: unknown): value is JsonPath => {
|
|
23
|
+
return isSome(S.validateOption(JsonPath)(value));
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a JsonPath from an array of path segments.
|
|
28
|
+
*
|
|
29
|
+
* Currently supports:
|
|
30
|
+
* - Simple property access (e.g., 'foo.bar')
|
|
31
|
+
* - Array indexing with non-negative integers (e.g., 'foo[0]')
|
|
32
|
+
* - Identifiers starting with letters, underscore, or $ (e.g., '$foo', '_bar')
|
|
33
|
+
* - Dot notation for nested properties (e.g., 'foo.bar.baz')
|
|
34
|
+
*
|
|
35
|
+
* Does not support (yet?).
|
|
36
|
+
* - Recursive descent (..)
|
|
37
|
+
* - Wildcards (*)
|
|
38
|
+
* - Array slicing
|
|
39
|
+
* - Filters
|
|
40
|
+
* - Negative indices
|
|
41
|
+
*
|
|
42
|
+
* @param path Array of string or number segments
|
|
43
|
+
* @returns Valid JsonPath or undefined if invalid
|
|
44
|
+
*/
|
|
45
|
+
export const createJsonPath = (path: (string | number)[]): JsonPath => {
|
|
46
|
+
const candidatePath = path
|
|
47
|
+
.map((p, i) => {
|
|
48
|
+
if (typeof p === 'number') {
|
|
49
|
+
return `[${p}]`;
|
|
50
|
+
} else {
|
|
51
|
+
return i === 0 ? p : `.${p}`;
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
.join('');
|
|
55
|
+
|
|
56
|
+
invariant(isJsonPath(candidatePath), `Invalid JsonPath: ${candidatePath}`);
|
|
57
|
+
return candidatePath;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Converts Effect validation path format (e.g. "addresses.[0].zip")
|
|
62
|
+
* to JsonPath format (e.g. "addresses[0].zip")
|
|
63
|
+
*/
|
|
64
|
+
export const fromEffectValidationPath = (effectPath: string): JsonPath => {
|
|
65
|
+
// Handle array notation: convert "prop.[0]" to "prop[0]"
|
|
66
|
+
const jsonPath = effectPath.replace(/\.\[(\d+)\]/g, '[$1]');
|
|
67
|
+
invariant(isJsonPath(jsonPath), `Invalid JsonPath: ${jsonPath}`);
|
|
68
|
+
return jsonPath;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Splits a JsonPath into its constituent parts.
|
|
73
|
+
* Handles property access and array indexing.
|
|
74
|
+
*/
|
|
75
|
+
export const splitJsonPath = (path: JsonPath): string[] => {
|
|
76
|
+
if (!isJsonPath(path)) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
path
|
|
82
|
+
.match(/[a-zA-Z_$][\w$]*|\[\d+\]/g)
|
|
83
|
+
?.map((part) => (part.startsWith('[') ? part.replace(/[[\]]/g, '') : part)) ?? []
|
|
84
|
+
);
|
|
85
|
+
};
|