@dxos/effect 0.8.4-main.bc674ce → 0.8.4-main.bd9b33e6c8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/effect",
3
- "version": "0.8.4-main.bc674ce",
3
+ "version": "0.8.4-main.bd9b33e6c8",
4
4
  "description": "Effect utils.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -38,28 +38,31 @@
38
38
  "src"
39
39
  ],
40
40
  "dependencies": {
41
- "@effect-atom/atom-react": "^0.4.6",
42
- "@effect/opentelemetry": "^0.59.2",
43
- "@effect/platform-browser": "0.73.0",
41
+ "@effect-atom/atom-react": "^0.5.0",
42
+ "@effect/opentelemetry": "^0.61.0",
44
43
  "@opentelemetry/api": "^1.9.0",
45
- "jsonpath-plus": "10.2.0",
46
- "@dxos/context": "0.8.4-main.bc674ce",
47
- "@dxos/invariant": "0.8.4-main.bc674ce",
48
- "@dxos/node-std": "0.8.4-main.bc674ce",
49
- "@dxos/util": "0.8.4-main.bc674ce"
44
+ "jsonpath-plus": "^10.3.0",
45
+ "@dxos/context": "0.8.4-main.bd9b33e6c8",
46
+ "@dxos/invariant": "0.8.4-main.bd9b33e6c8",
47
+ "@dxos/util": "0.8.4-main.bd9b33e6c8",
48
+ "@dxos/node-std": "0.8.4-main.bd9b33e6c8"
50
49
  },
51
50
  "devDependencies": {
51
+ "@effect/platform": "0.94.4",
52
+ "@effect/platform-browser": "0.74.0",
52
53
  "@opentelemetry/api-logs": "^0.203.0",
53
54
  "@opentelemetry/resources": "^2.1.0",
54
55
  "@opentelemetry/sdk-logs": "^0.203.0",
55
56
  "@opentelemetry/sdk-node": "^0.203.0",
56
57
  "@opentelemetry/sdk-trace-node": "^2.1.0",
57
58
  "@opentelemetry/semantic-conventions": "^1.37.0",
58
- "effect": "3.19.11",
59
- "@dxos/log": "0.8.4-main.bc674ce"
59
+ "effect": "3.20.0",
60
+ "@dxos/log": "0.8.4-main.bd9b33e6c8"
60
61
  },
61
62
  "peerDependencies": {
62
- "effect": "3.19.11"
63
+ "@effect/platform": "0.94.4",
64
+ "@effect/platform-browser": "0.74.0",
65
+ "effect": "3.20.0"
63
66
  },
64
67
  "publishConfig": {
65
68
  "access": "public"
@@ -0,0 +1,45 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import * as Effect from 'effect/Effect';
6
+ import type * as Exit from 'effect/Exit';
7
+
8
+ export interface AddTrackEntryOptions {
9
+ name: string;
10
+ devtools?: {
11
+ /**
12
+ * @example 'track-entry'
13
+ */
14
+ dataType: string;
15
+ track: string;
16
+ trackGroup: string;
17
+ /**
18
+ * @example 'tertiary-dark'
19
+ */
20
+ color: string;
21
+ properties?: [string, any][];
22
+ tooltipText?: string;
23
+ };
24
+ detail?: Record<string, unknown>;
25
+ }
26
+
27
+ /**
28
+ * Puts the effect span on the performance timeline in DevTools.
29
+ */
30
+ export const addTrackEntry =
31
+ <A, E>(options: AddTrackEntryOptions | ((exit: Exit.Exit<A, E>) => AddTrackEntryOptions)) =>
32
+ <R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
33
+ Effect.gen(function* () {
34
+ const start = performance.now();
35
+ const exit = yield* Effect.exit(effect);
36
+ const resolvedOptions = typeof options === 'function' ? options(exit) : options;
37
+ performance.measure(resolvedOptions.name, {
38
+ start: start,
39
+ detail: {
40
+ ...resolvedOptions.detail,
41
+ devtools: resolvedOptions.devtools,
42
+ },
43
+ });
44
+ return yield* exit;
45
+ });
package/src/ast.test.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  getAnnotation,
16
16
  getDiscriminatedType,
17
17
  getDiscriminatingProps,
18
+ getProperties,
18
19
  isArrayType,
19
20
  isDiscriminatedUnion,
20
21
  isOption,
@@ -90,6 +91,30 @@ describe('AST', () => {
90
91
  }
91
92
  });
92
93
 
94
+ test('getProperties preserves annotation on property type after refinements', ({ expect }) => {
95
+ // When a property is e.g. Format.Text.pipe(nonEmptyString(), maxLength(), Schema.annotations({ title, description })),
96
+ // the form uses getProperties(schema.ast) and then Format.FormatAnnotation.getFromAst(property.type).
97
+ // Custom title and description from the outer Schema.annotations() must not be lost.
98
+ const WithRefinements = Schema.Struct({
99
+ message: Schema.String.annotations({ title: 'Feedback' }).pipe(
100
+ Schema.minLength(1),
101
+ Schema.maxLength(4096),
102
+ Schema.annotations({
103
+ title: 'Feedback label',
104
+ description: 'Feedback placeholder',
105
+ }),
106
+ ),
107
+ });
108
+ const properties = getProperties(WithRefinements.ast);
109
+ const messageProp = properties.find((p) => p.name === 'message');
110
+ invariant(messageProp);
111
+ const title = findAnnotation(messageProp.type, SchemaAST.TitleAnnotationId);
112
+ const description = findAnnotation(messageProp.type, SchemaAST.DescriptionAnnotationId);
113
+ // Outer Schema.annotations() wins so form labels/placeholders are preserved.
114
+ expect(title).to.eq('Feedback label');
115
+ expect(description).to.eq('Feedback placeholder');
116
+ });
117
+
93
118
  test('findAnnotation', ({ expect }) => {
94
119
  const TestSchema = Schema.NonEmptyString.pipe(Schema.pattern(/^\d{5}$/)).annotations({
95
120
  title: 'original title',
package/src/ast.ts CHANGED
@@ -27,9 +27,11 @@ const reduceRefinements = (
27
27
  refinements: SchemaAST.Refinement['filter'][] = [],
28
28
  ): { type: SchemaAST.AST; refinements: SchemaAST.Refinement['filter'][] } => {
29
29
  if (SchemaAST.isRefinement(type)) {
30
- const annotations = type.annotations;
31
30
  const filter = type.filter;
32
- const nextType = { ...type.from, annotations: { ...type.annotations, ...annotations } } as SchemaAST.AST;
31
+ const nextType = {
32
+ ...type.from,
33
+ annotations: { ...type.from.annotations, ...type.annotations },
34
+ } as SchemaAST.AST;
33
35
  return reduceRefinements(nextType, [...refinements, filter]);
34
36
  }
35
37
 
@@ -252,10 +254,10 @@ export const findProperty = (
252
254
  //
253
255
 
254
256
  const defaultAnnotations: Record<string, SchemaAST.Annotated> = {
255
- ['ObjectKeyword' as const]: SchemaAST.objectKeyword,
256
- ['StringKeyword' as const]: SchemaAST.stringKeyword,
257
- ['NumberKeyword' as const]: SchemaAST.numberKeyword,
258
- ['BooleanKeyword' as const]: SchemaAST.booleanKeyword,
257
+ ObjectKeyword: SchemaAST.objectKeyword,
258
+ StringKeyword: SchemaAST.stringKeyword,
259
+ NumberKeyword: SchemaAST.numberKeyword,
260
+ BooleanKeyword: SchemaAST.booleanKeyword,
259
261
  };
260
262
 
261
263
  /**
package/src/errors.ts CHANGED
@@ -12,7 +12,6 @@ import * as Runtime from 'effect/Runtime';
12
12
  import type * as Tracer from 'effect/Tracer';
13
13
 
14
14
  const spanSymbol = Symbol.for('effect/SpanAnnotation');
15
- const originalSymbol = Symbol.for('effect/OriginalAnnotation');
16
15
  const spanToTrace = GlobalValue.globalValue('effect/Tracer/spanToTrace', () => new WeakMap());
17
16
  const locationRegex = /\((.*)\)/g;
18
17
 
@@ -31,7 +30,10 @@ const prettyErrorStack = (error: any, appendStacks: string[] = []): any => {
31
30
  const lines = typeof error.stack === 'string' ? error.stack.split('\n') : [];
32
31
  const out = [];
33
32
 
34
- let atStack = false;
33
+ // Very hacky way to remove effect runtime internal stack frames.
34
+ let atStack = false,
35
+ inCore = false,
36
+ passedScheduler = false;
35
37
  for (let i = 0; i < lines.length; i++) {
36
38
  if (!atStack && !lines[i].startsWith(' at ')) {
37
39
  out.push(lines[i]);
@@ -49,6 +51,26 @@ const prettyErrorStack = (error: any, appendStacks: string[] = []): any => {
49
51
  if (lines[i].includes('effect_internal_function')) {
50
52
  break;
51
53
  }
54
+
55
+ const filename = lines[i].match(/\/([a-zA-Z0-9_\-.]+):\d+:\d+\)$/)?.[1];
56
+
57
+ if (!inCore && ['core-effect.ts'].includes(filename)) {
58
+ inCore = true;
59
+ }
60
+
61
+ if (inCore && !passedScheduler && ['Scheduler.ts'].includes(filename)) {
62
+ passedScheduler = true;
63
+ continue;
64
+ }
65
+
66
+ if (passedScheduler && !['Scheduler.ts'].includes(filename)) {
67
+ inCore = false;
68
+ }
69
+
70
+ if (inCore) {
71
+ continue;
72
+ }
73
+
52
74
  out.push(
53
75
  lines[i]
54
76
  .replace(/at .*effect_instruction_i.*\((.*)\)/, 'at $1')
@@ -87,9 +109,7 @@ const prettyErrorStack = (error: any, appendStacks: string[] = []): any => {
87
109
 
88
110
  out.push(...appendStacks);
89
111
 
90
- if (error[originalSymbol]) {
91
- error = error[originalSymbol];
92
- }
112
+ error = Cause.originalError(error);
93
113
  if (error.cause) {
94
114
  error.cause = prettyErrorStack(error.cause);
95
115
  }
@@ -125,7 +145,7 @@ export const causeToError = (cause: Cause.Cause<any>): Error => {
125
145
  const getStackFrames = (): string[] => {
126
146
  // Bun requies the target object for `captureStackTrace` to be an Error.
127
147
  const o = new Error();
128
- Error.captureStackTrace(o, getStackFrames);
148
+ Error.captureStackTrace(o, causeToError);
129
149
  return o.stack!.split('\n').slice(1);
130
150
  };
131
151
 
@@ -180,13 +200,39 @@ export const runAndForwardErrors = async <A, E>(
180
200
  return unwrapExit(exit);
181
201
  };
182
202
 
183
- export const runInRuntime = async <A, E, R>(
184
- runtime: Runtime.Runtime<R>,
185
- effect: Effect.Effect<A, E, R>,
186
- options?: { signal?: AbortSignal },
187
- ): Promise<A> => {
188
- const exit = await Runtime.runPromiseExit(runtime, effect, options);
189
- return unwrapExit(exit);
203
+ /**
204
+ * Runs the embedded effect asynchronously and throws any failures and defects as errors.
205
+ */
206
+ export const runInRuntime: {
207
+ <R>(
208
+ runtime: Runtime.Runtime<R>,
209
+ ): <A, E>(effect: Effect.Effect<A, E, R>, options?: { signal?: AbortSignal } | undefined) => Promise<A>;
210
+ <R, A, E>(
211
+ runtime: Runtime.Runtime<R>,
212
+ effect: Effect.Effect<A, E, R>,
213
+ options?: { signal?: AbortSignal } | undefined,
214
+ ): Promise<A>;
215
+ } = (...args: any[]): any => {
216
+ if (args.length === 1) {
217
+ const [runtime] = args as [Runtime.Runtime<any>];
218
+ return async (
219
+ effect: Effect.Effect<any, any, any>,
220
+ options?: { signal?: AbortSignal } | undefined,
221
+ ): Promise<any> => {
222
+ const exit = await Runtime.runPromiseExit(runtime, effect, options);
223
+ return unwrapExit(exit);
224
+ };
225
+ } else {
226
+ const [runtime, effect, options] = args as [
227
+ Runtime.Runtime<any>,
228
+ Effect.Effect<any, any, any>,
229
+ { signal?: AbortSignal } | undefined,
230
+ ];
231
+ return (async () => {
232
+ const exit = await Runtime.runPromiseExit(runtime, effect, options);
233
+ return unwrapExit(exit);
234
+ })();
235
+ }
190
236
  };
191
237
 
192
238
  /**
package/src/index.ts CHANGED
@@ -11,3 +11,4 @@ export * from './json-path';
11
11
  export * from './resource';
12
12
  export * from './url';
13
13
  export * as RuntimeProvider from './RuntimeProvider';
14
+ export * as Performance from './Performance';
package/src/testing.ts CHANGED
@@ -63,6 +63,8 @@ export namespace TestHelpers {
63
63
  }
64
64
  });
65
65
 
66
+ export const tagEnabled = (tag: TestTag) => process.env.DX_TEST_TAGS?.includes(tag);
67
+
66
68
  /**
67
69
  * Skips this test if the tag is not in the list of tags to run.
68
70
  * Tags are specified in the `DX_TEST_TAGS` environment variable.
@@ -74,7 +76,7 @@ export namespace TestHelpers {
74
76
  (tag: TestTag) =>
75
77
  <A, E, R>(effect: Effect.Effect<A, E, R>, ctx: TestContext): Effect.Effect<A, E, R> =>
76
78
  Effect.gen(function* () {
77
- if (!process.env.DX_TEST_TAGS?.includes(tag)) {
79
+ if (!tagEnabled(tag)) {
78
80
  ctx.skip();
79
81
  } else {
80
82
  return yield* effect;