@mindees/core 0.22.5 → 0.22.6

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/index.d.ts CHANGED
@@ -14,7 +14,7 @@ import { ThreadPool, WorkerLike, WorkerPoolOptions, createInlineThreadPool, crea
14
14
  /** The npm package name. */
15
15
  declare const name = "@mindees/core";
16
16
  /** The package version. All `@mindees/*` packages share one locked version line. */
17
- declare const VERSION = "0.22.5";
17
+ declare const VERSION = "0.22.6";
18
18
  /**
19
19
  * Current maturity of this package. See the repository `STATUS.md`.
20
20
  *
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ import { createInlineThreadPool, createNativeThreadPool, createWorkerPool } from
12
12
  /** The npm package name. */
13
13
  const name = "@mindees/core";
14
14
  /** The package version. All `@mindees/*` packages share one locked version line. */
15
- const VERSION = "0.22.5";
15
+ const VERSION = "0.22.6";
16
16
  /**
17
17
  * Current maturity of this package. See the repository `STATUS.md`.
18
18
  *
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Maturity, PackageInfo } from './types'\n\n/**\n * Animation engine: reactive animated values + timing/spring drivers + interpolate, driven by an\n * injected frame source (RN Animated/Reanimated + Flutter AnimationController parity).\n */\nexport {\n _activeAnimationCount,\n _resetAnimation,\n type AnimatedValue,\n type AnimationHandle,\n animate,\n cubicBezier,\n type Easing,\n easeInOutQuad,\n easeInQuad,\n easeOutCubic,\n easeOutQuad,\n type FrameSource,\n getFrameSource,\n interpolate,\n linear,\n manualFrameSource,\n rafFrameSource,\n setFrameSource,\n spring,\n timing,\n} from './animation'\n/**\n * Component model: a renderer-agnostic element tree plus selector-based,\n * re-render-isolated context. (Phase 2)\n */\nexport {\n type Component,\n type Context,\n type ContextProvider,\n createContext,\n createElement,\n createProvider,\n ELEMENT_TYPE,\n type ElementType,\n Fragment,\n hasOwner,\n isElement,\n isKeyedRegion,\n isPortal,\n KEYED_REGION,\n type KeyedRegion,\n type KeyedRegionOptions,\n keyedRegion,\n type MindeesElement,\n type MindeesNode,\n PORTAL,\n type PortalRegion,\n portal,\n renderComponent,\n type SelectorEquals,\n} from './component'\nexport { NotImplementedError } from './errors'\n/**\n * Gesture recognizers: tap/longPress/pan/pinch/swipe → reactive state that drives styles and the\n * animation engine (RN Gesture Handler / Flutter GestureDetector parity).\n */\nexport {\n _setGestureClock,\n composeGestures,\n type GestureHandlers,\n longPress,\n normalizePointer,\n type PanEvent,\n type PanState,\n type PinchEvent,\n type PinchState,\n type PointerSample,\n pan,\n panAnimated,\n pinch,\n type Recognizer,\n type SwipeDirection,\n type SwipeEvent,\n swipe,\n type TapState,\n tap,\n} from './gesture'\nexport { notImplemented } from './not-implemented'\n/**\n * Fine-grained reactivity: signals, computed values, effects, batching, and\n * disposal scopes. This is the reactive core of MindeesNative.\n */\nexport {\n type Accessor,\n batch,\n type ComputedOptions,\n computed,\n createRoot,\n deferred,\n type EffectOptions,\n type EqualsFn,\n effect,\n getOwner,\n type Memo,\n memo,\n type Owner,\n on,\n onCleanup,\n runWithOwner,\n type Signal,\n type SignalOptions,\n setReactiveScheduler,\n signal,\n startTransition,\n untrack,\n} from './reactive'\n/**\n * Priority scheduler: two-lane (sync/normal), microtask-batched, with\n * cancellable and dedupable tasks. (Phase 2)\n */\nexport {\n createScheduler,\n type Priority,\n type ScheduledTask,\n type ScheduleOptions,\n Scheduler,\n type SchedulerOptions,\n type Task,\n} from './scheduler'\n/**\n * Threading abstraction: a {@link ThreadPool} contract with a working Web Worker\n * backend and an inline fallback. Native multi-threading is a research track. (Phase 2)\n */\nexport {\n createInlineThreadPool,\n createNativeThreadPool,\n createWorkerPool,\n type ThreadPool,\n type WorkerLike,\n type WorkerPoolOptions,\n} from './threading'\nexport type { Maturity, PackageInfo } from './types'\n\n/** The npm package name. */\nexport const name = '@mindees/core'\n\n/** The package version. All `@mindees/*` packages share one locked version line. */\nexport const VERSION = '0.22.5'\n\n/**\n * Current maturity of this package. See the repository `STATUS.md`.\n *\n * The reactivity layer (signals/computed/effect/batch), the component model with\n * selector-isolated context, the priority scheduler, and the thread-pool\n * abstraction (Web Worker + inline) are all implemented and tested. Native\n * multi-threading remains a research track (throws `NotImplementedError`).\n */\nexport const maturity: Maturity = 'experimental'\n\n/**\n * Static identity + maturity metadata for this package. Frozen so the\n * self-reported identity tooling introspects cannot be mutated at runtime,\n * matching the `readonly` fields of {@link PackageInfo}.\n */\nexport const info: PackageInfo = Object.freeze({ name, version: VERSION, maturity })\n"],"mappings":";;;;;;;;;;;;AA6IA,MAAa,OAAO;;AAGpB,MAAa,UAAU;;;;;;;;;AAUvB,MAAa,WAAqB;;;;;;AAOlC,MAAa,OAAoB,OAAO,OAAO;CAAE;CAAM,SAAS;CAAS;AAAS,CAAC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Maturity, PackageInfo } from './types'\n\n/**\n * Animation engine: reactive animated values + timing/spring drivers + interpolate, driven by an\n * injected frame source (RN Animated/Reanimated + Flutter AnimationController parity).\n */\nexport {\n _activeAnimationCount,\n _resetAnimation,\n type AnimatedValue,\n type AnimationHandle,\n animate,\n cubicBezier,\n type Easing,\n easeInOutQuad,\n easeInQuad,\n easeOutCubic,\n easeOutQuad,\n type FrameSource,\n getFrameSource,\n interpolate,\n linear,\n manualFrameSource,\n rafFrameSource,\n setFrameSource,\n spring,\n timing,\n} from './animation'\n/**\n * Component model: a renderer-agnostic element tree plus selector-based,\n * re-render-isolated context. (Phase 2)\n */\nexport {\n type Component,\n type Context,\n type ContextProvider,\n createContext,\n createElement,\n createProvider,\n ELEMENT_TYPE,\n type ElementType,\n Fragment,\n hasOwner,\n isElement,\n isKeyedRegion,\n isPortal,\n KEYED_REGION,\n type KeyedRegion,\n type KeyedRegionOptions,\n keyedRegion,\n type MindeesElement,\n type MindeesNode,\n PORTAL,\n type PortalRegion,\n portal,\n renderComponent,\n type SelectorEquals,\n} from './component'\nexport { NotImplementedError } from './errors'\n/**\n * Gesture recognizers: tap/longPress/pan/pinch/swipe → reactive state that drives styles and the\n * animation engine (RN Gesture Handler / Flutter GestureDetector parity).\n */\nexport {\n _setGestureClock,\n composeGestures,\n type GestureHandlers,\n longPress,\n normalizePointer,\n type PanEvent,\n type PanState,\n type PinchEvent,\n type PinchState,\n type PointerSample,\n pan,\n panAnimated,\n pinch,\n type Recognizer,\n type SwipeDirection,\n type SwipeEvent,\n swipe,\n type TapState,\n tap,\n} from './gesture'\nexport { notImplemented } from './not-implemented'\n/**\n * Fine-grained reactivity: signals, computed values, effects, batching, and\n * disposal scopes. This is the reactive core of MindeesNative.\n */\nexport {\n type Accessor,\n batch,\n type ComputedOptions,\n computed,\n createRoot,\n deferred,\n type EffectOptions,\n type EqualsFn,\n effect,\n getOwner,\n type Memo,\n memo,\n type Owner,\n on,\n onCleanup,\n runWithOwner,\n type Signal,\n type SignalOptions,\n setReactiveScheduler,\n signal,\n startTransition,\n untrack,\n} from './reactive'\n/**\n * Priority scheduler: two-lane (sync/normal), microtask-batched, with\n * cancellable and dedupable tasks. (Phase 2)\n */\nexport {\n createScheduler,\n type Priority,\n type ScheduledTask,\n type ScheduleOptions,\n Scheduler,\n type SchedulerOptions,\n type Task,\n} from './scheduler'\n/**\n * Threading abstraction: a {@link ThreadPool} contract with a working Web Worker\n * backend and an inline fallback. Native multi-threading is a research track. (Phase 2)\n */\nexport {\n createInlineThreadPool,\n createNativeThreadPool,\n createWorkerPool,\n type ThreadPool,\n type WorkerLike,\n type WorkerPoolOptions,\n} from './threading'\nexport type { Maturity, PackageInfo } from './types'\n\n/** The npm package name. */\nexport const name = '@mindees/core'\n\n/** The package version. All `@mindees/*` packages share one locked version line. */\nexport const VERSION = '0.22.6'\n\n/**\n * Current maturity of this package. See the repository `STATUS.md`.\n *\n * The reactivity layer (signals/computed/effect/batch), the component model with\n * selector-isolated context, the priority scheduler, and the thread-pool\n * abstraction (Web Worker + inline) are all implemented and tested. Native\n * multi-threading remains a research track (throws `NotImplementedError`).\n */\nexport const maturity: Maturity = 'experimental'\n\n/**\n * Static identity + maturity metadata for this package. Frozen so the\n * self-reported identity tooling introspects cannot be mutated at runtime,\n * matching the `readonly` fields of {@link PackageInfo}.\n */\nexport const info: PackageInfo = Object.freeze({ name, version: VERSION, maturity })\n"],"mappings":";;;;;;;;;;;;AA6IA,MAAa,OAAO;;AAGpB,MAAa,UAAU;;;;;;;;;AAUvB,MAAa,WAAqB;;;;;;AAOlC,MAAa,OAAoB,OAAO,OAAO;CAAE;CAAM,SAAS;CAAS;AAAS,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler.d.ts","names":[],"sources":["../../src/scheduler/scheduler.ts"],"mappings":";;AAmBA;;;;AAAoB;AAGpB;;;;AAAgB;AAGhB;;;;;;;KANY,QAAA;AAaP;AAAA,KAVO,IAAA;;UAGK,eAAA;EAiBf;EAfA,QAAA,GAAW,QAAQ;EA0BJ;;;;EArBf,GAAA;AAAA;;UAQe,aAAA;EAsBoB;EApBnC,MAAA;EAiCW;EAAA,SA/BF,OAAO;AAAA;;UASD,gBAAA;EAqCgB;;;;EAhC/B,OAAA,IAAW,KAAA;EAmBM;;;EAfjB,iBAAA,IAAqB,EAAA;AAAA;;;;cAaV,SAAA;EAAA,iBACM,IAAA;EAAA,iBACA,MAAA;EAAA,iBACA,KAAA;EAAA,QACT,eAAA;EAAA,QACA,QAAA;EAAA,iBACS,OAAA;EAAA,iBACA,iBAAA;cAEL,OAAA,GAAU,gBAAA;EAsEd;EAhER,QAAA,CAAS,IAAA,EAAM,IAAA,EAAM,OAAA,GAAU,eAAA,GAAkB,aAAA;EAuGzC;EAlFR,SAAA;EAwFkB;EAAA,IApDd,IAAA;EAAA,QAOI,YAAA;EAAA,QASA,GAAA;;;;;;;AAkD4D;UApB5D,QAAA;EAAA,QAMA,UAAA;AAAA;;iBAcM,eAAA,CAAgB,OAAA,GAAU,gBAAA,GAAmB,SAAS"}
1
+ {"version":3,"file":"scheduler.d.ts","names":[],"sources":["../../src/scheduler/scheduler.ts"],"mappings":";;AAmBA;;;;AAAoB;AAGpB;;;;AAAgB;AAGhB;;;;;;;KANY,QAAA;AAaP;AAAA,KAVO,IAAA;;UAGK,eAAA;EAiBf;EAfA,QAAA,GAAW,QAAQ;EA2BJ;;;;EAtBf,GAAA;AAAA;;UAQe,aAAA;EAuBoB;EArBnC,MAAA;EAkCW;EAAA,SAhCF,OAAO;AAAA;;UAUD,gBAAA;EAqCgB;;;;EAhC/B,OAAA,IAAW,KAAA;EAmBM;;;EAfjB,iBAAA,IAAqB,EAAA;AAAA;;;;cAaV,SAAA;EAAA,iBACM,IAAA;EAAA,iBACA,MAAA;EAAA,iBACA,KAAA;EAAA,QACT,eAAA;EAAA,QACA,QAAA;EAAA,iBACS,OAAA;EAAA,iBACA,iBAAA;cAEL,OAAA,GAAU,gBAAA;EAsFd;EAhFR,QAAA,CAAS,IAAA,EAAM,IAAA,EAAM,OAAA,GAAU,eAAA,GAAkB,aAAA;EAuHzC;EAxFR,SAAA;EA8FkB;EAAA,IApDd,IAAA;EAAA,QAOI,YAAA;EAAA,QASA,GAAA;;;;;;;AAkD4D;UApB5D,QAAA;EAAA,QAMA,UAAA;AAAA;;iBAcM,eAAA,CAAgB,OAAA,GAAU,gBAAA,GAAmB,SAAS"}
@@ -27,13 +27,26 @@ var Scheduler = class {
27
27
  if (key !== null) {
28
28
  const existing = this.keyed.get(key);
29
29
  if (existing && existing.fn !== null) {
30
- existing.fn = task;
31
- return this.makeHandle(existing);
30
+ if (existing.lane === priority) {
31
+ existing.fn = task;
32
+ return this.makeHandle(existing);
33
+ }
34
+ existing.fn = null;
35
+ const moved = {
36
+ key,
37
+ fn: task,
38
+ lane: priority
39
+ };
40
+ this.keyed.set(key, moved);
41
+ (priority === "sync" ? this.sync : this.normal).push(moved);
42
+ this.requestFlush();
43
+ return this.makeHandle(moved);
32
44
  }
33
45
  }
34
46
  const entry = {
35
47
  key,
36
- fn: task
48
+ fn: task,
49
+ lane: priority
37
50
  };
38
51
  if (key !== null) this.keyed.set(key, entry);
39
52
  (priority === "sync" ? this.sync : this.normal).push(entry);
@@ -44,6 +57,7 @@ var Scheduler = class {
44
57
  flushSync() {
45
58
  if (this.flushing) return;
46
59
  this.flushing = true;
60
+ let overflowed = false;
47
61
  try {
48
62
  let iterations = 0;
49
63
  while (this.sync.length > 0 || this.normal.length > 0) {
@@ -51,10 +65,8 @@ var Scheduler = class {
51
65
  this.sync.length = 0;
52
66
  this.normal.length = 0;
53
67
  this.keyed.clear();
54
- this.run(() => {
55
- throw new Error("MindeesNative: potential infinite scheduler loop — a task kept re-scheduling itself.");
56
- });
57
- return;
68
+ overflowed = true;
69
+ break;
58
70
  }
59
71
  const entry = this.sync.length > 0 ? this.sync.shift() : this.normal.shift();
60
72
  if (!entry) continue;
@@ -67,6 +79,9 @@ var Scheduler = class {
67
79
  this.flushing = false;
68
80
  this.microtaskQueued = false;
69
81
  }
82
+ if (overflowed) this.run(() => {
83
+ throw new Error("MindeesNative: potential infinite scheduler loop — a task kept re-scheduling itself.");
84
+ });
70
85
  }
71
86
  /** Number of pending tasks across both lanes (cancelled tasks excluded). */
72
87
  get size() {
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler.js","names":[],"sources":["../../src/scheduler/scheduler.ts"],"sourcesContent":["/**\n * MindeesNative scheduler — a small, deterministic priority scheduler.\n *\n * Two lanes:\n * - **`sync`** — high-priority work (interaction handlers, first frame). Drained\n * synchronously at the next flush point and always before the normal lane.\n * - **`normal`** — default work, drained on a microtask so multiple schedules in\n * the same tick coalesce into one flush.\n *\n * Tasks are **cancellable** (via the returned handle) and **dedupable** (two\n * tasks scheduled with the same `key` collapse to one — the latest callback\n * wins, preserving the earlier queue position). The scheduler never throws from\n * a task into the caller: task errors are collected and reported via an optional\n * `onError` hook, so one bad task can't stop the rest of the flush.\n *\n * @module\n */\n\n/** Scheduling lanes, highest priority first. */\nexport type Priority = 'sync' | 'normal'\n\n/** A unit of scheduled work. */\nexport type Task = () => void\n\n/** Options for {@link Scheduler.schedule}. */\nexport interface ScheduleOptions {\n /** Lane to run in. Defaults to `'normal'`. */\n priority?: Priority\n /**\n * Dedup key. Scheduling again with the same key replaces the pending task's\n * callback (latest wins) instead of enqueuing a second one.\n */\n key?: string\n}\n\n/** Safety valve: a single {@link Scheduler.flushSync} that drains more than this many tasks is\n * treated as a runaway re-scheduling loop and aborted (mirrors the reactive flush guard). */\nconst MAX_DRAIN_ITERATIONS = 100_000\n\n/** A handle to a scheduled task. */\nexport interface ScheduledTask {\n /** Remove the task if it hasn't run yet. Idempotent. */\n cancel(): void\n /** Whether the task is still pending (not yet run or cancelled). */\n readonly pending: boolean\n}\n\ninterface Entry {\n key: string | null\n fn: Task | null // null once cancelled\n}\n\n/** Options for {@link Scheduler}. */\nexport interface SchedulerOptions {\n /**\n * Called with any error thrown by a task. If omitted, errors are rethrown\n * asynchronously (so they surface to the host without aborting the flush).\n */\n onError?: (error: unknown) => void\n /**\n * Schedules a microtask. Injectable for testing; defaults to `queueMicrotask`.\n */\n scheduleMicrotask?: (cb: () => void) => void\n}\n\nconst defaultScheduleMicrotask: (cb: () => void) => void =\n typeof queueMicrotask === 'function'\n ? queueMicrotask\n : (cb) => {\n void Promise.resolve().then(cb)\n }\n\n/**\n * A deterministic two-lane priority scheduler. Create one with {@link createScheduler}.\n */\nexport class Scheduler {\n private readonly sync: Entry[] = []\n private readonly normal: Entry[] = []\n private readonly keyed = new Map<string, Entry>()\n private microtaskQueued = false\n private flushing = false\n private readonly onError: ((error: unknown) => void) | undefined\n private readonly scheduleMicrotask: (cb: () => void) => void\n\n constructor(options?: SchedulerOptions) {\n this.onError = options?.onError\n this.scheduleMicrotask = options?.scheduleMicrotask ?? defaultScheduleMicrotask\n }\n\n /** Schedule `task`. Returns a handle to cancel it or check its status. */\n schedule(task: Task, options?: ScheduleOptions): ScheduledTask {\n const priority = options?.priority ?? 'normal'\n const key = options?.key ?? null\n\n if (key !== null) {\n const existing = this.keyed.get(key)\n if (existing && existing.fn !== null) {\n // Dedup: replace the callback, keep the existing queue position.\n existing.fn = task\n return this.makeHandle(existing)\n }\n }\n\n const entry: Entry = { key, fn: task }\n if (key !== null) this.keyed.set(key, entry)\n ;(priority === 'sync' ? this.sync : this.normal).push(entry)\n this.requestFlush()\n return this.makeHandle(entry)\n }\n\n /** Run all pending tasks right now (sync lane first), draining both lanes. */\n flushSync(): void {\n if (this.flushing) return\n this.flushing = true\n try {\n // Drain in priority order. Re-check each loop so tasks scheduled by tasks\n // (e.g. a sync task that queues normal work) are handled in this flush.\n let iterations = 0\n while (this.sync.length > 0 || this.normal.length > 0) {\n // Safety valve: a task that perpetually re-schedules (e.g. two deferred reactive effects\n // writing each other's deps) would drain forever. Cap it, clear both lanes, and surface\n // an error — mirroring the reactive loop guard so a deferred cycle fails loudly, not hangs.\n if (++iterations > MAX_DRAIN_ITERATIONS) {\n this.sync.length = 0\n this.normal.length = 0\n this.keyed.clear()\n this.run(() => {\n throw new Error(\n 'MindeesNative: potential infinite scheduler loop — a task kept re-scheduling itself.',\n )\n })\n return\n }\n const entry = this.sync.length > 0 ? this.sync.shift() : this.normal.shift()\n if (!entry) continue\n this.clearKey(entry)\n const fn = entry.fn\n entry.fn = null\n if (fn) this.run(fn)\n }\n } finally {\n this.flushing = false\n this.microtaskQueued = false\n }\n }\n\n /** Number of pending tasks across both lanes (cancelled tasks excluded). */\n get size(): number {\n let n = 0\n for (const e of this.sync) if (e.fn !== null) n++\n for (const e of this.normal) if (e.fn !== null) n++\n return n\n }\n\n private requestFlush(): void {\n if (this.microtaskQueued || this.flushing) return\n this.microtaskQueued = true\n this.scheduleMicrotask(() => {\n this.microtaskQueued = false\n this.flushSync()\n })\n }\n\n private run(fn: Task): void {\n try {\n fn()\n } catch (error) {\n if (this.onError) {\n try {\n this.onError(error)\n } catch (hookError) {\n // A throwing onError hook must not abort the flush or strand the tasks\n // queued after it; surface it asynchronously like an unhandled task error.\n this.scheduleMicrotask(() => {\n throw hookError\n })\n }\n } else {\n // Surface without aborting the flush.\n this.scheduleMicrotask(() => {\n throw error\n })\n }\n }\n }\n\n /**\n * Remove an entry's dedup-key mapping, but only if the map still points at THIS\n * entry. Keys are reused over time (a 'render' key is scheduled, runs, then\n * scheduled again), so a stale handle or an already-dequeued entry must never\n * evict a newer live entry's mapping — doing so would break dedup and let two\n * same-key tasks both run.\n */\n private clearKey(entry: Entry): void {\n if (entry.key !== null && this.keyed.get(entry.key) === entry) {\n this.keyed.delete(entry.key)\n }\n }\n\n private makeHandle(entry: Entry): ScheduledTask {\n return {\n cancel: () => {\n entry.fn = null\n this.clearKey(entry)\n },\n get pending() {\n return entry.fn !== null\n },\n }\n }\n}\n\n/** Create a new {@link Scheduler}. */\nexport function createScheduler(options?: SchedulerOptions): Scheduler {\n return new Scheduler(options)\n}\n"],"mappings":";;;AAqCA,MAAM,uBAAuB;AA4B7B,MAAM,2BACJ,OAAO,mBAAmB,aACtB,kBACC,OAAO;CACN,QAAa,QAAQ,EAAE,KAAK,EAAE;AAChC;;;;AAKN,IAAa,YAAb,MAAuB;CACrB,OAAiC,CAAC;CAClC,SAAmC,CAAC;CACpC,wBAAyB,IAAI,IAAmB;CAChD,kBAA0B;CAC1B,WAAmB;CACnB;CACA;CAEA,YAAY,SAA4B;EACtC,KAAK,UAAU,SAAS;EACxB,KAAK,oBAAoB,SAAS,qBAAqB;CACzD;;CAGA,SAAS,MAAY,SAA0C;EAC7D,MAAM,WAAW,SAAS,YAAY;EACtC,MAAM,MAAM,SAAS,OAAO;EAE5B,IAAI,QAAQ,MAAM;GAChB,MAAM,WAAW,KAAK,MAAM,IAAI,GAAG;GACnC,IAAI,YAAY,SAAS,OAAO,MAAM;IAEpC,SAAS,KAAK;IACd,OAAO,KAAK,WAAW,QAAQ;GACjC;EACF;EAEA,MAAM,QAAe;GAAE;GAAK,IAAI;EAAK;EACrC,IAAI,QAAQ,MAAM,KAAK,MAAM,IAAI,KAAK,KAAK;EAC1C,CAAC,aAAa,SAAS,KAAK,OAAO,KAAK,QAAQ,KAAK,KAAK;EAC3D,KAAK,aAAa;EAClB,OAAO,KAAK,WAAW,KAAK;CAC9B;;CAGA,YAAkB;EAChB,IAAI,KAAK,UAAU;EACnB,KAAK,WAAW;EAChB,IAAI;GAGF,IAAI,aAAa;GACjB,OAAO,KAAK,KAAK,SAAS,KAAK,KAAK,OAAO,SAAS,GAAG;IAIrD,IAAI,EAAE,aAAa,sBAAsB;KACvC,KAAK,KAAK,SAAS;KACnB,KAAK,OAAO,SAAS;KACrB,KAAK,MAAM,MAAM;KACjB,KAAK,UAAU;MACb,MAAM,IAAI,MACR,sFACF;KACF,CAAC;KACD;IACF;IACA,MAAM,QAAQ,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,OAAO,MAAM;IAC3E,IAAI,CAAC,OAAO;IACZ,KAAK,SAAS,KAAK;IACnB,MAAM,KAAK,MAAM;IACjB,MAAM,KAAK;IACX,IAAI,IAAI,KAAK,IAAI,EAAE;GACrB;EACF,UAAU;GACR,KAAK,WAAW;GAChB,KAAK,kBAAkB;EACzB;CACF;;CAGA,IAAI,OAAe;EACjB,IAAI,IAAI;EACR,KAAK,MAAM,KAAK,KAAK,MAAM,IAAI,EAAE,OAAO,MAAM;EAC9C,KAAK,MAAM,KAAK,KAAK,QAAQ,IAAI,EAAE,OAAO,MAAM;EAChD,OAAO;CACT;CAEA,eAA6B;EAC3B,IAAI,KAAK,mBAAmB,KAAK,UAAU;EAC3C,KAAK,kBAAkB;EACvB,KAAK,wBAAwB;GAC3B,KAAK,kBAAkB;GACvB,KAAK,UAAU;EACjB,CAAC;CACH;CAEA,IAAY,IAAgB;EAC1B,IAAI;GACF,GAAG;EACL,SAAS,OAAO;GACd,IAAI,KAAK,SACP,IAAI;IACF,KAAK,QAAQ,KAAK;GACpB,SAAS,WAAW;IAGlB,KAAK,wBAAwB;KAC3B,MAAM;IACR,CAAC;GACH;QAGA,KAAK,wBAAwB;IAC3B,MAAM;GACR,CAAC;EAEL;CACF;;;;;;;;CASA,SAAiB,OAAoB;EACnC,IAAI,MAAM,QAAQ,QAAQ,KAAK,MAAM,IAAI,MAAM,GAAG,MAAM,OACtD,KAAK,MAAM,OAAO,MAAM,GAAG;CAE/B;CAEA,WAAmB,OAA6B;EAC9C,OAAO;GACL,cAAc;IACZ,MAAM,KAAK;IACX,KAAK,SAAS,KAAK;GACrB;GACA,IAAI,UAAU;IACZ,OAAO,MAAM,OAAO;GACtB;EACF;CACF;AACF;;AAGA,SAAgB,gBAAgB,SAAuC;CACrE,OAAO,IAAI,UAAU,OAAO;AAC9B"}
1
+ {"version":3,"file":"scheduler.js","names":[],"sources":["../../src/scheduler/scheduler.ts"],"sourcesContent":["/**\n * MindeesNative scheduler — a small, deterministic priority scheduler.\n *\n * Two lanes:\n * - **`sync`** — high-priority work (interaction handlers, first frame). Drained\n * synchronously at the next flush point and always before the normal lane.\n * - **`normal`** — default work, drained on a microtask so multiple schedules in\n * the same tick coalesce into one flush.\n *\n * Tasks are **cancellable** (via the returned handle) and **dedupable** (two\n * tasks scheduled with the same `key` collapse to one — the latest callback\n * wins, preserving the earlier queue position). The scheduler never throws from\n * a task into the caller: task errors are collected and reported via an optional\n * `onError` hook, so one bad task can't stop the rest of the flush.\n *\n * @module\n */\n\n/** Scheduling lanes, highest priority first. */\nexport type Priority = 'sync' | 'normal'\n\n/** A unit of scheduled work. */\nexport type Task = () => void\n\n/** Options for {@link Scheduler.schedule}. */\nexport interface ScheduleOptions {\n /** Lane to run in. Defaults to `'normal'`. */\n priority?: Priority\n /**\n * Dedup key. Scheduling again with the same key replaces the pending task's\n * callback (latest wins) instead of enqueuing a second one.\n */\n key?: string\n}\n\n/** Safety valve: a single {@link Scheduler.flushSync} that drains more than this many tasks is\n * treated as a runaway re-scheduling loop and aborted (mirrors the reactive flush guard). */\nconst MAX_DRAIN_ITERATIONS = 100_000\n\n/** A handle to a scheduled task. */\nexport interface ScheduledTask {\n /** Remove the task if it hasn't run yet. Idempotent. */\n cancel(): void\n /** Whether the task is still pending (not yet run or cancelled). */\n readonly pending: boolean\n}\n\ninterface Entry {\n key: string | null\n fn: Task | null // null once cancelled\n lane: Priority // which lane this entry lives in (so a re-key with a changed priority can relocate it)\n}\n\n/** Options for {@link Scheduler}. */\nexport interface SchedulerOptions {\n /**\n * Called with any error thrown by a task. If omitted, errors are rethrown\n * asynchronously (so they surface to the host without aborting the flush).\n */\n onError?: (error: unknown) => void\n /**\n * Schedules a microtask. Injectable for testing; defaults to `queueMicrotask`.\n */\n scheduleMicrotask?: (cb: () => void) => void\n}\n\nconst defaultScheduleMicrotask: (cb: () => void) => void =\n typeof queueMicrotask === 'function'\n ? queueMicrotask\n : (cb) => {\n void Promise.resolve().then(cb)\n }\n\n/**\n * A deterministic two-lane priority scheduler. Create one with {@link createScheduler}.\n */\nexport class Scheduler {\n private readonly sync: Entry[] = []\n private readonly normal: Entry[] = []\n private readonly keyed = new Map<string, Entry>()\n private microtaskQueued = false\n private flushing = false\n private readonly onError: ((error: unknown) => void) | undefined\n private readonly scheduleMicrotask: (cb: () => void) => void\n\n constructor(options?: SchedulerOptions) {\n this.onError = options?.onError\n this.scheduleMicrotask = options?.scheduleMicrotask ?? defaultScheduleMicrotask\n }\n\n /** Schedule `task`. Returns a handle to cancel it or check its status. */\n schedule(task: Task, options?: ScheduleOptions): ScheduledTask {\n const priority = options?.priority ?? 'normal'\n const key = options?.key ?? null\n\n if (key !== null) {\n const existing = this.keyed.get(key)\n if (existing && existing.fn !== null) {\n if (existing.lane === priority) {\n // Same lane → replace the callback, keep the existing queue position (latest callback wins).\n existing.fn = task\n return this.makeHandle(existing)\n }\n // Changed priority → relocate to the requested lane so the latest PRIORITY wins too: tombstone\n // the old-lane entry (its slot is skipped via the null fn) and push a fresh one.\n existing.fn = null\n const moved: Entry = { key, fn: task, lane: priority }\n this.keyed.set(key, moved)\n ;(priority === 'sync' ? this.sync : this.normal).push(moved)\n this.requestFlush()\n return this.makeHandle(moved)\n }\n }\n\n const entry: Entry = { key, fn: task, lane: priority }\n if (key !== null) this.keyed.set(key, entry)\n ;(priority === 'sync' ? this.sync : this.normal).push(entry)\n this.requestFlush()\n return this.makeHandle(entry)\n }\n\n /** Run all pending tasks right now (sync lane first), draining both lanes. */\n flushSync(): void {\n if (this.flushing) return\n this.flushing = true\n let overflowed = false\n try {\n // Drain in priority order. Re-check each loop so tasks scheduled by tasks\n // (e.g. a sync task that queues normal work) are handled in this flush.\n let iterations = 0\n while (this.sync.length > 0 || this.normal.length > 0) {\n // Safety valve: a task that perpetually re-schedules (e.g. two deferred reactive effects\n // writing each other's deps) would drain forever. Cap it, clear both lanes, and surface\n // an error — mirroring the reactive loop guard so a deferred cycle fails loudly, not hangs.\n if (++iterations > MAX_DRAIN_ITERATIONS) {\n this.sync.length = 0\n this.normal.length = 0\n this.keyed.clear()\n overflowed = true\n break\n }\n const entry = this.sync.length > 0 ? this.sync.shift() : this.normal.shift()\n if (!entry) continue\n this.clearKey(entry)\n const fn = entry.fn\n entry.fn = null\n if (fn) this.run(fn)\n }\n } finally {\n this.flushing = false\n this.microtaskQueued = false\n }\n if (overflowed) {\n // Report the overflow OUTSIDE the flushing window: if `onError` schedules a recovery task,\n // requestFlush() would no-op while `flushing` is true and strand it — now it arms a fresh flush.\n this.run(() => {\n throw new Error(\n 'MindeesNative: potential infinite scheduler loop — a task kept re-scheduling itself.',\n )\n })\n }\n }\n\n /** Number of pending tasks across both lanes (cancelled tasks excluded). */\n get size(): number {\n let n = 0\n for (const e of this.sync) if (e.fn !== null) n++\n for (const e of this.normal) if (e.fn !== null) n++\n return n\n }\n\n private requestFlush(): void {\n if (this.microtaskQueued || this.flushing) return\n this.microtaskQueued = true\n this.scheduleMicrotask(() => {\n this.microtaskQueued = false\n this.flushSync()\n })\n }\n\n private run(fn: Task): void {\n try {\n fn()\n } catch (error) {\n if (this.onError) {\n try {\n this.onError(error)\n } catch (hookError) {\n // A throwing onError hook must not abort the flush or strand the tasks\n // queued after it; surface it asynchronously like an unhandled task error.\n this.scheduleMicrotask(() => {\n throw hookError\n })\n }\n } else {\n // Surface without aborting the flush.\n this.scheduleMicrotask(() => {\n throw error\n })\n }\n }\n }\n\n /**\n * Remove an entry's dedup-key mapping, but only if the map still points at THIS\n * entry. Keys are reused over time (a 'render' key is scheduled, runs, then\n * scheduled again), so a stale handle or an already-dequeued entry must never\n * evict a newer live entry's mapping — doing so would break dedup and let two\n * same-key tasks both run.\n */\n private clearKey(entry: Entry): void {\n if (entry.key !== null && this.keyed.get(entry.key) === entry) {\n this.keyed.delete(entry.key)\n }\n }\n\n private makeHandle(entry: Entry): ScheduledTask {\n return {\n cancel: () => {\n entry.fn = null\n this.clearKey(entry)\n },\n get pending() {\n return entry.fn !== null\n },\n }\n }\n}\n\n/** Create a new {@link Scheduler}. */\nexport function createScheduler(options?: SchedulerOptions): Scheduler {\n return new Scheduler(options)\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindees/core",
3
- "version": "0.22.5",
3
+ "version": "0.22.6",
4
4
  "description": "MindeesNative core — fine-grained reactivity (signals/computed/effect/batch), component model with selector-isolated context, priority scheduler, and a thread-pool abstraction (Web Worker + inline; native is a research track).",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "type": "module",