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

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.
@@ -1 +1 @@
1
- {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../../../src/ast.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ast.d.ts","sourceRoot":"","sources":["../../../src/ast.ts"],"names":[],"mappings":"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"}
@@ -2,5 +2,6 @@ import { AST, JSONSchema, Schema as S } from '@effect/schema';
2
2
  import type * as Types from 'effect/Types';
3
3
  export { AST, JSONSchema, S, Types };
4
4
  export * from './ast';
5
+ export * from './jsonPath';
5
6
  export * from './url';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAC;AAG3C,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC;AAErC,cAAc,OAAO,CAAC;AACtB,cAAc,OAAO,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAC;AAG3C,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC;AAErC,cAAc,OAAO,CAAC;AACtB,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC"}
@@ -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,11 +1,12 @@
1
1
  {
2
2
  "name": "@dxos/effect",
3
- "version": "0.7.5-main.9d2a38b",
3
+ "version": "0.7.5-main.b19bfc8",
4
4
  "description": "Effect utils.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
7
  "license": "MIT",
8
8
  "author": "info@dxos.org",
9
+ "type": "module",
9
10
  "exports": {
10
11
  ".": {
11
12
  "types": "./dist/types/src/index.d.ts",
@@ -22,18 +23,18 @@
22
23
  "src"
23
24
  ],
24
25
  "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"
26
+ "@dxos/invariant": "0.7.5-main.b19bfc8",
27
+ "@dxos/node-std": "0.7.5-main.b19bfc8",
28
+ "@dxos/util": "0.7.5-main.b19bfc8"
28
29
  },
29
30
  "devDependencies": {
30
31
  "@effect/schema": "^0.75.5",
31
- "effect": "^3.12.1",
32
- "@dxos/log": "0.7.5-main.9d2a38b"
32
+ "effect": "^3.12.3",
33
+ "@dxos/log": "0.7.5-main.b19bfc8"
33
34
  },
34
35
  "peerDependencies": {
35
36
  "@effect/schema": "^0.75.5",
36
- "effect": "^3.9.2"
37
+ "effect": "^3.12.3"
37
38
  },
38
39
  "publishConfig": {
39
40
  "access": "public"
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
@@ -6,7 +6,9 @@ import { AST, Schema as S } from '@effect/schema';
6
6
  import { Option, pipe } from 'effect';
7
7
 
8
8
  import { invariant } from '@dxos/invariant';
9
- import { nonNullable } from '@dxos/util';
9
+ import { isNonNullable } from '@dxos/util';
10
+
11
+ import { type JsonPath, type JsonProp } from './jsonPath';
10
12
 
11
13
  //
12
14
  // Refs
@@ -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
  /**
@@ -397,11 +387,11 @@ export const getDiscriminatedType = (node: AST.AST, value: Record<string, any> =
397
387
  invariant(AST.isLiteral(literal.type));
398
388
  return literal.type.literal;
399
389
  })
400
- .filter(nonNullable);
390
+ .filter(isNonNullable);
401
391
 
402
392
  return literals.length ? [prop, S.Literal(...literals)] : undefined;
403
393
  })
404
- .filter(nonNullable),
394
+ .filter(isNonNullable),
405
395
  );
406
396
 
407
397
  const schema = S.Struct(fields);
@@ -413,13 +403,19 @@ export const getDiscriminatedType = (node: AST.AST, value: Record<string, any> =
413
403
  * The user is responsible for recursively calling {@link mapAst} on the AST.
414
404
  * NOTE: Will evaluate suspended ASTs.
415
405
  */
416
- export const mapAst = (ast: AST.AST, f: (ast: AST.AST) => AST.AST): AST.AST => {
406
+ export const mapAst = (ast: AST.AST, f: (ast: AST.AST, key: keyof any | undefined) => AST.AST): AST.AST => {
417
407
  switch (ast._tag) {
418
408
  case 'TypeLiteral':
419
409
  return new AST.TypeLiteral(
420
410
  ast.propertySignatures.map(
421
411
  (prop) =>
422
- new AST.PropertySignature(prop.name, f(prop.type), prop.isOptional, prop.isReadonly, prop.annotations),
412
+ new AST.PropertySignature(
413
+ prop.name,
414
+ f(prop.type, prop.name),
415
+ prop.isOptional,
416
+ prop.isReadonly,
417
+ prop.annotations,
418
+ ),
423
419
  ),
424
420
  ast.indexSignatures,
425
421
  );
@@ -427,13 +423,13 @@ export const mapAst = (ast: AST.AST, f: (ast: AST.AST) => AST.AST): AST.AST => {
427
423
  return AST.Union.make(ast.types.map(f), ast.annotations);
428
424
  case 'TupleType':
429
425
  return new AST.TupleType(
430
- ast.elements.map((t) => new AST.OptionalType(f(t.type), t.isOptional, t.annotations)),
431
- ast.rest.map((t) => new AST.Type(f(t.type), t.annotations)),
426
+ ast.elements.map((t, index) => new AST.OptionalType(f(t.type, index), t.isOptional, t.annotations)),
427
+ ast.rest.map((t) => new AST.Type(f(t.type, undefined), t.annotations)),
432
428
  ast.isReadonly,
433
429
  ast.annotations,
434
430
  );
435
431
  case 'Suspend': {
436
- const newAst = f(ast.f());
432
+ const newAst = f(ast.f(), undefined);
437
433
  return new AST.Suspend(() => newAst, ast.annotations);
438
434
  }
439
435
  default:
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
+ };