nexus-ai-pro 2.0.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.
Files changed (282) hide show
  1. package/API_STABILITY.md +65 -0
  2. package/CHANGELOG.md +267 -1
  3. package/MIGRATING.md +1 -1
  4. package/README.md +17 -8
  5. package/dist/agent/create-agent.d.ts +9 -1
  6. package/dist/agent/create-agent.js +40 -1
  7. package/dist/cache/memory-cache.d.ts +8 -0
  8. package/dist/cache/memory-cache.js +9 -0
  9. package/dist/cache/semantic-cache.d.ts +6 -3
  10. package/dist/cache/semantic-cache.js +5 -2
  11. package/dist/cli/args.js +3 -0
  12. package/dist/cli/db.d.ts +16 -4
  13. package/dist/cli/db.js +169 -22
  14. package/dist/cli/doctor.d.ts +17 -0
  15. package/dist/cli/doctor.js +53 -0
  16. package/dist/cli/graph.d.ts +9 -0
  17. package/dist/cli/graph.js +47 -0
  18. package/dist/cli.js +21 -2
  19. package/dist/core/lifecycle.js +11 -6
  20. package/dist/core/nexus.d.ts +12 -2
  21. package/dist/core/nexus.js +106 -23
  22. package/dist/doctor/index.d.ts +90 -0
  23. package/dist/doctor/index.js +237 -0
  24. package/dist/evals/judge.d.ts +11 -0
  25. package/dist/evals/judge.js +33 -0
  26. package/dist/evaluate/index.d.ts +1 -0
  27. package/dist/evaluate/index.js +1 -0
  28. package/dist/evaluate/provenance.d.ts +22 -0
  29. package/dist/evaluate/provenance.js +10 -0
  30. package/dist/evaluate/run.js +20 -4
  31. package/dist/graph/durability.d.ts +27 -0
  32. package/dist/graph/durability.js +66 -0
  33. package/dist/graph/errors.d.ts +30 -4
  34. package/dist/graph/errors.js +18 -2
  35. package/dist/graph/event-stream.d.ts +75 -0
  36. package/dist/graph/event-stream.js +289 -0
  37. package/dist/graph/functional.d.ts +80 -7
  38. package/dist/graph/functional.js +138 -35
  39. package/dist/graph/graph.d.ts +56 -8
  40. package/dist/graph/graph.js +331 -49
  41. package/dist/graph/index.d.ts +4 -2
  42. package/dist/graph/index.js +3 -1
  43. package/dist/graph/lint.d.ts +57 -0
  44. package/dist/graph/lint.js +141 -0
  45. package/dist/graph/run-control.d.ts +28 -0
  46. package/dist/graph/run-control.js +26 -0
  47. package/dist/images/evals.js +4 -2
  48. package/dist/operations/adapters.d.ts +52 -4
  49. package/dist/operations/adapters.js +194 -7
  50. package/dist/operations/runner.js +14 -15
  51. package/dist/operations/stats.js +8 -1
  52. package/dist/operations/store.d.ts +5 -1
  53. package/dist/operations/store.js +7 -0
  54. package/dist/ops/circuit-breaker.d.ts +52 -0
  55. package/dist/ops/circuit-breaker.js +4 -0
  56. package/dist/ops/circuit-store.d.ts +13 -2
  57. package/dist/ops/circuit-store.js +68 -0
  58. package/dist/ops/circuit-sync.d.ts +17 -0
  59. package/dist/ops/circuit-sync.js +86 -0
  60. package/dist/ops/health.d.ts +24 -0
  61. package/dist/ops/health.js +38 -10
  62. package/dist/ops/index.d.ts +1 -1
  63. package/dist/ops/index.js +1 -1
  64. package/dist/ops/rate-limit-adapters.d.ts +23 -0
  65. package/dist/ops/rate-limit-adapters.js +34 -0
  66. package/dist/ops/rate-limiter.d.ts +14 -2
  67. package/dist/ops/rate-limiter.js +31 -0
  68. package/dist/postgres/circuits.d.ts +21 -4
  69. package/dist/postgres/circuits.js +65 -4
  70. package/dist/postgres/client.d.ts +0 -2
  71. package/dist/postgres/client.js +0 -4
  72. package/dist/postgres/evaluate.d.ts +16 -4
  73. package/dist/postgres/evaluate.js +17 -5
  74. package/dist/postgres/index.d.ts +32 -11
  75. package/dist/postgres/index.js +65 -31
  76. package/dist/postgres/migrations.d.ts +60 -0
  77. package/dist/postgres/migrations.js +121 -0
  78. package/dist/postgres/operations.d.ts +14 -3
  79. package/dist/postgres/operations.js +45 -15
  80. package/dist/postgres/prompts.d.ts +11 -2
  81. package/dist/postgres/prompts.js +14 -3
  82. package/dist/postgres/rollups.d.ts +35 -0
  83. package/dist/postgres/rollups.js +129 -0
  84. package/dist/postgres/store.d.ts +11 -2
  85. package/dist/postgres/store.js +14 -3
  86. package/dist/postgres/traces.d.ts +13 -3
  87. package/dist/postgres/traces.js +25 -3
  88. package/dist/postgres/vectors.d.ts +11 -2
  89. package/dist/postgres/vectors.js +14 -3
  90. package/dist/router/auto-router.d.ts +34 -7
  91. package/dist/router/auto-router.js +201 -68
  92. package/dist/server/assistant.d.ts +44 -0
  93. package/dist/server/assistant.js +39 -0
  94. package/dist/server/errors.d.ts +12 -0
  95. package/dist/server/errors.js +7 -0
  96. package/dist/server/index.d.ts +1 -1
  97. package/dist/server/index.js +1 -1
  98. package/dist/server/runs.d.ts +28 -1
  99. package/dist/server/runs.js +110 -14
  100. package/dist/sqlite/index.d.ts +16 -3
  101. package/dist/sqlite/index.js +16 -3
  102. package/dist/sqlite/migrations.d.ts +26 -0
  103. package/dist/sqlite/migrations.js +63 -0
  104. package/dist/sqlite/operations.d.ts +14 -3
  105. package/dist/sqlite/operations.js +42 -15
  106. package/dist/sqlite/store.d.ts +9 -2
  107. package/dist/sqlite/store.js +13 -3
  108. package/dist/sqlite/vectors.d.ts +11 -2
  109. package/dist/sqlite/vectors.js +13 -2
  110. package/dist/store/tenant.d.ts +12 -0
  111. package/dist/store/tenant.js +29 -0
  112. package/dist/tenancy/index.d.ts +113 -0
  113. package/dist/tenancy/index.js +233 -0
  114. package/dist/testing/record.d.ts +42 -0
  115. package/dist/testing/record.js +57 -5
  116. package/dist/tracing/index.d.ts +3 -3
  117. package/dist/tracing/index.js +2 -2
  118. package/dist/tracing/instrument.js +30 -0
  119. package/dist/tracing/otlp.d.ts +91 -0
  120. package/dist/tracing/otlp.js +124 -0
  121. package/dist/tracing/rollups.d.ts +100 -0
  122. package/dist/tracing/rollups.js +139 -0
  123. package/dist/tracing/stores.d.ts +17 -0
  124. package/dist/tracing/stores.js +23 -0
  125. package/dist/tracing/tracer.d.ts +50 -4
  126. package/dist/tracing/tracer.js +85 -6
  127. package/dist/types/config.d.ts +33 -2
  128. package/dist/types/evaluate.d.ts +37 -0
  129. package/dist/types/graph.d.ts +354 -9
  130. package/dist/types/graph.js +3 -1
  131. package/dist/types/messages.d.ts +5 -0
  132. package/dist/types/operations.d.ts +22 -2
  133. package/dist/types/response.d.ts +20 -0
  134. package/dist/types/server.d.ts +18 -0
  135. package/dist/types/tracing.d.ts +11 -0
  136. package/dist/utils/schema-migrations.d.ts +97 -0
  137. package/dist/utils/schema-migrations.js +91 -0
  138. package/dist/utils/signals.d.ts +17 -0
  139. package/dist/utils/signals.js +26 -0
  140. package/dist/utils/tenant.d.ts +14 -0
  141. package/dist/utils/tenant.js +16 -0
  142. package/dist/version.d.ts +5 -0
  143. package/dist/version.js +1 -0
  144. package/dist-cjs/agent/create-agent.d.ts +9 -1
  145. package/dist-cjs/agent/create-agent.js +40 -1
  146. package/dist-cjs/cache/memory-cache.d.ts +8 -0
  147. package/dist-cjs/cache/memory-cache.js +9 -0
  148. package/dist-cjs/cache/semantic-cache.d.ts +6 -3
  149. package/dist-cjs/cache/semantic-cache.js +5 -2
  150. package/dist-cjs/cli/args.js +3 -0
  151. package/dist-cjs/cli/db.d.ts +16 -4
  152. package/dist-cjs/cli/db.js +200 -20
  153. package/dist-cjs/cli/doctor.d.ts +17 -0
  154. package/dist-cjs/cli/doctor.js +56 -0
  155. package/dist-cjs/cli/graph.d.ts +9 -0
  156. package/dist-cjs/cli/graph.js +50 -0
  157. package/dist-cjs/core/lifecycle.js +11 -6
  158. package/dist-cjs/core/nexus.d.ts +12 -2
  159. package/dist-cjs/core/nexus.js +106 -23
  160. package/dist-cjs/doctor/index.d.ts +90 -0
  161. package/dist-cjs/doctor/index.js +276 -0
  162. package/dist-cjs/evals/judge.d.ts +11 -0
  163. package/dist-cjs/evals/judge.js +33 -0
  164. package/dist-cjs/evaluate/index.d.ts +1 -0
  165. package/dist-cjs/evaluate/index.js +3 -1
  166. package/dist-cjs/evaluate/provenance.d.ts +22 -0
  167. package/dist-cjs/evaluate/provenance.js +14 -0
  168. package/dist-cjs/evaluate/run.js +20 -4
  169. package/dist-cjs/graph/durability.d.ts +27 -0
  170. package/dist-cjs/graph/durability.js +70 -0
  171. package/dist-cjs/graph/errors.d.ts +30 -4
  172. package/dist-cjs/graph/errors.js +20 -3
  173. package/dist-cjs/graph/event-stream.d.ts +75 -0
  174. package/dist-cjs/graph/event-stream.js +295 -0
  175. package/dist-cjs/graph/functional.d.ts +80 -7
  176. package/dist-cjs/graph/functional.js +137 -34
  177. package/dist-cjs/graph/graph.d.ts +56 -8
  178. package/dist-cjs/graph/graph.js +330 -48
  179. package/dist-cjs/graph/index.d.ts +4 -2
  180. package/dist-cjs/graph/index.js +7 -1
  181. package/dist-cjs/graph/lint.d.ts +57 -0
  182. package/dist-cjs/graph/lint.js +144 -0
  183. package/dist-cjs/graph/run-control.d.ts +28 -0
  184. package/dist-cjs/graph/run-control.js +30 -0
  185. package/dist-cjs/images/evals.js +4 -2
  186. package/dist-cjs/operations/adapters.d.ts +52 -4
  187. package/dist-cjs/operations/adapters.js +193 -6
  188. package/dist-cjs/operations/runner.js +14 -15
  189. package/dist-cjs/operations/stats.js +8 -1
  190. package/dist-cjs/operations/store.d.ts +5 -1
  191. package/dist-cjs/operations/store.js +7 -0
  192. package/dist-cjs/ops/circuit-breaker.d.ts +52 -0
  193. package/dist-cjs/ops/circuit-breaker.js +4 -0
  194. package/dist-cjs/ops/circuit-store.d.ts +13 -2
  195. package/dist-cjs/ops/circuit-store.js +68 -0
  196. package/dist-cjs/ops/circuit-sync.d.ts +17 -0
  197. package/dist-cjs/ops/circuit-sync.js +87 -0
  198. package/dist-cjs/ops/health.d.ts +24 -0
  199. package/dist-cjs/ops/health.js +38 -10
  200. package/dist-cjs/ops/index.d.ts +1 -1
  201. package/dist-cjs/ops/rate-limit-adapters.d.ts +23 -0
  202. package/dist-cjs/ops/rate-limit-adapters.js +35 -1
  203. package/dist-cjs/ops/rate-limiter.d.ts +14 -2
  204. package/dist-cjs/ops/rate-limiter.js +32 -0
  205. package/dist-cjs/postgres/circuits.d.ts +21 -4
  206. package/dist-cjs/postgres/circuits.js +98 -3
  207. package/dist-cjs/postgres/client.d.ts +0 -2
  208. package/dist-cjs/postgres/client.js +0 -5
  209. package/dist-cjs/postgres/evaluate.d.ts +16 -4
  210. package/dist-cjs/postgres/evaluate.js +50 -4
  211. package/dist-cjs/postgres/index.d.ts +32 -11
  212. package/dist-cjs/postgres/index.js +69 -20
  213. package/dist-cjs/postgres/migrations.d.ts +60 -0
  214. package/dist-cjs/postgres/migrations.js +129 -0
  215. package/dist-cjs/postgres/operations.d.ts +14 -3
  216. package/dist-cjs/postgres/operations.js +78 -14
  217. package/dist-cjs/postgres/prompts.d.ts +11 -2
  218. package/dist-cjs/postgres/prompts.js +47 -2
  219. package/dist-cjs/postgres/rollups.d.ts +35 -0
  220. package/dist-cjs/postgres/rollups.js +168 -0
  221. package/dist-cjs/postgres/store.d.ts +11 -2
  222. package/dist-cjs/postgres/store.js +47 -2
  223. package/dist-cjs/postgres/traces.d.ts +13 -3
  224. package/dist-cjs/postgres/traces.js +58 -2
  225. package/dist-cjs/postgres/vectors.d.ts +11 -2
  226. package/dist-cjs/postgres/vectors.js +47 -2
  227. package/dist-cjs/router/auto-router.d.ts +34 -7
  228. package/dist-cjs/router/auto-router.js +201 -68
  229. package/dist-cjs/server/assistant.d.ts +44 -0
  230. package/dist-cjs/server/assistant.js +39 -0
  231. package/dist-cjs/server/errors.d.ts +12 -0
  232. package/dist-cjs/server/errors.js +9 -1
  233. package/dist-cjs/server/index.d.ts +1 -1
  234. package/dist-cjs/server/index.js +2 -1
  235. package/dist-cjs/server/runs.d.ts +28 -1
  236. package/dist-cjs/server/runs.js +109 -13
  237. package/dist-cjs/sqlite/index.d.ts +16 -3
  238. package/dist-cjs/sqlite/index.js +31 -10
  239. package/dist-cjs/sqlite/migrations.d.ts +26 -0
  240. package/dist-cjs/sqlite/migrations.js +71 -0
  241. package/dist-cjs/sqlite/operations.d.ts +14 -3
  242. package/dist-cjs/sqlite/operations.js +76 -15
  243. package/dist-cjs/sqlite/store.d.ts +9 -2
  244. package/dist-cjs/sqlite/store.js +47 -3
  245. package/dist-cjs/sqlite/vectors.d.ts +11 -2
  246. package/dist-cjs/sqlite/vectors.js +47 -2
  247. package/dist-cjs/store/tenant.d.ts +12 -0
  248. package/dist-cjs/store/tenant.js +33 -0
  249. package/dist-cjs/tenancy/index.d.ts +113 -0
  250. package/dist-cjs/tenancy/index.js +250 -0
  251. package/dist-cjs/testing/record.d.ts +42 -0
  252. package/dist-cjs/testing/record.js +57 -5
  253. package/dist-cjs/tracing/index.d.ts +3 -3
  254. package/dist-cjs/tracing/index.js +4 -1
  255. package/dist-cjs/tracing/instrument.js +30 -0
  256. package/dist-cjs/tracing/otlp.d.ts +91 -0
  257. package/dist-cjs/tracing/otlp.js +129 -0
  258. package/dist-cjs/tracing/rollups.d.ts +100 -0
  259. package/dist-cjs/tracing/rollups.js +147 -0
  260. package/dist-cjs/tracing/stores.d.ts +17 -0
  261. package/dist-cjs/tracing/stores.js +24 -0
  262. package/dist-cjs/tracing/tracer.d.ts +50 -4
  263. package/dist-cjs/tracing/tracer.js +87 -6
  264. package/dist-cjs/types/config.d.ts +33 -2
  265. package/dist-cjs/types/evaluate.d.ts +37 -0
  266. package/dist-cjs/types/graph.d.ts +354 -9
  267. package/dist-cjs/types/graph.js +3 -1
  268. package/dist-cjs/types/messages.d.ts +5 -0
  269. package/dist-cjs/types/operations.d.ts +22 -2
  270. package/dist-cjs/types/response.d.ts +20 -0
  271. package/dist-cjs/types/server.d.ts +18 -0
  272. package/dist-cjs/types/tracing.d.ts +11 -0
  273. package/dist-cjs/utils/schema-migrations.d.ts +97 -0
  274. package/dist-cjs/utils/schema-migrations.js +102 -0
  275. package/dist-cjs/utils/signals.d.ts +17 -0
  276. package/dist-cjs/utils/signals.js +29 -0
  277. package/dist-cjs/utils/tenant.d.ts +14 -0
  278. package/dist-cjs/utils/tenant.js +22 -0
  279. package/dist-cjs/version.d.ts +5 -0
  280. package/dist-cjs/version.js +4 -0
  281. package/llms.txt +4 -3
  282. package/package.json +110 -2
@@ -1,3 +1,4 @@
1
+ import type { GraphStreamProjection } from '../types/graph.js';
1
2
  import type { AssistantRunContext, ServerAssistant } from '../types/server.js';
2
3
  /**
3
4
  * The part of a compiled graph the adapter uses.
@@ -11,11 +12,15 @@ export interface GraphLike {
11
12
  threadId?: string;
12
13
  signal?: AbortSignal;
13
14
  metadata?: Record<string, unknown>;
15
+ tenantId?: string;
16
+ control?: DrainSwitch;
14
17
  }): AsyncIterable<unknown>;
15
18
  /** Answers an interrupt and continues the thread. */
16
19
  resume(threadId: string, value: unknown, options: {
17
20
  signal?: AbortSignal;
18
21
  metadata?: Record<string, unknown>;
22
+ tenantId?: string;
23
+ control?: DrainSwitch;
19
24
  }): AsyncIterable<unknown>;
20
25
  /** The thread's checkpoint, at its latest step or at one given. */
21
26
  state(threadId: string, step?: number): Promise<{
@@ -28,18 +33,56 @@ export interface GraphLike {
28
33
  continue?(threadId: string, options: {
29
34
  signal?: AbortSignal;
30
35
  metadata?: Record<string, unknown>;
36
+ tenantId?: string;
37
+ control?: DrainSwitch;
31
38
  }): AsyncIterable<unknown>;
32
39
  /** Writes values into a thread's state, which is how a rollback is applied. */
33
40
  updateState?(threadId: string, values: Record<string, unknown>, options?: {
34
41
  asNode?: string;
35
42
  }): Promise<unknown>;
43
+ /** Runs an input as a stream of typed events. Used when the assistant serves `events`. */
44
+ events?(input: unknown, options: GraphEventOptionsLike): GraphEventsLike;
45
+ /** Like `events()`, answering an interrupt. */
46
+ resumeEvents?(threadId: string, value: unknown, options: GraphEventOptionsLike): GraphEventsLike;
47
+ /** Like `events()`, continuing a thread from its latest checkpoint. */
48
+ continueEvents?(threadId: string, options: GraphEventOptionsLike): GraphEventsLike;
36
49
  }
50
+ /** The options of a graph's `events()` the adapter passes. */
51
+ type GraphEventOptionsLike = {
52
+ threadId?: string;
53
+ tenantId?: string;
54
+ signal?: AbortSignal;
55
+ metadata?: Record<string, unknown>;
56
+ control?: DrainSwitch;
57
+ include?: GraphStreamProjection[];
58
+ };
59
+ /** A graph's event stream, as the adapter reads it: the events, then the run's result. */
60
+ type GraphEventsLike = AsyncIterable<unknown> & {
61
+ result: Promise<{
62
+ status: string;
63
+ state: unknown;
64
+ interrupt?: unknown;
65
+ interrupts?: unknown[];
66
+ }>;
67
+ };
68
+ /** Asks a graph to stop after its current superstep. A graph's `RunControl` has this shape. */
69
+ type DrainSwitch = {
70
+ readonly draining: boolean;
71
+ readonly reason?: string;
72
+ };
37
73
  /** Options for `graphAssistant()`. */
38
74
  export interface GraphAssistantOptions {
39
75
  /** What the assistant is, reported by the assistants endpoint. */
40
76
  description?: string;
41
77
  /** Metadata recorded on every checkpoint the server's runs create. */
42
78
  metadata?: Record<string, unknown>;
79
+ /**
80
+ * Records these projections of the graph's event stream on each run, such as `messages` and
81
+ * `tools`, so a client following the run sees model output and tool calls as they happen. The
82
+ * run's last event still carries its state and any question, as without it. Defaults to none: one
83
+ * event per superstep.
84
+ */
85
+ events?: GraphStreamProjection[];
43
86
  }
44
87
  /**
45
88
  * Serves a compiled graph as an assistant.
@@ -61,3 +104,4 @@ export declare function graphAssistant(graph: GraphLike, options?: GraphAssistan
61
104
  export declare function functionAssistant(run: (input: unknown, context: AssistantRunContext) => AsyncIterable<unknown> | Promise<unknown> | unknown, options?: {
62
105
  description?: string;
63
106
  }): ServerAssistant;
107
+ export {};
@@ -1,17 +1,36 @@
1
1
  export function graphAssistant(graph, options = {}) {
2
+ const projections = options.events?.length ? options.events : undefined;
3
+ const eventOptions = (context, threadId) => ({
4
+ ...(threadId ? { threadId } : {}),
5
+ signal: context.signal,
6
+ metadata: { ...options.metadata, runId: context.runId },
7
+ ...tenantOf(context),
8
+ ...(context.control ? { control: context.control } : {}),
9
+ include: [...(projections ?? []), 'values'],
10
+ });
2
11
  return {
3
12
  description: options.description,
4
13
  stream(input, context) {
14
+ if (projections && graph.events) {
15
+ return withResult(graph.events(input ?? {}, eventOptions(context, context.threadId)));
16
+ }
5
17
  return graph.stream(input ?? {}, {
6
18
  threadId: context.threadId,
7
19
  signal: context.signal,
8
20
  metadata: { ...options.metadata, runId: context.runId },
21
+ ...tenantOf(context),
22
+ ...(context.control ? { control: context.control } : {}),
9
23
  });
10
24
  },
11
25
  resume(threadId, value, context) {
26
+ if (projections && graph.resumeEvents) {
27
+ return withResult(graph.resumeEvents(threadId, value, eventOptions(context)));
28
+ }
12
29
  return graph.resume(threadId, value, {
13
30
  signal: context.signal,
14
31
  metadata: { ...options.metadata, runId: context.runId },
32
+ ...tenantOf(context),
33
+ ...(context.control ? { control: context.control } : {}),
15
34
  });
16
35
  },
17
36
  async state(threadId) {
@@ -29,9 +48,13 @@ export function graphAssistant(graph, options = {}) {
29
48
  if (checkpoint.status !== 'running' && checkpoint.status !== 'failed' && checkpoint.status !== 'interrupted') {
30
49
  return undefined;
31
50
  }
51
+ if (projections && graph.continueEvents)
52
+ return withResult(graph.continueEvents(threadId, eventOptions(context)));
32
53
  return graph.continue(threadId, {
33
54
  signal: context.signal,
34
55
  metadata: { ...options.metadata, runId: context.runId },
56
+ ...tenantOf(context),
57
+ ...(context.control ? { control: context.control } : {}),
35
58
  });
36
59
  },
37
60
  async restore(threadId, step) {
@@ -57,3 +80,19 @@ export function functionAssistant(run, options = {}) {
57
80
  },
58
81
  };
59
82
  }
83
+ function tenantOf(context) {
84
+ const tenantId = context.principal?.tenantId;
85
+ return tenantId ? { tenantId } : {};
86
+ }
87
+ async function* withResult(events) {
88
+ for await (const event of events)
89
+ yield event;
90
+ const result = await events.result;
91
+ yield {
92
+ type: result.status === 'awaiting_input' ? 'interrupt' : 'done',
93
+ status: result.status,
94
+ state: result.state,
95
+ ...(result.interrupt === undefined ? {} : { interrupt: result.interrupt }),
96
+ ...(result.interrupts === undefined ? {} : { interrupts: result.interrupts }),
97
+ };
98
+ }
@@ -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. */
@@ -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;
@@ -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';
@@ -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';
@@ -97,6 +97,10 @@ export declare class RunManager {
97
97
  private readonly local;
98
98
  /** Runs holding one of this replica's execution slots. */
99
99
  private readonly slots;
100
+ /** The drain switch of each run this replica is executing, so `drain()` can reach graph runs. */
101
+ private readonly controls;
102
+ /** Runs handed back to the queue during a drain, as each graph run stopped at a boundary. */
103
+ private readonly drainedRuns;
100
104
  private readonly state;
101
105
  private readonly events;
102
106
  private readonly now;
@@ -155,6 +159,8 @@ export declare class RunManager {
155
159
  * it with a `TenantLimitError`.
156
160
  */
157
161
  start(options: StartRunOptions): Promise<RunRecord>;
162
+ /** The rest of `start()`, once the thread, if any, is this run's. */
163
+ private startClaimed;
158
164
  /** Cancels a run, including one another replica is executing. */
159
165
  cancel(runId: string, principal?: Principal, reason?: string): Promise<RunRecord>;
160
166
  /**
@@ -206,8 +212,29 @@ export declare class RunManager {
206
212
  private freeSlot;
207
213
  private schedule;
208
214
  private tick;
209
- /** Applies the busy policy, returning the thread once it is free to run something new. */
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
+ */
210
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;
211
238
  /** Waits for a run to finish, for the `enqueue` policy. */
212
239
  private waitForRun;
213
240
  private releaseThread;
@@ -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;
@@ -26,6 +28,8 @@ export class RunManager {
26
28
  store;
27
29
  local = new Map();
28
30
  slots = new Set();
31
+ controls = new Map();
32
+ drainedRuns = new Set();
29
33
  state;
30
34
  events;
31
35
  now;
@@ -118,12 +122,22 @@ export class RunManager {
118
122
  }
119
123
  async start(options) {
120
124
  const base = this.assistant(options.assistant);
125
+ const runId = `run-${randomId()}`;
121
126
  let thread;
122
127
  if (options.threadId) {
123
128
  thread = await this.thread(options.threadId, options.principal);
124
- thread = await this.settleBusyThread(thread, options);
129
+ thread = await this.settleBusyThread(thread, options, runId);
125
130
  }
126
- const runId = `run-${randomId()}`;
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) {
127
141
  let assistant = base;
128
142
  let revision;
129
143
  if (base.route) {
@@ -217,11 +231,18 @@ export class RunManager {
217
231
  async drain(options = {}) {
218
232
  this.draining = true;
219
233
  this.stopWorking();
234
+ if (this.options.queue) {
235
+ for (const control of this.controls.values()) {
236
+ control.draining = true;
237
+ control.reason = 'replica draining';
238
+ }
239
+ }
220
240
  const running = [...this.slots];
221
241
  const deadline = Date.now() + (options.timeoutMs ?? 25_000);
222
242
  while (this.slots.size > 0 && Date.now() < deadline)
223
243
  await pause(Math.min(50, deadline - Date.now()));
224
- const released = [];
244
+ const released = [...this.drainedRuns];
245
+ this.drainedRuns.clear();
225
246
  if (this.options.queue) {
226
247
  for (const id of [...this.slots]) {
227
248
  if (this.runner.release(id))
@@ -341,6 +362,8 @@ export class RunManager {
341
362
  acceptedId = (await this.runner.enqueue(submit)).id;
342
363
  }
343
364
  if (acceptedId !== runId) {
365
+ if (thread)
366
+ await this.releaseClaim(thread.id, runId);
344
367
  await this.state.delete(RUNS, runId);
345
368
  await this.releaseTenant(runId, options.principal?.tenantId);
346
369
  return (await this.state.get(RUNS, acceptedId)) ?? run;
@@ -374,14 +397,18 @@ export class RunManager {
374
397
  async execute(plan, context) {
375
398
  const runId = context.operationId;
376
399
  let cost = 0;
400
+ const control = { draining: false };
377
401
  const runContext = {
378
402
  runId,
379
403
  threadId: plan.threadId,
380
404
  signal: context.signal,
405
+ control,
381
406
  principal: plan.principal,
382
407
  metadata: plan.metadata,
383
408
  attempt: context.attempt,
384
409
  ...(plan.revision ? { revision: plan.revision.id } : {}),
410
+ saveProgress: (details) => context.heartbeat(details),
411
+ ...(context.previousHeartbeat === undefined ? {} : { progress: context.previousHeartbeat }),
385
412
  recordCost: async (usd) => {
386
413
  if (!Number.isFinite(usd) || usd <= 0)
387
414
  return;
@@ -411,6 +438,7 @@ export class RunManager {
411
438
  ? plan.assistant.resume(plan.threadId, plan.resume, runContext)
412
439
  : plan.assistant.stream(plan.input, runContext));
413
440
  let last;
441
+ this.controls.set(runId, control);
414
442
  try {
415
443
  for await (const event of stream) {
416
444
  last = event;
@@ -419,6 +447,8 @@ export class RunManager {
419
447
  }
420
448
  }
421
449
  catch (error) {
450
+ if (control.draining && isDrained(error) && this.runner.release(runId))
451
+ this.drainedRuns.add(runId);
422
452
  if (context.signal.aborted && context.signal.reason instanceof OperationReleasedError) {
423
453
  await this.record(runId, 'status', { status: 'queued', reason: 'handed to another worker' });
424
454
  await this.settle(runId, 'queued', { addCost: cost });
@@ -439,6 +469,9 @@ export class RunManager {
439
469
  await this.releaseTenant(runId, plan.principal?.tenantId);
440
470
  throw error;
441
471
  }
472
+ finally {
473
+ this.controls.delete(runId);
474
+ }
442
475
  const interrupt = interruptOf(last);
443
476
  const status = interrupt ? 'awaiting_input' : 'succeeded';
444
477
  const output = outputOf(last);
@@ -517,21 +550,21 @@ export class RunManager {
517
550
  const pollMs = this.options.queue?.pollMs ?? DEFAULT_POLL_MS;
518
551
  this.schedule(claimed > 0 && this.freeSlots() > 0 ? 0 : Math.min(pollMs, this.options.recoverEveryMs ?? pollMs));
519
552
  }
520
- async settleBusyThread(thread, options) {
553
+ async settleBusyThread(thread, options, runId) {
521
554
  const policy = options.onBusy ?? this.options.onBusy ?? 'reject';
522
555
  let current = thread;
523
- for (let attempt = 0; attempt < 2; attempt += 1) {
524
- const activeId = current.activeRunId;
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);
525
559
  if (!activeId)
526
560
  return current;
527
561
  const active = await this.state.get(RUNS, activeId);
528
- if (!active || isFinished(active.status)) {
529
- current = { ...current, activeRunId: undefined };
530
- await this.state.put(THREADS, current.id, current);
531
- return current;
532
- }
533
562
  if (policy === 'reject')
534
563
  throw new ThreadBusyError(current.id, activeId);
564
+ if (!active) {
565
+ await delay(50);
566
+ continue;
567
+ }
535
568
  if (policy === 'enqueue') {
536
569
  await this.waitForRun(activeId);
537
570
  current = await this.thread(current.id, options.principal);
@@ -548,9 +581,67 @@ export class RunManager {
548
581
  }
549
582
  current = { ...current, activeRunId: undefined };
550
583
  await this.state.put(THREADS, current.id, current);
551
- return current;
552
584
  }
553
- return current;
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);
554
645
  }
555
646
  async waitForRun(runId) {
556
647
  const deadline = this.now().getTime() + (this.options.queueTimeoutMs ?? 30_000);
@@ -567,6 +658,7 @@ export class RunManager {
567
658
  async releaseThread(threadId, runId) {
568
659
  if (!threadId)
569
660
  return;
661
+ await this.releaseClaim(threadId, runId).catch((error) => this.report(error, { runId }));
570
662
  const thread = await this.state.get(THREADS, threadId);
571
663
  if (!thread || thread.activeRunId !== runId)
572
664
  return;
@@ -660,3 +752,7 @@ function delay(ms) {
660
752
  timer.unref?.();
661
753
  });
662
754
  }
755
+ function isDrained(error) {
756
+ const code = typeof error === 'object' && error !== null ? error.code : undefined;
757
+ return code === 'GRAPH_DRAINED' || code === 'RUN_HANDED_OFF';
758
+ }
@@ -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 { SqliteOperationStore, type SqliteOperationStoreOptions, sqliteOperationStoreMigration } from './operations.js';
3
- export { SqliteStore, type SqliteStoreOptions, sqliteStoreMigration } from './store.js';
4
- export { SqliteVectorStore, type SqliteVectorStoreOptions, sqliteVectorStoreMigration } from './vectors.js';
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[];
@@ -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 { SqliteOperationStore, sqliteOperationStoreMigration } from './operations.js';
3
- export { SqliteStore, sqliteStoreMigration } from './store.js';
4
- export { SqliteVectorStore, sqliteVectorStoreMigration } from './vectors.js';
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
+ }