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
|
@@ -12,12 +12,14 @@ export interface GraphLike {
|
|
|
12
12
|
threadId?: string;
|
|
13
13
|
signal?: AbortSignal;
|
|
14
14
|
metadata?: Record<string, unknown>;
|
|
15
|
+
tenantId?: string;
|
|
15
16
|
control?: DrainSwitch;
|
|
16
17
|
}): AsyncIterable<unknown>;
|
|
17
18
|
/** Answers an interrupt and continues the thread. */
|
|
18
19
|
resume(threadId: string, value: unknown, options: {
|
|
19
20
|
signal?: AbortSignal;
|
|
20
21
|
metadata?: Record<string, unknown>;
|
|
22
|
+
tenantId?: string;
|
|
21
23
|
control?: DrainSwitch;
|
|
22
24
|
}): AsyncIterable<unknown>;
|
|
23
25
|
/** The thread's checkpoint, at its latest step or at one given. */
|
|
@@ -31,6 +33,7 @@ export interface GraphLike {
|
|
|
31
33
|
continue?(threadId: string, options: {
|
|
32
34
|
signal?: AbortSignal;
|
|
33
35
|
metadata?: Record<string, unknown>;
|
|
36
|
+
tenantId?: string;
|
|
34
37
|
control?: DrainSwitch;
|
|
35
38
|
}): AsyncIterable<unknown>;
|
|
36
39
|
/** Writes values into a thread's state, which is how a rollback is applied. */
|
|
@@ -47,6 +50,7 @@ export interface GraphLike {
|
|
|
47
50
|
/** The options of a graph's `events()` the adapter passes. */
|
|
48
51
|
type GraphEventOptionsLike = {
|
|
49
52
|
threadId?: string;
|
|
53
|
+
tenantId?: string;
|
|
50
54
|
signal?: AbortSignal;
|
|
51
55
|
metadata?: Record<string, unknown>;
|
|
52
56
|
control?: DrainSwitch;
|
package/dist/server/assistant.js
CHANGED
|
@@ -4,6 +4,7 @@ export function graphAssistant(graph, options = {}) {
|
|
|
4
4
|
...(threadId ? { threadId } : {}),
|
|
5
5
|
signal: context.signal,
|
|
6
6
|
metadata: { ...options.metadata, runId: context.runId },
|
|
7
|
+
...tenantOf(context),
|
|
7
8
|
...(context.control ? { control: context.control } : {}),
|
|
8
9
|
include: [...(projections ?? []), 'values'],
|
|
9
10
|
});
|
|
@@ -17,6 +18,7 @@ export function graphAssistant(graph, options = {}) {
|
|
|
17
18
|
threadId: context.threadId,
|
|
18
19
|
signal: context.signal,
|
|
19
20
|
metadata: { ...options.metadata, runId: context.runId },
|
|
21
|
+
...tenantOf(context),
|
|
20
22
|
...(context.control ? { control: context.control } : {}),
|
|
21
23
|
});
|
|
22
24
|
},
|
|
@@ -27,6 +29,7 @@ export function graphAssistant(graph, options = {}) {
|
|
|
27
29
|
return graph.resume(threadId, value, {
|
|
28
30
|
signal: context.signal,
|
|
29
31
|
metadata: { ...options.metadata, runId: context.runId },
|
|
32
|
+
...tenantOf(context),
|
|
30
33
|
...(context.control ? { control: context.control } : {}),
|
|
31
34
|
});
|
|
32
35
|
},
|
|
@@ -50,6 +53,7 @@ export function graphAssistant(graph, options = {}) {
|
|
|
50
53
|
return graph.continue(threadId, {
|
|
51
54
|
signal: context.signal,
|
|
52
55
|
metadata: { ...options.metadata, runId: context.runId },
|
|
56
|
+
...tenantOf(context),
|
|
53
57
|
...(context.control ? { control: context.control } : {}),
|
|
54
58
|
});
|
|
55
59
|
},
|
|
@@ -76,6 +80,10 @@ export function functionAssistant(run, options = {}) {
|
|
|
76
80
|
},
|
|
77
81
|
};
|
|
78
82
|
}
|
|
83
|
+
function tenantOf(context) {
|
|
84
|
+
const tenantId = context.principal?.tenantId;
|
|
85
|
+
return tenantId ? { tenantId } : {};
|
|
86
|
+
}
|
|
79
87
|
async function* withResult(events) {
|
|
80
88
|
for await (const event of events)
|
|
81
89
|
yield event;
|
package/dist/server/errors.d.ts
CHANGED
|
@@ -32,6 +32,18 @@ export declare class ForbiddenError extends ServerError {
|
|
|
32
32
|
/** The scope the route required. */
|
|
33
33
|
scope: string);
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Thrown by an assistant to hand its run to another worker while this replica drains: the server puts
|
|
37
|
+
* the run back on the queue as its next attempt instead of failing it. A function assistant checks
|
|
38
|
+
* `context.control?.draining` between its own steps, saves where it got with `context.saveProgress()`,
|
|
39
|
+
* and throws this; the next worker reads `context.progress`. Thrown when the replica is not draining,
|
|
40
|
+
* it fails the run like any error.
|
|
41
|
+
*/
|
|
42
|
+
export declare class RunHandOffError extends Error {
|
|
43
|
+
/** Always `RUN_HANDED_OFF`. */
|
|
44
|
+
readonly code = "RUN_HANDED_OFF";
|
|
45
|
+
constructor(message?: string);
|
|
46
|
+
}
|
|
35
47
|
/** Raised when a thread is already running something and the busy policy is `reject`. */
|
|
36
48
|
export declare class ThreadBusyError extends ServerError {
|
|
37
49
|
/** The thread. */
|
package/dist/server/errors.js
CHANGED
|
@@ -35,6 +35,13 @@ export class ForbiddenError extends ServerError {
|
|
|
35
35
|
this.name = 'ForbiddenError';
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
export class RunHandOffError extends Error {
|
|
39
|
+
code = 'RUN_HANDED_OFF';
|
|
40
|
+
constructor(message = 'Handed to another worker while this one drains') {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = 'RunHandOffError';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
38
45
|
export class ThreadBusyError extends ServerError {
|
|
39
46
|
threadId;
|
|
40
47
|
runId;
|
package/dist/server/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export { functionAssistant, type GraphAssistantOptions, graphAssistant, type GraphLike, } from './assistant.js';
|
|
11
11
|
export { CronScheduler, type CronSchedulerOptions, type DueCronJob, parseCron } from './cron.js';
|
|
12
|
-
export { AssistantCapabilityError, BadRequestError, ForbiddenError, NotFoundError, ServerError, type TenantLimit, TenantLimitError, ThreadBusyError, UnauthorizedError, } from './errors.js';
|
|
12
|
+
export { AssistantCapabilityError, BadRequestError, ForbiddenError, NotFoundError, RunHandOffError, ServerError, type TenantLimit, TenantLimitError, ThreadBusyError, UnauthorizedError, } from './errors.js';
|
|
13
13
|
export { MemoryRunEventLog, type MemoryRunEventLogOptions, type RedisEventLogLikeClient, RedisRunEventLog, type RedisRunEventLogOptions, } from './events.js';
|
|
14
14
|
export { type NodeListenerOptions, type NodeRequestLike, type NodeResponseLike, toNodeListener, } from './node.js';
|
|
15
15
|
export { type DrainResult, RunManager, type RunManagerOptions, type RunQueueOptions, type StartRunOptions, } from './runs.js';
|
package/dist/server/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { functionAssistant, graphAssistant, } from './assistant.js';
|
|
2
2
|
export { CronScheduler, parseCron } from './cron.js';
|
|
3
|
-
export { AssistantCapabilityError, BadRequestError, ForbiddenError, NotFoundError, ServerError, TenantLimitError, ThreadBusyError, UnauthorizedError, } from './errors.js';
|
|
3
|
+
export { AssistantCapabilityError, BadRequestError, ForbiddenError, NotFoundError, RunHandOffError, ServerError, TenantLimitError, ThreadBusyError, UnauthorizedError, } from './errors.js';
|
|
4
4
|
export { MemoryRunEventLog, RedisRunEventLog, } from './events.js';
|
|
5
5
|
export { toNodeListener, } from './node.js';
|
|
6
6
|
export { RunManager, } from './runs.js';
|
package/dist/server/runs.d.ts
CHANGED
|
@@ -159,6 +159,8 @@ export declare class RunManager {
|
|
|
159
159
|
* it with a `TenantLimitError`.
|
|
160
160
|
*/
|
|
161
161
|
start(options: StartRunOptions): Promise<RunRecord>;
|
|
162
|
+
/** The rest of `start()`, once the thread, if any, is this run's. */
|
|
163
|
+
private startClaimed;
|
|
162
164
|
/** Cancels a run, including one another replica is executing. */
|
|
163
165
|
cancel(runId: string, principal?: Principal, reason?: string): Promise<RunRecord>;
|
|
164
166
|
/**
|
|
@@ -210,8 +212,29 @@ export declare class RunManager {
|
|
|
210
212
|
private freeSlot;
|
|
211
213
|
private schedule;
|
|
212
214
|
private tick;
|
|
213
|
-
/**
|
|
215
|
+
/**
|
|
216
|
+
* Applies the busy policy, returning the thread once this run holds it. The thread is claimed
|
|
217
|
+
* atomically, so of several replicas starting runs on one thread at once, exactly one proceeds and
|
|
218
|
+
* the rest meet the policy; a thread still busy after the policy has run its course is refused.
|
|
219
|
+
*/
|
|
214
220
|
private settleBusyThread;
|
|
221
|
+
/**
|
|
222
|
+
* Claims a thread for a run. The claim is a record in the operation store under a unique
|
|
223
|
+
* idempotency key, moved from run to run by compare-and-set, so two replicas cannot both take a free
|
|
224
|
+
* thread: creating it races on the key, and taking it over races on the sequence. Resolves
|
|
225
|
+
* undefined once this run holds the thread, or the id of the unfinished run that does.
|
|
226
|
+
*
|
|
227
|
+
* A store without `findByIdempotencyKey` falls back to the thread record's `activeRunId`, which is
|
|
228
|
+
* what every release before 2.2 used, and which two replicas racing can both see empty.
|
|
229
|
+
*/
|
|
230
|
+
private claimThread;
|
|
231
|
+
/**
|
|
232
|
+
* Whether a thread's holder no longer needs it: its run finished, or it was never recorded and its
|
|
233
|
+
* claim is old enough that the replica starting it must have died.
|
|
234
|
+
*/
|
|
235
|
+
private runIsFree;
|
|
236
|
+
/** Frees a thread this run holds. Losing the compare-and-set means another run took it over, which is fine. */
|
|
237
|
+
private releaseClaim;
|
|
215
238
|
/** Waits for a run to finish, for the `enqueue` policy. */
|
|
216
239
|
private waitForRun;
|
|
217
240
|
private releaseThread;
|
package/dist/server/runs.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { OperationReleasedError } from '../operations/errors.js';
|
|
1
|
+
import { OperationDuplicateError, OperationReleasedError } from '../operations/errors.js';
|
|
2
2
|
import { OperationRunner } from '../operations/runner.js';
|
|
3
3
|
import { operationStats } from '../operations/stats.js';
|
|
4
4
|
import { MemoryOperationStore } from '../operations/store.js';
|
|
@@ -9,6 +9,8 @@ import { MemoryServerStore, RUNS_NAMESPACE } from './state.js';
|
|
|
9
9
|
const THREADS = ['nexus', 'server', 'threads'];
|
|
10
10
|
const RUNS = RUNS_NAMESPACE;
|
|
11
11
|
const KIND_PREFIX = 'assistant:';
|
|
12
|
+
const THREAD_CLAIM = 'nexus-thread:';
|
|
13
|
+
const UNRECORDED_CLAIM_MS = 60_000;
|
|
12
14
|
const DEFAULT_CONCURRENCY = 10;
|
|
13
15
|
const DEFAULT_POLL_MS = 1_000;
|
|
14
16
|
const DEFAULT_RECOVER_MS = 30_000;
|
|
@@ -120,12 +122,22 @@ export class RunManager {
|
|
|
120
122
|
}
|
|
121
123
|
async start(options) {
|
|
122
124
|
const base = this.assistant(options.assistant);
|
|
125
|
+
const runId = `run-${randomId()}`;
|
|
123
126
|
let thread;
|
|
124
127
|
if (options.threadId) {
|
|
125
128
|
thread = await this.thread(options.threadId, options.principal);
|
|
126
|
-
thread = await this.settleBusyThread(thread, options);
|
|
129
|
+
thread = await this.settleBusyThread(thread, options, runId);
|
|
127
130
|
}
|
|
128
|
-
|
|
131
|
+
try {
|
|
132
|
+
return await this.startClaimed(runId, base, thread, options);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (thread)
|
|
136
|
+
await this.releaseClaim(thread.id, runId).catch((cause) => this.report(cause, { runId }));
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async startClaimed(runId, base, thread, options) {
|
|
129
141
|
let assistant = base;
|
|
130
142
|
let revision;
|
|
131
143
|
if (base.route) {
|
|
@@ -350,6 +362,8 @@ export class RunManager {
|
|
|
350
362
|
acceptedId = (await this.runner.enqueue(submit)).id;
|
|
351
363
|
}
|
|
352
364
|
if (acceptedId !== runId) {
|
|
365
|
+
if (thread)
|
|
366
|
+
await this.releaseClaim(thread.id, runId);
|
|
353
367
|
await this.state.delete(RUNS, runId);
|
|
354
368
|
await this.releaseTenant(runId, options.principal?.tenantId);
|
|
355
369
|
return (await this.state.get(RUNS, acceptedId)) ?? run;
|
|
@@ -393,6 +407,8 @@ export class RunManager {
|
|
|
393
407
|
metadata: plan.metadata,
|
|
394
408
|
attempt: context.attempt,
|
|
395
409
|
...(plan.revision ? { revision: plan.revision.id } : {}),
|
|
410
|
+
saveProgress: (details) => context.heartbeat(details),
|
|
411
|
+
...(context.previousHeartbeat === undefined ? {} : { progress: context.previousHeartbeat }),
|
|
396
412
|
recordCost: async (usd) => {
|
|
397
413
|
if (!Number.isFinite(usd) || usd <= 0)
|
|
398
414
|
return;
|
|
@@ -534,21 +550,21 @@ export class RunManager {
|
|
|
534
550
|
const pollMs = this.options.queue?.pollMs ?? DEFAULT_POLL_MS;
|
|
535
551
|
this.schedule(claimed > 0 && this.freeSlots() > 0 ? 0 : Math.min(pollMs, this.options.recoverEveryMs ?? pollMs));
|
|
536
552
|
}
|
|
537
|
-
async settleBusyThread(thread, options) {
|
|
553
|
+
async settleBusyThread(thread, options, runId) {
|
|
538
554
|
const policy = options.onBusy ?? this.options.onBusy ?? 'reject';
|
|
539
555
|
let current = thread;
|
|
540
|
-
|
|
541
|
-
|
|
556
|
+
const deadline = this.now().getTime() + (this.options.queueTimeoutMs ?? 30_000);
|
|
557
|
+
for (let attempt = 0; attempt < 4 || (policy === 'enqueue' && this.now().getTime() < deadline); attempt += 1) {
|
|
558
|
+
const activeId = await this.claimThread(current, runId);
|
|
542
559
|
if (!activeId)
|
|
543
560
|
return current;
|
|
544
561
|
const active = await this.state.get(RUNS, activeId);
|
|
545
|
-
if (!active || isFinished(active.status)) {
|
|
546
|
-
current = { ...current, activeRunId: undefined };
|
|
547
|
-
await this.state.put(THREADS, current.id, current);
|
|
548
|
-
return current;
|
|
549
|
-
}
|
|
550
562
|
if (policy === 'reject')
|
|
551
563
|
throw new ThreadBusyError(current.id, activeId);
|
|
564
|
+
if (!active) {
|
|
565
|
+
await delay(50);
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
552
568
|
if (policy === 'enqueue') {
|
|
553
569
|
await this.waitForRun(activeId);
|
|
554
570
|
current = await this.thread(current.id, options.principal);
|
|
@@ -565,9 +581,67 @@ export class RunManager {
|
|
|
565
581
|
}
|
|
566
582
|
current = { ...current, activeRunId: undefined };
|
|
567
583
|
await this.state.put(THREADS, current.id, current);
|
|
568
|
-
return current;
|
|
569
584
|
}
|
|
570
|
-
|
|
585
|
+
throw new ThreadBusyError(current.id, current.activeRunId ?? 'another run');
|
|
586
|
+
}
|
|
587
|
+
async claimThread(thread, runId) {
|
|
588
|
+
if (!this.store.findByIdempotencyKey) {
|
|
589
|
+
const activeId = thread.activeRunId;
|
|
590
|
+
return activeId && !(await this.runIsFree(activeId, undefined)) ? activeId : undefined;
|
|
591
|
+
}
|
|
592
|
+
const key = `${THREAD_CLAIM}${thread.id}`;
|
|
593
|
+
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
594
|
+
const claim = await this.store.findByIdempotencyKey(key);
|
|
595
|
+
const at = this.now().toISOString();
|
|
596
|
+
if (!claim) {
|
|
597
|
+
try {
|
|
598
|
+
await this.store.create({
|
|
599
|
+
id: `${key}:${runId}`,
|
|
600
|
+
status: 'succeeded',
|
|
601
|
+
attempt: 1,
|
|
602
|
+
maxAttempts: 1,
|
|
603
|
+
sequence: 0,
|
|
604
|
+
createdAt: at,
|
|
605
|
+
updatedAt: at,
|
|
606
|
+
kind: 'server.thread',
|
|
607
|
+
idempotencyKey: key,
|
|
608
|
+
result: { runId },
|
|
609
|
+
});
|
|
610
|
+
return undefined;
|
|
611
|
+
}
|
|
612
|
+
catch (error) {
|
|
613
|
+
if (error instanceof OperationDuplicateError)
|
|
614
|
+
continue;
|
|
615
|
+
throw error;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
const holder = claim.result?.runId;
|
|
619
|
+
if (holder === runId)
|
|
620
|
+
return undefined;
|
|
621
|
+
if (holder && !(await this.runIsFree(holder, claim.updatedAt)))
|
|
622
|
+
return holder;
|
|
623
|
+
const taken = await this.store.update({ ...claim, result: { runId }, sequence: claim.sequence + 1, updatedAt: at }, claim.sequence);
|
|
624
|
+
if (taken)
|
|
625
|
+
return undefined;
|
|
626
|
+
}
|
|
627
|
+
const holder = (await this.store.findByIdempotencyKey(key))?.result?.runId;
|
|
628
|
+
return holder ?? 'another run';
|
|
629
|
+
}
|
|
630
|
+
async runIsFree(runId, claimedAt) {
|
|
631
|
+
const run = await this.state.get(RUNS, runId);
|
|
632
|
+
if (run)
|
|
633
|
+
return isFinished(run.status);
|
|
634
|
+
if (claimedAt === undefined)
|
|
635
|
+
return true;
|
|
636
|
+
return this.now().getTime() - Date.parse(claimedAt) > UNRECORDED_CLAIM_MS;
|
|
637
|
+
}
|
|
638
|
+
async releaseClaim(threadId, runId) {
|
|
639
|
+
if (!this.store.findByIdempotencyKey)
|
|
640
|
+
return;
|
|
641
|
+
const claim = await this.store.findByIdempotencyKey(`${THREAD_CLAIM}${threadId}`);
|
|
642
|
+
if (!claim || claim.result?.runId !== runId)
|
|
643
|
+
return;
|
|
644
|
+
await this.store.update({ ...claim, result: {}, sequence: claim.sequence + 1, updatedAt: this.now().toISOString() }, claim.sequence);
|
|
571
645
|
}
|
|
572
646
|
async waitForRun(runId) {
|
|
573
647
|
const deadline = this.now().getTime() + (this.options.queueTimeoutMs ?? 30_000);
|
|
@@ -584,6 +658,7 @@ export class RunManager {
|
|
|
584
658
|
async releaseThread(threadId, runId) {
|
|
585
659
|
if (!threadId)
|
|
586
660
|
return;
|
|
661
|
+
await this.releaseClaim(threadId, runId).catch((error) => this.report(error, { runId }));
|
|
587
662
|
const thread = await this.state.get(THREADS, threadId);
|
|
588
663
|
if (!thread || thread.activeRunId !== runId)
|
|
589
664
|
return;
|
|
@@ -678,5 +753,6 @@ function delay(ms) {
|
|
|
678
753
|
});
|
|
679
754
|
}
|
|
680
755
|
function isDrained(error) {
|
|
681
|
-
|
|
756
|
+
const code = typeof error === 'object' && error !== null ? error.code : undefined;
|
|
757
|
+
return code === 'GRAPH_DRAINED' || code === 'RUN_HANDED_OFF';
|
|
682
758
|
}
|
package/dist/sqlite/index.d.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
|
+
import type { SchemaMigration } from '../utils/schema-migrations.js';
|
|
2
|
+
export { type AppliedMigration, type MigrationResult, type MigrationStatus, migrationChecksum, type SchemaMigration, SchemaMigrationError, } from '../utils/schema-migrations.js';
|
|
1
3
|
export { fromLibsql, fromSqliteDatabase, type LibsqlLikeClient, type SqliteDatabaseLike, type SqliteLikeClient, type SqliteValue, } from './client.js';
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
4
|
+
export { applySqliteMigrations, type SqliteMigrateOptions, sqliteMigrationStatus } from './migrations.js';
|
|
5
|
+
export { SqliteOperationStore, type SqliteOperationStoreOptions, sqliteOperationStoreMigration, sqliteOperationStoreMigrations, } from './operations.js';
|
|
6
|
+
export { SqliteStore, type SqliteStoreOptions, sqliteStoreMigration, sqliteStoreMigrations } from './store.js';
|
|
7
|
+
export { SqliteVectorStore, type SqliteVectorStoreOptions, sqliteVectorStoreMigration, sqliteVectorStoreMigrations, } from './vectors.js';
|
|
8
|
+
/** The SQLite adapters a migration can include. */
|
|
9
|
+
export type SqliteAdapter = 'operations' | 'store' | 'vectors';
|
|
10
|
+
/**
|
|
11
|
+
* The versioned migrations of the chosen adapters on their default tables, for
|
|
12
|
+
* `applySqliteMigrations()`, `sqliteMigrationStatus()`, and `nexus db migrate`. Defaults to every
|
|
13
|
+
* adapter. A store on a custom table migrates through its own `migrate()`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function sqliteMigrations(options?: {
|
|
16
|
+
adapters?: readonly SqliteAdapter[];
|
|
17
|
+
}): SchemaMigration[];
|
package/dist/sqlite/index.js
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
|
+
import { sqliteOperationStoreMigrations } from './operations.js';
|
|
2
|
+
import { sqliteStoreMigrations } from './store.js';
|
|
3
|
+
import { sqliteVectorStoreMigrations } from './vectors.js';
|
|
4
|
+
export { migrationChecksum, SchemaMigrationError, } from '../utils/schema-migrations.js';
|
|
1
5
|
export { fromLibsql, fromSqliteDatabase, } from './client.js';
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
6
|
+
export { applySqliteMigrations, sqliteMigrationStatus } from './migrations.js';
|
|
7
|
+
export { SqliteOperationStore, sqliteOperationStoreMigration, sqliteOperationStoreMigrations, } from './operations.js';
|
|
8
|
+
export { SqliteStore, sqliteStoreMigration, sqliteStoreMigrations } from './store.js';
|
|
9
|
+
export { SqliteVectorStore, sqliteVectorStoreMigration, sqliteVectorStoreMigrations, } from './vectors.js';
|
|
10
|
+
export function sqliteMigrations(options = {}) {
|
|
11
|
+
const adapters = new Set(options.adapters ?? ['operations', 'store', 'vectors']);
|
|
12
|
+
return [
|
|
13
|
+
...(adapters.has('operations') ? sqliteOperationStoreMigrations() : []),
|
|
14
|
+
...(adapters.has('store') ? sqliteStoreMigrations() : []),
|
|
15
|
+
...(adapters.has('vectors') ? sqliteVectorStoreMigrations() : []),
|
|
16
|
+
];
|
|
17
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type MigrationResult, type MigrationStatus, type SchemaMigration } from '../utils/schema-migrations.js';
|
|
2
|
+
import { type SqliteDatabaseLike, type SqliteLikeClient } from './client.js';
|
|
3
|
+
export { type AppliedMigration, type MigrationResult, type MigrationStatus, migrationChecksum, type SchemaMigration, SchemaMigrationError, } from '../utils/schema-migrations.js';
|
|
4
|
+
/** Options for applying or inspecting SQLite migrations. */
|
|
5
|
+
export interface SqliteMigrateOptions {
|
|
6
|
+
/** Plans without writing anything, not even the table that records migrations. */
|
|
7
|
+
dryRun?: boolean;
|
|
8
|
+
/** The table that records applied migrations. Defaults to `nexus_schema_migrations`. */
|
|
9
|
+
table?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Where a SQLite database stands against a list of migrations. Reads only: on a database that has
|
|
13
|
+
* never been migrated, everything is pending.
|
|
14
|
+
*/
|
|
15
|
+
export declare function sqliteMigrationStatus(database: SqliteLikeClient | SqliteDatabaseLike, migrations: readonly SchemaMigration[], options?: Pick<SqliteMigrateOptions, 'table'>): Promise<MigrationStatus>;
|
|
16
|
+
/**
|
|
17
|
+
* Applies the pending migrations in order, recording each with a checksum.
|
|
18
|
+
*
|
|
19
|
+
* Each migration runs inside `BEGIN IMMEDIATE`, which takes SQLite's write lock: its statements and
|
|
20
|
+
* the row recording it land together or not at all, and a second process migrating the same file
|
|
21
|
+
* waits for the first instead of interleaving with it. A migration whose statements changed after it
|
|
22
|
+
* ran is refused before anything is written.
|
|
23
|
+
*/
|
|
24
|
+
export declare function applySqliteMigrations(database: SqliteLikeClient | SqliteDatabaseLike, migrations: readonly SchemaMigration[], options?: SqliteMigrateOptions): Promise<MigrationResult>;
|
|
25
|
+
/** The statement that creates the migrations table, which `migrate()` runs first. */
|
|
26
|
+
export declare function sqliteMigrationsTableStatement(table?: string): string;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { assertUnchanged, MIGRATIONS_TABLE, migrationChecksum, planMigrations, sqlLiteral, } from '../utils/schema-migrations.js';
|
|
2
|
+
import { quoteSqliteTable, toSqliteClient } from './client.js';
|
|
3
|
+
export { migrationChecksum, SchemaMigrationError, } from '../utils/schema-migrations.js';
|
|
4
|
+
export async function sqliteMigrationStatus(database, migrations, options = {}) {
|
|
5
|
+
return planMigrations(migrations, await readApplied(toSqliteClient(database), options.table ?? MIGRATIONS_TABLE));
|
|
6
|
+
}
|
|
7
|
+
export async function applySqliteMigrations(database, migrations, options = {}) {
|
|
8
|
+
const client = toSqliteClient(database);
|
|
9
|
+
const tableName = options.table ?? MIGRATIONS_TABLE;
|
|
10
|
+
if (options.dryRun) {
|
|
11
|
+
const status = await sqliteMigrationStatus(client, migrations, options);
|
|
12
|
+
assertUnchanged(status);
|
|
13
|
+
return { applied: status.pending, statements: status.pending.flatMap((m) => [...m.statements]), dryRun: true };
|
|
14
|
+
}
|
|
15
|
+
planMigrations(migrations, []);
|
|
16
|
+
await client.exec(sqliteMigrationsTableStatement(tableName));
|
|
17
|
+
const status = planMigrations(migrations, await readApplied(client, tableName));
|
|
18
|
+
assertUnchanged(status);
|
|
19
|
+
const table = quoteSqliteTable(tableName);
|
|
20
|
+
const statements = [];
|
|
21
|
+
for (const migration of status.pending) {
|
|
22
|
+
const record = `INSERT OR IGNORE INTO ${table} (component, version, name, checksum, applied_at) VALUES (${sqlLiteral(migration.component)}, ${migration.version}, ${sqlLiteral(migration.name)}, ${sqlLiteral(migrationChecksum(migration))}, ${sqlLiteral(new Date().toISOString())})`;
|
|
23
|
+
const script = ['BEGIN IMMEDIATE', ...migration.statements, record, 'COMMIT'].join(';\n');
|
|
24
|
+
try {
|
|
25
|
+
await client.exec(`${script};`);
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
try {
|
|
29
|
+
await client.exec('ROLLBACK');
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
statements.push(...migration.statements);
|
|
36
|
+
}
|
|
37
|
+
return { applied: status.pending, statements, dryRun: false };
|
|
38
|
+
}
|
|
39
|
+
export function sqliteMigrationsTableStatement(table = MIGRATIONS_TABLE) {
|
|
40
|
+
return `CREATE TABLE IF NOT EXISTS ${quoteSqliteTable(table)} (
|
|
41
|
+
component TEXT NOT NULL,
|
|
42
|
+
version INTEGER NOT NULL,
|
|
43
|
+
name TEXT NOT NULL,
|
|
44
|
+
checksum TEXT NOT NULL,
|
|
45
|
+
applied_at TEXT NOT NULL,
|
|
46
|
+
PRIMARY KEY (component, version)
|
|
47
|
+
)`;
|
|
48
|
+
}
|
|
49
|
+
async function readApplied(client, table) {
|
|
50
|
+
const exists = (await client.all(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?`, [
|
|
51
|
+
table,
|
|
52
|
+
]));
|
|
53
|
+
if (exists.length === 0)
|
|
54
|
+
return [];
|
|
55
|
+
const rows = (await client.all(`SELECT component, version, name, checksum, applied_at FROM ${quoteSqliteTable(table)} ORDER BY applied_at, component, version`, []));
|
|
56
|
+
return rows.map((row) => ({
|
|
57
|
+
component: String(row.component),
|
|
58
|
+
version: Number(row.version),
|
|
59
|
+
name: String(row.name),
|
|
60
|
+
checksum: String(row.checksum),
|
|
61
|
+
appliedAt: String(row.applied_at),
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import { type OperationRecord, type OperationStore, type OperationStoreFilter, type OperationStoreStats } from '../types/operations.js';
|
|
2
|
+
import type { MigrationResult, SchemaMigration } from '../utils/schema-migrations.js';
|
|
2
3
|
import { type SqliteDatabaseLike, type SqliteLikeClient } from './client.js';
|
|
4
|
+
import type { SqliteMigrateOptions } from './migrations.js';
|
|
3
5
|
/** Options for the SQLite operation store. */
|
|
4
6
|
export interface SqliteOperationStoreOptions {
|
|
5
7
|
/** Table name. Defaults to `nexus_operations`. */
|
|
6
8
|
table?: string;
|
|
7
9
|
}
|
|
8
|
-
/** The schema, as statements
|
|
10
|
+
/** The schema, as statements: every migration's, in order. For the application's own tooling. */
|
|
9
11
|
export declare function sqliteOperationStoreMigration(options?: SqliteOperationStoreOptions): string[];
|
|
12
|
+
/**
|
|
13
|
+
* The versioned schema, which `migrate()` applies. Version 1 is the 2.0 table; version 2 indexes
|
|
14
|
+
* queued work by age, so a claim reads the oldest few rows instead of sorting the queue; version 3
|
|
15
|
+
* indexes queued work by tenant.
|
|
16
|
+
*/
|
|
17
|
+
export declare function sqliteOperationStoreMigrations(options?: SqliteOperationStoreOptions): SchemaMigration[];
|
|
10
18
|
/**
|
|
11
19
|
* Operation records in SQLite: durable operations, and — through `OperationStoreCheckpointer` — graph
|
|
12
20
|
* and workflow checkpoints, on one machine with no server to run.
|
|
@@ -20,8 +28,11 @@ export declare class SqliteOperationStore<TResult = unknown> implements Operatio
|
|
|
20
28
|
private readonly client;
|
|
21
29
|
private readonly table;
|
|
22
30
|
constructor(database: SqliteLikeClient | SqliteDatabaseLike, options?: SqliteOperationStoreOptions);
|
|
23
|
-
/**
|
|
24
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Applies this store's pending migrations, recorded in `nexus_schema_migrations`, each inside
|
|
33
|
+
* `BEGIN IMMEDIATE`. Never runs implicitly. Safe on a file an older release created.
|
|
34
|
+
*/
|
|
35
|
+
migrate(options?: SqliteMigrateOptions): Promise<MigrationResult>;
|
|
25
36
|
/**
|
|
26
37
|
* Inserts a new record. Throws `OperationDuplicateError` when its idempotency key is already
|
|
27
38
|
* claimed. Refuses records carrying raw bytes.
|
|
@@ -5,8 +5,31 @@ import { isSqliteUniqueViolation, quoteSqliteTable, toSqliteClient, } from './cl
|
|
|
5
5
|
const DEFAULT_TABLE = 'nexus_operations';
|
|
6
6
|
const TERMINAL = TERMINAL_OPERATION_STATUSES.map((status) => `'${status}'`).join(', ');
|
|
7
7
|
export function sqliteOperationStoreMigration(options = {}) {
|
|
8
|
+
return sqliteOperationStoreMigrations(options).flatMap((migration) => [...migration.statements]);
|
|
9
|
+
}
|
|
10
|
+
export function sqliteOperationStoreMigrations(options = {}) {
|
|
8
11
|
const name = options.table ?? DEFAULT_TABLE;
|
|
9
12
|
const table = quoteSqliteTable(name);
|
|
13
|
+
const component = `operations:${name}`;
|
|
14
|
+
return [
|
|
15
|
+
{ component, version: 1, name: 'create the operations table', statements: createStatements(name, table) },
|
|
16
|
+
{
|
|
17
|
+
component,
|
|
18
|
+
version: 2,
|
|
19
|
+
name: 'index queued work by age',
|
|
20
|
+
statements: [`CREATE INDEX IF NOT EXISTS "${name}_queued" ON ${table} (status, created_at, id)`],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
component,
|
|
24
|
+
version: 3,
|
|
25
|
+
name: 'index queued work by tenant',
|
|
26
|
+
statements: [
|
|
27
|
+
`CREATE INDEX IF NOT EXISTS "${name}_tenant_queued" ON ${table} (status, json_extract(doc, '$.tenantId'), created_at, id)`,
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
function createStatements(name, table) {
|
|
10
33
|
return [
|
|
11
34
|
`CREATE TABLE IF NOT EXISTS ${table} (
|
|
12
35
|
id TEXT PRIMARY KEY,
|
|
@@ -32,9 +55,9 @@ export class SqliteOperationStore {
|
|
|
32
55
|
this.client = toSqliteClient(database);
|
|
33
56
|
this.table = quoteSqliteTable(options.table ?? DEFAULT_TABLE);
|
|
34
57
|
}
|
|
35
|
-
async migrate() {
|
|
36
|
-
|
|
37
|
-
|
|
58
|
+
async migrate(options = {}) {
|
|
59
|
+
const { applySqliteMigrations } = await import('./migrations.js');
|
|
60
|
+
return applySqliteMigrations(this.client, sqliteOperationStoreMigrations(this.options), options);
|
|
38
61
|
}
|
|
39
62
|
async create(record) {
|
|
40
63
|
assertSerializableRecord(record);
|
|
@@ -99,29 +122,21 @@ export class SqliteOperationStore {
|
|
|
99
122
|
}
|
|
100
123
|
async listQueued(limit, filter) {
|
|
101
124
|
const params = [new Date().toISOString()];
|
|
102
|
-
|
|
103
|
-
if (filter?.kindPrefix) {
|
|
104
|
-
params.push(filter.kindPrefix, filter.kindPrefix);
|
|
105
|
-
kind = ' AND substr(kind, 1, length(?)) = ?';
|
|
106
|
-
}
|
|
125
|
+
const narrowed = filterSql(filter, params);
|
|
107
126
|
params.push(limit);
|
|
108
127
|
return this.many(`SELECT doc FROM ${this.table}
|
|
109
|
-
WHERE status = 'queued' AND (lease_expires_at IS NULL OR lease_expires_at <= ?)${
|
|
128
|
+
WHERE status = 'queued' AND (lease_expires_at IS NULL OR lease_expires_at <= ?)${narrowed}
|
|
110
129
|
ORDER BY created_at, id
|
|
111
130
|
LIMIT ?`, params);
|
|
112
131
|
}
|
|
113
132
|
async stats(now, filter) {
|
|
114
133
|
const params = [now];
|
|
115
|
-
|
|
116
|
-
if (filter?.kindPrefix) {
|
|
117
|
-
params.push(filter.kindPrefix, filter.kindPrefix);
|
|
118
|
-
kind = ' AND substr(kind, 1, length(?)) = ?';
|
|
119
|
-
}
|
|
134
|
+
const narrowed = filterSql(filter, params);
|
|
120
135
|
const rows = (await this.client.all(`SELECT status, count(*) AS count,
|
|
121
136
|
min(CASE WHEN status = 'queued' THEN created_at END) AS oldest,
|
|
122
137
|
sum(CASE WHEN status = 'running' AND (lease_expires_at IS NULL OR lease_expires_at <= ?) THEN 1 ELSE 0 END) AS lapsed
|
|
123
138
|
FROM ${this.table}
|
|
124
|
-
WHERE status NOT IN (${TERMINAL})${
|
|
139
|
+
WHERE status NOT IN (${TERMINAL})${narrowed}
|
|
125
140
|
GROUP BY status`, params));
|
|
126
141
|
const byStatus = {};
|
|
127
142
|
let oldestQueuedAt;
|
|
@@ -146,3 +161,15 @@ export class SqliteOperationStore {
|
|
|
146
161
|
return rows.map((row) => JSON.parse(row.doc));
|
|
147
162
|
}
|
|
148
163
|
}
|
|
164
|
+
function filterSql(filter, params) {
|
|
165
|
+
let sql = '';
|
|
166
|
+
if (filter?.kindPrefix) {
|
|
167
|
+
params.push(filter.kindPrefix, filter.kindPrefix);
|
|
168
|
+
sql += ' AND substr(kind, 1, length(?)) = ?';
|
|
169
|
+
}
|
|
170
|
+
if (filter?.tenantId !== undefined) {
|
|
171
|
+
params.push(filter.tenantId);
|
|
172
|
+
sql += " AND json_extract(doc, '$.tenantId') = ?";
|
|
173
|
+
}
|
|
174
|
+
return sql;
|
|
175
|
+
}
|
package/dist/sqlite/store.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Store, StoreIndexOptions, StoreItem, StoreNamespace, StorePutOptions, StoreSearchOptions } from '../types/store.js';
|
|
2
|
+
import type { MigrationResult, SchemaMigration } from '../utils/schema-migrations.js';
|
|
2
3
|
import { type SqliteDatabaseLike, type SqliteLikeClient } from './client.js';
|
|
4
|
+
import type { SqliteMigrateOptions } from './migrations.js';
|
|
3
5
|
/** Options for the SQLite store. */
|
|
4
6
|
export interface SqliteStoreOptions {
|
|
5
7
|
/** Table name. Defaults to `nexus_store`. */
|
|
@@ -11,6 +13,8 @@ export interface SqliteStoreOptions {
|
|
|
11
13
|
}
|
|
12
14
|
/** The schema, as statements. */
|
|
13
15
|
export declare function sqliteStoreMigration(options?: Pick<SqliteStoreOptions, 'table'>): string[];
|
|
16
|
+
/** The versioned schema, which `migrate()` applies. Version 1 is the 2.0 schema. */
|
|
17
|
+
export declare function sqliteStoreMigrations(options?: Pick<SqliteStoreOptions, 'table'>): SchemaMigration[];
|
|
14
18
|
/**
|
|
15
19
|
* Long-term memory in SQLite, for one machine that needs memory to survive a restart.
|
|
16
20
|
*
|
|
@@ -25,8 +29,11 @@ export declare class SqliteStore implements Store {
|
|
|
25
29
|
private readonly table;
|
|
26
30
|
private readonly now;
|
|
27
31
|
constructor(database: SqliteLikeClient | SqliteDatabaseLike, options?: SqliteStoreOptions);
|
|
28
|
-
/**
|
|
29
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Applies this store's pending migrations, recorded in `nexus_schema_migrations`, each inside
|
|
34
|
+
* `BEGIN IMMEDIATE`. Never runs implicitly. Safe on a file an older release created.
|
|
35
|
+
*/
|
|
36
|
+
migrate(options?: SqliteMigrateOptions): Promise<MigrationResult>;
|
|
30
37
|
/** Stores an item, keeping its original `createdAt` when it replaces one. */
|
|
31
38
|
put<V>(namespace: StoreNamespace, key: string, value: V, options?: StorePutOptions): Promise<void>;
|
|
32
39
|
/** Reads an item, or `undefined` when it does not exist or has expired. */
|