@dxos/async 0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264 → 0.13.0

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.
Files changed (59) hide show
  1. package/dist/lib/chunk-events.mjs.map +1 -1
  2. package/dist/lib/index.mjs +66 -14
  3. package/dist/lib/index.mjs.map +1 -1
  4. package/dist/lib/testing.mjs +27 -13
  5. package/dist/lib/testing.mjs.map +1 -1
  6. package/dist/types/src/errors.d.ts +1 -1
  7. package/dist/types/src/errors.d.ts.map +1 -1
  8. package/dist/types/src/events.d.ts +1 -1
  9. package/dist/types/src/events.d.ts.map +1 -1
  10. package/dist/types/src/index.d.ts +22 -21
  11. package/dist/types/src/index.d.ts.map +1 -1
  12. package/dist/types/src/observable-value.d.ts +1 -1
  13. package/dist/types/src/observable-value.d.ts.map +1 -1
  14. package/dist/types/src/observable.d.ts +1 -1
  15. package/dist/types/src/observable.d.ts.map +1 -1
  16. package/dist/types/src/stream.d.ts.map +1 -1
  17. package/dist/types/src/test-stream.d.ts +9 -9
  18. package/dist/types/src/test-stream.d.ts.map +1 -1
  19. package/dist/types/src/timer.d.ts +1 -1
  20. package/dist/types/src/timer.d.ts.map +1 -1
  21. package/dist/types/src/update-scheduler.d.ts.map +1 -1
  22. package/dist/types/src/yield.d.ts +19 -0
  23. package/dist/types/src/yield.d.ts.map +1 -0
  24. package/dist/types/src/yield.test.d.ts +2 -0
  25. package/dist/types/src/yield.test.d.ts.map +1 -0
  26. package/dist/types/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +9 -8
  28. package/src/debounce.test.ts +1 -1
  29. package/src/errors.ts +1 -1
  30. package/src/event-emitter.test.ts +3 -3
  31. package/src/event-emitter.ts +1 -1
  32. package/src/events.test.ts +2 -2
  33. package/src/events.ts +1 -1
  34. package/src/index.ts +22 -21
  35. package/src/mutex.test.ts +2 -2
  36. package/src/observable-value.test.ts +3 -3
  37. package/src/observable-value.ts +1 -1
  38. package/src/observable.test.ts +3 -3
  39. package/src/observable.ts +2 -2
  40. package/src/persistent-lifecycle.test.ts +3 -3
  41. package/src/persistent-lifecycle.ts +3 -3
  42. package/src/push-iterable.ts +1 -1
  43. package/src/stream.test.ts +1 -1
  44. package/src/stream.ts +12 -9
  45. package/src/task-scheduling.test.ts +2 -2
  46. package/src/task-scheduling.ts +2 -2
  47. package/src/test-stream.test.ts +3 -2
  48. package/src/test-stream.ts +39 -18
  49. package/src/testing.test.ts +2 -2
  50. package/src/testing.ts +2 -2
  51. package/src/timeout.test.ts +1 -1
  52. package/src/timeout.ts +1 -1
  53. package/src/timer.ts +1 -1
  54. package/src/trigger.test.ts +2 -2
  55. package/src/trigger.ts +2 -2
  56. package/src/update-scheduler.test.ts +2 -2
  57. package/src/update-scheduler.ts +5 -3
  58. package/src/yield.test.ts +63 -0
  59. package/src/yield.ts +75 -0
@@ -1 +1 @@
1
- {"version":3,"file":"chunk-events.mjs","names":[],"sources":["../../src/errors.ts","../../src/timeout.ts","../../src/events.ts"],"sourcesContent":["//\n// Copyright 2020 DXOS.org\n//\n\nimport { type ObservableProvider } from './observable-value';\n\n// TODO(burdon): Move to debug.\n// Use: https://nodejs.org/api/errors.html\nexport const toError = (err: any) => (err === undefined || typeof err === 'string' ? new Error(err) : err);\n\nexport class TimeoutError extends Error {\n constructor(timeout?: number, label?: string) {\n super(timeout ? `Timeout [${timeout.toLocaleString()}ms]${label === undefined ? '' : `: ${label}`}` : 'Timeout');\n }\n}\n\nexport interface AsyncEvents<T = any> {\n onSuccess?(result: T): void;\n onTimeout?(err: TimeoutError): void;\n onError(err: any): void;\n}\n\nexport const observableError = (observable: ObservableProvider<AsyncEvents>, err: any) => {\n if (err instanceof TimeoutError) {\n observable.callback.onTimeout?.(err);\n } else {\n observable.callback.onError(toError(err));\n }\n};\n","//\n// Copyright 2020 DXOS.org\n//\n\nimport { type Context, ContextDisposedError } from '@dxos/context';\n\nimport { TimeoutError } from './errors';\n\n/**\n * Times out after delay.\n */\nexport const sleep = (ms: number) => {\n return new Promise<void>((resolve) => {\n const finish = Date.now() + ms;\n\n // `setTimeout` does not guarantee execution at >= the scheduled time and may execute slightly early.\n const sleeper = () => {\n const delta = finish - Date.now();\n if (delta > 0) {\n setTimeout(sleeper, delta);\n } else {\n resolve();\n }\n };\n\n sleeper();\n });\n};\n\n// TODO(burdon): Reconcile with sleep.\nexport const sleepWithContext = (ctx: Context, ms: number) => {\n const error = new ContextDisposedError();\n return new Promise<void>((resolve, reject) => {\n if (ctx.disposed) {\n reject(error);\n return;\n }\n\n const timeout = setTimeout(() => {\n clearDispose();\n resolve();\n }, ms);\n\n const clearDispose = ctx.onDispose(() => {\n clearTimeout(timeout);\n reject(error);\n });\n });\n};\n\n/**\n * Can be used in long-running tasks to let other callbacks be invoked.\n */\nexport const asyncReturn = () => sleep(0);\n\n/**\n * Wait for promise or throw error.\n */\nexport const asyncTimeout = async <T>(promise: Promise<T>, timeout: number, err?: Error | string): Promise<T> => {\n if (typeof promise === 'function') {\n throw new Error('First argument must be a promise.');\n }\n\n let timeoutId: NodeJS.Timeout;\n const throwable = err === undefined || typeof err === 'string' ? new TimeoutError(timeout, err) : err;\n const timeoutPromise = new Promise<T>((resolve, reject) => {\n timeoutId = setTimeout(() => {\n reject(throwable);\n }, timeout);\n\n unrefTimeout(timeoutId);\n });\n\n return await Promise.race([promise, timeoutPromise]).finally(() => {\n clearTimeout(timeoutId);\n });\n};\n\n/**\n * In Node.JS, `unref` prevents the timeout from blocking the process from exiting. Not available in browsers.\n * https://nodejs.org/api/timers.html#timeoutunref\n */\nexport const unrefTimeout = (timeoutId: NodeJS.Timeout) => {\n if (typeof timeoutId === 'object' && 'unref' in timeoutId) {\n timeoutId.unref();\n }\n};\n","//\n// Copyright 2020 DXOS.org\n//\n\nimport { Context } from '@dxos/context';\nimport { type MaybePromise } from '@dxos/util';\n\nimport { type CleanupFn } from './cleanup';\n\nexport type Effect = () => CleanupFn | undefined;\n\n/**\n * Effect that's been added to a specific Event.\n */\ninterface MaterializedEffect {\n effect: Effect;\n cleanup: CleanupFn | undefined;\n}\n\ninterface EventEmitterLike {\n on(event: string, cb: (data?: any) => void): void;\n off(event: string, cb: (data?: any) => void): void;\n}\n\nexport type ListenerOptions = {\n weak?: boolean;\n once?: boolean;\n};\n\ntype EventCallback<T> = (data: T) => MaybePromise<void>;\n\n// TODO(dmaretskyi): Remove this once the code is cleaned up.\nconst DO_NOT_ERROR_ON_ASYNC_CALLBACK = true;\n\n/**\n * An EventEmitter variant that does not do event multiplexing and represents a single event.\n *\n * ## Typical usage:\n * ```javascript\n * class Model {\n * public readonly update = new Event<UpdateData>();\n * private readonly privateEvent = new Event<void>();\n *\n * onUpdate(data: UpdateData) {\n * ...\n * this.update.emit(data);\n * }\n * }\n *\n * model.update.on(data => {\n * ...\n * });\n * ```\n *\n * ## Advantages over traditional EventEmitter:\n * 1. User describes explicitly what events a class has as they are defined as class fields.\n * 2. User can explicitly define event visibility (public, protected, private).\n * 3. Having events as class fields allows the compiler to check for correct event usage.\n * 4. User can type the data that event will be emitting.\n * 5. Uses composition instead of inheritance.\n * 6. Removes the cases where event names intersect when used in cases with inheritance.\n * 7. Remove the need to namespace events when developing a class with events that will be used as a base-class.\n */\n// TODO(burdon): Rename EventSink? (Event is a built-in type).\nexport class Event<T = void> implements ReadOnlyEvent<T> {\n /**\n * Wrap objects that have on/off style event emitters.\n */\n static wrap<T>(emitter: EventEmitterLike, eventName: string): Event<T> {\n const event = new Event<T>();\n\n event.addEffect(() => {\n const onEvent = (data: any) => event.emit(data);\n emitter.on(eventName, onEvent);\n return () => emitter.off(eventName, onEvent);\n });\n\n return event;\n }\n\n private readonly _listeners = new Set<EventListener<T>>();\n private readonly _effects = new Set<MaterializedEffect>();\n\n /**\n * Emit an event.\n * In most cases should only be called by the class or entity containing the event.\n * All listeners are called in order of subscription with persistent ones first.\n * Listeners are called synchronously in the same stack.\n * A thrown exception in the listener will stop the event from being emitted to the rest of the listeners.\n *\n * @param data param that will be passed to all listeners.\n */\n emit(data: T): void {\n for (const listener of this._listeners) {\n listener.trigger(data);\n\n if (listener.once) {\n this._listeners.delete(listener);\n }\n }\n }\n\n /**\n * Emit an event and wait for async listeners to complete.\n * In most cases should only be called by the class or entity containing the event.\n * All listeners are called in order of subscription with persistent ones first.\n * Listeners are called sequentially.\n *\n * @param data param that will be passed to all listeners.\n */\n async emitAsync(data: T): Promise<void> {\n for (const listener of this._listeners) {\n await listener.triggerAsync(data);\n\n if (listener.once) {\n this._listeners.delete(listener);\n }\n }\n }\n\n /**\n * Register an event listener.\n * If provided callback was already registered as once-listener, it is made permanent.\n *\n * @param callback\n * @param options.weak If true, the callback will be weakly referenced and will be garbage collected if no other references to it exist.\n * @returns function that unsubscribes this event listener\n */\n on(callback: EventCallback<T>): CleanupFn;\n on(ctx: Context, callback: EventCallback<T>, options?: ListenerOptions): CleanupFn;\n on(_ctx: any, _callback?: EventCallback<T>, options?: ListenerOptions): CleanupFn {\n const [ctx, callback] = _ctx instanceof Context ? [_ctx, _callback] : [new Context(), _ctx];\n const weak = !!options?.weak;\n const once = !!options?.once;\n\n const listener = new EventListener(this, callback, ctx, once, weak);\n\n this._addListener(listener);\n\n return () => {\n this._removeListener(listener);\n };\n }\n\n /**\n * Unsubscribe this callback from new events. Includes persistent and once-listeners.\n * NOTE: It is recommended to use `Event.on`'s return value instead.\n * If the callback is not subscribed this is no-op.\n *\n * @param callback\n */\n off(callback: (data: T) => void): void {\n for (const listener of this._listeners) {\n if (listener.derefCallback() === callback) {\n this._removeListener(listener);\n }\n }\n }\n\n /**\n * Register a callback to be called only once when the next event is emitted.\n * If this callback is already registered as permanent listener, this is no-op.\n *\n * @param callback\n */\n once(callback: (data: T) => void): CleanupFn;\n once(ctx: Context, callback: (data: T) => void): CleanupFn;\n once(_ctx: any, _callback?: (data: T) => void): CleanupFn {\n const [ctx, callback] = _ctx instanceof Context ? [_ctx, _callback] : [new Context(), _ctx];\n\n const listener = new EventListener(this, callback, ctx, true, false);\n\n this._addListener(listener);\n\n return () => {\n this._removeListener(listener);\n };\n }\n\n /**\n * An async iterator that iterates over events.\n * This iterator runs indefinitely.\n */\n async *[Symbol.asyncIterator](): AsyncIterator<T> {\n while (true) {\n yield await new Promise((resolve) => {\n this.once(resolve);\n });\n }\n }\n\n /**\n * Returns a promise that resolves with the first event emitted that matches the provided predicate.\n *\n * @param predicate\n */\n waitFor(predicate: (data: T) => boolean): Promise<T> {\n return new Promise((resolve) => {\n const unsubscribe = this.on((data) => {\n if (predicate(data)) {\n unsubscribe();\n resolve(data);\n }\n });\n });\n }\n\n /**\n * Returns a promise that resolves once a specific number of events was emitted since this method was called.\n *\n * @param expectedCount\n */\n waitForCount(expectedCount: number): Promise<T> {\n let count = 0;\n return this.waitFor(() => ++count === expectedCount);\n }\n\n /**\n * Similar to waitFor, but the promise resolves immediately if the condition is already true.\n */\n // TODO(burdon): Should pass event property to predicate.\n async waitForCondition(predicate: () => boolean): Promise<void> {\n if (!predicate()) {\n await this.waitFor(predicate);\n }\n }\n\n /**\n * Returns the number of persistent listeners.\n */\n listenerCount(): number {\n return this._listeners.size;\n }\n\n /**\n * Add a side effect that will be activated once the event has at least one subscriber.\n * The provided callback can return a function that will be used to clean up after the last subscriber unsubscribes from the event.\n * The API is similar to `useEffect` from React.\n *\n * ## Example:\n * ```typescript\n * event.addEffect(() => {\n * // do stuff\n * return () => {\n * // clean-up\n * };\n * });\n * ```\n *\n * @returns Callback that will remove this effect once called.\n */\n addEffect(effect: Effect): CleanupFn {\n const handle: MaterializedEffect = { effect, cleanup: undefined };\n\n if (this.listenerCount() > 0) {\n handle.cleanup = handle.effect();\n }\n\n this._effects.add(handle);\n return () => {\n // eslint-disable-next-line no-unused-expressions\n handle.cleanup?.();\n this._effects.delete(handle);\n };\n }\n\n /**\n * Triggers an event with at least `timeout` milliseconds between each event.\n * If the event is triggered more often, the event is delayed until the timeout is reached.\n * If event is emitted for the first time or event wasn't fired for `timeout` milliseconds,\n * the event is emitted after `timeout / 8` ms.\n */\n // TODO(burdon): Factor out generic function.\n debounce(timeout = 0): Event<void> {\n let firing: NodeJS.Timeout | undefined;\n let lastFired: number | undefined;\n\n const debouncedEvent = new Event<void>();\n debouncedEvent.addEffect(() => {\n const unsubscribe = this.on(() => {\n if (!firing) {\n const fireIn = !lastFired || Date.now() - lastFired > timeout ? timeout / 8 : timeout;\n firing = setTimeout(() => {\n lastFired = Date.now();\n firing = undefined;\n debouncedEvent.emit();\n }, fireIn);\n }\n });\n\n return () => {\n unsubscribe();\n clearTimeout(firing);\n };\n });\n\n return debouncedEvent;\n }\n\n /**\n * Turn any variant of `Event<T>` into an `Event<void>` discarding the callback parameter.\n */\n discardParameter(): Event<void> {\n return this as any;\n }\n\n /**\n * Pipe the events into another event.\n * @param event\n */\n pipeInto(event: Event<T>): CleanupFn {\n return this.on((data) => event.emit(data));\n }\n\n /**\n * Overridden to not return implementation details.\n */\n toJSON(): { listenerCount: number } {\n return {\n listenerCount: this.listenerCount(),\n };\n }\n\n private _addListener(listener: EventListener<T>): void {\n this._listeners.add(listener);\n\n if (this.listenerCount() === 1) {\n this._runEffects();\n }\n }\n\n /**\n * @internal\n */\n _removeListener(listener: EventListener<T>): void {\n this._listeners.delete(listener);\n listener.remove();\n\n if (this.listenerCount() === 0) {\n this._cleanupEffects();\n }\n }\n\n private _runEffects(): void {\n for (const handle of this._effects) {\n handle.cleanup = handle.effect();\n }\n }\n\n private _cleanupEffects(): void {\n for (const handle of this._effects) {\n // eslint-disable-next-line no-unused-expressions\n handle.cleanup?.();\n handle.cleanup = undefined;\n }\n }\n}\n\n/**\n * A version of Event class which only has subscribe methods.\n * Useful in cases where you want to explicitly prohibit calling `emit` method.\n */\nexport interface ReadOnlyEvent<T = void> {\n /**\n * Register an event listener.\n * If provided callback was already registered as once-listener, it is made permanent.\n *\n * @param callback\n * @param options.weak If true, the callback will be weakly referenced and will be garbage collected if no other references to it exist.\n * @returns function that unsubscribes this event listener\n */\n on(callback: (data: T) => void): CleanupFn;\n on(ctx: Context, callback: (data: T) => void, options?: ListenerOptions): CleanupFn;\n\n /**\n * Unsubscribes this callback from new events. Includes persistent and once-listeners.\n * NOTE: It is recommended to us `Event.on`'s return value.\n * If the callback is not subscribed this is no-op.\n *\n * @param callback\n */\n off(callback: (data: T) => void): void;\n\n /**\n * Register a callback to be called only once when the next event is emitted.\n * If this callback is already registered as permanent listener, this is no-op.\n *\n * @param callback\n */\n once(callback: (data: T) => void): CleanupFn;\n\n /**\n * An async iterator that iterates over events.\n * This iterator runs indefinitely.\n */\n [Symbol.asyncIterator](): AsyncIterator<T>;\n\n /**\n * Returns a promise that resolves with the first event emitted that matches the provided predicate.\n *\n * @param predicate\n */\n waitFor(predicate: (data: T) => boolean): Promise<T>;\n\n /**\n * Returns a promise that resolves once a specific number of events was emitted since this method was called.\n *\n * @param expectedCount\n */\n waitForCount(expectedCount: number): Promise<T>;\n\n /**\n * Turn any variant of `Event<T>` into an `Event<void>` discarding the callback parameter.\n */\n discardParameter(): Event<void>;\n\n /**\n * Triggers an event with at least `timeout` milliseconds between each event.\n * If the event is triggered more often, the event is delayed until the timeout is reached.\n * If event is emitted for the first time or event wasn't fired for `timeout` milliseconds,\n * the event is emitted after `timeout / 8` ms.\n */\n debounce(timeout?: number): Event<void>;\n}\n\nclass EventListener<T> {\n public readonly callback: EventCallback<T> | WeakRef<EventCallback<T>>;\n\n private readonly _clearDispose?: () => void = undefined;\n\n constructor(\n event: Event<T>,\n listener: EventCallback<T>,\n public readonly ctx: Context,\n public readonly once: boolean,\n public readonly weak: boolean,\n ) {\n this._clearDispose = ctx.onDispose(() => {\n event._removeListener(this);\n });\n\n if (weak) {\n this.callback = new WeakRef(listener);\n weakListeners().registry?.register(\n listener,\n {\n event: new WeakRef(event),\n listener: this,\n },\n this,\n );\n } else {\n this.callback = listener;\n }\n }\n\n derefCallback(): EventCallback<T> | undefined {\n return this.weak ? (this.callback as WeakRef<EventCallback<T>>).deref() : (this.callback as EventCallback<T>);\n }\n\n trigger(data: T): void {\n let result!: MaybePromise<void>;\n try {\n const callback = this.derefCallback();\n result = callback?.(data);\n } catch (err: any) {\n this.ctx.raise(err);\n }\n\n if (!DO_NOT_ERROR_ON_ASYNC_CALLBACK) {\n if (result instanceof Promise) {\n throw new TypeError('Event has async callbacks, use emitAsync instead');\n }\n }\n }\n\n async triggerAsync(data: T): Promise<void> {\n try {\n const callback = this.derefCallback();\n await callback?.(data);\n } catch (err: any) {\n this.ctx.raise(err);\n }\n }\n\n remove(): void {\n this._clearDispose?.();\n weakListeners().registry?.unregister(this);\n }\n}\n\ntype HeldValue = {\n event: WeakRef<Event<any>>;\n listener: EventListener<any>;\n};\n\nlet weakListenersState: FinalizationRegistry<HeldValue> | null = null;\n\ntype WeakListeners = {\n registry: FinalizationRegistry<HeldValue> | undefined;\n};\n\nconst FINALIZATION_REGISTRY_SUPPORTED = !!globalThis.FinalizationRegistry;\n\nconst weakListeners = (): WeakListeners => {\n if (!FINALIZATION_REGISTRY_SUPPORTED) {\n return { registry: undefined };\n }\n weakListenersState ??= new FinalizationRegistry(({ event, listener }) => {\n event.deref()?._removeListener(listener);\n });\n return { registry: weakListenersState };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"chunk-events.mjs","names":[],"sources":["../../src/errors.ts","../../src/timeout.ts","../../src/events.ts"],"sourcesContent":["//\n// Copyright 2020 DXOS.org\n//\n\nimport { type ObservableProvider } from './observable-value.ts';\n\n// TODO(burdon): Move to debug.\n// Use: https://nodejs.org/api/errors.html\nexport const toError = (err: any) => (err === undefined || typeof err === 'string' ? new Error(err) : err);\n\nexport class TimeoutError extends Error {\n constructor(timeout?: number, label?: string) {\n super(timeout ? `Timeout [${timeout.toLocaleString()}ms]${label === undefined ? '' : `: ${label}`}` : 'Timeout');\n }\n}\n\nexport interface AsyncEvents<T = any> {\n onSuccess?(result: T): void;\n onTimeout?(err: TimeoutError): void;\n onError(err: any): void;\n}\n\nexport const observableError = (observable: ObservableProvider<AsyncEvents>, err: any) => {\n if (err instanceof TimeoutError) {\n observable.callback.onTimeout?.(err);\n } else {\n observable.callback.onError(toError(err));\n }\n};\n","//\n// Copyright 2020 DXOS.org\n//\n\nimport { type Context, ContextDisposedError } from '@dxos/context';\n\nimport { TimeoutError } from './errors.ts';\n\n/**\n * Times out after delay.\n */\nexport const sleep = (ms: number) => {\n return new Promise<void>((resolve) => {\n const finish = Date.now() + ms;\n\n // `setTimeout` does not guarantee execution at >= the scheduled time and may execute slightly early.\n const sleeper = () => {\n const delta = finish - Date.now();\n if (delta > 0) {\n setTimeout(sleeper, delta);\n } else {\n resolve();\n }\n };\n\n sleeper();\n });\n};\n\n// TODO(burdon): Reconcile with sleep.\nexport const sleepWithContext = (ctx: Context, ms: number) => {\n const error = new ContextDisposedError();\n return new Promise<void>((resolve, reject) => {\n if (ctx.disposed) {\n reject(error);\n return;\n }\n\n const timeout = setTimeout(() => {\n clearDispose();\n resolve();\n }, ms);\n\n const clearDispose = ctx.onDispose(() => {\n clearTimeout(timeout);\n reject(error);\n });\n });\n};\n\n/**\n * Can be used in long-running tasks to let other callbacks be invoked.\n */\nexport const asyncReturn = () => sleep(0);\n\n/**\n * Wait for promise or throw error.\n */\nexport const asyncTimeout = async <T>(promise: Promise<T>, timeout: number, err?: Error | string): Promise<T> => {\n if (typeof promise === 'function') {\n throw new Error('First argument must be a promise.');\n }\n\n let timeoutId: NodeJS.Timeout;\n const throwable = err === undefined || typeof err === 'string' ? new TimeoutError(timeout, err) : err;\n const timeoutPromise = new Promise<T>((resolve, reject) => {\n timeoutId = setTimeout(() => {\n reject(throwable);\n }, timeout);\n\n unrefTimeout(timeoutId);\n });\n\n return await Promise.race([promise, timeoutPromise]).finally(() => {\n clearTimeout(timeoutId);\n });\n};\n\n/**\n * In Node.JS, `unref` prevents the timeout from blocking the process from exiting. Not available in browsers.\n * https://nodejs.org/api/timers.html#timeoutunref\n */\nexport const unrefTimeout = (timeoutId: NodeJS.Timeout) => {\n if (typeof timeoutId === 'object' && 'unref' in timeoutId) {\n timeoutId.unref();\n }\n};\n","//\n// Copyright 2020 DXOS.org\n//\n\nimport { Context } from '@dxos/context';\nimport { type MaybePromise } from '@dxos/util';\n\nimport { type CleanupFn } from './cleanup.ts';\n\nexport type Effect = () => CleanupFn | undefined;\n\n/**\n * Effect that's been added to a specific Event.\n */\ninterface MaterializedEffect {\n effect: Effect;\n cleanup: CleanupFn | undefined;\n}\n\ninterface EventEmitterLike {\n on(event: string, cb: (data?: any) => void): void;\n off(event: string, cb: (data?: any) => void): void;\n}\n\nexport type ListenerOptions = {\n weak?: boolean;\n once?: boolean;\n};\n\ntype EventCallback<T> = (data: T) => MaybePromise<void>;\n\n// TODO(dmaretskyi): Remove this once the code is cleaned up.\nconst DO_NOT_ERROR_ON_ASYNC_CALLBACK = true;\n\n/**\n * An EventEmitter variant that does not do event multiplexing and represents a single event.\n *\n * ## Typical usage:\n * ```javascript\n * class Model {\n * public readonly update = new Event<UpdateData>();\n * private readonly privateEvent = new Event<void>();\n *\n * onUpdate(data: UpdateData) {\n * ...\n * this.update.emit(data);\n * }\n * }\n *\n * model.update.on(data => {\n * ...\n * });\n * ```\n *\n * ## Advantages over traditional EventEmitter:\n * 1. User describes explicitly what events a class has as they are defined as class fields.\n * 2. User can explicitly define event visibility (public, protected, private).\n * 3. Having events as class fields allows the compiler to check for correct event usage.\n * 4. User can type the data that event will be emitting.\n * 5. Uses composition instead of inheritance.\n * 6. Removes the cases where event names intersect when used in cases with inheritance.\n * 7. Remove the need to namespace events when developing a class with events that will be used as a base-class.\n */\n// TODO(burdon): Rename EventSink? (Event is a built-in type).\nexport class Event<T = void> implements ReadOnlyEvent<T> {\n /**\n * Wrap objects that have on/off style event emitters.\n */\n static wrap<T>(emitter: EventEmitterLike, eventName: string): Event<T> {\n const event = new Event<T>();\n\n event.addEffect(() => {\n const onEvent = (data: any) => event.emit(data);\n emitter.on(eventName, onEvent);\n return () => emitter.off(eventName, onEvent);\n });\n\n return event;\n }\n\n private readonly _listeners = new Set<EventListener<T>>();\n private readonly _effects = new Set<MaterializedEffect>();\n\n /**\n * Emit an event.\n * In most cases should only be called by the class or entity containing the event.\n * All listeners are called in order of subscription with persistent ones first.\n * Listeners are called synchronously in the same stack.\n * A thrown exception in the listener will stop the event from being emitted to the rest of the listeners.\n *\n * @param data param that will be passed to all listeners.\n */\n emit(data: T): void {\n for (const listener of this._listeners) {\n listener.trigger(data);\n\n if (listener.once) {\n this._listeners.delete(listener);\n }\n }\n }\n\n /**\n * Emit an event and wait for async listeners to complete.\n * In most cases should only be called by the class or entity containing the event.\n * All listeners are called in order of subscription with persistent ones first.\n * Listeners are called sequentially.\n *\n * @param data param that will be passed to all listeners.\n */\n async emitAsync(data: T): Promise<void> {\n for (const listener of this._listeners) {\n await listener.triggerAsync(data);\n\n if (listener.once) {\n this._listeners.delete(listener);\n }\n }\n }\n\n /**\n * Register an event listener.\n * If provided callback was already registered as once-listener, it is made permanent.\n *\n * @param callback\n * @param options.weak If true, the callback will be weakly referenced and will be garbage collected if no other references to it exist.\n * @returns function that unsubscribes this event listener\n */\n on(callback: EventCallback<T>): CleanupFn;\n on(ctx: Context, callback: EventCallback<T>, options?: ListenerOptions): CleanupFn;\n on(_ctx: any, _callback?: EventCallback<T>, options?: ListenerOptions): CleanupFn {\n const [ctx, callback] = _ctx instanceof Context ? [_ctx, _callback] : [new Context(), _ctx];\n const weak = !!options?.weak;\n const once = !!options?.once;\n\n const listener = new EventListener(this, callback, ctx, once, weak);\n\n this._addListener(listener);\n\n return () => {\n this._removeListener(listener);\n };\n }\n\n /**\n * Unsubscribe this callback from new events. Includes persistent and once-listeners.\n * NOTE: It is recommended to use `Event.on`'s return value instead.\n * If the callback is not subscribed this is no-op.\n *\n * @param callback\n */\n off(callback: (data: T) => void): void {\n for (const listener of this._listeners) {\n if (listener.derefCallback() === callback) {\n this._removeListener(listener);\n }\n }\n }\n\n /**\n * Register a callback to be called only once when the next event is emitted.\n * If this callback is already registered as permanent listener, this is no-op.\n *\n * @param callback\n */\n once(callback: (data: T) => void): CleanupFn;\n once(ctx: Context, callback: (data: T) => void): CleanupFn;\n once(_ctx: any, _callback?: (data: T) => void): CleanupFn {\n const [ctx, callback] = _ctx instanceof Context ? [_ctx, _callback] : [new Context(), _ctx];\n\n const listener = new EventListener(this, callback, ctx, true, false);\n\n this._addListener(listener);\n\n return () => {\n this._removeListener(listener);\n };\n }\n\n /**\n * An async iterator that iterates over events.\n * This iterator runs indefinitely.\n */\n async *[Symbol.asyncIterator](): AsyncIterator<T> {\n while (true) {\n yield await new Promise((resolve) => {\n this.once(resolve);\n });\n }\n }\n\n /**\n * Returns a promise that resolves with the first event emitted that matches the provided predicate.\n *\n * @param predicate\n */\n waitFor(predicate: (data: T) => boolean): Promise<T> {\n return new Promise((resolve) => {\n const unsubscribe = this.on((data) => {\n if (predicate(data)) {\n unsubscribe();\n resolve(data);\n }\n });\n });\n }\n\n /**\n * Returns a promise that resolves once a specific number of events was emitted since this method was called.\n *\n * @param expectedCount\n */\n waitForCount(expectedCount: number): Promise<T> {\n let count = 0;\n return this.waitFor(() => ++count === expectedCount);\n }\n\n /**\n * Similar to waitFor, but the promise resolves immediately if the condition is already true.\n */\n // TODO(burdon): Should pass event property to predicate.\n async waitForCondition(predicate: () => boolean): Promise<void> {\n if (!predicate()) {\n await this.waitFor(predicate);\n }\n }\n\n /**\n * Returns the number of persistent listeners.\n */\n listenerCount(): number {\n return this._listeners.size;\n }\n\n /**\n * Add a side effect that will be activated once the event has at least one subscriber.\n * The provided callback can return a function that will be used to clean up after the last subscriber unsubscribes from the event.\n * The API is similar to `useEffect` from React.\n *\n * ## Example:\n * ```typescript\n * event.addEffect(() => {\n * // do stuff\n * return () => {\n * // clean-up\n * };\n * });\n * ```\n *\n * @returns Callback that will remove this effect once called.\n */\n addEffect(effect: Effect): CleanupFn {\n const handle: MaterializedEffect = { effect, cleanup: undefined };\n\n if (this.listenerCount() > 0) {\n handle.cleanup = handle.effect();\n }\n\n this._effects.add(handle);\n return () => {\n // eslint-disable-next-line no-unused-expressions\n handle.cleanup?.();\n this._effects.delete(handle);\n };\n }\n\n /**\n * Triggers an event with at least `timeout` milliseconds between each event.\n * If the event is triggered more often, the event is delayed until the timeout is reached.\n * If event is emitted for the first time or event wasn't fired for `timeout` milliseconds,\n * the event is emitted after `timeout / 8` ms.\n */\n // TODO(burdon): Factor out generic function.\n debounce(timeout = 0): Event<void> {\n let firing: NodeJS.Timeout | undefined;\n let lastFired: number | undefined;\n\n const debouncedEvent = new Event<void>();\n debouncedEvent.addEffect(() => {\n const unsubscribe = this.on(() => {\n if (!firing) {\n const fireIn = !lastFired || Date.now() - lastFired > timeout ? timeout / 8 : timeout;\n firing = setTimeout(() => {\n lastFired = Date.now();\n firing = undefined;\n debouncedEvent.emit();\n }, fireIn);\n }\n });\n\n return () => {\n unsubscribe();\n clearTimeout(firing);\n };\n });\n\n return debouncedEvent;\n }\n\n /**\n * Turn any variant of `Event<T>` into an `Event<void>` discarding the callback parameter.\n */\n discardParameter(): Event<void> {\n return this as any;\n }\n\n /**\n * Pipe the events into another event.\n * @param event\n */\n pipeInto(event: Event<T>): CleanupFn {\n return this.on((data) => event.emit(data));\n }\n\n /**\n * Overridden to not return implementation details.\n */\n toJSON(): { listenerCount: number } {\n return {\n listenerCount: this.listenerCount(),\n };\n }\n\n private _addListener(listener: EventListener<T>): void {\n this._listeners.add(listener);\n\n if (this.listenerCount() === 1) {\n this._runEffects();\n }\n }\n\n /**\n * @internal\n */\n _removeListener(listener: EventListener<T>): void {\n this._listeners.delete(listener);\n listener.remove();\n\n if (this.listenerCount() === 0) {\n this._cleanupEffects();\n }\n }\n\n private _runEffects(): void {\n for (const handle of this._effects) {\n handle.cleanup = handle.effect();\n }\n }\n\n private _cleanupEffects(): void {\n for (const handle of this._effects) {\n // eslint-disable-next-line no-unused-expressions\n handle.cleanup?.();\n handle.cleanup = undefined;\n }\n }\n}\n\n/**\n * A version of Event class which only has subscribe methods.\n * Useful in cases where you want to explicitly prohibit calling `emit` method.\n */\nexport interface ReadOnlyEvent<T = void> {\n /**\n * Register an event listener.\n * If provided callback was already registered as once-listener, it is made permanent.\n *\n * @param callback\n * @param options.weak If true, the callback will be weakly referenced and will be garbage collected if no other references to it exist.\n * @returns function that unsubscribes this event listener\n */\n on(callback: (data: T) => void): CleanupFn;\n on(ctx: Context, callback: (data: T) => void, options?: ListenerOptions): CleanupFn;\n\n /**\n * Unsubscribes this callback from new events. Includes persistent and once-listeners.\n * NOTE: It is recommended to us `Event.on`'s return value.\n * If the callback is not subscribed this is no-op.\n *\n * @param callback\n */\n off(callback: (data: T) => void): void;\n\n /**\n * Register a callback to be called only once when the next event is emitted.\n * If this callback is already registered as permanent listener, this is no-op.\n *\n * @param callback\n */\n once(callback: (data: T) => void): CleanupFn;\n\n /**\n * An async iterator that iterates over events.\n * This iterator runs indefinitely.\n */\n [Symbol.asyncIterator](): AsyncIterator<T>;\n\n /**\n * Returns a promise that resolves with the first event emitted that matches the provided predicate.\n *\n * @param predicate\n */\n waitFor(predicate: (data: T) => boolean): Promise<T>;\n\n /**\n * Returns a promise that resolves once a specific number of events was emitted since this method was called.\n *\n * @param expectedCount\n */\n waitForCount(expectedCount: number): Promise<T>;\n\n /**\n * Turn any variant of `Event<T>` into an `Event<void>` discarding the callback parameter.\n */\n discardParameter(): Event<void>;\n\n /**\n * Triggers an event with at least `timeout` milliseconds between each event.\n * If the event is triggered more often, the event is delayed until the timeout is reached.\n * If event is emitted for the first time or event wasn't fired for `timeout` milliseconds,\n * the event is emitted after `timeout / 8` ms.\n */\n debounce(timeout?: number): Event<void>;\n}\n\nclass EventListener<T> {\n public readonly callback: EventCallback<T> | WeakRef<EventCallback<T>>;\n\n private readonly _clearDispose?: () => void = undefined;\n\n constructor(\n event: Event<T>,\n listener: EventCallback<T>,\n public readonly ctx: Context,\n public readonly once: boolean,\n public readonly weak: boolean,\n ) {\n this._clearDispose = ctx.onDispose(() => {\n event._removeListener(this);\n });\n\n if (weak) {\n this.callback = new WeakRef(listener);\n weakListeners().registry?.register(\n listener,\n {\n event: new WeakRef(event),\n listener: this,\n },\n this,\n );\n } else {\n this.callback = listener;\n }\n }\n\n derefCallback(): EventCallback<T> | undefined {\n return this.weak ? (this.callback as WeakRef<EventCallback<T>>).deref() : (this.callback as EventCallback<T>);\n }\n\n trigger(data: T): void {\n let result!: MaybePromise<void>;\n try {\n const callback = this.derefCallback();\n result = callback?.(data);\n } catch (err: any) {\n this.ctx.raise(err);\n }\n\n if (!DO_NOT_ERROR_ON_ASYNC_CALLBACK) {\n if (result instanceof Promise) {\n throw new TypeError('Event has async callbacks, use emitAsync instead');\n }\n }\n }\n\n async triggerAsync(data: T): Promise<void> {\n try {\n const callback = this.derefCallback();\n await callback?.(data);\n } catch (err: any) {\n this.ctx.raise(err);\n }\n }\n\n remove(): void {\n this._clearDispose?.();\n weakListeners().registry?.unregister(this);\n }\n}\n\ntype HeldValue = {\n event: WeakRef<Event<any>>;\n listener: EventListener<any>;\n};\n\nlet weakListenersState: FinalizationRegistry<HeldValue> | null = null;\n\ntype WeakListeners = {\n registry: FinalizationRegistry<HeldValue> | undefined;\n};\n\nconst FINALIZATION_REGISTRY_SUPPORTED = !!globalThis.FinalizationRegistry;\n\nconst weakListeners = (): WeakListeners => {\n if (!FINALIZATION_REGISTRY_SUPPORTED) {\n return { registry: undefined };\n }\n weakListenersState ??= new FinalizationRegistry(({ event, listener }) => {\n event.deref()?._removeListener(listener);\n });\n return { registry: weakListenersState };\n};\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { a as sleepWithContext, c as observableError, i as sleep, l as toError, n as asyncReturn, o as unrefTimeout, r as asyncTimeout, s as TimeoutError, t as Event } from "./chunk-events.mjs";
2
- import { ComplexMap, createSetDispatch, throwUnhandledError } from "@dxos/util";
2
+ import { ComplexMap, createSetDispatch, isNode, throwUnhandledError } from "@dxos/util";
3
3
  import { Context, ContextDisposedError, LifecycleState, Resource, cancelWithContext } from "@dxos/context";
4
4
  import { StackTrace, warnAfterTimeout } from "@dxos/debug";
5
5
  import Observable from "zen-observable";
@@ -1025,6 +1025,8 @@ var makePushIterable = () => {
1025
1025
  //#endregion
1026
1026
  //#region src/stream.ts
1027
1027
  var __dxlog_file = "/__w/dxos/dxos/packages/common/async/src/stream.ts";
1028
+ /** Narrows a caught `unknown` to `Error` — a thrown value is not guaranteed to be one. */
1029
+ var toCaughtError = (err) => err instanceof Error ? err : new Error(String(err));
1028
1030
  /**
1029
1031
  * Represents a typed stream of data.
1030
1032
  * In concept it's a Promise that can resolve multiple times.
@@ -1143,7 +1145,7 @@ var Stream = class Stream {
1143
1145
  }, {
1144
1146
  "~LogMeta": "~LogMeta",
1145
1147
  F: __dxlog_file,
1146
- L: 198
1148
+ L: 201
1147
1149
  });
1148
1150
  this._ctx.onDispose(() => this.close());
1149
1151
  try {
@@ -1157,7 +1159,7 @@ var Stream = class Stream {
1157
1159
  log("Stream is closed, dropping message.", void 0, {
1158
1160
  "~LogMeta": "~LogMeta",
1159
1161
  F: __dxlog_file,
1160
- L: 224,
1162
+ L: 227,
1161
1163
  S: this
1162
1164
  });
1163
1165
  return;
@@ -1166,13 +1168,13 @@ var Stream = class Stream {
1166
1168
  if (this._messageHandler) try {
1167
1169
  this._messageHandler(msg);
1168
1170
  } catch (err) {
1169
- throwUnhandledError(err);
1171
+ throwUnhandledError(toCaughtError(err));
1170
1172
  }
1171
1173
  else {
1172
1174
  invariant(this._buffer, void 0, {
1173
1175
  "~LogMeta": "~LogMeta",
1174
1176
  F: __dxlog_file,
1175
- L: 238,
1177
+ L: 241,
1176
1178
  S: this,
1177
1179
  A: ["this._buffer", ""]
1178
1180
  });
@@ -1187,14 +1189,14 @@ var Stream = class Stream {
1187
1189
  try {
1188
1190
  this._closeHandler?.(err);
1189
1191
  } catch (err) {
1190
- throwUnhandledError(err);
1192
+ throwUnhandledError(toCaughtError(err));
1191
1193
  }
1192
1194
  this._ctx.dispose();
1193
1195
  }
1194
1196
  });
1195
1197
  if (producerCleanup) this._producerCleanup = producerCleanup;
1196
1198
  } catch (err) {
1197
- this._ctx.raise(err);
1199
+ this._ctx.raise(toCaughtError(err));
1198
1200
  }
1199
1201
  }
1200
1202
  _markAsReady() {
@@ -1208,28 +1210,28 @@ var Stream = class Stream {
1208
1210
  invariant(!this._messageHandler, "Stream is already subscribed to.", {
1209
1211
  "~LogMeta": "~LogMeta",
1210
1212
  F: __dxlog_file,
1211
- L: 279,
1213
+ L: 282,
1212
1214
  S: this,
1213
1215
  A: ["!this._messageHandler", "'Stream is already subscribed to.'"]
1214
1216
  });
1215
1217
  invariant(!this._closeHandler, "Stream is already subscribed to.", {
1216
1218
  "~LogMeta": "~LogMeta",
1217
1219
  F: __dxlog_file,
1218
- L: 280,
1220
+ L: 283,
1219
1221
  S: this,
1220
1222
  A: ["!this._closeHandler", "'Stream is already subscribed to.'"]
1221
1223
  });
1222
1224
  invariant(this._buffer, void 0, {
1223
1225
  "~LogMeta": "~LogMeta",
1224
1226
  F: __dxlog_file,
1225
- L: 281,
1227
+ L: 284,
1226
1228
  S: this,
1227
1229
  A: ["this._buffer", ""]
1228
1230
  });
1229
1231
  for (const message of this._buffer) try {
1230
1232
  onMessage(message);
1231
1233
  } catch (err) {
1232
- throwUnhandledError(err);
1234
+ throwUnhandledError(toCaughtError(err));
1233
1235
  }
1234
1236
  this._buffer = null;
1235
1237
  if (this._isClosed) {
@@ -1252,7 +1254,7 @@ var Stream = class Stream {
1252
1254
  invariant(!this._readyHandler, "Stream already has a handler for the ready event.", {
1253
1255
  "~LogMeta": "~LogMeta",
1254
1256
  F: __dxlog_file,
1255
- L: 316,
1257
+ L: 319,
1256
1258
  S: this,
1257
1259
  A: ["!this._readyHandler", "'Stream already has a handler for the ready event.'"]
1258
1260
  });
@@ -1516,7 +1518,7 @@ var UpdateScheduler = class {
1516
1518
  if (this._params.maxFrequency) {
1517
1519
  const now = performance.now();
1518
1520
  const delay = this._lastUpdateTime + TIME_PERIOD / this._params.maxFrequency - now;
1519
- if (delay > 0) {
1521
+ if (delay > 0 && this._skipDelay.state !== TriggerState.RESOLVED) {
1520
1522
  const skipDelay = this._skipDelay;
1521
1523
  await new Promise((resolve) => {
1522
1524
  const timeoutId = setTimeout(() => {
@@ -1589,6 +1591,56 @@ var UpdateScheduler = class {
1589
1591
  }
1590
1592
  };
1591
1593
  //#endregion
1592
- export { AsyncTask, CancellableObservableProvider, DeferredTask, Event, MulticastObservable, Mutex, MutexGuard, Observable, ObservableProvider, PersistentLifecycle, PushStream, Stream, SubscriptionList, SubscriptionSet, TimeoutError, Timer, Trigger, TriggerState, UpdateScheduler, addEventListener, addListener, asyncReturn, asyncTimeout, chain, combine, debounce, debounceAndThrottle, delay, dumpLeaks, getFirstStreamValue, interval, latch, makePushIterable, observableError, onEvent, promiseFromCallback, runInContext, runInContextAsync, scheduleExponentialBackoffTaskInterval, scheduleMicroTask, scheduleTask, scheduleTaskInterval, sleep, sleepWithContext, streamToArray, synchronized, throttle, timeout, toError, trackLeaks, trackResource, trigger, unrefTimeout, until, untilError, untilPromise, waitForCondition, waitForEvent };
1594
+ //#region src/yield.ts
1595
+ /** One channel for every yield: a port per call would leave an open handle behind each one. */
1596
+ var channel;
1597
+ var waiting = [];
1598
+ var yieldViaMessageChannel = () => new Promise((resolve) => {
1599
+ if (!channel) {
1600
+ channel = new MessageChannel();
1601
+ channel.port1.onmessage = () => waiting.shift()?.();
1602
+ }
1603
+ waiting.push(resolve);
1604
+ channel.port2.postMessage(void 0);
1605
+ });
1606
+ /**
1607
+ * Resolves in a later task so queued input and rendering get a turn between slices of a long job.
1608
+ *
1609
+ * `scheduler.yield` keeps the continuation ahead of other same-priority work where the host has it.
1610
+ * A message task is the browser fallback because timers are clamped in a hidden tab, which would
1611
+ * stall a background sync; Node drains message ports without visiting its timers, so it gets the
1612
+ * timer instead.
1613
+ */
1614
+ var yieldToEventLoop = () => {
1615
+ const scheduler = globalThis.scheduler;
1616
+ if (scheduler?.yield) return scheduler.yield();
1617
+ if (!isNode() && typeof MessageChannel !== "undefined") return yieldViaMessageChannel();
1618
+ return new Promise((resolve) => setTimeout(resolve, 0));
1619
+ };
1620
+ var SLICE_BUDGET_MS = {
1621
+ interactive: 83,
1622
+ smooth: 13,
1623
+ idle: 5
1624
+ };
1625
+ var sliceUntilNextTurn;
1626
+ var openSliceUntilNextTurn = () => {
1627
+ if (!sliceUntilNextTurn) {
1628
+ const started = { start: performance.now() };
1629
+ sliceUntilNextTurn = started;
1630
+ yieldToEventLoop().then(() => {
1631
+ if (sliceUntilNextTurn === started) sliceUntilNextTurn = void 0;
1632
+ });
1633
+ }
1634
+ return sliceUntilNextTurn;
1635
+ };
1636
+ /** Whether the current slice has used its strategy's budget, for callers that yield by other means. */
1637
+ var shouldYield = (strategy) => performance.now() - openSliceUntilNextTurn().start >= SLICE_BUDGET_MS[strategy];
1638
+ /** Yields to the event loop once the current slice has used its strategy's budget, and resolves at once otherwise. */
1639
+ var yieldOrContinue = async (strategy) => {
1640
+ if (!shouldYield(strategy)) return;
1641
+ await yieldToEventLoop();
1642
+ };
1643
+ //#endregion
1644
+ export { AsyncTask, CancellableObservableProvider, DeferredTask, Event, MulticastObservable, Mutex, MutexGuard, Observable, ObservableProvider, PersistentLifecycle, PushStream, Stream, SubscriptionList, SubscriptionSet, TimeoutError, Timer, Trigger, TriggerState, UpdateScheduler, addEventListener, addListener, asyncReturn, asyncTimeout, chain, combine, debounce, debounceAndThrottle, delay, dumpLeaks, getFirstStreamValue, interval, latch, makePushIterable, observableError, onEvent, promiseFromCallback, runInContext, runInContextAsync, scheduleExponentialBackoffTaskInterval, scheduleMicroTask, scheduleTask, scheduleTaskInterval, shouldYield, sleep, sleepWithContext, streamToArray, synchronized, throttle, timeout, toError, trackLeaks, trackResource, trigger, unrefTimeout, until, untilError, untilPromise, waitForCondition, waitForEvent, yieldOrContinue, yieldToEventLoop };
1593
1645
 
1594
1646
  //# sourceMappingURL=index.mjs.map