nexus-ai-pro 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API_STABILITY.md +38 -0
- package/CHANGELOG.md +145 -1
- package/README.md +13 -8
- package/dist/cache/memory-cache.d.ts +8 -0
- package/dist/cache/memory-cache.js +9 -0
- package/dist/cache/semantic-cache.d.ts +6 -3
- package/dist/cache/semantic-cache.js +5 -2
- package/dist/cli/args.js +3 -0
- package/dist/cli/db.d.ts +16 -4
- package/dist/cli/db.js +169 -22
- package/dist/cli/doctor.d.ts +17 -0
- package/dist/cli/doctor.js +53 -0
- package/dist/cli.js +12 -1
- package/dist/core/lifecycle.js +11 -6
- package/dist/core/nexus.d.ts +6 -0
- package/dist/core/nexus.js +103 -22
- package/dist/doctor/index.d.ts +90 -0
- package/dist/doctor/index.js +237 -0
- package/dist/graph/functional.d.ts +80 -7
- package/dist/graph/functional.js +138 -35
- package/dist/graph/graph.d.ts +15 -5
- package/dist/graph/graph.js +72 -20
- package/dist/images/evals.js +4 -2
- package/dist/operations/adapters.d.ts +52 -4
- package/dist/operations/adapters.js +194 -7
- package/dist/operations/runner.js +1 -0
- package/dist/operations/stats.js +8 -1
- package/dist/operations/store.d.ts +5 -1
- package/dist/operations/store.js +7 -0
- package/dist/ops/circuit-breaker.d.ts +52 -0
- package/dist/ops/circuit-breaker.js +4 -0
- package/dist/ops/circuit-store.d.ts +13 -2
- package/dist/ops/circuit-store.js +68 -0
- package/dist/ops/circuit-sync.d.ts +17 -0
- package/dist/ops/circuit-sync.js +86 -0
- package/dist/ops/rate-limiter.d.ts +2 -0
- package/dist/ops/rate-limiter.js +2 -0
- package/dist/postgres/circuits.d.ts +21 -4
- package/dist/postgres/circuits.js +65 -4
- package/dist/postgres/client.d.ts +0 -2
- package/dist/postgres/client.js +0 -4
- package/dist/postgres/evaluate.d.ts +16 -4
- package/dist/postgres/evaluate.js +17 -5
- package/dist/postgres/index.d.ts +32 -11
- package/dist/postgres/index.js +65 -31
- package/dist/postgres/migrations.d.ts +60 -0
- package/dist/postgres/migrations.js +121 -0
- package/dist/postgres/operations.d.ts +14 -3
- package/dist/postgres/operations.js +45 -15
- package/dist/postgres/prompts.d.ts +11 -2
- package/dist/postgres/prompts.js +14 -3
- package/dist/postgres/rollups.d.ts +35 -0
- package/dist/postgres/rollups.js +129 -0
- package/dist/postgres/store.d.ts +11 -2
- package/dist/postgres/store.js +14 -3
- package/dist/postgres/traces.d.ts +13 -3
- package/dist/postgres/traces.js +25 -3
- package/dist/postgres/vectors.d.ts +11 -2
- package/dist/postgres/vectors.js +14 -3
- package/dist/router/auto-router.d.ts +34 -7
- package/dist/router/auto-router.js +201 -68
- package/dist/server/assistant.d.ts +4 -0
- package/dist/server/assistant.js +8 -0
- package/dist/server/errors.d.ts +12 -0
- package/dist/server/errors.js +7 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/runs.d.ts +24 -1
- package/dist/server/runs.js +90 -14
- package/dist/sqlite/index.d.ts +16 -3
- package/dist/sqlite/index.js +16 -3
- package/dist/sqlite/migrations.d.ts +26 -0
- package/dist/sqlite/migrations.js +63 -0
- package/dist/sqlite/operations.d.ts +14 -3
- package/dist/sqlite/operations.js +42 -15
- package/dist/sqlite/store.d.ts +9 -2
- package/dist/sqlite/store.js +13 -3
- package/dist/sqlite/vectors.d.ts +11 -2
- package/dist/sqlite/vectors.js +13 -2
- package/dist/store/tenant.d.ts +12 -0
- package/dist/store/tenant.js +29 -0
- package/dist/tenancy/index.d.ts +113 -0
- package/dist/tenancy/index.js +233 -0
- package/dist/tracing/instrument.js +2 -0
- package/dist/types/config.d.ts +24 -2
- package/dist/types/graph.d.ts +11 -1
- package/dist/types/messages.d.ts +5 -0
- package/dist/types/operations.d.ts +6 -0
- package/dist/types/response.d.ts +20 -0
- package/dist/types/server.d.ts +10 -1
- package/dist/utils/schema-migrations.d.ts +97 -0
- package/dist/utils/schema-migrations.js +91 -0
- package/dist/utils/signals.d.ts +17 -0
- package/dist/utils/signals.js +26 -0
- package/dist/utils/tenant.d.ts +14 -0
- package/dist/utils/tenant.js +16 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist-cjs/cache/memory-cache.d.ts +8 -0
- package/dist-cjs/cache/memory-cache.js +9 -0
- package/dist-cjs/cache/semantic-cache.d.ts +6 -3
- package/dist-cjs/cache/semantic-cache.js +5 -2
- package/dist-cjs/cli/args.js +3 -0
- package/dist-cjs/cli/db.d.ts +16 -4
- package/dist-cjs/cli/db.js +200 -20
- package/dist-cjs/cli/doctor.d.ts +17 -0
- package/dist-cjs/cli/doctor.js +56 -0
- package/dist-cjs/core/lifecycle.js +11 -6
- package/dist-cjs/core/nexus.d.ts +6 -0
- package/dist-cjs/core/nexus.js +103 -22
- package/dist-cjs/doctor/index.d.ts +90 -0
- package/dist-cjs/doctor/index.js +276 -0
- package/dist-cjs/graph/functional.d.ts +80 -7
- package/dist-cjs/graph/functional.js +137 -34
- package/dist-cjs/graph/graph.d.ts +15 -5
- package/dist-cjs/graph/graph.js +72 -20
- package/dist-cjs/images/evals.js +4 -2
- package/dist-cjs/operations/adapters.d.ts +52 -4
- package/dist-cjs/operations/adapters.js +193 -6
- package/dist-cjs/operations/runner.js +1 -0
- package/dist-cjs/operations/stats.js +8 -1
- package/dist-cjs/operations/store.d.ts +5 -1
- package/dist-cjs/operations/store.js +7 -0
- package/dist-cjs/ops/circuit-breaker.d.ts +52 -0
- package/dist-cjs/ops/circuit-breaker.js +4 -0
- package/dist-cjs/ops/circuit-store.d.ts +13 -2
- package/dist-cjs/ops/circuit-store.js +68 -0
- package/dist-cjs/ops/circuit-sync.d.ts +17 -0
- package/dist-cjs/ops/circuit-sync.js +87 -0
- package/dist-cjs/ops/rate-limiter.d.ts +2 -0
- package/dist-cjs/ops/rate-limiter.js +2 -0
- package/dist-cjs/postgres/circuits.d.ts +21 -4
- package/dist-cjs/postgres/circuits.js +98 -3
- package/dist-cjs/postgres/client.d.ts +0 -2
- package/dist-cjs/postgres/client.js +0 -5
- package/dist-cjs/postgres/evaluate.d.ts +16 -4
- package/dist-cjs/postgres/evaluate.js +50 -4
- package/dist-cjs/postgres/index.d.ts +32 -11
- package/dist-cjs/postgres/index.js +69 -20
- package/dist-cjs/postgres/migrations.d.ts +60 -0
- package/dist-cjs/postgres/migrations.js +129 -0
- package/dist-cjs/postgres/operations.d.ts +14 -3
- package/dist-cjs/postgres/operations.js +78 -14
- package/dist-cjs/postgres/prompts.d.ts +11 -2
- package/dist-cjs/postgres/prompts.js +47 -2
- package/dist-cjs/postgres/rollups.d.ts +35 -0
- package/dist-cjs/postgres/rollups.js +168 -0
- package/dist-cjs/postgres/store.d.ts +11 -2
- package/dist-cjs/postgres/store.js +47 -2
- package/dist-cjs/postgres/traces.d.ts +13 -3
- package/dist-cjs/postgres/traces.js +58 -2
- package/dist-cjs/postgres/vectors.d.ts +11 -2
- package/dist-cjs/postgres/vectors.js +47 -2
- package/dist-cjs/router/auto-router.d.ts +34 -7
- package/dist-cjs/router/auto-router.js +201 -68
- package/dist-cjs/server/assistant.d.ts +4 -0
- package/dist-cjs/server/assistant.js +8 -0
- package/dist-cjs/server/errors.d.ts +12 -0
- package/dist-cjs/server/errors.js +9 -1
- package/dist-cjs/server/index.d.ts +1 -1
- package/dist-cjs/server/index.js +2 -1
- package/dist-cjs/server/runs.d.ts +24 -1
- package/dist-cjs/server/runs.js +89 -13
- package/dist-cjs/sqlite/index.d.ts +16 -3
- package/dist-cjs/sqlite/index.js +31 -10
- package/dist-cjs/sqlite/migrations.d.ts +26 -0
- package/dist-cjs/sqlite/migrations.js +71 -0
- package/dist-cjs/sqlite/operations.d.ts +14 -3
- package/dist-cjs/sqlite/operations.js +76 -15
- package/dist-cjs/sqlite/store.d.ts +9 -2
- package/dist-cjs/sqlite/store.js +47 -3
- package/dist-cjs/sqlite/vectors.d.ts +11 -2
- package/dist-cjs/sqlite/vectors.js +47 -2
- package/dist-cjs/store/tenant.d.ts +12 -0
- package/dist-cjs/store/tenant.js +33 -0
- package/dist-cjs/tenancy/index.d.ts +113 -0
- package/dist-cjs/tenancy/index.js +250 -0
- package/dist-cjs/tracing/instrument.js +2 -0
- package/dist-cjs/types/config.d.ts +24 -2
- package/dist-cjs/types/graph.d.ts +11 -1
- package/dist-cjs/types/messages.d.ts +5 -0
- package/dist-cjs/types/operations.d.ts +6 -0
- package/dist-cjs/types/response.d.ts +20 -0
- package/dist-cjs/types/server.d.ts +10 -1
- package/dist-cjs/utils/schema-migrations.d.ts +97 -0
- package/dist-cjs/utils/schema-migrations.js +102 -0
- package/dist-cjs/utils/signals.d.ts +17 -0
- package/dist-cjs/utils/signals.js +29 -0
- package/dist-cjs/utils/tenant.d.ts +14 -0
- package/dist-cjs/utils/tenant.js +22 -0
- package/dist-cjs/version.d.ts +1 -1
- package/dist-cjs/version.js +1 -1
- package/llms.txt +4 -3
- package/package.json +71 -2
package/API_STABILITY.md
CHANGED
|
@@ -41,6 +41,44 @@ Both are covered by this policy:
|
|
|
41
41
|
Deep imports into `dist`, `dist-cjs`, or `src` are not supported. Import only from `nexus-ai-pro` or
|
|
42
42
|
one of its explicit subpaths.
|
|
43
43
|
|
|
44
|
+
## 2.2 stage (2.2.0)
|
|
45
|
+
|
|
46
|
+
Everything 2.2 adds is stable from the start and additive: no default changes meaning.
|
|
47
|
+
|
|
48
|
+
- versioned migrations: `SchemaMigration`, `AppliedMigration`, `MigrationStatus`, `MigrationResult`,
|
|
49
|
+
`SchemaMigrationError`, `migrationChecksum()`, `applyPostgresMigrations()`,
|
|
50
|
+
`postgresMigrationStatus()`, `postgresMigrations()`, `PostgresMigrateOptions`, every adapter's
|
|
51
|
+
`…Migrations()`, and the SQLite twins (`nexus-ai-pro/postgres`, `nexus-ai-pro/postgres/migrations`,
|
|
52
|
+
`nexus-ai-pro/sqlite`, `nexus-ai-pro/sqlite/migrations`). A migration, once released, never
|
|
53
|
+
changes; later releases add versions. `migrate()` now resolves to a `MigrationResult` instead of
|
|
54
|
+
nothing;
|
|
55
|
+
- `nexus db status` and `nexus db migrate`, and `nexus db sql --record`;
|
|
56
|
+
- `RedisOperationStoreOptions.index` and `reindex()`;
|
|
57
|
+
- `CircuitBreakerConfig.shareObservations`, `CircuitStateStore.observe()`, `CircuitObservation`,
|
|
58
|
+
`CircuitWindow`, and `CircuitSnapshot.shared`;
|
|
59
|
+
- `PostgresRollupStore` (`nexus-ai-pro/postgres/rollups`);
|
|
60
|
+
- the tenant model (`nexus-ai-pro/tenancy`): every `tenant…()` view, `tenantScope()`,
|
|
61
|
+
`TenantAssetStore`, `TenantStores`, `assertTenantId()`, `TenantIdError`, and `TENANT_NAMESPACE`.
|
|
62
|
+
The prefix a view stores names under (`tenant/<id>/`) is part of the contract and does not change
|
|
63
|
+
within 2.x; `GraphRunOptions.tenantId`, `NodeContext.tenantId`, the `tenantId` options of `state()`,
|
|
64
|
+
`history()`, `fork()`, and `updateState()`, `OperationRecord.tenantId`,
|
|
65
|
+
`OperationSubmitOptions.tenantId`, `OperationStoreFilter.tenantId`, `RateLimitedRequest.tenantId`,
|
|
66
|
+
and `rateLimit.key: 'tenantId'`;
|
|
67
|
+
- `CacheConfig.namespace`, `CacheNamespace`, `CacheOutcome` and `ResponseMeta.cache`,
|
|
68
|
+
`CompletionRequest.responseCache`, `MemoryCache.lookup()`, and the `scope` of `SemanticCache`'s
|
|
69
|
+
`get()` and `set()`;
|
|
70
|
+
- `diagnose()` and its types (`nexus-ai-pro/doctor`), and `nexus doctor`. Checks may be added in a
|
|
71
|
+
minor release; a check's id and what makes it fail do not change within 2.x;
|
|
72
|
+
- workflow steps' `timeout`, `onError`, `StepTimeout`, `StepFailure`, `StepRecovery`,
|
|
73
|
+
`StepContext.heartbeat()`, `WorkflowOptions.stepDefaults`, `WorkflowRunOptions.control` and
|
|
74
|
+
`tenantId`, `WorkflowContext.tenantId`, and `WorkflowStepTimeoutError.kind`;
|
|
75
|
+
- `RunHandOffError`, `AssistantRunContext.saveProgress()` and `progress`.
|
|
76
|
+
|
|
77
|
+
Three races are closed rather than added: a busy thread is claimed atomically, and the in-memory and
|
|
78
|
+
Redis operation stores refuse a second record under a held idempotency key, as the SQL stores
|
|
79
|
+
already did. The semantic cache no longer matches across tenants, and a failing response cache no
|
|
80
|
+
longer fails a request.
|
|
81
|
+
|
|
44
82
|
## 2.1 stage (2.1.0)
|
|
45
83
|
|
|
46
84
|
Everything 2.1 adds is stable from the start and additive: no default changes meaning.
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,149 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.2.0] - 2026-10-05
|
|
8
|
+
|
|
9
|
+
Production scale and safe upgrades. Every database schema is versioned, so an upgrade applies only
|
|
10
|
+
what changed, and a worker of the previous release keeps running beside the new one. A claim costs
|
|
11
|
+
the same however long the queue is, routing costs the same however many models are configured, a
|
|
12
|
+
circuit breaker counts every worker's failures, and work that several replicas race for is taken by
|
|
13
|
+
exactly one. One tenant model reaches every store, and a deployment can check itself. Nothing
|
|
14
|
+
changes until it is opted into, except races and leaks that are now closed.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **Versioned migrations.** Every Postgres and SQLite adapter's schema is a list of numbered
|
|
19
|
+
`SchemaMigration`s, recorded in `nexus_schema_migrations` with a checksum. `migrate()` applies what
|
|
20
|
+
is pending, under a lock that holds through a connection pool, and refuses a migration whose
|
|
21
|
+
statements changed after it ran (`SchemaMigrationError`). Version 1 is the schema 2.0 created, so an
|
|
22
|
+
existing database is recognized as it is. `applyPostgresMigrations()`, `postgresMigrationStatus()`,
|
|
23
|
+
`postgresMigrations()`, and their SQLite twins work on any list, from `nexus-ai-pro/postgres`,
|
|
24
|
+
`nexus-ai-pro/sqlite`, and the new `nexus-ai-pro/postgres/migrations` and
|
|
25
|
+
`nexus-ai-pro/sqlite/migrations`. `PostgresMigrateOptions.transaction` runs each migration in one
|
|
26
|
+
transaction on one connection, and `postgresMigration({ record: true })` records what the script
|
|
27
|
+
creates.
|
|
28
|
+
- **`nexus db status` and `nexus db migrate`**, through a module that exports your own client:
|
|
29
|
+
applied, pending, changed, and newer migrations, `--check` for a deploy step, and `--dry-run`.
|
|
30
|
+
- **Indexed dispatch.** Version 2 of the Postgres and SQLite operation schemas indexes queued work by
|
|
31
|
+
age, so a claim reads the oldest few rows instead of sorting the queue, and version 3 by tenant.
|
|
32
|
+
`RedisOperationStore` takes `index: true`: queued work in a sorted set by age and leased work in one
|
|
33
|
+
by lease expiry, kept in the same Lua call as the record, with `reindex()` to index what is already
|
|
34
|
+
stored. A claim reads the same handful of records with 100,000 operations queued as with 1,000.
|
|
35
|
+
- **A circuit breaker that counts across workers.** `shareObservations: true` adds each worker's
|
|
36
|
+
calls to a rolling window in the shared store, so nine failures over three workers count as nine
|
|
37
|
+
and the circuit opens on all of them. Checks stay synchronous; a worker learns the shared count on
|
|
38
|
+
its next sync. `CircuitStateStore.observe()` is implemented by the memory, Redis, and Postgres
|
|
39
|
+
stores, and `CircuitSnapshot.shared` reports the window.
|
|
40
|
+
- **`PostgresRollupStore`**, on the new `nexus-ai-pro/postgres/rollups`: hourly rollups beside the
|
|
41
|
+
traces, summed in one statement so two workers adding to one hour both count.
|
|
42
|
+
- **A routing index.** The auto-router plans once per configuration — candidates, allow and deny
|
|
43
|
+
lists, capability requirements as bitmasks, and strategy scores — and a request pays only for open
|
|
44
|
+
circuits and health. Over 1,000 models a warm route takes about 15 µs at p50 and 23 µs at p99.
|
|
45
|
+
- **One tenant model**, on the new `nexus-ai-pro/tenancy`: a tenant's view of every store —
|
|
46
|
+
`tenantStore()`, `tenantTraceStore()`, `tenantOperationStore()`, `tenantPromptStore()` (and with it
|
|
47
|
+
the context hub), `tenantDatasetStore()`, `tenantExperimentStore()`, `tenantVectorStore()`,
|
|
48
|
+
`tenantCache()`, `tenantAssetStore()`, `tenantRollupStore()` — each implementing the store's own
|
|
49
|
+
contract over any backend, and `tenantScope()` for all of them at once. Graph runs take a
|
|
50
|
+
`tenantId`: recorded on every checkpoint, read as `context.tenantId`, scoping `context.store`, and
|
|
51
|
+
refusing another tenant's resume, continue, fork, or edit. `graphAssistant()` passes the
|
|
52
|
+
principal's tenant. Operations carry `tenantId`, and `OperationStoreFilter.tenantId` narrows queue
|
|
53
|
+
reads. Model calls are traced with their `tenantId`, so rollups count model spend per tenant, and
|
|
54
|
+
`rateLimit.key: 'tenantId'` gives each tenant its own share of a client's rate limit.
|
|
55
|
+
- **Cache namespaces and outcomes.** `cache.namespace` keeps responses apart by tenant, environment,
|
|
56
|
+
model revision, and context version. Every response reports `meta.cache` — `hit`, `miss`, `stale`,
|
|
57
|
+
`bypass`, or `error`, with its layer and reason — and the request's trace records it.
|
|
58
|
+
`CompletionRequest.responseCache` skips the cache (`bypass`) or refreshes it (`refresh`).
|
|
59
|
+
- **`nexus doctor`**, and `diagnose()` on the new `nexus-ai-pro/doctor`: the Node.js version,
|
|
60
|
+
optional packages, provider credentials (by name only), database connectivity and pending or
|
|
61
|
+
changed migrations, Redis, lapsed leases and a stalled queue, the registry's age, stale provider
|
|
62
|
+
health, and process-local stores behind several replicas. It exits 1 on a failure, or with
|
|
63
|
+
`--strict` on a warning.
|
|
64
|
+
- **Runtime budgets.** `npm run bench:runtime`, part of the release gate, times fourteen paths — graph
|
|
65
|
+
creation, a 1,000-node compile, `Send`, checkpoints, routing at three registry sizes, a
|
|
66
|
+
20-middleware agent, traces, the event stream, server event fan-out, claims, and cache lookups —
|
|
67
|
+
as ratios to a calibration loop, so a budget holds across machines, and checks that 100,000
|
|
68
|
+
checkpoints and 10,000 runs leave the heap where it was.
|
|
69
|
+
- **Workflow steps have what graph nodes have.** `timeout: { runMs, idleMs }` with
|
|
70
|
+
`context.heartbeat()`, an `onError` that decides once retries run out — recorded as `recovered`,
|
|
71
|
+
so a resume repeats neither the step nor its recovery — `stepDefaults`, a `control` that drains
|
|
72
|
+
between steps and ends in `GraphDrainedError`, and `tenantId`. A workflow the agent server serves
|
|
73
|
+
is handed off on a drain like a graph.
|
|
74
|
+
- **A function assistant hands its run off on a drain**: it checks `context.control?.draining`,
|
|
75
|
+
saves how far it got with `context.saveProgress()`, and throws `RunHandOffError`; the next worker
|
|
76
|
+
reads `context.progress` and carries on.
|
|
77
|
+
|
|
78
|
+
### Changed
|
|
79
|
+
|
|
80
|
+
- `migrate()` on every Postgres and SQLite adapter resolves to a `MigrationResult` instead of
|
|
81
|
+
nothing, records what it applied, and loads the migration runner only when called.
|
|
82
|
+
- `nexus db sql` and `postgresMigration()` include the rollups table and the new indexes by
|
|
83
|
+
default; every statement stays idempotent.
|
|
84
|
+
- The root import grew by 11 KB, to 336 KB, with the routing plan, cache outcomes, and linked
|
|
85
|
+
signals; `nexus-ai-pro/graph` by 5 KB, to 78 KB, with tenant runs; and
|
|
86
|
+
`nexus-ai-pro/graph/functional` by 6 KB, to 32 KB, with step timeouts, recovery, drain, and
|
|
87
|
+
tenants.
|
|
88
|
+
|
|
89
|
+
### Fixed
|
|
90
|
+
|
|
91
|
+
- **Two replicas could both start a run on one busy thread.** The busy check read and wrote
|
|
92
|
+
separately. A thread is now claimed through the operation store under a unique key with
|
|
93
|
+
compare-and-set, so exactly one run proceeds and the rest meet the busy policy. An `enqueue` start
|
|
94
|
+
still busy when `queueTimeoutMs` runs out is refused instead of running beside the other.
|
|
95
|
+
- **Two submissions under one idempotency key could both run** on the in-memory and Redis operation
|
|
96
|
+
stores, which overwrote the key's owner. Both now refuse a second record under a key another record
|
|
97
|
+
holds, as Postgres and SQLite do, and Redis checks and writes in one Lua call.
|
|
98
|
+
- A circuit breaker's calls recorded before its shared-state module finished loading were not
|
|
99
|
+
shared.
|
|
100
|
+
- **Security: the semantic response cache answered one tenant with another's response** when their
|
|
101
|
+
questions were alike, because it compared every entry regardless of tenant. A semantic match is
|
|
102
|
+
now kept to the request's tenant and cache namespace.
|
|
103
|
+
- A response cache that threw failed the request, and one that failed to store an answer failed it
|
|
104
|
+
after the provider had answered. Both are now reported in `meta.cache`, and the request goes on.
|
|
105
|
+
- The auto-router rebuilt the model registry for every candidate on every request: over 1,000 models
|
|
106
|
+
a request took most of a second.
|
|
107
|
+
- **Memory grew with every run that shared a signal.** `AbortSignal.any()`, which composed each task
|
|
108
|
+
attempt's signal, keeps a reference per call for as long as any source lives in Node 22, so a
|
|
109
|
+
server's or a worker's long-lived signal held about 5 KB per graph run, and every client operation
|
|
110
|
+
with a deadline held one too. Signals are now linked and released when their work ends; 10,000
|
|
111
|
+
runs leave the heap within 0.3 MB of where it started, down from 52 MB.
|
|
112
|
+
|
|
113
|
+
### Proof
|
|
114
|
+
|
|
115
|
+
- A database created by the published 2.0.0 package upgrades with 2.2's migrations, on Postgres and
|
|
116
|
+
SQLite, and a published 2.1.0 worker and a 2.2 worker drain one queue together, nothing run twice.
|
|
117
|
+
- Ten workers race one thread, one idempotency key, one queue, one tenant's slots and budget, and one
|
|
118
|
+
circuit, on every shared store, and every invariant holds.
|
|
119
|
+
- A Redis claim reads the same number of records at 1,000 and 100,000 queued; Postgres and SQLite
|
|
120
|
+
plan a claim through the age index with no sort.
|
|
121
|
+
- The Redis stores' Lua runs for real in the tests, in a Lua VM over an in-memory Redis.
|
|
122
|
+
- The routing index decides exactly as the published 2.1.0 router on 300 random configurations, and
|
|
123
|
+
a warm route over 1,000 models stays under 100 µs at p50 and 1 ms at p99.
|
|
124
|
+
- A tenant-isolation suite runs against every storage adapter — memory, Redis, Postgres, SQLite, and
|
|
125
|
+
files — and fails on any cross-tenant get, list, search, delete, fork, resume, trace query, vector
|
|
126
|
+
search, or cache hit.
|
|
127
|
+
- `nexus doctor` reports each misconfiguration a fixture deployment is seeded with — an old Node.js,
|
|
128
|
+
no credentials, an old registry, pending migrations, an unreachable Redis, a lapsed lease and a
|
|
129
|
+
stalled queue, stale health, and memory stores behind three replicas — and passes it once fixed.
|
|
130
|
+
- A workflow and a function assistant are each drained on one worker and finished on the next, with
|
|
131
|
+
every step and every item run exactly once.
|
|
132
|
+
|
|
133
|
+
### Not in this release, and why
|
|
134
|
+
|
|
135
|
+
- **The migration lock is a row with a lease, not a session advisory lock.** A session lock does not
|
|
136
|
+
hold through a connection pool or a serverless driver, where each statement may reach another
|
|
137
|
+
connection; a lease row does, everywhere. Without a `transaction` function, statements run one at a
|
|
138
|
+
time, which is safe because every bundled statement is idempotent.
|
|
139
|
+
- **The Redis dispatch index is opt-in**, since a 2.1 worker writes records without index entries;
|
|
140
|
+
`stats()` on Redis still reads every record.
|
|
141
|
+
- **Consecutive failures across workers are counted as failures since any worker's last success.**
|
|
142
|
+
Calls are reported in batches, so two workers' failures and successes in the same second may be
|
|
143
|
+
ordered differently than they happened.
|
|
144
|
+
- **Tenant views scope by prefix and stamp, not by a column**, so they work over every backend
|
|
145
|
+
unchanged; data written before a view was used is not the tenant's until it is copied under its
|
|
146
|
+
prefix.
|
|
147
|
+
- **Runtime budgets compare ratios to a calibration loop**, which catches a cliff, not a 10% drift;
|
|
148
|
+
the tolerance is three times the recorded ratio.
|
|
149
|
+
|
|
7
150
|
## [2.1.0] - 2026-10-04
|
|
8
151
|
|
|
9
152
|
Fault-tolerant graphs and crash-safe observability. A graph now chooses when its checkpoints are
|
|
@@ -1494,7 +1637,8 @@ shared policy vocabulary is in place for a future release that revisits this.
|
|
|
1494
1637
|
- URL fetching rejects unsafe private-network targets and limits response reads.
|
|
1495
1638
|
- CLI and audit findings no longer disclose detected secret values.
|
|
1496
1639
|
|
|
1497
|
-
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.
|
|
1640
|
+
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.2.0...HEAD
|
|
1641
|
+
[2.2.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.1.0...v2.2.0
|
|
1498
1642
|
[2.1.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.0.0...v2.1.0
|
|
1499
1643
|
[2.0.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.25.0...v2.0.0
|
|
1500
1644
|
[1.25.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.24.0...v1.25.0
|
package/README.md
CHANGED
|
@@ -8,12 +8,12 @@ team can share.
|
|
|
8
8
|
Import only what you use. The root import is the client and nothing else, and every other capability
|
|
9
9
|
has its own entry point with a size budget CI enforces; [the packaging guide](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/packaging.md)
|
|
10
10
|
publishes what each one costs. The package has no required dependencies: a graph-only application
|
|
11
|
-
loads
|
|
11
|
+
loads 78 KB, and installs nothing but this package.
|
|
12
12
|
|
|
13
13
|
- NPM: https://www.npmjs.com/package/nexus-ai-pro
|
|
14
14
|
- GitHub: https://github.com/mkhitar-abrahamyan/nexus-ai
|
|
15
15
|
- Contributing, testing, and release procedure: [CONTRIBUTING.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/CONTRIBUTING.md)
|
|
16
|
-
-
|
|
16
|
+
- What is planned next: [ROADMAP.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md)
|
|
17
17
|
- For AI assistants: [llms.txt](https://raw.githubusercontent.com/mkhitar-abrahamyan/nexus-ai/main/llms.txt) indexes every guide, and [llms-full.txt](https://raw.githubusercontent.com/mkhitar-abrahamyan/nexus-ai/main/llms-full.txt) holds them all in one file
|
|
18
18
|
|
|
19
19
|
## Install
|
|
@@ -158,6 +158,7 @@ from the doc comments. The guides live in the repository, so these links go to G
|
|
|
158
158
|
| [Studio](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/studio.md) | A shared UI for traces, threads, approvals, experiments, prompts, deployments, costs, and health, as a separate package |
|
|
159
159
|
| [Agent server](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/server.md) | Self-hosted HTTP server for assistants: threads, durable runs, resumable streams, cron, a worker queue, and metrics |
|
|
160
160
|
| [Deployments](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/deployments.md) | Revisions and canaries with automatic rollback, autoscaling on Kubernetes and Helm, and per-tenant limits |
|
|
161
|
+
| [Tenancy](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/tenancy.md) | One tenant model: a tenant's view of every store, tenant graph runs, and the isolation suite |
|
|
161
162
|
| [Workflows](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/workflows.md) | Ready-made chains and domain workflows |
|
|
162
163
|
| [Command line](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/cli.md) | The `nexus` command |
|
|
163
164
|
| [Packaging and install weight](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/packaging.md) | Every entry point and what it costs |
|
|
@@ -200,7 +201,8 @@ npm run check:release
|
|
|
200
201
|
|
|
201
202
|
`check` runs formatting and lint gates, source/test/example type checks, the build, unit and mock
|
|
202
203
|
conformance tests, coverage thresholds, package import checks, and an external type-consumer test.
|
|
203
|
-
`check:release` additionally
|
|
204
|
+
`check:release` additionally holds the scheduling and runtime budgets — speed against a calibration
|
|
205
|
+
loop, and memory by retention — and verifies the dry-run tarball and a clean packed-package install.
|
|
204
206
|
|
|
205
207
|
`npm run docs:check` fails when any public export, or any public member of an exported class,
|
|
206
208
|
interface, or enum, has no doc comment, and `npm run docs:guides` fails when a guide does not name
|
|
@@ -216,8 +218,8 @@ npm run test:vectors:live
|
|
|
216
218
|
|
|
217
219
|
## Roadmap
|
|
218
220
|
|
|
219
|
-
See [ROADMAP.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) for what
|
|
220
|
-
|
|
221
|
+
See [ROADMAP.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) for what is
|
|
222
|
+
planned, and [CHANGELOG.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/CHANGELOG.md) for what has shipped. It is a design proposal, not a compatibility promise; the guarantees
|
|
221
223
|
live in [API_STABILITY.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/API_STABILITY.md).
|
|
222
224
|
|
|
223
225
|
Shipped so far:
|
|
@@ -246,12 +248,15 @@ Shipped so far:
|
|
|
246
248
|
every provider;
|
|
247
249
|
- 2.1 fault tolerance and crash-safe observability: checkpoints written on your terms, recovery after
|
|
248
250
|
retries, idle timeouts and a clean drain, traces that survive a crash and reach any OpenTelemetry
|
|
249
|
-
backend, one event stream, and dashboard rollups
|
|
251
|
+
backend, one event stream, and dashboard rollups;
|
|
252
|
+
- 2.2 production scale and safe upgrades: versioned database migrations, queue claims and routing
|
|
253
|
+
that cost the same at any size, a circuit breaker that counts across workers, one tenant model
|
|
254
|
+
across every store, and `nexus doctor` to check a deployment before it serves traffic.
|
|
250
255
|
|
|
251
256
|
Upgrading from 1.x: `npx nexus migrate src --write` moves your imports, and
|
|
252
257
|
[MIGRATING.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/MIGRATING.md) covers the rest.
|
|
253
|
-
Next, 2.
|
|
254
|
-
|
|
258
|
+
Next, 2.3 gives agents a real security boundary — capabilities, permissions, and a sandbox contract —
|
|
259
|
+
with a complete middleware catalog, and retrieval that works at production scale. The [roadmap](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) lists the
|
|
255
260
|
releases after it.
|
|
256
261
|
|
|
257
262
|
## Before Production
|
|
@@ -15,6 +15,14 @@ export declare class MemoryCache<T> {
|
|
|
15
15
|
constructor(maxEntries?: number);
|
|
16
16
|
/** Returns a value, or `undefined` for a miss or an expired entry. A hit counts as recent use. */
|
|
17
17
|
get(key: string): T | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Like `get()`, also saying whether an entry was there but had expired, which a caller can report
|
|
20
|
+
* as stale rather than missing. The expired entry is removed either way.
|
|
21
|
+
*/
|
|
22
|
+
lookup(key: string): {
|
|
23
|
+
value?: T;
|
|
24
|
+
stale: boolean;
|
|
25
|
+
};
|
|
18
26
|
/** Stores a value. Defaults to 5 minutes. */
|
|
19
27
|
set(key: string, value: T, ttlSeconds?: number): void;
|
|
20
28
|
/** Removes every entry. */
|
|
@@ -16,6 +16,15 @@ export class MemoryCache {
|
|
|
16
16
|
this.entries.set(key, entry);
|
|
17
17
|
return entry.value;
|
|
18
18
|
}
|
|
19
|
+
lookup(key) {
|
|
20
|
+
const entry = this.entries.get(key);
|
|
21
|
+
if (entry && entry.expiresAt < Date.now()) {
|
|
22
|
+
this.entries.delete(key);
|
|
23
|
+
return { stale: true };
|
|
24
|
+
}
|
|
25
|
+
const value = this.get(key);
|
|
26
|
+
return value === undefined ? { stale: false } : { value, stale: false };
|
|
27
|
+
}
|
|
19
28
|
set(key, value, ttlSeconds = 300) {
|
|
20
29
|
if (this.entries.size >= this.maxEntries) {
|
|
21
30
|
const oldest = this.entries.keys().next().value;
|
|
@@ -25,10 +25,13 @@ export declare class SemanticCache {
|
|
|
25
25
|
constructor(options?: SemanticCacheOptions);
|
|
26
26
|
/**
|
|
27
27
|
* The cached response for the most similar earlier request above the threshold, or `undefined`.
|
|
28
|
+
* Only entries stored under the same `scope` are compared: the client passes the request's tenant
|
|
29
|
+
* and cache namespace, so one tenant is never answered from another's entry however alike the
|
|
30
|
+
* questions are.
|
|
28
31
|
*/
|
|
29
|
-
get(request: CompletionRequest): Promise<NexusResponse | undefined>;
|
|
30
|
-
/** Caches a response under its request's embedding. */
|
|
31
|
-
set(key: string, request: CompletionRequest, response: NexusResponse): Promise<void>;
|
|
32
|
+
get(request: CompletionRequest, scope?: string): Promise<NexusResponse | undefined>;
|
|
33
|
+
/** Caches a response under its request's embedding, in a `scope` lookups must match. */
|
|
34
|
+
set(key: string, request: CompletionRequest, response: NexusResponse, scope?: string): Promise<void>;
|
|
32
35
|
/** Removes every entry. */
|
|
33
36
|
clear(): void;
|
|
34
37
|
}
|
|
@@ -7,7 +7,7 @@ export class SemanticCache {
|
|
|
7
7
|
this.options = options;
|
|
8
8
|
this.embed = options.embed || createHashEmbeddings;
|
|
9
9
|
}
|
|
10
|
-
async get(request) {
|
|
10
|
+
async get(request, scope = '') {
|
|
11
11
|
if (!this.options.enabled)
|
|
12
12
|
return undefined;
|
|
13
13
|
const text = requestToSearchText(request);
|
|
@@ -17,6 +17,8 @@ export class SemanticCache {
|
|
|
17
17
|
let best;
|
|
18
18
|
this.entries = this.entries.filter((entry) => entry.expiresAt > now);
|
|
19
19
|
for (const entry of this.entries) {
|
|
20
|
+
if (entry.scope !== scope)
|
|
21
|
+
continue;
|
|
20
22
|
const score = cosineSimilarity(embedding, entry.embedding);
|
|
21
23
|
if (score >= threshold && (!best || score > best.score)) {
|
|
22
24
|
best = { entry, score };
|
|
@@ -37,7 +39,7 @@ export class SemanticCache {
|
|
|
37
39
|
},
|
|
38
40
|
};
|
|
39
41
|
}
|
|
40
|
-
async set(key, request, response) {
|
|
42
|
+
async set(key, request, response, scope = '') {
|
|
41
43
|
if (!this.options.enabled)
|
|
42
44
|
return;
|
|
43
45
|
const text = requestToSearchText(request);
|
|
@@ -47,6 +49,7 @@ export class SemanticCache {
|
|
|
47
49
|
this.entries.shift();
|
|
48
50
|
this.entries.push({
|
|
49
51
|
key,
|
|
52
|
+
scope,
|
|
50
53
|
text,
|
|
51
54
|
embedding,
|
|
52
55
|
response,
|
package/dist/cli/args.js
CHANGED
|
@@ -5,12 +5,15 @@ export const BOOLEAN_FLAGS = new Set([
|
|
|
5
5
|
'allow-dataset-mismatch',
|
|
6
6
|
'check',
|
|
7
7
|
'densify',
|
|
8
|
+
'dry-run',
|
|
8
9
|
'fail',
|
|
9
10
|
'fail-on-regression',
|
|
10
11
|
'help',
|
|
11
12
|
'json',
|
|
12
13
|
'print',
|
|
14
|
+
'record',
|
|
13
15
|
'reveal-values',
|
|
16
|
+
'strict',
|
|
14
17
|
'write',
|
|
15
18
|
]);
|
|
16
19
|
export function parseArgs(args) {
|
package/dist/cli/db.d.ts
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
import { type ParsedArgs } from './args.js';
|
|
2
2
|
/**
|
|
3
|
-
* `nexus db sql
|
|
3
|
+
* `nexus db sql | status | migrate`.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* `sql` prints the Postgres schema, so the CLI needs no driver and the schema can go through the
|
|
6
|
+
* application's own review and tooling: `nexus db sql | psql "$URL"`. `status` and `migrate` reach
|
|
7
|
+
* the database through a module the application writes, which exports its own client:
|
|
8
|
+
*
|
|
9
|
+
* ```js
|
|
10
|
+
* // db.mjs
|
|
11
|
+
* import pg from 'pg';
|
|
12
|
+
* export const client = new pg.Pool({ connectionString: process.env.DATABASE_URL });
|
|
13
|
+
* export const close = () => client.end();
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* The module may also export `dialect` (`postgres` or `sqlite`), a `transaction` function (see
|
|
17
|
+
* `PostgresMigrateOptions`), and `migrations`, a list replacing the bundled ones, for stores on
|
|
18
|
+
* custom tables.
|
|
7
19
|
*/
|
|
8
|
-
export declare function runDbCommand(subcommand: string | undefined,
|
|
20
|
+
export declare function runDbCommand(subcommand: string | undefined, args: ParsedArgs): Promise<void>;
|
package/dist/cli/db.js
CHANGED
|
@@ -1,28 +1,175 @@
|
|
|
1
|
-
import { postgresMigration } from '../postgres/index.js';
|
|
2
|
-
import { CliUsageError, listFlag, numberFlag } from './args.js';
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
'
|
|
6
|
-
|
|
7
|
-
'
|
|
8
|
-
|
|
9
|
-
'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (subcommand !== 'sql')
|
|
14
|
-
throw new CliUsageError(`Unknown db command "${subcommand ?? ''}". Use sql.`);
|
|
15
|
-
const adapters = listFlag(flags, 'adapters');
|
|
16
|
-
const unknown = adapters?.filter((adapter) => !ADAPTERS.includes(adapter));
|
|
17
|
-
if (unknown?.length) {
|
|
18
|
-
throw new CliUsageError(`Unknown adapter ${unknown.join(', ')}. Choose from ${ADAPTERS.join(', ')}.`);
|
|
19
|
-
}
|
|
1
|
+
import { POSTGRES_ADAPTERS, postgresMigration, postgresMigrations } from '../postgres/index.js';
|
|
2
|
+
import { CliUsageError, flagBool, listFlag, loadModule, numberFlag, printTable, stringFlag, writeJson, } from './args.js';
|
|
3
|
+
const SQLITE_ADAPTERS = ['operations', 'store', 'vectors'];
|
|
4
|
+
export async function runDbCommand(subcommand, args) {
|
|
5
|
+
if (subcommand === 'sql')
|
|
6
|
+
return printSql(args);
|
|
7
|
+
if (subcommand === 'status' || subcommand === 'migrate')
|
|
8
|
+
return withDatabase(subcommand, args);
|
|
9
|
+
throw new CliUsageError(`Unknown db command "${subcommand ?? ''}". Use sql, status, or migrate.`);
|
|
10
|
+
}
|
|
11
|
+
function printSql({ flags }) {
|
|
12
|
+
const adapters = postgresAdapters(flags);
|
|
20
13
|
const vectorDimensions = numberFlag(flags, 'vector-dimensions');
|
|
21
|
-
if (adapters?.includes('vectors') && vectorDimensions === undefined) {
|
|
22
|
-
throw new CliUsageError('The vectors adapter needs --vector-dimensions.');
|
|
23
|
-
}
|
|
24
14
|
process.stdout.write(postgresMigration({
|
|
25
15
|
...(adapters ? { adapters } : {}),
|
|
26
16
|
...(vectorDimensions === undefined ? {} : { vectorDimensions }),
|
|
17
|
+
...(flagBool(flags, 'record') ? { record: true } : {}),
|
|
27
18
|
}));
|
|
28
19
|
}
|
|
20
|
+
function postgresAdapters(flags) {
|
|
21
|
+
const adapters = listFlag(flags, 'adapters');
|
|
22
|
+
const unknown = adapters?.filter((adapter) => !POSTGRES_ADAPTERS.includes(adapter));
|
|
23
|
+
if (unknown?.length) {
|
|
24
|
+
throw new CliUsageError(`Unknown adapter ${unknown.join(', ')}. Choose from ${POSTGRES_ADAPTERS.join(', ')}.`);
|
|
25
|
+
}
|
|
26
|
+
if (adapters?.includes('vectors') && numberFlag(flags, 'vector-dimensions') === undefined) {
|
|
27
|
+
throw new CliUsageError('The vectors adapter needs --vector-dimensions.');
|
|
28
|
+
}
|
|
29
|
+
return adapters;
|
|
30
|
+
}
|
|
31
|
+
async function withDatabase(command, { flags }) {
|
|
32
|
+
const location = stringFlag(flags, 'client');
|
|
33
|
+
if (!location)
|
|
34
|
+
throw new CliUsageError(`nexus db ${command} needs --client <db.mjs>, a module exporting \`client\`.`);
|
|
35
|
+
const database = await openDatabase(location, stringFlag(flags, 'dialect'));
|
|
36
|
+
try {
|
|
37
|
+
const migrations = database.migrations ?? (await bundledMigrations(database.dialect, flags));
|
|
38
|
+
const table = stringFlag(flags, 'table');
|
|
39
|
+
if (command === 'status') {
|
|
40
|
+
const status = await statusOf(database, migrations, table);
|
|
41
|
+
if (flagBool(flags, 'json'))
|
|
42
|
+
writeJson(status);
|
|
43
|
+
else
|
|
44
|
+
printStatus(status);
|
|
45
|
+
if (flagBool(flags, 'check') && !status.current)
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const result = await migrate(database, migrations, { dryRun: flagBool(flags, 'dry-run'), table });
|
|
50
|
+
if (flagBool(flags, 'json'))
|
|
51
|
+
writeJson(result);
|
|
52
|
+
else
|
|
53
|
+
printResult(result);
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
await database.close?.();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function openDatabase(location, dialectFlag) {
|
|
60
|
+
const loaded = await loadModule(location);
|
|
61
|
+
const client = loaded.client ?? loaded.default;
|
|
62
|
+
if (!client || typeof client !== 'object') {
|
|
63
|
+
throw new CliUsageError(`${location} must export a database client as \`client\`.`);
|
|
64
|
+
}
|
|
65
|
+
const candidate = client;
|
|
66
|
+
const dialect = (dialectFlag ?? loaded.dialect);
|
|
67
|
+
const detected = dialect ??
|
|
68
|
+
(typeof candidate.query === 'function'
|
|
69
|
+
? 'postgres'
|
|
70
|
+
: typeof candidate.prepare === 'function' || typeof candidate.exec === 'function'
|
|
71
|
+
? 'sqlite'
|
|
72
|
+
: undefined);
|
|
73
|
+
if (detected !== 'postgres' && detected !== 'sqlite') {
|
|
74
|
+
throw new CliUsageError(`${location} exports a client this CLI cannot recognize. Export \`dialect\` as "postgres" or "sqlite".`);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
dialect: detected,
|
|
78
|
+
client,
|
|
79
|
+
...(typeof loaded.transaction === 'function'
|
|
80
|
+
? { transaction: loaded.transaction }
|
|
81
|
+
: {}),
|
|
82
|
+
...(Array.isArray(loaded.migrations) ? { migrations: loaded.migrations } : {}),
|
|
83
|
+
...(typeof loaded.close === 'function' ? { close: loaded.close } : {}),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async function bundledMigrations(dialect, flags) {
|
|
87
|
+
if (dialect === 'postgres') {
|
|
88
|
+
const adapters = postgresAdapters(flags);
|
|
89
|
+
const vectorDimensions = numberFlag(flags, 'vector-dimensions');
|
|
90
|
+
return postgresMigrations({
|
|
91
|
+
...(adapters ? { adapters } : {}),
|
|
92
|
+
...(vectorDimensions === undefined ? {} : { vectorDimensions }),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const adapters = listFlag(flags, 'adapters');
|
|
96
|
+
const unknown = adapters?.filter((adapter) => !SQLITE_ADAPTERS.includes(adapter));
|
|
97
|
+
if (unknown?.length) {
|
|
98
|
+
throw new CliUsageError(`Unknown SQLite adapter ${unknown.join(', ')}. Choose from ${SQLITE_ADAPTERS.join(', ')}.`);
|
|
99
|
+
}
|
|
100
|
+
const { sqliteMigrations } = await import('../sqlite/index.js');
|
|
101
|
+
return sqliteMigrations(adapters ? { adapters } : {});
|
|
102
|
+
}
|
|
103
|
+
async function statusOf(database, migrations, table) {
|
|
104
|
+
const options = table ? { table } : {};
|
|
105
|
+
if (database.dialect === 'postgres') {
|
|
106
|
+
const { postgresMigrationStatus } = await import('../postgres/migrations.js');
|
|
107
|
+
return postgresMigrationStatus(database.client, migrations, options);
|
|
108
|
+
}
|
|
109
|
+
const { sqliteMigrationStatus } = await import('../sqlite/migrations.js');
|
|
110
|
+
return sqliteMigrationStatus(database.client, migrations, options);
|
|
111
|
+
}
|
|
112
|
+
async function migrate(database, migrations, options) {
|
|
113
|
+
const common = { dryRun: options.dryRun, ...(options.table ? { table: options.table } : {}) };
|
|
114
|
+
if (database.dialect === 'postgres') {
|
|
115
|
+
const { applyPostgresMigrations } = await import('../postgres/migrations.js');
|
|
116
|
+
return applyPostgresMigrations(database.client, migrations, {
|
|
117
|
+
...common,
|
|
118
|
+
...(database.transaction ? { transaction: database.transaction } : {}),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const { applySqliteMigrations } = await import('../sqlite/migrations.js');
|
|
122
|
+
return applySqliteMigrations(database.client, migrations, common);
|
|
123
|
+
}
|
|
124
|
+
function printStatus(status) {
|
|
125
|
+
const changed = new Set(status.changed.map(({ migration }) => `${migration.component}@${migration.version}`));
|
|
126
|
+
const rows = [
|
|
127
|
+
...status.applied
|
|
128
|
+
.filter((row) => !status.unknown.includes(row))
|
|
129
|
+
.map((row) => {
|
|
130
|
+
const key = `${row.component}@${row.version}`;
|
|
131
|
+
return {
|
|
132
|
+
component: row.component,
|
|
133
|
+
version: String(row.version),
|
|
134
|
+
name: row.name,
|
|
135
|
+
state: changed.has(key) ? 'changed' : 'applied',
|
|
136
|
+
applied: row.appliedAt,
|
|
137
|
+
};
|
|
138
|
+
}),
|
|
139
|
+
...status.unknown.map((row) => ({
|
|
140
|
+
component: row.component,
|
|
141
|
+
version: String(row.version),
|
|
142
|
+
name: row.name,
|
|
143
|
+
state: 'newer release',
|
|
144
|
+
applied: row.appliedAt,
|
|
145
|
+
})),
|
|
146
|
+
...status.pending.map((migration) => ({
|
|
147
|
+
component: migration.component,
|
|
148
|
+
version: String(migration.version),
|
|
149
|
+
name: migration.name,
|
|
150
|
+
state: 'pending',
|
|
151
|
+
applied: '',
|
|
152
|
+
})),
|
|
153
|
+
];
|
|
154
|
+
if (rows.length === 0) {
|
|
155
|
+
console.log('No migrations known or recorded.');
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
printTable(rows, ['component', 'version', 'name', 'state', 'applied']);
|
|
159
|
+
console.log(status.current
|
|
160
|
+
? '\nUp to date.'
|
|
161
|
+
: `\n${status.pending.length} pending${status.changed.length ? `, ${status.changed.length} changed after they ran` : ''}. Run: nexus db migrate`);
|
|
162
|
+
}
|
|
163
|
+
function printResult(result) {
|
|
164
|
+
if (result.applied.length === 0) {
|
|
165
|
+
console.log('Nothing to apply: the database is up to date.');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const verb = result.dryRun ? 'Would apply' : 'Applied';
|
|
169
|
+
for (const migration of result.applied)
|
|
170
|
+
console.log(`${verb} ${migration.component} v${migration.version}: ${migration.name}`);
|
|
171
|
+
if (result.dryRun) {
|
|
172
|
+
console.log('\n-- The statements, in order:\n');
|
|
173
|
+
console.log(`${result.statements.join(';\n\n')};`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ParsedArgs } from './args.js';
|
|
2
|
+
/**
|
|
3
|
+
* `nexus doctor` checks a deployment and exits 1 when a check fails, or with `--strict` when one
|
|
4
|
+
* warns. Without `--module` it checks what it can see from the shell: Node.js, packages, credentials,
|
|
5
|
+
* and the model registry. A module reaches the rest, exporting any of:
|
|
6
|
+
*
|
|
7
|
+
* ```js
|
|
8
|
+
* // doctor.mjs
|
|
9
|
+
* export const database = { client: pool }; // or `client` alone, as for nexus db
|
|
10
|
+
* export const redis = new Redis(process.env.REDIS_URL);
|
|
11
|
+
* export const operations = new PostgresOperationStore(pool);
|
|
12
|
+
* export const ai = client;
|
|
13
|
+
* export const deployment = { replicas: 3, stores: { checkpointer, state, tenantUsage } };
|
|
14
|
+
* export const close = async () => { await pool.end(); redis.disconnect(); };
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function runDoctorCommand({ flags }: ParsedArgs): Promise<void>;
|