{"_id":"@affino/projection-engine","_rev":"2-137fa2666cea33e0f4151a971550fd43","name":"@affino/projection-engine","dist-tags":{"latest":"0.2.2"},"versions":{"0.2.1":{"name":"@affino/projection-engine","version":"0.2.1","keywords":["affino","projection","stage-graph","core","internal"],"author":{"name":"Anton Pavlov","email":"a.pavlov@affino.dev"},"license":"MIT","_id":"@affino/projection-engine@0.2.1","maintainers":[{"name":"affino","email":"anton.pavlov.personal@gmail.com"}],"homepage":"https://affino.dev","bugs":{"url":"https://github.com/affinio/affinio/issues"},"dist":{"shasum":"8bdc04ed2864e6571a65bdf607d85e6e8a6df501","tarball":"https://registry.npmjs.org/@affino/projection-engine/-/projection-engine-0.2.1.tgz","fileCount":6,"integrity":"sha512-aYtDf+L80GnyXgibeU7NuwcneIU/gluLZYKNVPFqZyHurjSNfc2CqN8vig+y9zl4Q1AsVZBWY2DHPwsWMqyvaA==","signatures":[{"sig":"MEQCICYkUl4UQfhb7VblyY7rtUQOKBPu2HkTjJ4+eY7B5pfcAiAhI088etQjyUYK8u0fo4Y4E75x9iMbUjlX6xEYUGunzA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":23555},"main":"dist/index.js","type":"module","_from":"file:affino-projection-engine-0.2.1.tgz","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"scripts":{"test":"vitest run","build":"tsc -p tsconfig.json"},"_npmUser":{"name":"affino","email":"anton.pavlov.personal@gmail.com"},"_resolved":"/tmp/c9e36652228faaa2c93a633ae1f40ab2/affino-projection-engine-0.2.1.tgz","_integrity":"sha512-aYtDf+L80GnyXgibeU7NuwcneIU/gluLZYKNVPFqZyHurjSNfc2CqN8vig+y9zl4Q1AsVZBWY2DHPwsWMqyvaA==","repository":{"url":"git+https://github.com/affinio/affinio.git#main","type":"git"},"_npmVersion":"10.8.2","description":"Internal projection stage graph runtime for Affino headless cores","directories":{},"sideEffects":false,"_nodeVersion":"20.20.0","_hasShrinkwrap":false,"devDependencies":{"vitest":"^4.0.15"},"_npmOperationalInternal":{"tmp":"tmp/projection-engine_0.2.1_1771764135520_0.34325482161609044","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@affino/projection-engine","version":"0.2.2","author":{"name":"Anton Pavlov","email":"a.pavlov@affino.dev"},"type":"module","description":"Projection stage graph engine for Affino data pipelines","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"main":"dist/index.js","types":"dist/index.d.ts","sideEffects":false,"license":"MIT","devDependencies":{"vitest":"^4.0.15"},"repository":{"type":"git","url":"git+https://github.com/affinio/affinio.git","directory":"packages/projection-engine"},"homepage":"https://github.com/affinio/affinio/tree/main/packages/projection-engine#readme","bugs":{"url":"https://github.com/affinio/affinio/issues"},"keywords":["affino","projection","stage-graph","core","internal"],"module":"dist/index.js","scripts":{"build":"tsc -p tsconfig.json","test":"vitest run"},"_id":"@affino/projection-engine@0.2.2","_integrity":"sha512-nHw1bl3gkZeq2R6y7T5O4tZ3ur9XCBR9MY8BQy7wIWBEzIs26R54+KYim81AFVNc4zFwl0Iyr2nI6Yg4Lo/QGA==","_resolved":"/tmp/bba13163a744f8fe7a03dd7d3eca89bb/affino-projection-engine-0.2.2.tgz","_from":"file:affino-projection-engine-0.2.2.tgz","_nodeVersion":"20.20.0","_npmVersion":"10.8.2","dist":{"integrity":"sha512-nHw1bl3gkZeq2R6y7T5O4tZ3ur9XCBR9MY8BQy7wIWBEzIs26R54+KYim81AFVNc4zFwl0Iyr2nI6Yg4Lo/QGA==","shasum":"742a9e715f65dca33a1586612d48b05089f2e2b3","tarball":"https://registry.npmjs.org/@affino/projection-engine/-/projection-engine-0.2.2.tgz","fileCount":6,"unpackedSize":24783,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQC5hAq2N/hHeRo8eZcFOWzezEN2ZkRTdVqtwHqzUqinmQIhAKwUuhQ6vY97vmqR3vJevvAp5fMUm2rOk0BObZZkEV7y"}]},"_npmUser":{"name":"affino","email":"anton.pavlov.personal@gmail.com"},"directories":{},"maintainers":[{"name":"affino","email":"anton.pavlov.personal@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/projection-engine_0.2.2_1773840870037_0.6032997505329076"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-22T12:42:15.446Z","modified":"2026-03-18T13:34:30.334Z","0.2.1":"2026-02-22T12:42:15.683Z","0.2.2":"2026-03-18T13:34:30.190Z"},"bugs":{"url":"https://github.com/affinio/affinio/issues"},"author":{"name":"Anton Pavlov","email":"a.pavlov@affino.dev"},"license":"MIT","homepage":"https://github.com/affinio/affinio/tree/main/packages/projection-engine#readme","keywords":["affino","projection","stage-graph","core","internal"],"repository":{"type":"git","url":"git+https://github.com/affinio/affinio.git","directory":"packages/projection-engine"},"description":"Projection stage graph engine for Affino data pipelines","maintainers":[{"name":"affino","email":"anton.pavlov.personal@gmail.com"}],"readme":"# @affino/projection-engine\n\nSmall, framework-agnostic runtime for projection pipelines modeled as a declarative DAG.\n\nDesigned for Affino core packages (`datagrid-core`, `treeview-core`, and future engines) where data transformations are split into dependent stages (for example: filter -> sort -> group -> paginate -> visible).\n\nThink of it as a minimal spreadsheet-style recalculation engine for ordered projection pipelines.\n\n## Problem It Solves\n\nWhen a source update happens, not every stage must be recomputed. This package provides:\n\n- stage dependency graph with automatic topological execution order\n- dirty stage propagation from requested roots\n- selective recompute with optional blocked stages\n- stale tracking (`requested > computed`) for diagnostics and deferred healing\n\n## Core Model\n\nGraph is declared by nodes and upstream dependencies:\n\n```ts\nconst graph = {\n  nodes: {\n    filter: {},\n    sort: { dependsOn: [\"filter\"] },\n    group: { dependsOn: [\"sort\"] },\n    paginate: { dependsOn: [\"group\"] },\n    visible: { dependsOn: [\"paginate\"] },\n  },\n} as const\n```\n\nEngine derives:\n\n- `stageOrder` (topological order)\n- `dependents` (downstream edges)\n- `downstreamByStage` closure for fast invalidation\n\n## API\n\n### `createProjectionStageEngine(options)`\n\nCreates runtime with:\n\n- `requestStages(stages, { trackRequested? })`\n- `requestRefreshPass()`\n- `recompute(executeStage, { blockedStages? })`\n- `recomputeFromStage(stage, executeStage, options?)`\n- `expandStages(stages)`\n- `getStaleStages()`\n\nOptional: pass `preparedGraph` from `prepareProjectionStageGraph(...)` to reuse prevalidated topology in engine creation.\n\n### `prepareProjectionStageGraph(graph, options?)`\n\nPrepares and validates graph once. Use this for repeated expansion operations to avoid repeated graph resolution/validation.\n\n### `expandProjectionStages(stages, graphOrPreparedGraph)`\n\nReturns downstream closure for the requested roots.\n\nImportant contract:\n\n- expansion is **inclusive**, so each requested stage is included in result together with its downstream dependents.\n\n## Example\n\n```ts\nimport { createProjectionStageEngine } from \"@affino/projection-engine\"\n\ntype Stage = \"filter\" | \"sort\" | \"group\" | \"visible\"\n\nconst engine = createProjectionStageEngine<Stage>({\n  nodes: {\n    filter: {},\n    sort: { dependsOn: [\"filter\"] },\n    group: { dependsOn: [\"sort\"] },\n    visible: { dependsOn: [\"group\"] },\n  },\n  refreshEntryStage: \"filter\",\n})\n\nconst state = {\n  filterApplied: false,\n  sortApplied: false,\n  groupApplied: false,\n  visibleApplied: false,\n}\n\nfunction executeStage(stage: Stage, shouldRecompute: boolean): boolean {\n  if (!shouldRecompute) {\n    return false\n  }\n  if (stage === \"filter\") state.filterApplied = true\n  if (stage === \"sort\") state.sortApplied = true\n  if (stage === \"group\") state.groupApplied = true\n  if (stage === \"visible\") state.visibleApplied = true\n  return true\n}\n\n// 1) Incoming row update affects filter roots\nengine.requestStages([\"filter\"])\n\n// 2) Run recompute cycle\nconst meta = engine.recompute(executeStage)\n// meta.recomputedStages -> [\"filter\", \"sort\", \"group\", \"visible\"]\n\n// 3) Deferred mode: block sort/group for one cycle\nengine.requestStages([\"filter\"])\nengine.recompute(executeStage, { blockedStages: [\"sort\", \"group\"] })\n\n// 4) Check unresolved stages and heal later\nconst stale = engine.getStaleStages()\nif (stale.length > 0) {\n  engine.recompute(executeStage)\n}\n```\n\n## Recompute Contract\n\n`recompute` calls `executeStage(stage, shouldRecompute)` only for dirty stages in topological order.\n\n- `shouldRecompute = false` for blocked stages\n- if a stage is stale (`requested > computed`) and not actually recomputed, it stays dirty\n- if a stage is actually recomputed, `computed` catches up to `requested`\n\nThis allows deferred projection updates without losing consistency diagnostics.\n\n## Validation Rules\n\nGraph validation fails fast on:\n\n- empty graph\n- unknown dependency references\n- duplicate dependencies in one node\n- cycles\n- `refreshEntryStage` not present in graph\n\n## Intended Usage\n\nUse this package in core engines where:\n\n- projection is a DAG\n- updates frequently touch only part of the pipeline\n- you need deterministic recompute and stale diagnostics\n\nDesigned for small to medium DAGs (typically 5-50 stages).\n\n## Non-goals (Current)\n\n- async stage orchestration\n- built-in memoization/hashing of stage inputs\n- domain-specific projection logic\n\n## Build / Test\n\n```bash\npnpm --filter @affino/projection-engine build\npnpm --filter @affino/projection-engine test\n```\n","readmeFilename":"README.md"}