@dxos/effect 0.7.5-main.9d2a38b → 0.7.5-main.ff8607b

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.
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=jsonPath.test.d.ts.map
@@ -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.9d2a38b",
3
+ "version": "0.7.5-main.ff8607b",
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.9d2a38b",
26
- "@dxos/node-std": "0.7.5-main.9d2a38b",
27
- "@dxos/util": "0.7.5-main.9d2a38b"
25
+ "@dxos/invariant": "0.7.5-main.ff8607b",
26
+ "@dxos/util": "0.7.5-main.ff8607b",
27
+ "@dxos/node-std": "0.7.5-main.ff8607b"
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.9d2a38b"
32
+ "@dxos/log": "0.7.5-main.ff8607b"
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
@@ -9,4 +9,5 @@ import type * as Types from 'effect/Types';
9
9
  export { AST, JSONSchema, S, Types };
10
10
 
11
11
  export * from './ast';
12
+ export * from './jsonPath';
12
13
  export * from './url';
@@ -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
+ });
@@ -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
+ };