@substrate-ai/core 0.19.54
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/README.md +55 -0
- package/dist/__tests__/adapter.test.d.ts +12 -0
- package/dist/__tests__/adapter.test.d.ts.map +1 -0
- package/dist/__tests__/adapter.test.js +259 -0
- package/dist/__tests__/adapter.test.js.map +1 -0
- package/dist/__tests__/event-bus.test.d.ts +14 -0
- package/dist/__tests__/event-bus.test.d.ts.map +1 -0
- package/dist/__tests__/event-bus.test.js +199 -0
- package/dist/__tests__/event-bus.test.js.map +1 -0
- package/dist/__tests__/output-quality.test.d.ts +8 -0
- package/dist/__tests__/output-quality.test.d.ts.map +1 -0
- package/dist/__tests__/output-quality.test.js +166 -0
- package/dist/__tests__/output-quality.test.js.map +1 -0
- package/dist/__tests__/schema-suffix.test.d.ts +9 -0
- package/dist/__tests__/schema-suffix.test.d.ts.map +1 -0
- package/dist/__tests__/schema-suffix.test.js +126 -0
- package/dist/__tests__/schema-suffix.test.js.map +1 -0
- package/dist/__tests__/yaml-parser.test.d.ts +18 -0
- package/dist/__tests__/yaml-parser.test.d.ts.map +1 -0
- package/dist/__tests__/yaml-parser.test.js +475 -0
- package/dist/__tests__/yaml-parser.test.js.map +1 -0
- package/dist/__type-checks__.d.ts +11 -0
- package/dist/__type-checks__.d.ts.map +1 -0
- package/dist/__type-checks__.js +19 -0
- package/dist/__type-checks__.js.map +1 -0
- package/dist/adapters/__tests__/adapter-output-normalizer.test.d.ts +12 -0
- package/dist/adapters/__tests__/adapter-output-normalizer.test.d.ts.map +1 -0
- package/dist/adapters/__tests__/adapter-output-normalizer.test.js +193 -0
- package/dist/adapters/__tests__/adapter-output-normalizer.test.js.map +1 -0
- package/dist/adapters/adapter-format-error.d.ts +35 -0
- package/dist/adapters/adapter-format-error.d.ts.map +1 -0
- package/dist/adapters/adapter-format-error.js +38 -0
- package/dist/adapters/adapter-format-error.js.map +1 -0
- package/dist/adapters/adapter-output-normalizer.d.ts +50 -0
- package/dist/adapters/adapter-output-normalizer.d.ts.map +1 -0
- package/dist/adapters/adapter-output-normalizer.js +233 -0
- package/dist/adapters/adapter-output-normalizer.js.map +1 -0
- package/dist/adapters/adapter-registry.d.ts +50 -0
- package/dist/adapters/adapter-registry.d.ts.map +1 -0
- package/dist/adapters/adapter-registry.js +101 -0
- package/dist/adapters/adapter-registry.js.map +1 -0
- package/dist/adapters/claude-adapter.d.ts +59 -0
- package/dist/adapters/claude-adapter.d.ts.map +1 -0
- package/dist/adapters/claude-adapter.js +367 -0
- package/dist/adapters/claude-adapter.js.map +1 -0
- package/dist/adapters/codex-adapter.d.ts +64 -0
- package/dist/adapters/codex-adapter.d.ts.map +1 -0
- package/dist/adapters/codex-adapter.js +263 -0
- package/dist/adapters/codex-adapter.js.map +1 -0
- package/dist/adapters/gemini-adapter.d.ts +57 -0
- package/dist/adapters/gemini-adapter.d.ts.map +1 -0
- package/dist/adapters/gemini-adapter.js +311 -0
- package/dist/adapters/gemini-adapter.js.map +1 -0
- package/dist/adapters/index.d.ts +10 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +14 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/schemas.d.ts +137 -0
- package/dist/adapters/schemas.d.ts.map +1 -0
- package/dist/adapters/schemas.js +140 -0
- package/dist/adapters/schemas.js.map +1 -0
- package/dist/adapters/types.d.ts +245 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +6 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/adapters/worker-adapter.d.ts +188 -0
- package/dist/adapters/worker-adapter.d.ts.map +1 -0
- package/dist/adapters/worker-adapter.js +19 -0
- package/dist/adapters/worker-adapter.js.map +1 -0
- package/dist/budget/budget-tracker.d.ts +22 -0
- package/dist/budget/budget-tracker.d.ts.map +1 -0
- package/dist/budget/budget-tracker.js +39 -0
- package/dist/budget/budget-tracker.js.map +1 -0
- package/dist/budget/index.d.ts +6 -0
- package/dist/budget/index.d.ts.map +1 -0
- package/dist/budget/index.js +5 -0
- package/dist/budget/index.js.map +1 -0
- package/dist/config/config-migrator.d.ts +58 -0
- package/dist/config/config-migrator.d.ts.map +1 -0
- package/dist/config/config-migrator.js +158 -0
- package/dist/config/config-migrator.js.map +1 -0
- package/dist/config/config-system-impl.d.ts +63 -0
- package/dist/config/config-system-impl.d.ts.map +1 -0
- package/dist/config/config-system-impl.js +364 -0
- package/dist/config/config-system-impl.js.map +1 -0
- package/dist/config/config-watcher.d.ts +59 -0
- package/dist/config/config-watcher.d.ts.map +1 -0
- package/dist/config/config-watcher.js +137 -0
- package/dist/config/config-watcher.js.map +1 -0
- package/dist/config/defaults.d.ts +13 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +62 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/errors.d.ts +25 -0
- package/dist/config/errors.d.ts.map +1 -0
- package/dist/config/errors.js +49 -0
- package/dist/config/errors.js.map +1 -0
- package/dist/config/index.d.ts +19 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +39 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/types.d.ts +456 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +174 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config/version-utils.d.ts +39 -0
- package/dist/config/version-utils.d.ts.map +1 -0
- package/dist/config/version-utils.js +66 -0
- package/dist/config/version-utils.js.map +1 -0
- package/dist/context/index.d.ts +2 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +2 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/types.d.ts +113 -0
- package/dist/context/types.d.ts.map +1 -0
- package/dist/context/types.js +59 -0
- package/dist/context/types.js.map +1 -0
- package/dist/cost-tracker/cost-tracker-impl.d.ts +51 -0
- package/dist/cost-tracker/cost-tracker-impl.d.ts.map +1 -0
- package/dist/cost-tracker/cost-tracker-impl.js +85 -0
- package/dist/cost-tracker/cost-tracker-impl.js.map +1 -0
- package/dist/cost-tracker/cost-tracker-subscriber.d.ts +31 -0
- package/dist/cost-tracker/cost-tracker-subscriber.d.ts.map +1 -0
- package/dist/cost-tracker/cost-tracker-subscriber.js +116 -0
- package/dist/cost-tracker/cost-tracker-subscriber.js.map +1 -0
- package/dist/cost-tracker/index.d.ts +11 -0
- package/dist/cost-tracker/index.d.ts.map +1 -0
- package/dist/cost-tracker/index.js +7 -0
- package/dist/cost-tracker/index.js.map +1 -0
- package/dist/cost-tracker/token-rates.d.ts +25 -0
- package/dist/cost-tracker/token-rates.d.ts.map +1 -0
- package/dist/cost-tracker/token-rates.js +99 -0
- package/dist/cost-tracker/token-rates.js.map +1 -0
- package/dist/cost-tracker/types.d.ts +6 -0
- package/dist/cost-tracker/types.d.ts.map +1 -0
- package/dist/cost-tracker/types.js +2 -0
- package/dist/cost-tracker/types.js.map +1 -0
- package/dist/dispatch/dispatcher-impl.d.ts +92 -0
- package/dist/dispatch/dispatcher-impl.d.ts.map +1 -0
- package/dist/dispatch/dispatcher-impl.js +847 -0
- package/dist/dispatch/dispatcher-impl.js.map +1 -0
- package/dist/dispatch/index.d.ts +15 -0
- package/dist/dispatch/index.d.ts.map +1 -0
- package/dist/dispatch/index.js +14 -0
- package/dist/dispatch/index.js.map +1 -0
- package/dist/dispatch/interface-change-detector.d.ts +46 -0
- package/dist/dispatch/interface-change-detector.d.ts.map +1 -0
- package/dist/dispatch/interface-change-detector.js +135 -0
- package/dist/dispatch/interface-change-detector.js.map +1 -0
- package/dist/dispatch/output-quality.d.ts +43 -0
- package/dist/dispatch/output-quality.d.ts.map +1 -0
- package/dist/dispatch/output-quality.js +148 -0
- package/dist/dispatch/output-quality.js.map +1 -0
- package/dist/dispatch/types.d.ts +271 -0
- package/dist/dispatch/types.d.ts.map +1 -0
- package/dist/dispatch/types.js +76 -0
- package/dist/dispatch/types.js.map +1 -0
- package/dist/dispatch/yaml-parser.d.ts +40 -0
- package/dist/dispatch/yaml-parser.d.ts.map +1 -0
- package/dist/dispatch/yaml-parser.js +323 -0
- package/dist/dispatch/yaml-parser.js.map +1 -0
- package/dist/events/core-events.d.ts +288 -0
- package/dist/events/core-events.d.ts.map +1 -0
- package/dist/events/core-events.js +10 -0
- package/dist/events/core-events.js.map +1 -0
- package/dist/events/event-bus.d.ts +55 -0
- package/dist/events/event-bus.d.ts.map +1 -0
- package/dist/events/event-bus.js +52 -0
- package/dist/events/event-bus.js.map +1 -0
- package/dist/events/index.d.ts +9 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +6 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/types.d.ts +21 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/events/types.js +8 -0
- package/dist/events/types.js.map +1 -0
- package/dist/git/git-manager.d.ts +31 -0
- package/dist/git/git-manager.d.ts.map +1 -0
- package/dist/git/git-manager.js +46 -0
- package/dist/git/git-manager.js.map +1 -0
- package/dist/git/git-utils.d.ts +166 -0
- package/dist/git/git-utils.d.ts.map +1 -0
- package/dist/git/git-utils.js +347 -0
- package/dist/git/git-utils.js.map +1 -0
- package/dist/git/git-worktree-manager-impl.d.ts +58 -0
- package/dist/git/git-worktree-manager-impl.d.ts.map +1 -0
- package/dist/git/git-worktree-manager-impl.js +336 -0
- package/dist/git/git-worktree-manager-impl.js.map +1 -0
- package/dist/git/git-worktree-manager.d.ts +122 -0
- package/dist/git/git-worktree-manager.d.ts.map +1 -0
- package/dist/git/git-worktree-manager.js +14 -0
- package/dist/git/git-worktree-manager.js.map +1 -0
- package/dist/git/index.d.ts +11 -0
- package/dist/git/index.d.ts.map +1 -0
- package/dist/git/index.js +8 -0
- package/dist/git/index.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/client.d.ts +42 -0
- package/dist/llm/client.d.ts.map +1 -0
- package/dist/llm/client.js +27 -0
- package/dist/llm/client.js.map +1 -0
- package/dist/monitor/index.d.ts +15 -0
- package/dist/monitor/index.d.ts.map +1 -0
- package/dist/monitor/index.js +9 -0
- package/dist/monitor/index.js.map +1 -0
- package/dist/monitor/monitor-agent-impl.d.ts +56 -0
- package/dist/monitor/monitor-agent-impl.d.ts.map +1 -0
- package/dist/monitor/monitor-agent-impl.js +178 -0
- package/dist/monitor/monitor-agent-impl.js.map +1 -0
- package/dist/monitor/monitor-agent.d.ts +36 -0
- package/dist/monitor/monitor-agent.d.ts.map +1 -0
- package/dist/monitor/monitor-agent.js +6 -0
- package/dist/monitor/monitor-agent.js.map +1 -0
- package/dist/monitor/performance-aggregates.d.ts +41 -0
- package/dist/monitor/performance-aggregates.d.ts.map +1 -0
- package/dist/monitor/performance-aggregates.js +6 -0
- package/dist/monitor/performance-aggregates.js.map +1 -0
- package/dist/monitor/recommendation-engine.d.ts +27 -0
- package/dist/monitor/recommendation-engine.d.ts.map +1 -0
- package/dist/monitor/recommendation-engine.js +140 -0
- package/dist/monitor/recommendation-engine.js.map +1 -0
- package/dist/monitor/recommendation-types.d.ts +30 -0
- package/dist/monitor/recommendation-types.d.ts.map +1 -0
- package/dist/monitor/recommendation-types.js +43 -0
- package/dist/monitor/recommendation-types.js.map +1 -0
- package/dist/monitor/report-generator.d.ts +48 -0
- package/dist/monitor/report-generator.d.ts.map +1 -0
- package/dist/monitor/report-generator.js +123 -0
- package/dist/monitor/report-generator.js.map +1 -0
- package/dist/monitor/task-type-classifier.d.ts +19 -0
- package/dist/monitor/task-type-classifier.d.ts.map +1 -0
- package/dist/monitor/task-type-classifier.js +68 -0
- package/dist/monitor/task-type-classifier.js.map +1 -0
- package/dist/persistence/adapter.d.ts +4 -0
- package/dist/persistence/adapter.d.ts.map +1 -0
- package/dist/persistence/adapter.js +56 -0
- package/dist/persistence/adapter.js.map +1 -0
- package/dist/persistence/cost-types.d.ts +87 -0
- package/dist/persistence/cost-types.d.ts.map +1 -0
- package/dist/persistence/cost-types.js +14 -0
- package/dist/persistence/cost-types.js.map +1 -0
- package/dist/persistence/dolt-adapter-transaction.test.d.ts +10 -0
- package/dist/persistence/dolt-adapter-transaction.test.d.ts.map +1 -0
- package/dist/persistence/dolt-adapter-transaction.test.js +359 -0
- package/dist/persistence/dolt-adapter-transaction.test.js.map +1 -0
- package/dist/persistence/dolt-adapter.d.ts +77 -0
- package/dist/persistence/dolt-adapter.d.ts.map +1 -0
- package/dist/persistence/dolt-adapter.js +98 -0
- package/dist/persistence/dolt-adapter.js.map +1 -0
- package/dist/persistence/dolt-client.d.ts +69 -0
- package/dist/persistence/dolt-client.d.ts.map +1 -0
- package/dist/persistence/dolt-client.js +278 -0
- package/dist/persistence/dolt-client.js.map +1 -0
- package/dist/persistence/dolt-errors.d.ts +10 -0
- package/dist/persistence/dolt-errors.d.ts.map +1 -0
- package/dist/persistence/dolt-errors.js +15 -0
- package/dist/persistence/dolt-errors.js.map +1 -0
- package/dist/persistence/dolt-init.d.ts +64 -0
- package/dist/persistence/dolt-init.d.ts.map +1 -0
- package/dist/persistence/dolt-init.js +233 -0
- package/dist/persistence/dolt-init.js.map +1 -0
- package/dist/persistence/index.d.ts +23 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +23 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/memory-adapter.d.ts +156 -0
- package/dist/persistence/memory-adapter.d.ts.map +1 -0
- package/dist/persistence/memory-adapter.js +1167 -0
- package/dist/persistence/memory-adapter.js.map +1 -0
- package/dist/persistence/monitor-database.d.ts +113 -0
- package/dist/persistence/monitor-database.d.ts.map +1 -0
- package/dist/persistence/monitor-database.js +345 -0
- package/dist/persistence/monitor-database.js.map +1 -0
- package/dist/persistence/queries/amendments.d.ts +91 -0
- package/dist/persistence/queries/amendments.d.ts.map +1 -0
- package/dist/persistence/queries/amendments.js +185 -0
- package/dist/persistence/queries/amendments.js.map +1 -0
- package/dist/persistence/queries/cost.d.ts +130 -0
- package/dist/persistence/queries/cost.d.ts.map +1 -0
- package/dist/persistence/queries/cost.js +384 -0
- package/dist/persistence/queries/cost.js.map +1 -0
- package/dist/persistence/queries/decisions.d.ts +131 -0
- package/dist/persistence/queries/decisions.d.ts.map +1 -0
- package/dist/persistence/queries/decisions.js +339 -0
- package/dist/persistence/queries/decisions.js.map +1 -0
- package/dist/persistence/queries/metrics.d.ts +155 -0
- package/dist/persistence/queries/metrics.d.ts.map +1 -0
- package/dist/persistence/queries/metrics.js +237 -0
- package/dist/persistence/queries/metrics.js.map +1 -0
- package/dist/persistence/queries/retry-escalated.d.ts +35 -0
- package/dist/persistence/queries/retry-escalated.d.ts.map +1 -0
- package/dist/persistence/queries/retry-escalated.js +83 -0
- package/dist/persistence/queries/retry-escalated.js.map +1 -0
- package/dist/persistence/schema-version.d.ts +89 -0
- package/dist/persistence/schema-version.d.ts.map +1 -0
- package/dist/persistence/schema-version.js +67 -0
- package/dist/persistence/schema-version.js.map +1 -0
- package/dist/persistence/schema.d.ts +26 -0
- package/dist/persistence/schema.d.ts.map +1 -0
- package/dist/persistence/schema.js +509 -0
- package/dist/persistence/schema.js.map +1 -0
- package/dist/persistence/schemas/decisions.d.ts +176 -0
- package/dist/persistence/schemas/decisions.d.ts.map +1 -0
- package/dist/persistence/schemas/decisions.js +139 -0
- package/dist/persistence/schemas/decisions.js.map +1 -0
- package/dist/persistence/schemas/operational.d.ts +194 -0
- package/dist/persistence/schemas/operational.d.ts.map +1 -0
- package/dist/persistence/schemas/operational.js +197 -0
- package/dist/persistence/schemas/operational.js.map +1 -0
- package/dist/persistence/types.d.ts +98 -0
- package/dist/persistence/types.d.ts.map +1 -0
- package/dist/persistence/types.js +22 -0
- package/dist/persistence/types.js.map +1 -0
- package/dist/quality-gates/index.d.ts +2 -0
- package/dist/quality-gates/index.d.ts.map +1 -0
- package/dist/quality-gates/index.js +2 -0
- package/dist/quality-gates/index.js.map +1 -0
- package/dist/quality-gates/types.d.ts +106 -0
- package/dist/quality-gates/types.d.ts.map +1 -0
- package/dist/quality-gates/types.js +5 -0
- package/dist/quality-gates/types.js.map +1 -0
- package/dist/routing/index.d.ts +19 -0
- package/dist/routing/index.d.ts.map +1 -0
- package/dist/routing/index.js +32 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/routing/model-routing-config.d.ts +75 -0
- package/dist/routing/model-routing-config.d.ts.map +1 -0
- package/dist/routing/model-routing-config.js +110 -0
- package/dist/routing/model-routing-config.js.map +1 -0
- package/dist/routing/model-routing-resolver.d.ts +48 -0
- package/dist/routing/model-routing-resolver.d.ts.map +1 -0
- package/dist/routing/model-routing-resolver.js +105 -0
- package/dist/routing/model-routing-resolver.js.map +1 -0
- package/dist/routing/model-tier.d.ts +21 -0
- package/dist/routing/model-tier.d.ts.map +1 -0
- package/dist/routing/model-tier.js +34 -0
- package/dist/routing/model-tier.js.map +1 -0
- package/dist/routing/provider-status.d.ts +99 -0
- package/dist/routing/provider-status.d.ts.map +1 -0
- package/dist/routing/provider-status.js +163 -0
- package/dist/routing/provider-status.js.map +1 -0
- package/dist/routing/routing-decision.d.ts +127 -0
- package/dist/routing/routing-decision.d.ts.map +1 -0
- package/dist/routing/routing-decision.js +111 -0
- package/dist/routing/routing-decision.js.map +1 -0
- package/dist/routing/routing-engine-impl.d.ts +132 -0
- package/dist/routing/routing-engine-impl.d.ts.map +1 -0
- package/dist/routing/routing-engine-impl.js +450 -0
- package/dist/routing/routing-engine-impl.js.map +1 -0
- package/dist/routing/routing-engine.d.ts +83 -0
- package/dist/routing/routing-engine.d.ts.map +1 -0
- package/dist/routing/routing-engine.js +24 -0
- package/dist/routing/routing-engine.js.map +1 -0
- package/dist/routing/routing-policy.d.ts +138 -0
- package/dist/routing/routing-policy.d.ts.map +1 -0
- package/dist/routing/routing-policy.js +159 -0
- package/dist/routing/routing-policy.js.map +1 -0
- package/dist/routing/routing-recommender.d.ts +60 -0
- package/dist/routing/routing-recommender.d.ts.map +1 -0
- package/dist/routing/routing-recommender.js +209 -0
- package/dist/routing/routing-recommender.js.map +1 -0
- package/dist/routing/routing-telemetry.d.ts +36 -0
- package/dist/routing/routing-telemetry.d.ts.map +1 -0
- package/dist/routing/routing-telemetry.js +39 -0
- package/dist/routing/routing-telemetry.js.map +1 -0
- package/dist/routing/routing-token-accumulator.d.ts +68 -0
- package/dist/routing/routing-token-accumulator.d.ts.map +1 -0
- package/dist/routing/routing-token-accumulator.js +111 -0
- package/dist/routing/routing-token-accumulator.js.map +1 -0
- package/dist/routing/routing-tuner.d.ts +69 -0
- package/dist/routing/routing-tuner.d.ts.map +1 -0
- package/dist/routing/routing-tuner.js +217 -0
- package/dist/routing/routing-tuner.js.map +1 -0
- package/dist/routing/types.d.ts +152 -0
- package/dist/routing/types.d.ts.map +1 -0
- package/dist/routing/types.js +13 -0
- package/dist/routing/types.js.map +1 -0
- package/dist/supervisor/analysis.d.ts +178 -0
- package/dist/supervisor/analysis.d.ts.map +1 -0
- package/dist/supervisor/analysis.js +420 -0
- package/dist/supervisor/analysis.js.map +1 -0
- package/dist/supervisor/experimenter.d.ts +118 -0
- package/dist/supervisor/experimenter.d.ts.map +1 -0
- package/dist/supervisor/experimenter.js +493 -0
- package/dist/supervisor/experimenter.js.map +1 -0
- package/dist/supervisor/index.d.ts +13 -0
- package/dist/supervisor/index.d.ts.map +1 -0
- package/dist/supervisor/index.js +11 -0
- package/dist/supervisor/index.js.map +1 -0
- package/dist/telemetry/batch-buffer.d.ts +53 -0
- package/dist/telemetry/batch-buffer.d.ts.map +1 -0
- package/dist/telemetry/batch-buffer.js +83 -0
- package/dist/telemetry/batch-buffer.js.map +1 -0
- package/dist/telemetry/categorizer.d.ts +65 -0
- package/dist/telemetry/categorizer.d.ts.map +1 -0
- package/dist/telemetry/categorizer.js +338 -0
- package/dist/telemetry/categorizer.js.map +1 -0
- package/dist/telemetry/consumer-analyzer.d.ts +53 -0
- package/dist/telemetry/consumer-analyzer.d.ts.map +1 -0
- package/dist/telemetry/consumer-analyzer.js +182 -0
- package/dist/telemetry/consumer-analyzer.js.map +1 -0
- package/dist/telemetry/cost-table.d.ts +36 -0
- package/dist/telemetry/cost-table.d.ts.map +1 -0
- package/dist/telemetry/cost-table.js +127 -0
- package/dist/telemetry/cost-table.js.map +1 -0
- package/dist/telemetry/efficiency-scorer.d.ts +103 -0
- package/dist/telemetry/efficiency-scorer.d.ts.map +1 -0
- package/dist/telemetry/efficiency-scorer.js +311 -0
- package/dist/telemetry/efficiency-scorer.js.map +1 -0
- package/dist/telemetry/index.d.ts +28 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +37 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/ingestion-server.d.ts +99 -0
- package/dist/telemetry/ingestion-server.d.ts.map +1 -0
- package/dist/telemetry/ingestion-server.js +315 -0
- package/dist/telemetry/ingestion-server.js.map +1 -0
- package/dist/telemetry/log-turn-analyzer.d.ts +35 -0
- package/dist/telemetry/log-turn-analyzer.d.ts.map +1 -0
- package/dist/telemetry/log-turn-analyzer.js +132 -0
- package/dist/telemetry/log-turn-analyzer.js.map +1 -0
- package/dist/telemetry/normalizer.d.ts +43 -0
- package/dist/telemetry/normalizer.d.ts.map +1 -0
- package/dist/telemetry/normalizer.js +320 -0
- package/dist/telemetry/normalizer.js.map +1 -0
- package/dist/telemetry/recommender.d.ts +116 -0
- package/dist/telemetry/recommender.d.ts.map +1 -0
- package/dist/telemetry/recommender.js +532 -0
- package/dist/telemetry/recommender.js.map +1 -0
- package/dist/telemetry/source-detector.d.ts +19 -0
- package/dist/telemetry/source-detector.d.ts.map +1 -0
- package/dist/telemetry/source-detector.js +73 -0
- package/dist/telemetry/source-detector.js.map +1 -0
- package/dist/telemetry/task-baselines.d.ts +30 -0
- package/dist/telemetry/task-baselines.d.ts.map +1 -0
- package/dist/telemetry/task-baselines.js +44 -0
- package/dist/telemetry/task-baselines.js.map +1 -0
- package/dist/telemetry/telemetry-pipeline.d.ts +122 -0
- package/dist/telemetry/telemetry-pipeline.d.ts.map +1 -0
- package/dist/telemetry/telemetry-pipeline.js +349 -0
- package/dist/telemetry/telemetry-pipeline.js.map +1 -0
- package/dist/telemetry/timestamp-normalizer.d.ts +32 -0
- package/dist/telemetry/timestamp-normalizer.d.ts.map +1 -0
- package/dist/telemetry/timestamp-normalizer.js +133 -0
- package/dist/telemetry/timestamp-normalizer.js.map +1 -0
- package/dist/telemetry/token-extractor.d.ts +57 -0
- package/dist/telemetry/token-extractor.d.ts.map +1 -0
- package/dist/telemetry/token-extractor.js +200 -0
- package/dist/telemetry/token-extractor.js.map +1 -0
- package/dist/telemetry/turn-analyzer.d.ts +34 -0
- package/dist/telemetry/turn-analyzer.d.ts.map +1 -0
- package/dist/telemetry/turn-analyzer.js +101 -0
- package/dist/telemetry/turn-analyzer.js.map +1 -0
- package/dist/telemetry/types.d.ts +456 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/telemetry/types.js +186 -0
- package/dist/telemetry/types.js.map +1 -0
- package/dist/types.d.ts +80 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/version-manager/index.d.ts +11 -0
- package/dist/version-manager/index.d.ts.map +1 -0
- package/dist/version-manager/index.js +8 -0
- package/dist/version-manager/index.js.map +1 -0
- package/dist/version-manager/update-checker.d.ts +44 -0
- package/dist/version-manager/update-checker.d.ts.map +1 -0
- package/dist/version-manager/update-checker.js +171 -0
- package/dist/version-manager/update-checker.js.map +1 -0
- package/dist/version-manager/version-cache.d.ts +42 -0
- package/dist/version-manager/version-cache.d.ts.map +1 -0
- package/dist/version-manager/version-cache.js +69 -0
- package/dist/version-manager/version-cache.js.map +1 -0
- package/dist/version-manager/version-manager-impl.d.ts +81 -0
- package/dist/version-manager/version-manager-impl.d.ts.map +1 -0
- package/dist/version-manager/version-manager-impl.js +223 -0
- package/dist/version-manager/version-manager-impl.js.map +1 -0
- package/dist/version-manager/version-manager.d.ts +70 -0
- package/dist/version-manager/version-manager.d.ts.map +1 -0
- package/dist/version-manager/version-manager.js +8 -0
- package/dist/version-manager/version-manager.js.map +1 -0
- package/package.json +27 -0
|
@@ -0,0 +1,1167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InMemoryDatabaseAdapter — satisfies the DatabaseAdapter interface using
|
|
3
|
+
* plain in-memory Maps and arrays. Designed for CI environments and unit
|
|
4
|
+
* tests where no external database is available.
|
|
5
|
+
*
|
|
6
|
+
* SQL support is intentionally limited to the patterns used by
|
|
7
|
+
* `src/persistence/queries/`:
|
|
8
|
+
* - CREATE TABLE [IF NOT EXISTS] name (col type, ...)
|
|
9
|
+
* - DROP TABLE [IF EXISTS] name
|
|
10
|
+
* - INSERT INTO name (cols) VALUES (vals)
|
|
11
|
+
* - SELECT * / cols FROM name [WHERE simple-conditions]
|
|
12
|
+
* - SELECT literal-expressions (no FROM clause)
|
|
13
|
+
* - UPDATE name SET col = val [WHERE simple-conditions]
|
|
14
|
+
* - DELETE FROM name [WHERE simple-conditions]
|
|
15
|
+
*
|
|
16
|
+
* WHERE clauses support simple equality conditions joined by AND.
|
|
17
|
+
* Transactions use a snapshot-and-restore pattern.
|
|
18
|
+
*/
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// InMemoryDatabaseAdapter
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
export class InMemoryDatabaseAdapter {
|
|
23
|
+
backendType = 'memory';
|
|
24
|
+
_tables = new Map();
|
|
25
|
+
_indexes = [];
|
|
26
|
+
/** Maps table name → auto-increment column name */
|
|
27
|
+
_autoIncrementCols = new Map();
|
|
28
|
+
/** Maps table name → last assigned auto-increment value */
|
|
29
|
+
_autoIncrementCounters = new Map();
|
|
30
|
+
/** Maps "tableName.colName" → default value expression ('CURRENT_TIMESTAMP' → ISO string, else literal) */
|
|
31
|
+
_columnDefaults = new Map();
|
|
32
|
+
/** Maps table name → array of primary key column names */
|
|
33
|
+
_primaryKeys = new Map();
|
|
34
|
+
/** Maps table name → ordered list of column names (from CREATE TABLE) */
|
|
35
|
+
_tableColumns = new Map();
|
|
36
|
+
// -------------------------------------------------------------------------
|
|
37
|
+
// DatabaseAdapter implementation
|
|
38
|
+
// -------------------------------------------------------------------------
|
|
39
|
+
async query(sql, params) {
|
|
40
|
+
const rows = this._execute(sql.trim(), params);
|
|
41
|
+
return rows;
|
|
42
|
+
}
|
|
43
|
+
async exec(sql) {
|
|
44
|
+
this._execute(sql.trim(), undefined);
|
|
45
|
+
}
|
|
46
|
+
// -------------------------------------------------------------------------
|
|
47
|
+
// SyncAdapter implementation
|
|
48
|
+
// -------------------------------------------------------------------------
|
|
49
|
+
querySync(sql, params) {
|
|
50
|
+
const rows = this._execute(sql.trim(), params);
|
|
51
|
+
return rows;
|
|
52
|
+
}
|
|
53
|
+
execSync(sql) {
|
|
54
|
+
this._execute(sql.trim(), undefined);
|
|
55
|
+
}
|
|
56
|
+
async transaction(fn) {
|
|
57
|
+
// Snapshot: deep-clone each table's rows and auto-increment counters
|
|
58
|
+
const snapshot = new Map();
|
|
59
|
+
for (const [name, rows] of this._tables) {
|
|
60
|
+
snapshot.set(name, rows.map((r) => ({ ...r })));
|
|
61
|
+
}
|
|
62
|
+
const counterSnapshot = new Map(this._autoIncrementCounters);
|
|
63
|
+
try {
|
|
64
|
+
const result = await fn(this);
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
// Restore snapshot on failure (data + counters; schema/defaults are immutable per-table)
|
|
69
|
+
this._tables = snapshot;
|
|
70
|
+
this._autoIncrementCounters = counterSnapshot;
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async close() {
|
|
75
|
+
this._tables.clear();
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Work graph not supported in InMemoryDatabaseAdapter.
|
|
79
|
+
* Returns `[]` to signal the caller to use the legacy discovery path.
|
|
80
|
+
*/
|
|
81
|
+
async queryReadyStories() {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
// -------------------------------------------------------------------------
|
|
85
|
+
// SQL execution dispatcher
|
|
86
|
+
// -------------------------------------------------------------------------
|
|
87
|
+
_execute(sql, params) {
|
|
88
|
+
const resolved = this._substituteParams(sql, params);
|
|
89
|
+
const upper = resolved.trimStart().toUpperCase();
|
|
90
|
+
if (/^CREATE\s+TABLE/i.test(upper)) {
|
|
91
|
+
return this._createTable(resolved);
|
|
92
|
+
}
|
|
93
|
+
if (/^DROP\s+TABLE/i.test(upper)) {
|
|
94
|
+
return this._dropTable(resolved);
|
|
95
|
+
}
|
|
96
|
+
if (/^CREATE\s+(?:UNIQUE\s+)?INDEX/i.test(upper)) {
|
|
97
|
+
return this._createIndex(resolved);
|
|
98
|
+
}
|
|
99
|
+
if (/^DROP\s+INDEX/i.test(upper)) {
|
|
100
|
+
return this._dropIndex(resolved);
|
|
101
|
+
}
|
|
102
|
+
if (/^CREATE\s+(?:OR\s+REPLACE\s+)?VIEW/i.test(upper)) {
|
|
103
|
+
// VIEWs are not supported in InMemoryDatabaseAdapter — treat as no-op.
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
if (/^INSERT\s+(?:IGNORE\s+)?INTO/i.test(upper)) {
|
|
107
|
+
return this._insert(resolved, /^INSERT\s+IGNORE\s+INTO/i.test(upper));
|
|
108
|
+
}
|
|
109
|
+
if (/^SELECT/i.test(upper)) {
|
|
110
|
+
return this._select(resolved);
|
|
111
|
+
}
|
|
112
|
+
if (/^UPDATE/i.test(upper)) {
|
|
113
|
+
return this._update(resolved);
|
|
114
|
+
}
|
|
115
|
+
if (/^DELETE\s+FROM/i.test(upper)) {
|
|
116
|
+
return this._delete(resolved);
|
|
117
|
+
}
|
|
118
|
+
// PRAGMA table_info(tableName) — return column metadata
|
|
119
|
+
if (/^PRAGMA\s+table_info\s*\(/i.test(upper)) {
|
|
120
|
+
return this._pragmaTableInfo(resolved);
|
|
121
|
+
}
|
|
122
|
+
// Unknown statement — silently ignore (BEGIN, COMMIT, ROLLBACK, other pragmas, etc.)
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
// -------------------------------------------------------------------------
|
|
126
|
+
// Parameter substitution
|
|
127
|
+
// -------------------------------------------------------------------------
|
|
128
|
+
/**
|
|
129
|
+
* Replace each `?` placeholder with an escaped literal value.
|
|
130
|
+
*/
|
|
131
|
+
_substituteParams(sql, params) {
|
|
132
|
+
if (!params || params.length === 0)
|
|
133
|
+
return sql;
|
|
134
|
+
let idx = 0;
|
|
135
|
+
return sql.replace(/\?/g, () => {
|
|
136
|
+
const val = params[idx++];
|
|
137
|
+
if (val === null || val === undefined)
|
|
138
|
+
return 'NULL';
|
|
139
|
+
if (typeof val === 'number')
|
|
140
|
+
return String(val);
|
|
141
|
+
if (typeof val === 'boolean')
|
|
142
|
+
return val ? '1' : '0';
|
|
143
|
+
return `'${String(val).replace(/'/g, "''")}'`;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
// -------------------------------------------------------------------------
|
|
147
|
+
// CREATE TABLE
|
|
148
|
+
// -------------------------------------------------------------------------
|
|
149
|
+
_createTable(sql) {
|
|
150
|
+
// CREATE TABLE [IF NOT EXISTS] tableName (...)
|
|
151
|
+
const m = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(\w+)/i.exec(sql);
|
|
152
|
+
if (m) {
|
|
153
|
+
const name = m[1];
|
|
154
|
+
if (!this._tables.has(name)) {
|
|
155
|
+
this._tables.set(name, []);
|
|
156
|
+
}
|
|
157
|
+
// Detect AUTO_INCREMENT / AUTOINCREMENT column and DEFAULT values
|
|
158
|
+
const colDefsM = /\((.+)\)\s*$/is.exec(sql);
|
|
159
|
+
if (colDefsM) {
|
|
160
|
+
const colDefs = colDefsM[1];
|
|
161
|
+
// Find column with AUTO_INCREMENT
|
|
162
|
+
const aiMatch = /^\s*(\w+)\s+\w+.*?(?:AUTO_INCREMENT|AUTOINCREMENT)/im.exec(colDefs);
|
|
163
|
+
if (aiMatch) {
|
|
164
|
+
this._autoIncrementCols.set(name, aiMatch[1]);
|
|
165
|
+
if (!this._autoIncrementCounters.has(name)) {
|
|
166
|
+
this._autoIncrementCounters.set(name, 0);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Find columns with DEFAULT values and PRIMARY KEY constraints
|
|
170
|
+
const colLines = this._splitTopLevelCommas(colDefs);
|
|
171
|
+
const pkCols = [];
|
|
172
|
+
for (const colLine of colLines) {
|
|
173
|
+
const trimmedLine = colLine.trim();
|
|
174
|
+
// Table-level PRIMARY KEY constraint: PRIMARY KEY (col1, col2, ...)
|
|
175
|
+
const tablePkM = /^PRIMARY\s+KEY\s*\(([^)]+)\)/i.exec(trimmedLine);
|
|
176
|
+
if (tablePkM) {
|
|
177
|
+
const cols = tablePkM[1].split(',').map((c) => c.trim().replace(/^[`"](.+)[`"]$/, '$1'));
|
|
178
|
+
pkCols.push(...cols);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
// Column-level DEFAULT value
|
|
182
|
+
const defaultM = /^\s*(\w+)\s+\S+.*?\bDEFAULT\s+(.+?)(?:\s*,?\s*$)/i.exec(trimmedLine);
|
|
183
|
+
if (defaultM) {
|
|
184
|
+
const colName = defaultM[1];
|
|
185
|
+
const defaultVal = defaultM[2].trim();
|
|
186
|
+
this._columnDefaults.set(`${name}.${colName}`, defaultVal);
|
|
187
|
+
}
|
|
188
|
+
// Column-level PRIMARY KEY: colName TYPE ... PRIMARY KEY
|
|
189
|
+
const colPkM = /^(\w+)\s+\w+.*?\bPRIMARY\s+KEY\b/i.exec(trimmedLine);
|
|
190
|
+
if (colPkM && !/^PRIMARY\s+KEY\b/i.test(trimmedLine)) {
|
|
191
|
+
pkCols.push(colPkM[1]);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (pkCols.length > 0 && !this._primaryKeys.has(name)) {
|
|
195
|
+
this._primaryKeys.set(name, pkCols);
|
|
196
|
+
}
|
|
197
|
+
// Track column names in definition order (skip table-level constraints)
|
|
198
|
+
if (!this._tableColumns.has(name)) {
|
|
199
|
+
const colNames = [];
|
|
200
|
+
for (const colLine of colLines) {
|
|
201
|
+
const trimmedLine = colLine.trim();
|
|
202
|
+
// Skip table-level constraints (PRIMARY KEY, UNIQUE, FOREIGN KEY, CHECK)
|
|
203
|
+
if (/^(?:PRIMARY\s+KEY|UNIQUE|FOREIGN\s+KEY|CHECK)\s*[\s(]/i.test(trimmedLine))
|
|
204
|
+
continue;
|
|
205
|
+
// Extract column name: first identifier (bare word or backtick/double-quote quoted)
|
|
206
|
+
const quotedM = /^[`"](\w+)[`"]\s+\S/i.exec(trimmedLine);
|
|
207
|
+
if (quotedM) {
|
|
208
|
+
colNames.push(quotedM[1]);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const bareM = /^(\w+)\s+\S/i.exec(trimmedLine);
|
|
212
|
+
if (bareM)
|
|
213
|
+
colNames.push(bareM[1]);
|
|
214
|
+
}
|
|
215
|
+
if (colNames.length > 0)
|
|
216
|
+
this._tableColumns.set(name, colNames);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
// -------------------------------------------------------------------------
|
|
223
|
+
// DROP TABLE
|
|
224
|
+
// -------------------------------------------------------------------------
|
|
225
|
+
_dropTable(sql) {
|
|
226
|
+
const m = /DROP\s+TABLE\s+(?:IF\s+EXISTS\s+)?(\w+)/i.exec(sql);
|
|
227
|
+
if (m) {
|
|
228
|
+
this._tables.delete(m[1]);
|
|
229
|
+
}
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
// -------------------------------------------------------------------------
|
|
233
|
+
// CREATE INDEX
|
|
234
|
+
// -------------------------------------------------------------------------
|
|
235
|
+
_createIndex(sql) {
|
|
236
|
+
// CREATE [UNIQUE] INDEX [IF NOT EXISTS] indexName ON tableName (...)
|
|
237
|
+
const m = /CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?(\w+)\s+ON\s+(\w+)/i.exec(sql);
|
|
238
|
+
if (m) {
|
|
239
|
+
const name = m[1];
|
|
240
|
+
const tbl_name = m[2];
|
|
241
|
+
// Don't add duplicates
|
|
242
|
+
if (!this._indexes.some((idx) => idx.name === name)) {
|
|
243
|
+
this._indexes.push({ name, tbl_name, type: 'index' });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return [];
|
|
247
|
+
}
|
|
248
|
+
// -------------------------------------------------------------------------
|
|
249
|
+
// DROP INDEX
|
|
250
|
+
// -------------------------------------------------------------------------
|
|
251
|
+
_dropIndex(sql) {
|
|
252
|
+
const m = /DROP\s+INDEX\s+(?:IF\s+EXISTS\s+)?(\w+)/i.exec(sql);
|
|
253
|
+
if (m) {
|
|
254
|
+
const name = m[1];
|
|
255
|
+
this._indexes = this._indexes.filter((idx) => idx.name !== name);
|
|
256
|
+
}
|
|
257
|
+
return [];
|
|
258
|
+
}
|
|
259
|
+
// -------------------------------------------------------------------------
|
|
260
|
+
// PRAGMA table_info
|
|
261
|
+
// -------------------------------------------------------------------------
|
|
262
|
+
/**
|
|
263
|
+
* Emulate PRAGMA table_info(tableName) — returns one row per column with
|
|
264
|
+
* {cid, name, type, notnull, dflt_value, pk} shape.
|
|
265
|
+
* Column names are derived from the CREATE TABLE definition order.
|
|
266
|
+
* This is sufficient for schema compatibility checks.
|
|
267
|
+
*/
|
|
268
|
+
_pragmaTableInfo(sql) {
|
|
269
|
+
const m = /PRAGMA\s+table_info\s*\(\s*[`'"]?(\w+)[`'"]?\s*\)/i.exec(sql);
|
|
270
|
+
if (!m)
|
|
271
|
+
return [];
|
|
272
|
+
const tableName = m[1];
|
|
273
|
+
const colNames = this._tableColumns.get(tableName);
|
|
274
|
+
if (!colNames || colNames.length === 0)
|
|
275
|
+
return [];
|
|
276
|
+
const pkCols = this._primaryKeys.get(tableName) ?? [];
|
|
277
|
+
return colNames.map((name, cid) => ({
|
|
278
|
+
cid,
|
|
279
|
+
name,
|
|
280
|
+
type: 'TEXT',
|
|
281
|
+
notnull: 0,
|
|
282
|
+
dflt_value: null,
|
|
283
|
+
pk: pkCols.includes(name) ? 1 : 0,
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
// -------------------------------------------------------------------------
|
|
287
|
+
// sqlite_master virtual table
|
|
288
|
+
// -------------------------------------------------------------------------
|
|
289
|
+
_selectFromSqliteMaster(sql) {
|
|
290
|
+
// Build virtual rows: tables + indexes
|
|
291
|
+
const masterRows = [];
|
|
292
|
+
for (const name of this._tables.keys()) {
|
|
293
|
+
masterRows.push({ type: 'table', name, tbl_name: name, rootpage: 0, sql: null });
|
|
294
|
+
}
|
|
295
|
+
for (const idx of this._indexes) {
|
|
296
|
+
masterRows.push({ type: 'index', name: idx.name, tbl_name: idx.tbl_name, rootpage: 0, sql: null });
|
|
297
|
+
}
|
|
298
|
+
// Extract WHERE clause conditions manually
|
|
299
|
+
const whereM = /WHERE\s+(.+?)(?:\s+ORDER\s+BY|\s+LIMIT|\s*$)/is.exec(sql);
|
|
300
|
+
let rows = masterRows;
|
|
301
|
+
if (whereM) {
|
|
302
|
+
rows = rows.filter((row) => this._matchWhere(whereM[1].trim(), row));
|
|
303
|
+
}
|
|
304
|
+
// Extract SELECT columns
|
|
305
|
+
const colsM = /SELECT\s+(.+?)\s+FROM\s+sqlite_master/is.exec(sql);
|
|
306
|
+
if (!colsM)
|
|
307
|
+
return rows;
|
|
308
|
+
const colsStr = colsM[1].trim();
|
|
309
|
+
if (colsStr === '*')
|
|
310
|
+
return rows;
|
|
311
|
+
return rows.map((row) => this._projectCols(colsStr, row));
|
|
312
|
+
}
|
|
313
|
+
// -------------------------------------------------------------------------
|
|
314
|
+
// INSERT
|
|
315
|
+
// -------------------------------------------------------------------------
|
|
316
|
+
_insert(sql, _ignoreConflicts = false) {
|
|
317
|
+
// INSERT INTO tableName (col1, col2, ...) VALUES (val1, val2, ...)
|
|
318
|
+
const m = /INSERT\s+(?:IGNORE\s+)?INTO\s+(\w+)\s*\(([^)]+)\)\s*VALUES\s*\((.+)\)\s*$/is.exec(sql);
|
|
319
|
+
if (!m)
|
|
320
|
+
return [];
|
|
321
|
+
const tableName = m[1];
|
|
322
|
+
// Strip backtick and double-quote quoting from column names
|
|
323
|
+
const cols = m[2].split(',').map((c) => c.trim().replace(/^[`"](.+)[`"]$/, '$1'));
|
|
324
|
+
const valStr = m[3];
|
|
325
|
+
const vals = this._parseValueList(valStr);
|
|
326
|
+
const row = {};
|
|
327
|
+
for (let i = 0; i < cols.length; i++) {
|
|
328
|
+
row[cols[i]] = vals[i] ?? null;
|
|
329
|
+
}
|
|
330
|
+
// Auto-assign auto-increment ID if the column is missing from INSERT
|
|
331
|
+
const aiCol = this._autoIncrementCols.get(tableName);
|
|
332
|
+
if (aiCol && !(aiCol in row)) {
|
|
333
|
+
const next = (this._autoIncrementCounters.get(tableName) ?? 0) + 1;
|
|
334
|
+
this._autoIncrementCounters.set(tableName, next);
|
|
335
|
+
row[aiCol] = next;
|
|
336
|
+
}
|
|
337
|
+
// Apply DEFAULT values for columns not present in INSERT
|
|
338
|
+
for (const [key, defaultExpr] of this._columnDefaults) {
|
|
339
|
+
const [tbl, col] = key.split('.');
|
|
340
|
+
if (tbl === tableName && !(col in row)) {
|
|
341
|
+
if (/^CURRENT_TIMESTAMP$/i.test(defaultExpr)) {
|
|
342
|
+
row[col] = new Date().toISOString();
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
row[col] = this._evalLiteral(defaultExpr);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (!this._tables.has(tableName)) {
|
|
350
|
+
this._tables.set(tableName, []);
|
|
351
|
+
}
|
|
352
|
+
// Enforce PRIMARY KEY uniqueness (throw to support INSERT OR IGNORE via try/catch)
|
|
353
|
+
const pkCols = this._primaryKeys.get(tableName);
|
|
354
|
+
if (pkCols && pkCols.length > 0 && !_ignoreConflicts) {
|
|
355
|
+
const table = this._tables.get(tableName);
|
|
356
|
+
const isDuplicate = table.some((existingRow) => pkCols.every((col) => existingRow[col] !== undefined && String(existingRow[col]) === String(row[col])));
|
|
357
|
+
if (isDuplicate) {
|
|
358
|
+
throw new Error(`UNIQUE constraint failed: ${tableName} (${pkCols.join(', ')})`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
this._tables.get(tableName).push(row);
|
|
362
|
+
return [];
|
|
363
|
+
}
|
|
364
|
+
// -------------------------------------------------------------------------
|
|
365
|
+
// SELECT
|
|
366
|
+
// -------------------------------------------------------------------------
|
|
367
|
+
_select(sql) {
|
|
368
|
+
// SELECT without FROM: evaluate literal expressions
|
|
369
|
+
if (!/FROM/i.test(sql)) {
|
|
370
|
+
const m = /SELECT\s+(.+)$/is.exec(sql);
|
|
371
|
+
if (!m)
|
|
372
|
+
return [];
|
|
373
|
+
return [this._evalSelectExprs(m[1].trim())];
|
|
374
|
+
}
|
|
375
|
+
// Handle sqlite_master virtual table queries (for index/schema inspection)
|
|
376
|
+
if (/FROM\s+sqlite_master/i.test(sql)) {
|
|
377
|
+
return this._selectFromSqliteMaster(sql);
|
|
378
|
+
}
|
|
379
|
+
// Extract LIMIT before stripping
|
|
380
|
+
let limitValue;
|
|
381
|
+
const limitMatch = /\s+LIMIT\s+(\d+)\s*$/is.exec(sql);
|
|
382
|
+
if (limitMatch) {
|
|
383
|
+
limitValue = parseInt(limitMatch[1], 10);
|
|
384
|
+
}
|
|
385
|
+
// Extract ORDER BY clause before stripping
|
|
386
|
+
let orderByExprs;
|
|
387
|
+
const orderByMatch = /\s+ORDER\s+BY\s+(.+?)(?:\s+LIMIT\s+\d+\s*)?$/is.exec(sql);
|
|
388
|
+
if (orderByMatch) {
|
|
389
|
+
orderByExprs = this._parseOrderBy(orderByMatch[1].trim());
|
|
390
|
+
}
|
|
391
|
+
// Strip ORDER BY and LIMIT clauses
|
|
392
|
+
let stripped = sql.replace(/\s+ORDER\s+BY\s+.+?(?=\s+LIMIT\s|\s*$)/is, '')
|
|
393
|
+
.replace(/\s+LIMIT\s+\d+\s*$/is, '');
|
|
394
|
+
// Extract GROUP BY clause (present before ORDER BY, after WHERE)
|
|
395
|
+
let groupByCols = null;
|
|
396
|
+
const groupByMatch = /\s+GROUP\s+BY\s+(.+?)(?:\s+HAVING\s+.+?)?$/is.exec(stripped);
|
|
397
|
+
if (groupByMatch) {
|
|
398
|
+
groupByCols = groupByMatch[1].split(',').map((c) => c.trim());
|
|
399
|
+
stripped = stripped.replace(/\s+GROUP\s+BY\s+.+$/is, '');
|
|
400
|
+
}
|
|
401
|
+
// SELECT cols FROM tableName [WHERE ...]
|
|
402
|
+
const m = /SELECT\s+(.+?)\s+FROM\s+(\w+)(?:\s+WHERE\s+(.+))?$/is.exec(stripped);
|
|
403
|
+
if (!m)
|
|
404
|
+
return [];
|
|
405
|
+
const colsStr = m[1].trim();
|
|
406
|
+
const tableName = m[2];
|
|
407
|
+
const whereStr = m[3];
|
|
408
|
+
const table = this._tables.get(tableName) ?? [];
|
|
409
|
+
let rows = table.map((r) => ({ ...r }));
|
|
410
|
+
if (whereStr) {
|
|
411
|
+
rows = rows.filter((row) => this._matchWhere(whereStr.trim(), row));
|
|
412
|
+
}
|
|
413
|
+
// Apply GROUP BY when present
|
|
414
|
+
if (groupByCols !== null) {
|
|
415
|
+
const grouped = this._applyGroupBy(colsStr, rows, groupByCols);
|
|
416
|
+
const sorted = orderByExprs ? this._applyOrderBy(grouped, orderByExprs) : grouped;
|
|
417
|
+
return limitValue !== undefined ? sorted.slice(0, limitValue) : sorted;
|
|
418
|
+
}
|
|
419
|
+
// Apply ORDER BY
|
|
420
|
+
if (orderByExprs) {
|
|
421
|
+
rows = this._applyOrderBy(rows, orderByExprs);
|
|
422
|
+
}
|
|
423
|
+
// Apply LIMIT
|
|
424
|
+
if (limitValue !== undefined) {
|
|
425
|
+
rows = rows.slice(0, limitValue);
|
|
426
|
+
}
|
|
427
|
+
if (colsStr === '*') {
|
|
428
|
+
return rows;
|
|
429
|
+
}
|
|
430
|
+
// Detect aggregate functions (SUM, COALESCE, COUNT, etc.)
|
|
431
|
+
if (/\b(?:SUM|COALESCE|COUNT|AVG|MIN|MAX)\s*\(/i.test(colsStr)) {
|
|
432
|
+
return [this._evalAggregate(colsStr, rows)];
|
|
433
|
+
}
|
|
434
|
+
// Project specific columns
|
|
435
|
+
return rows.map((row) => this._projectCols(colsStr, row));
|
|
436
|
+
}
|
|
437
|
+
// -------------------------------------------------------------------------
|
|
438
|
+
// GROUP BY helpers
|
|
439
|
+
// -------------------------------------------------------------------------
|
|
440
|
+
/**
|
|
441
|
+
* Apply GROUP BY: bucket rows by the group-by columns, then evaluate
|
|
442
|
+
* aggregate expressions for each bucket. Plain column references in the
|
|
443
|
+
* SELECT list that are also GROUP BY columns return the group value from
|
|
444
|
+
* the first row in the bucket (all rows in a group share the same value).
|
|
445
|
+
*/
|
|
446
|
+
_applyGroupBy(colsStr, rows, groupByCols) {
|
|
447
|
+
// Bucket rows by the GROUP BY key
|
|
448
|
+
const groups = new Map();
|
|
449
|
+
for (const row of rows) {
|
|
450
|
+
const key = groupByCols.map((col) => String(row[col] ?? '')).join('\0');
|
|
451
|
+
if (!groups.has(key))
|
|
452
|
+
groups.set(key, []);
|
|
453
|
+
groups.get(key).push(row);
|
|
454
|
+
}
|
|
455
|
+
const result = [];
|
|
456
|
+
for (const [, groupRows] of groups) {
|
|
457
|
+
result.push(this._evalAggregateGroup(colsStr, groupRows));
|
|
458
|
+
}
|
|
459
|
+
return result;
|
|
460
|
+
}
|
|
461
|
+
// -------------------------------------------------------------------------
|
|
462
|
+
// ORDER BY helpers
|
|
463
|
+
// -------------------------------------------------------------------------
|
|
464
|
+
/**
|
|
465
|
+
* Parse ORDER BY expression list into sort keys.
|
|
466
|
+
* Handles: simple columns, COALESCE(col, default) DESC, CASE...END expressions.
|
|
467
|
+
*/
|
|
468
|
+
_parseOrderBy(orderByStr) {
|
|
469
|
+
// Split by top-level commas
|
|
470
|
+
const parts = this._splitTopLevelCommas(orderByStr);
|
|
471
|
+
return parts.map((part) => {
|
|
472
|
+
const trimmed = part.trim();
|
|
473
|
+
// Check for trailing ASC/DESC
|
|
474
|
+
const dirMatch = /^(.*?)\s+(ASC|DESC)\s*$/i.exec(trimmed);
|
|
475
|
+
if (dirMatch) {
|
|
476
|
+
return { expr: dirMatch[1].trim(), dir: dirMatch[2].toUpperCase() };
|
|
477
|
+
}
|
|
478
|
+
return { expr: trimmed, dir: 'ASC' };
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Sort rows according to ORDER BY expression list.
|
|
483
|
+
*/
|
|
484
|
+
_applyOrderBy(rows, orderBy) {
|
|
485
|
+
return [...rows].sort((a, b) => {
|
|
486
|
+
for (const { expr, dir } of orderBy) {
|
|
487
|
+
const aVal = this._evalOrderByExpr(expr, a);
|
|
488
|
+
const bVal = this._evalOrderByExpr(expr, b);
|
|
489
|
+
const cmp = this._compareValues(aVal, bVal);
|
|
490
|
+
if (cmp !== 0)
|
|
491
|
+
return dir === 'DESC' ? -cmp : cmp;
|
|
492
|
+
}
|
|
493
|
+
return 0;
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Evaluate an ORDER BY expression for a single row.
|
|
498
|
+
*/
|
|
499
|
+
_evalOrderByExpr(expr, row) {
|
|
500
|
+
const trimmed = expr.trim();
|
|
501
|
+
// Simple CASE: CASE col WHEN val1 THEN r1 [WHEN val2 THEN r2]... [ELSE default] END
|
|
502
|
+
const simpleCaseM = /^CASE\s+(\w+)\s+(.+?)\s+END$/is.exec(trimmed);
|
|
503
|
+
if (simpleCaseM) {
|
|
504
|
+
const col = simpleCaseM[1];
|
|
505
|
+
const colVal = String(row[col] ?? '');
|
|
506
|
+
const body = simpleCaseM[2].trim();
|
|
507
|
+
// Match WHEN 'val' THEN result pairs
|
|
508
|
+
const whenMatches = [...body.matchAll(/WHEN\s+'([^']*)'\s+THEN\s+(\S+)/gi)];
|
|
509
|
+
for (const wm of whenMatches) {
|
|
510
|
+
if (colVal === wm[1])
|
|
511
|
+
return this._evalLiteral(wm[2]);
|
|
512
|
+
}
|
|
513
|
+
// ELSE clause
|
|
514
|
+
const elseM = /ELSE\s+(\S+)\s*$/i.exec(body);
|
|
515
|
+
if (elseM)
|
|
516
|
+
return this._evalLiteral(elseM[1]);
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
// Fall through to row expression evaluator
|
|
520
|
+
return this._evalRowExpr(trimmed, row);
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Compare two values for sorting (handles null, numbers, strings).
|
|
524
|
+
* Returns negative if a < b, 0 if equal, positive if a > b.
|
|
525
|
+
*/
|
|
526
|
+
_compareValues(a, b) {
|
|
527
|
+
if (a === null || a === undefined)
|
|
528
|
+
return b === null || b === undefined ? 0 : 1;
|
|
529
|
+
if (b === null || b === undefined)
|
|
530
|
+
return -1;
|
|
531
|
+
if (typeof a === 'number' && typeof b === 'number')
|
|
532
|
+
return a - b;
|
|
533
|
+
return String(a).localeCompare(String(b));
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Evaluate SELECT expressions against a single GROUP BY bucket.
|
|
537
|
+
* Handles both aggregate expressions (SUM, COUNT, COALESCE) and plain
|
|
538
|
+
* column references (for GROUP BY projected columns).
|
|
539
|
+
*/
|
|
540
|
+
_evalAggregateGroup(colsStr, rows) {
|
|
541
|
+
const result = {};
|
|
542
|
+
const cols = this._splitTopLevelCommas(colsStr);
|
|
543
|
+
for (const col of cols) {
|
|
544
|
+
const aliasM = /^(.+?)\s+AS\s+(\w+)$/i.exec(col);
|
|
545
|
+
const expr = aliasM ? aliasM[1].trim() : col.trim();
|
|
546
|
+
const alias = aliasM ? aliasM[2] : col.trim();
|
|
547
|
+
result[alias] = this._evalAggregateExprGrouped(expr, rows);
|
|
548
|
+
}
|
|
549
|
+
return result;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Evaluate a single aggregate expression against a GROUP BY bucket.
|
|
553
|
+
* Extends _evalAggregateExpr with:
|
|
554
|
+
* - SUM(expr) where expr may be CASE WHEN ... END
|
|
555
|
+
* - Plain column references (returns first-row value, for GROUP BY cols)
|
|
556
|
+
*/
|
|
557
|
+
_evalAggregateExprGrouped(expr, rows) {
|
|
558
|
+
const trimmed = expr.trim();
|
|
559
|
+
// COALESCE(expr, default)
|
|
560
|
+
const coalesceM = /^COALESCE\((.+)\)$/i.exec(trimmed);
|
|
561
|
+
if (coalesceM) {
|
|
562
|
+
const args = this._splitTopLevelCommas(coalesceM[1]);
|
|
563
|
+
for (const arg of args) {
|
|
564
|
+
const val = this._evalAggregateExprGrouped(arg.trim(), rows);
|
|
565
|
+
if (val !== null && val !== undefined)
|
|
566
|
+
return val;
|
|
567
|
+
}
|
|
568
|
+
return null;
|
|
569
|
+
}
|
|
570
|
+
// SUM(expr) — expr may be CASE WHEN ... or a plain column name
|
|
571
|
+
const sumM = /^SUM\((.+)\)$/i.exec(trimmed);
|
|
572
|
+
if (sumM) {
|
|
573
|
+
if (rows.length === 0)
|
|
574
|
+
return null;
|
|
575
|
+
let total = 0;
|
|
576
|
+
for (const row of rows) {
|
|
577
|
+
total += Number(this._evalRowExpr(sumM[1].trim(), row) ?? 0);
|
|
578
|
+
}
|
|
579
|
+
return total;
|
|
580
|
+
}
|
|
581
|
+
// COUNT(*)
|
|
582
|
+
if (/^COUNT\(\*\)$/i.test(trimmed)) {
|
|
583
|
+
return rows.length;
|
|
584
|
+
}
|
|
585
|
+
// COUNT(col)
|
|
586
|
+
const countColM = /^COUNT\((\w+)\)$/i.exec(trimmed);
|
|
587
|
+
if (countColM) {
|
|
588
|
+
const col = countColM[1];
|
|
589
|
+
return rows.filter((r) => r[col] !== null && r[col] !== undefined).length;
|
|
590
|
+
}
|
|
591
|
+
// MAX(col)
|
|
592
|
+
const maxColM = /^MAX\((\w+)\)$/i.exec(trimmed);
|
|
593
|
+
if (maxColM) {
|
|
594
|
+
const col = maxColM[1];
|
|
595
|
+
const values = rows.map((r) => r[col]).filter((v) => v !== null && v !== undefined);
|
|
596
|
+
if (values.length === 0)
|
|
597
|
+
return null;
|
|
598
|
+
return values.reduce((a, b) => (String(a) >= String(b) ? a : b));
|
|
599
|
+
}
|
|
600
|
+
// MIN(col)
|
|
601
|
+
const minColM = /^MIN\((\w+)\)$/i.exec(trimmed);
|
|
602
|
+
if (minColM) {
|
|
603
|
+
const col = minColM[1];
|
|
604
|
+
const values = rows.map((r) => r[col]).filter((v) => v !== null && v !== undefined);
|
|
605
|
+
if (values.length === 0)
|
|
606
|
+
return null;
|
|
607
|
+
return values.reduce((a, b) => (String(a) <= String(b) ? a : b));
|
|
608
|
+
}
|
|
609
|
+
// Plain column reference — GROUP BY projected columns share the same value
|
|
610
|
+
if (/^\w+$/.test(trimmed) && rows.length > 0 && trimmed in rows[0]) {
|
|
611
|
+
return rows[0][trimmed];
|
|
612
|
+
}
|
|
613
|
+
// Literal value (e.g. the 0 in COALESCE(SUM(x), 0))
|
|
614
|
+
return this._evalLiteral(trimmed);
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Evaluate an expression for a single row.
|
|
618
|
+
* Supports CASE WHEN conditions, COALESCE, column references, and literals.
|
|
619
|
+
* Used by _evalAggregateExprGrouped to evaluate the argument of SUM().
|
|
620
|
+
*/
|
|
621
|
+
_evalRowExpr(expr, row) {
|
|
622
|
+
const trimmed = expr.trim();
|
|
623
|
+
// COALESCE(expr1, expr2, ...)
|
|
624
|
+
const coalesceM = /^COALESCE\((.+)\)$/i.exec(trimmed);
|
|
625
|
+
if (coalesceM) {
|
|
626
|
+
const args = this._splitTopLevelCommas(coalesceM[1]);
|
|
627
|
+
for (const arg of args) {
|
|
628
|
+
const val = this._evalRowExpr(arg.trim(), row);
|
|
629
|
+
if (val !== null && val !== undefined)
|
|
630
|
+
return val;
|
|
631
|
+
}
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
// CASE WHEN condition THEN thenExpr ELSE elseExpr END
|
|
635
|
+
const caseM = /^CASE\s+WHEN\s+(.+?)\s+THEN\s+(.+?)\s+ELSE\s+(.+?)\s+END$/is.exec(trimmed);
|
|
636
|
+
if (caseM) {
|
|
637
|
+
const matches = this._evalRowCondition(caseM[1].trim(), row);
|
|
638
|
+
return this._evalRowExpr(matches ? caseM[2].trim() : caseM[3].trim(), row);
|
|
639
|
+
}
|
|
640
|
+
// Plain column reference (identifier starting with a letter or underscore)
|
|
641
|
+
if (/^\w+$/.test(trimmed) && /^[a-zA-Z_]/.test(trimmed)) {
|
|
642
|
+
if (trimmed in row)
|
|
643
|
+
return row[trimmed];
|
|
644
|
+
// Column not in row — SQL NULL semantics (avoid treating as string literal)
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
// Literal (number, quoted string, etc.)
|
|
648
|
+
return this._evalLiteral(trimmed);
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Evaluate a simple condition (col = 'str', col = num) for a single row.
|
|
652
|
+
* Returns true if the condition holds.
|
|
653
|
+
*/
|
|
654
|
+
_evalRowCondition(condition, row) {
|
|
655
|
+
const trimmed = condition.trim();
|
|
656
|
+
// col = 'string'
|
|
657
|
+
const strM = /^(\w+)\s*=\s*'(.*)'$/is.exec(trimmed);
|
|
658
|
+
if (strM) {
|
|
659
|
+
const colVal = String(row[strM[1]] ?? '');
|
|
660
|
+
const literal = strM[2].replace(/''/g, "'");
|
|
661
|
+
return colVal === literal;
|
|
662
|
+
}
|
|
663
|
+
// col = numeric
|
|
664
|
+
const numM = /^(\w+)\s*=\s*(-?\d+(?:\.\d+)?)$/.exec(trimmed);
|
|
665
|
+
if (numM) {
|
|
666
|
+
return Number(row[numM[1]]) === parseFloat(numM[2]);
|
|
667
|
+
}
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
// -------------------------------------------------------------------------
|
|
671
|
+
// UPDATE
|
|
672
|
+
// -------------------------------------------------------------------------
|
|
673
|
+
_update(sql) {
|
|
674
|
+
// UPDATE tableName SET col1 = val1, col2 = val2 [WHERE ...]
|
|
675
|
+
const m = /UPDATE\s+(\w+)\s+SET\s+(.+?)(?:\s+WHERE\s+(.+))?$/is.exec(sql);
|
|
676
|
+
if (!m)
|
|
677
|
+
return [];
|
|
678
|
+
const tableName = m[1];
|
|
679
|
+
const setStr = m[2];
|
|
680
|
+
const whereStr = m[3];
|
|
681
|
+
const table = this._tables.get(tableName);
|
|
682
|
+
if (!table)
|
|
683
|
+
return [];
|
|
684
|
+
for (const row of table) {
|
|
685
|
+
if (!whereStr || this._matchWhere(whereStr.trim(), row)) {
|
|
686
|
+
const assignments = this._parseAssignmentsForRow(setStr, row);
|
|
687
|
+
for (const [col, val] of assignments) {
|
|
688
|
+
row[col] = val;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return [];
|
|
693
|
+
}
|
|
694
|
+
// -------------------------------------------------------------------------
|
|
695
|
+
// DELETE
|
|
696
|
+
// -------------------------------------------------------------------------
|
|
697
|
+
_delete(sql) {
|
|
698
|
+
// DELETE FROM tableName [WHERE ...]
|
|
699
|
+
const m = /DELETE\s+FROM\s+(\w+)(?:\s+WHERE\s+(.+))?$/is.exec(sql);
|
|
700
|
+
if (!m)
|
|
701
|
+
return [];
|
|
702
|
+
const tableName = m[1];
|
|
703
|
+
const whereStr = m[2];
|
|
704
|
+
const table = this._tables.get(tableName);
|
|
705
|
+
if (!table)
|
|
706
|
+
return [];
|
|
707
|
+
if (!whereStr) {
|
|
708
|
+
this._tables.set(tableName, []);
|
|
709
|
+
return [];
|
|
710
|
+
}
|
|
711
|
+
const kept = table.filter((row) => !this._matchWhere(whereStr.trim(), row));
|
|
712
|
+
this._tables.set(tableName, kept);
|
|
713
|
+
return [];
|
|
714
|
+
}
|
|
715
|
+
// -------------------------------------------------------------------------
|
|
716
|
+
// Helpers: WHERE evaluation
|
|
717
|
+
// -------------------------------------------------------------------------
|
|
718
|
+
/**
|
|
719
|
+
* Evaluate a simple WHERE clause against a row.
|
|
720
|
+
* Supports: `col = val` conditions joined by AND.
|
|
721
|
+
*/
|
|
722
|
+
_matchWhere(whereClause, row) {
|
|
723
|
+
// Split by AND (simple case — no OR, no nested parens)
|
|
724
|
+
const conditions = whereClause.split(/\s+AND\s+/i);
|
|
725
|
+
for (const condition of conditions) {
|
|
726
|
+
const trimmed = condition.trim();
|
|
727
|
+
// col = 'string literal' (col may be backtick or double-quote quoted)
|
|
728
|
+
const strM = /^[`"]?(\w+)[`"]?\s*=\s*'(.*)'$/is.exec(trimmed);
|
|
729
|
+
if (strM) {
|
|
730
|
+
const colVal = String(row[strM[1]] ?? '');
|
|
731
|
+
const literal = strM[2].replace(/''/g, "'");
|
|
732
|
+
if (colVal !== literal)
|
|
733
|
+
return false;
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
// col != 'string literal' (inequality)
|
|
737
|
+
const strNeqM = /^[`"]?(\w+)[`"]?\s*!=\s*'(.*)'$/is.exec(trimmed);
|
|
738
|
+
if (strNeqM) {
|
|
739
|
+
const colVal = String(row[strNeqM[1]] ?? '');
|
|
740
|
+
const literal = strNeqM[2].replace(/''/g, "'");
|
|
741
|
+
if (colVal === literal)
|
|
742
|
+
return false;
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
// col = numeric literal
|
|
746
|
+
const numM = /^[`"]?(\w+)[`"]?\s*=\s*(-?\d+(?:\.\d+)?)$/.exec(trimmed);
|
|
747
|
+
if (numM) {
|
|
748
|
+
if (Number(row[numM[1]]) !== parseFloat(numM[2]))
|
|
749
|
+
return false;
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
// col != numeric literal (inequality)
|
|
753
|
+
const numNeqM = /^[`"]?(\w+)[`"]?\s*!=\s*(-?\d+(?:\.\d+)?)$/.exec(trimmed);
|
|
754
|
+
if (numNeqM) {
|
|
755
|
+
if (Number(row[numNeqM[1]]) === parseFloat(numNeqM[2]))
|
|
756
|
+
return false;
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
// col < / > / <= / >= 'string literal' (handles ISO date comparisons)
|
|
760
|
+
const strCmpM = /^[`"]?(\w+)[`"]?\s*(>=|<=|>|<)\s*'(.*)'$/s.exec(trimmed);
|
|
761
|
+
if (strCmpM) {
|
|
762
|
+
const colVal = row[strCmpM[1]];
|
|
763
|
+
if (colVal === null || colVal === undefined)
|
|
764
|
+
return false;
|
|
765
|
+
const lhs = String(colVal);
|
|
766
|
+
const rhs = strCmpM[3].replace(/''/g, "'");
|
|
767
|
+
const op = strCmpM[2];
|
|
768
|
+
if (op === '<' && !(lhs < rhs))
|
|
769
|
+
return false;
|
|
770
|
+
if (op === '<=' && !(lhs <= rhs))
|
|
771
|
+
return false;
|
|
772
|
+
if (op === '>' && !(lhs > rhs))
|
|
773
|
+
return false;
|
|
774
|
+
if (op === '>=' && !(lhs >= rhs))
|
|
775
|
+
return false;
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
// col < / > / <= / >= numeric literal
|
|
779
|
+
const numCmpM = /^[`"]?(\w+)[`"]?\s*(>=|<=|>|<)\s*(-?\d+(?:\.\d+)?)$/.exec(trimmed);
|
|
780
|
+
if (numCmpM) {
|
|
781
|
+
const colVal = Number(row[numCmpM[1]] ?? 0);
|
|
782
|
+
const rhs = parseFloat(numCmpM[3]);
|
|
783
|
+
const op = numCmpM[2];
|
|
784
|
+
if (op === '<' && !(colVal < rhs))
|
|
785
|
+
return false;
|
|
786
|
+
if (op === '<=' && !(colVal <= rhs))
|
|
787
|
+
return false;
|
|
788
|
+
if (op === '>' && !(colVal > rhs))
|
|
789
|
+
return false;
|
|
790
|
+
if (op === '>=' && !(colVal >= rhs))
|
|
791
|
+
return false;
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
// col IS NULL
|
|
795
|
+
const nullM = /^[`"]?(\w+)[`"]?\s+IS\s+NULL$/i.exec(trimmed);
|
|
796
|
+
if (nullM) {
|
|
797
|
+
if (row[nullM[1]] !== null && row[nullM[1]] !== undefined)
|
|
798
|
+
return false;
|
|
799
|
+
continue;
|
|
800
|
+
}
|
|
801
|
+
// col IS NOT NULL
|
|
802
|
+
const notNullM = /^[`"]?(\w+)[`"]?\s+IS\s+NOT\s+NULL$/i.exec(trimmed);
|
|
803
|
+
if (notNullM) {
|
|
804
|
+
if (row[notNullM[1]] === null || row[notNullM[1]] === undefined)
|
|
805
|
+
return false;
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
// col LIKE 'pattern' (supports % wildcard)
|
|
809
|
+
const likeM = /^[`"]?(\w+)[`"]?\s+LIKE\s+'(.*)'$/is.exec(trimmed);
|
|
810
|
+
if (likeM) {
|
|
811
|
+
const colVal = row[likeM[1]];
|
|
812
|
+
if (colVal === null || colVal === undefined)
|
|
813
|
+
return false;
|
|
814
|
+
const pattern = likeM[2].replace(/''/g, "'");
|
|
815
|
+
// Convert SQL LIKE pattern to regex: % → .*, _ → .
|
|
816
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, (ch) => ch === '%' || ch === '_' ? ch : '\\' + ch);
|
|
817
|
+
const regex = new RegExp('^' + escaped.replace(/%/g, '.*').replace(/_/g, '.') + '$', 's');
|
|
818
|
+
if (!regex.test(String(colVal)))
|
|
819
|
+
return false;
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
// col IN ('val1', 'val2', ...)
|
|
823
|
+
const inM = /^[`"]?(\w+)[`"]?\s+IN\s*\((.+)\)$/is.exec(trimmed);
|
|
824
|
+
if (inM) {
|
|
825
|
+
const colVal = row[inM[1]];
|
|
826
|
+
const inValues = this._parseValueList(inM[2]);
|
|
827
|
+
const colStr = colVal === null || colVal === undefined ? null : typeof colVal === 'number' ? colVal : String(colVal);
|
|
828
|
+
if (!inValues.some((v) => v === colStr || String(v) === String(colStr)))
|
|
829
|
+
return false;
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
// col NOT IN ('val1', 'val2', ...)
|
|
833
|
+
const notInM = /^[`"]?(\w+)[`"]?\s+NOT\s+IN\s*\((.+)\)$/is.exec(trimmed);
|
|
834
|
+
if (notInM) {
|
|
835
|
+
const colVal = row[notInM[1]];
|
|
836
|
+
const notInValues = this._parseValueList(notInM[2]);
|
|
837
|
+
const colStr = colVal === null || colVal === undefined ? null : typeof colVal === 'number' ? colVal : String(colVal);
|
|
838
|
+
if (notInValues.some((v) => v === colStr || String(v) === String(colStr)))
|
|
839
|
+
return false;
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
// Unrecognised condition: skip (treat as matching)
|
|
843
|
+
}
|
|
844
|
+
return true;
|
|
845
|
+
}
|
|
846
|
+
// -------------------------------------------------------------------------
|
|
847
|
+
// Helpers: column projection
|
|
848
|
+
// -------------------------------------------------------------------------
|
|
849
|
+
_projectCols(colsStr, row) {
|
|
850
|
+
const result = {};
|
|
851
|
+
const cols = this._splitTopLevelCommas(colsStr);
|
|
852
|
+
for (const col of cols) {
|
|
853
|
+
// Handle "expr AS alias" or plain "col"
|
|
854
|
+
const aliasM = /^(.+?)\s+AS\s+(\w+)$/i.exec(col);
|
|
855
|
+
if (aliasM) {
|
|
856
|
+
result[aliasM[2]] = this._evalExprAgainstRow(aliasM[1].trim(), row);
|
|
857
|
+
}
|
|
858
|
+
else {
|
|
859
|
+
// Strip backtick/double-quote quoting from column names (e.g. `key` → key)
|
|
860
|
+
const unquoted = col.replace(/^[`"](.+)[`"]$/, '$1');
|
|
861
|
+
// Return null for columns not present in the row (matches SQLite NULL behavior)
|
|
862
|
+
result[unquoted] = unquoted in row ? row[unquoted] : null;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return result;
|
|
866
|
+
}
|
|
867
|
+
// -------------------------------------------------------------------------
|
|
868
|
+
// Helpers: literal expression evaluation (for SELECT without FROM)
|
|
869
|
+
// -------------------------------------------------------------------------
|
|
870
|
+
_evalSelectExprs(exprs) {
|
|
871
|
+
const result = {};
|
|
872
|
+
const parts = this._splitTopLevelCommas(exprs);
|
|
873
|
+
for (const part of parts) {
|
|
874
|
+
const aliasM = /^(.+?)\s+AS\s+(\w+)$/i.exec(part);
|
|
875
|
+
if (aliasM) {
|
|
876
|
+
result[aliasM[2]] = this._evalLiteral(aliasM[1].trim());
|
|
877
|
+
}
|
|
878
|
+
else {
|
|
879
|
+
result[part] = this._evalLiteral(part);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
return result;
|
|
883
|
+
}
|
|
884
|
+
_evalLiteral(expr) {
|
|
885
|
+
const trimmed = expr.trim();
|
|
886
|
+
if (trimmed.toUpperCase() === 'NULL')
|
|
887
|
+
return null;
|
|
888
|
+
if (/^'.*'$/.test(trimmed))
|
|
889
|
+
return trimmed.slice(1, -1).replace(/''/g, "'");
|
|
890
|
+
if (/^-?\d+$/.test(trimmed))
|
|
891
|
+
return parseInt(trimmed, 10);
|
|
892
|
+
if (/^-?\d+\.\d+$/.test(trimmed))
|
|
893
|
+
return parseFloat(trimmed);
|
|
894
|
+
return trimmed;
|
|
895
|
+
}
|
|
896
|
+
_evalExprAgainstRow(expr, row) {
|
|
897
|
+
// Try literal first, then column name lookup
|
|
898
|
+
const literal = this._evalLiteral(expr);
|
|
899
|
+
if (typeof literal !== 'string')
|
|
900
|
+
return literal;
|
|
901
|
+
// If it looks like an identifier (word chars only) and exists in row, use it
|
|
902
|
+
if (/^\w+$/.test(expr) && expr in row)
|
|
903
|
+
return row[expr];
|
|
904
|
+
return literal;
|
|
905
|
+
}
|
|
906
|
+
// -------------------------------------------------------------------------
|
|
907
|
+
// Helpers: parenthesis-aware comma splitting
|
|
908
|
+
// -------------------------------------------------------------------------
|
|
909
|
+
/**
|
|
910
|
+
* Split a string by commas that are NOT inside parentheses.
|
|
911
|
+
* E.g. "COALESCE(SUM(x), 0) as a, y" → ["COALESCE(SUM(x), 0) as a", "y"]
|
|
912
|
+
*/
|
|
913
|
+
_splitTopLevelCommas(str) {
|
|
914
|
+
const parts = [];
|
|
915
|
+
let current = '';
|
|
916
|
+
let depth = 0;
|
|
917
|
+
let inStr = false;
|
|
918
|
+
for (let i = 0; i < str.length; i++) {
|
|
919
|
+
const ch = str[i];
|
|
920
|
+
if (ch === "'" && !inStr) {
|
|
921
|
+
inStr = true;
|
|
922
|
+
current += ch;
|
|
923
|
+
}
|
|
924
|
+
else if (ch === "'" && inStr) {
|
|
925
|
+
if (str[i + 1] === "'") {
|
|
926
|
+
current += "''";
|
|
927
|
+
i++;
|
|
928
|
+
}
|
|
929
|
+
else {
|
|
930
|
+
inStr = false;
|
|
931
|
+
current += ch;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
else if (!inStr && ch === '(') {
|
|
935
|
+
depth++;
|
|
936
|
+
current += ch;
|
|
937
|
+
}
|
|
938
|
+
else if (!inStr && ch === ')') {
|
|
939
|
+
depth--;
|
|
940
|
+
current += ch;
|
|
941
|
+
}
|
|
942
|
+
else if (!inStr && ch === ',' && depth === 0) {
|
|
943
|
+
parts.push(current.trim());
|
|
944
|
+
current = '';
|
|
945
|
+
}
|
|
946
|
+
else {
|
|
947
|
+
current += ch;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
if (current.trim() !== '')
|
|
951
|
+
parts.push(current.trim());
|
|
952
|
+
return parts;
|
|
953
|
+
}
|
|
954
|
+
// -------------------------------------------------------------------------
|
|
955
|
+
// Helpers: aggregate evaluation
|
|
956
|
+
// -------------------------------------------------------------------------
|
|
957
|
+
/**
|
|
958
|
+
* Evaluate aggregate SELECT expressions (SUM, COALESCE, COUNT) across
|
|
959
|
+
* a set of filtered rows, returning a single result row.
|
|
960
|
+
*/
|
|
961
|
+
_evalAggregate(colsStr, rows) {
|
|
962
|
+
const result = {};
|
|
963
|
+
const cols = this._splitTopLevelCommas(colsStr);
|
|
964
|
+
for (const col of cols) {
|
|
965
|
+
const aliasM = /^(.+?)\s+AS\s+(\w+)$/i.exec(col);
|
|
966
|
+
const expr = aliasM ? aliasM[1].trim() : col.trim();
|
|
967
|
+
const alias = aliasM ? aliasM[2] : col.trim();
|
|
968
|
+
result[alias] = this._evalAggregateExpr(expr, rows);
|
|
969
|
+
}
|
|
970
|
+
return result;
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Evaluate a single aggregate expression against a set of rows.
|
|
974
|
+
* Supports: SUM(expr), COALESCE(expr, default), COUNT(*), MAX(col), MIN(col).
|
|
975
|
+
*/
|
|
976
|
+
_evalAggregateExpr(expr, rows) {
|
|
977
|
+
const trimmed = expr.trim();
|
|
978
|
+
// COALESCE(expr, default)
|
|
979
|
+
const coalesceM = /^COALESCE\((.+)\)$/i.exec(trimmed);
|
|
980
|
+
if (coalesceM) {
|
|
981
|
+
const args = this._splitTopLevelCommas(coalesceM[1]);
|
|
982
|
+
for (const arg of args) {
|
|
983
|
+
const val = this._evalAggregateExpr(arg.trim(), rows);
|
|
984
|
+
if (val !== null && val !== undefined)
|
|
985
|
+
return val;
|
|
986
|
+
}
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
// SUM(expr) — expr may be a plain column name or CASE WHEN ... expression
|
|
990
|
+
const sumM = /^SUM\((.+)\)$/i.exec(trimmed);
|
|
991
|
+
if (sumM) {
|
|
992
|
+
if (rows.length === 0)
|
|
993
|
+
return null;
|
|
994
|
+
let total = 0;
|
|
995
|
+
for (const row of rows) {
|
|
996
|
+
total += Number(this._evalRowExpr(sumM[1].trim(), row) ?? 0);
|
|
997
|
+
}
|
|
998
|
+
return total;
|
|
999
|
+
}
|
|
1000
|
+
// COUNT(*)
|
|
1001
|
+
if (/^COUNT\(\*\)$/i.test(trimmed)) {
|
|
1002
|
+
return rows.length;
|
|
1003
|
+
}
|
|
1004
|
+
// COUNT(DISTINCT col)
|
|
1005
|
+
const countDistinctM = /^COUNT\(\s*DISTINCT\s+(\w+)\s*\)$/i.exec(trimmed);
|
|
1006
|
+
if (countDistinctM) {
|
|
1007
|
+
const col = countDistinctM[1];
|
|
1008
|
+
const distinct = new Set(rows.map((r) => r[col]).filter((v) => v !== null && v !== undefined));
|
|
1009
|
+
return distinct.size;
|
|
1010
|
+
}
|
|
1011
|
+
// COUNT(col)
|
|
1012
|
+
const countM = /^COUNT\((\w+)\)$/i.exec(trimmed);
|
|
1013
|
+
if (countM) {
|
|
1014
|
+
const col = countM[1];
|
|
1015
|
+
return rows.filter((r) => r[col] !== null && r[col] !== undefined).length;
|
|
1016
|
+
}
|
|
1017
|
+
// MAX(col)
|
|
1018
|
+
const maxM = /^MAX\((\w+)\)$/i.exec(trimmed);
|
|
1019
|
+
if (maxM) {
|
|
1020
|
+
const col = maxM[1];
|
|
1021
|
+
const values = rows.map((r) => r[col]).filter((v) => v !== null && v !== undefined);
|
|
1022
|
+
if (values.length === 0)
|
|
1023
|
+
return null;
|
|
1024
|
+
return values.reduce((a, b) => (String(a) >= String(b) ? a : b));
|
|
1025
|
+
}
|
|
1026
|
+
// MIN(col)
|
|
1027
|
+
const minM = /^MIN\((\w+)\)$/i.exec(trimmed);
|
|
1028
|
+
if (minM) {
|
|
1029
|
+
const col = minM[1];
|
|
1030
|
+
const values = rows.map((r) => r[col]).filter((v) => v !== null && v !== undefined);
|
|
1031
|
+
if (values.length === 0)
|
|
1032
|
+
return null;
|
|
1033
|
+
return values.reduce((a, b) => (String(a) <= String(b) ? a : b));
|
|
1034
|
+
}
|
|
1035
|
+
// Literal value (e.g. the 0 in COALESCE(SUM(x), 0))
|
|
1036
|
+
return this._evalLiteral(trimmed);
|
|
1037
|
+
}
|
|
1038
|
+
// -------------------------------------------------------------------------
|
|
1039
|
+
// Helpers: value list parsing
|
|
1040
|
+
// -------------------------------------------------------------------------
|
|
1041
|
+
/**
|
|
1042
|
+
* Parse a comma-separated list of SQL literal values.
|
|
1043
|
+
* Handles: NULL, numbers, single-quoted strings.
|
|
1044
|
+
* Simple split by comma (assumes no commas inside string values).
|
|
1045
|
+
*/
|
|
1046
|
+
_parseValueList(valStr) {
|
|
1047
|
+
// Tokenise respecting single-quoted strings
|
|
1048
|
+
const tokens = [];
|
|
1049
|
+
let current = '';
|
|
1050
|
+
let inStr = false;
|
|
1051
|
+
for (let i = 0; i < valStr.length; i++) {
|
|
1052
|
+
const ch = valStr[i];
|
|
1053
|
+
if (ch === "'" && !inStr) {
|
|
1054
|
+
inStr = true;
|
|
1055
|
+
current += ch;
|
|
1056
|
+
}
|
|
1057
|
+
else if (ch === "'" && inStr) {
|
|
1058
|
+
// Peek ahead for escaped quote ('')
|
|
1059
|
+
if (valStr[i + 1] === "'") {
|
|
1060
|
+
current += "''";
|
|
1061
|
+
i++;
|
|
1062
|
+
}
|
|
1063
|
+
else {
|
|
1064
|
+
inStr = false;
|
|
1065
|
+
current += ch;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
else if (ch === ',' && !inStr) {
|
|
1069
|
+
tokens.push(current.trim());
|
|
1070
|
+
current = '';
|
|
1071
|
+
}
|
|
1072
|
+
else {
|
|
1073
|
+
current += ch;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
if (current.trim() !== '')
|
|
1077
|
+
tokens.push(current.trim());
|
|
1078
|
+
return tokens.map((t) => this._evalLiteral(t));
|
|
1079
|
+
}
|
|
1080
|
+
// -------------------------------------------------------------------------
|
|
1081
|
+
// Helpers: SET clause parsing
|
|
1082
|
+
// -------------------------------------------------------------------------
|
|
1083
|
+
/**
|
|
1084
|
+
* Parse `col1 = val1, col2 = val2` assignments into an array of [col, val] pairs.
|
|
1085
|
+
* Evaluates each RHS expression against the provided row for arithmetic support.
|
|
1086
|
+
*/
|
|
1087
|
+
_parseAssignmentsForRow(setStr, row) {
|
|
1088
|
+
const assignments = [];
|
|
1089
|
+
const parts = [];
|
|
1090
|
+
let current = '';
|
|
1091
|
+
let inStr = false;
|
|
1092
|
+
for (let i = 0; i < setStr.length; i++) {
|
|
1093
|
+
const ch = setStr[i];
|
|
1094
|
+
if (ch === "'" && !inStr) {
|
|
1095
|
+
inStr = true;
|
|
1096
|
+
current += ch;
|
|
1097
|
+
}
|
|
1098
|
+
else if (ch === "'" && inStr) {
|
|
1099
|
+
if (setStr[i + 1] === "'") {
|
|
1100
|
+
current += "''";
|
|
1101
|
+
i++;
|
|
1102
|
+
}
|
|
1103
|
+
else {
|
|
1104
|
+
inStr = false;
|
|
1105
|
+
current += ch;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
else if (ch === ',' && !inStr) {
|
|
1109
|
+
parts.push(current.trim());
|
|
1110
|
+
current = '';
|
|
1111
|
+
}
|
|
1112
|
+
else {
|
|
1113
|
+
current += ch;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
if (current.trim() !== '')
|
|
1117
|
+
parts.push(current.trim());
|
|
1118
|
+
for (const part of parts) {
|
|
1119
|
+
const eqIdx = part.indexOf('=');
|
|
1120
|
+
if (eqIdx === -1)
|
|
1121
|
+
continue;
|
|
1122
|
+
const col = part.slice(0, eqIdx).trim();
|
|
1123
|
+
const valStr = part.slice(eqIdx + 1).trim();
|
|
1124
|
+
assignments.push([col, this._evalAssignmentExpr(valStr, row)]);
|
|
1125
|
+
}
|
|
1126
|
+
return assignments;
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Evaluate a SET assignment RHS expression against the current row.
|
|
1130
|
+
* Handles: simple literals, column arithmetic (col +/- val), COALESCE, CURRENT_TIMESTAMP.
|
|
1131
|
+
*/
|
|
1132
|
+
_evalAssignmentExpr(expr, row) {
|
|
1133
|
+
const trimmed = expr.trim();
|
|
1134
|
+
// CURRENT_TIMESTAMP
|
|
1135
|
+
if (/^CURRENT_TIMESTAMP$/i.test(trimmed)) {
|
|
1136
|
+
return new Date().toISOString();
|
|
1137
|
+
}
|
|
1138
|
+
// COALESCE(expr, default)
|
|
1139
|
+
const coalesceM = /^COALESCE\((.+)\)$/i.exec(trimmed);
|
|
1140
|
+
if (coalesceM) {
|
|
1141
|
+
const args = this._splitTopLevelCommas(coalesceM[1]);
|
|
1142
|
+
for (const arg of args) {
|
|
1143
|
+
const val = this._evalAssignmentExpr(arg.trim(), row);
|
|
1144
|
+
if (val !== null && val !== undefined)
|
|
1145
|
+
return val;
|
|
1146
|
+
}
|
|
1147
|
+
return null;
|
|
1148
|
+
}
|
|
1149
|
+
// Arithmetic: col +/- numeric (e.g. cost_usd + 0.0105)
|
|
1150
|
+
const arithM = /^(\w+)\s*([+\-*\/])\s*(-?\d+(?:\.\d+)?)$/.exec(trimmed);
|
|
1151
|
+
if (arithM) {
|
|
1152
|
+
const colName = arithM[1];
|
|
1153
|
+
const op = arithM[2];
|
|
1154
|
+
const num = parseFloat(arithM[3]);
|
|
1155
|
+
const colVal = Number(row[colName] ?? 0);
|
|
1156
|
+
switch (op) {
|
|
1157
|
+
case '+': return colVal + num;
|
|
1158
|
+
case '-': return colVal - num;
|
|
1159
|
+
case '*': return colVal * num;
|
|
1160
|
+
case '/': return num !== 0 ? colVal / num : null;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
// Plain literal
|
|
1164
|
+
return this._evalLiteral(trimmed);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
//# sourceMappingURL=memory-adapter.js.map
|