@lota-sdk/core 0.1.14 → 0.1.16
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/infrastructure/schema/00_identity.surql +0 -2
- package/infrastructure/schema/01_memory.surql +1 -1
- package/infrastructure/schema/02_execution_plan.surql +62 -1
- package/infrastructure/schema/03_learned_skill.surql +1 -1
- package/infrastructure/schema/06_playbook.surql +25 -0
- package/infrastructure/schema/07_institutional_memory.surql +13 -0
- package/infrastructure/schema/08_quality_metrics.surql +17 -0
- package/package.json +9 -8
- package/src/ai/definitions.ts +80 -2
- package/src/ai/embedding-cache.ts +7 -6
- package/src/ai/index.ts +0 -1
- package/src/bifrost/bifrost.ts +14 -14
- package/src/config/agent-defaults.ts +32 -22
- package/src/config/agent-types.ts +11 -0
- package/src/config/constants.ts +2 -14
- package/src/config/debug-logger.ts +5 -1
- package/src/config/index.ts +3 -0
- package/src/config/logger.ts +7 -9
- package/src/config/model-constants.ts +16 -34
- package/src/config/search.ts +1 -15
- package/src/create-runtime.ts +453 -0
- package/src/db/cursor-pagination.ts +3 -6
- package/src/db/index.ts +2 -0
- package/src/db/memory-store.rows.ts +7 -7
- package/src/db/memory-store.ts +24 -24
- package/src/db/memory.ts +18 -16
- package/src/db/schema-fingerprint.ts +1 -0
- package/src/db/service.ts +193 -122
- package/src/db/startup.ts +9 -13
- package/src/db/surreal-mutation.ts +43 -0
- package/src/db/tables.ts +7 -0
- package/src/db/workstream-message-row.ts +15 -0
- package/src/embeddings/provider.ts +1 -1
- package/src/index.ts +1 -1
- package/src/queues/context-compaction.queue.ts +17 -52
- package/src/queues/delayed-node-promotion.queue.ts +41 -0
- package/src/queues/document-processor.queue.ts +7 -7
- package/src/queues/index.ts +3 -0
- package/src/queues/memory-consolidation.queue.ts +18 -54
- package/src/queues/plan-scheduler.queue.ts +97 -0
- package/src/queues/post-chat-memory.queue.ts +15 -60
- package/src/queues/queue-factory.ts +100 -0
- package/src/queues/recent-activity-title-refinement.queue.ts +15 -54
- package/src/queues/regular-chat-memory-digest.queue.ts +16 -55
- package/src/queues/skill-extraction.queue.ts +15 -50
- package/src/queues/workstream-title-generation.queue.ts +15 -51
- package/src/redis/connection.ts +12 -3
- package/src/redis/index.ts +2 -1
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +41 -8
- package/src/redis/stream-context.ts +11 -0
- package/src/runtime/agent-runtime-policy.ts +106 -21
- package/src/runtime/agent-stream-helpers.ts +2 -1
- package/src/runtime/approval-continuation.ts +12 -6
- package/src/runtime/context-compaction-constants.ts +1 -1
- package/src/runtime/context-compaction-runtime.ts +7 -5
- package/src/runtime/context-compaction.ts +40 -97
- package/src/runtime/execution-plan.ts +23 -19
- package/src/runtime/graph-designer.ts +15 -0
- package/src/runtime/helper-model.ts +10 -196
- package/src/runtime/index.ts +14 -1
- package/src/runtime/llm-content.ts +1 -1
- package/src/runtime/memory-block.ts +11 -12
- package/src/runtime/memory-pipeline.ts +26 -10
- package/src/runtime/plugin-resolution.ts +35 -0
- package/src/runtime/plugin-types.ts +73 -1
- package/src/runtime/retrieval-adapters.ts +1 -1
- package/src/runtime/runtime-config.ts +25 -12
- package/src/runtime/runtime-extensions.ts +91 -15
- package/src/runtime/runtime-worker-registry.ts +6 -0
- package/src/runtime/team-consultation-orchestrator.ts +45 -28
- package/src/runtime/team-consultation-prompts.ts +11 -2
- package/src/runtime/title-helpers.ts +11 -4
- package/src/runtime/workstream-chat-helpers.ts +6 -7
- package/src/runtime/workstream-routing-policy.ts +0 -30
- package/src/runtime/workstream-state.ts +17 -7
- package/src/services/adaptive-playbook.service.ts +152 -0
- package/src/services/agent-executor.service.ts +293 -0
- package/src/services/artifact-provenance.service.ts +172 -0
- package/src/services/attachment.service.ts +7 -12
- package/src/services/context-compaction.service.ts +75 -58
- package/src/services/context-enrichment.service.ts +33 -0
- package/src/services/coordination-registry.service.ts +117 -0
- package/src/services/document-chunk.service.ts +38 -33
- package/src/services/domain-agent-executor.service.ts +71 -0
- package/src/services/execution-plan.service.ts +271 -50
- package/src/services/feedback-loop.service.ts +96 -0
- package/src/services/global-orchestrator.service.ts +148 -0
- package/src/services/index.ts +26 -0
- package/src/services/institutional-memory.service.ts +145 -0
- package/src/services/learned-skill.service.ts +30 -15
- package/src/services/memory-assessment.service.ts +3 -2
- package/src/services/{memory.utils.ts → memory-utils.ts} +4 -13
- package/src/services/memory.service.ts +55 -69
- package/src/services/monitoring-window.service.ts +86 -0
- package/src/services/mutating-approval.service.ts +1 -1
- package/src/services/node-workspace.service.ts +155 -0
- package/src/services/notification.service.ts +39 -0
- package/src/services/organization-member.service.ts +12 -5
- package/src/services/organization.service.ts +5 -5
- package/src/services/ownership-dispatcher.service.ts +403 -0
- package/src/services/plan-approval.service.ts +1 -1
- package/src/services/plan-artifact.service.ts +1 -0
- package/src/services/plan-builder.service.ts +1 -0
- package/src/services/plan-checkpoint.service.ts +30 -2
- package/src/services/plan-compiler.service.ts +5 -0
- package/src/services/plan-coordination.service.ts +152 -0
- package/src/services/plan-cycle.service.ts +284 -0
- package/src/services/plan-deadline.service.ts +287 -0
- package/src/services/plan-executor.service.ts +386 -58
- package/src/services/plan-helpers.ts +15 -0
- package/src/services/plan-run.service.ts +41 -7
- package/src/services/plan-scheduler.service.ts +240 -0
- package/src/services/plan-template.service.ts +117 -0
- package/src/services/plan-validator.service.ts +87 -20
- package/src/services/plan-workspace.service.ts +83 -0
- package/src/services/playbook-registry.service.ts +67 -0
- package/src/services/plugin-executor.service.ts +103 -0
- package/src/services/quality-metrics.service.ts +132 -0
- package/src/services/recent-activity-title.service.ts +3 -10
- package/src/services/recent-activity.service.ts +33 -43
- package/src/services/skill-resolver.service.ts +19 -0
- package/src/services/system-executor.service.ts +105 -0
- package/src/services/workstream-message.service.ts +29 -41
- package/src/services/workstream-plan-registry.service.ts +22 -0
- package/src/services/workstream-title.service.ts +3 -9
- package/src/services/{workstream-turn-preparation.ts → workstream-turn-preparation.service.ts} +428 -373
- package/src/services/workstream-turn.ts +2 -2
- package/src/services/workstream.service.ts +55 -65
- package/src/services/workstream.types.ts +10 -19
- package/src/services/write-intent-validator.service.ts +81 -0
- package/src/storage/attachment-parser.ts +1 -1
- package/src/storage/attachment-storage.service.ts +4 -4
- package/src/storage/{attachments.utils.ts → attachment-utils.ts} +2 -5
- package/src/storage/generated-document-storage.service.ts +3 -2
- package/src/storage/index.ts +2 -2
- package/src/system-agents/{context-compacter.agent.ts → context-compaction.agent.ts} +4 -4
- package/src/system-agents/delegated-agent-factory.ts +5 -2
- package/src/system-agents/index.ts +8 -0
- package/src/system-agents/memory-reranker.agent.ts +1 -1
- package/src/system-agents/memory.agent.ts +1 -1
- package/src/system-agents/recent-activity-title-refiner.agent.ts +1 -1
- package/src/tools/execution-plan.tool.ts +17 -19
- package/src/tools/fetch-webpage.tool.ts +20 -18
- package/src/tools/index.ts +2 -3
- package/src/tools/read-file-parts.tool.ts +1 -1
- package/src/tools/search-web.tool.ts +18 -15
- package/src/tools/{search-tools.ts → search.tool.ts} +1 -1
- package/src/tools/team-think.tool.ts +14 -8
- package/src/tools/{tool-contract.ts → tool-contracts.ts} +9 -2
- package/src/utils/async.ts +3 -2
- package/src/utils/date-time.ts +4 -32
- package/src/utils/env.ts +8 -0
- package/src/utils/errors.ts +47 -0
- package/src/utils/hono-error-handler.ts +1 -2
- package/src/utils/index.ts +19 -2
- package/src/utils/string.ts +128 -1
- package/src/workers/bootstrap.ts +2 -2
- package/src/workers/index.ts +1 -0
- package/src/workers/memory-consolidation.worker.ts +12 -12
- package/src/workers/regular-chat-memory-digest.helpers.ts +2 -7
- package/src/workers/regular-chat-memory-digest.runner.ts +11 -105
- package/src/workers/skill-extraction.runner.ts +8 -102
- package/src/workers/utils/file-section-chunker.ts +6 -3
- package/src/workers/utils/repomix-file-sections.ts +2 -2
- package/src/workers/utils/sandbox-error.ts +11 -2
- package/src/workers/utils/workstream-message-query.ts +97 -0
- package/src/workers/worker-utils.ts +6 -2
- package/src/runtime/retrieval-pipeline.ts +0 -3
- package/src/runtime.ts +0 -387
- package/src/tools/log-hello-world.tool.ts +0 -17
- package/src/utils/error.ts +0 -10
- /package/src/services/{context-compaction-runtime.ts → context-compaction-runtime.singleton.ts} +0 -0
- /package/src/storage/{attachments.types.ts → attachment-types.ts} +0 -0
package/src/db/service.ts
CHANGED
|
@@ -12,6 +12,9 @@ import {
|
|
|
12
12
|
import type { ExprLike, Mutation, SurrealTransaction, Values } from 'surrealdb'
|
|
13
13
|
import type { z } from 'zod'
|
|
14
14
|
|
|
15
|
+
import { serverLogger } from '../config/logger'
|
|
16
|
+
import { withTimeout } from '../utils/async'
|
|
17
|
+
import { isRecord } from '../utils/string'
|
|
15
18
|
import type { RecordIdInput } from './record-id'
|
|
16
19
|
import { ensureRecordId, readCustomStringValue } from './record-id'
|
|
17
20
|
import type { DatabaseTable } from './tables'
|
|
@@ -88,12 +91,8 @@ function configureMutation(
|
|
|
88
91
|
return builder.merge(data)
|
|
89
92
|
}
|
|
90
93
|
|
|
91
|
-
function isRecordValue(value: unknown): value is Record<string, unknown> {
|
|
92
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
94
|
function isBoundQueryLike(value: unknown): value is BoundQueryLike {
|
|
96
|
-
if (!
|
|
95
|
+
if (!isRecord(value)) {
|
|
97
96
|
return false
|
|
98
97
|
}
|
|
99
98
|
|
|
@@ -101,7 +100,7 @@ function isBoundQueryLike(value: unknown): value is BoundQueryLike {
|
|
|
101
100
|
return false
|
|
102
101
|
}
|
|
103
102
|
|
|
104
|
-
return value.bindings === undefined ||
|
|
103
|
+
return value.bindings === undefined || isRecord(value.bindings)
|
|
105
104
|
}
|
|
106
105
|
|
|
107
106
|
function toStringLikeValue(value: unknown): string | null {
|
|
@@ -122,23 +121,6 @@ const CONNECT_RETRY_BASE_DELAY_MS = 100
|
|
|
122
121
|
const CONNECT_RETRY_JITTER_MS = 50
|
|
123
122
|
const CONNECT_ATTEMPT_TIMEOUT_MS = 5_000
|
|
124
123
|
|
|
125
|
-
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
|
|
126
|
-
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
return await Promise.race([
|
|
130
|
-
promise,
|
|
131
|
-
new Promise<T>((_, reject) => {
|
|
132
|
-
timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs)
|
|
133
|
-
}),
|
|
134
|
-
])
|
|
135
|
-
} finally {
|
|
136
|
-
if (timeoutId) {
|
|
137
|
-
clearTimeout(timeoutId)
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
124
|
export class SurrealDBService {
|
|
143
125
|
private client: Surreal | null = null
|
|
144
126
|
private isConnected = false
|
|
@@ -204,7 +186,8 @@ export class SurrealDBService {
|
|
|
204
186
|
|
|
205
187
|
try {
|
|
206
188
|
await this.client.close()
|
|
207
|
-
} catch {
|
|
189
|
+
} catch (error) {
|
|
190
|
+
serverLogger.warn`Failed to close database client: ${error}`
|
|
208
191
|
} finally {
|
|
209
192
|
this.client = null
|
|
210
193
|
}
|
|
@@ -252,7 +235,7 @@ export class SurrealDBService {
|
|
|
252
235
|
: { username: this.config.username ?? '', password: this.config.password ?? '' },
|
|
253
236
|
}),
|
|
254
237
|
CONNECT_ATTEMPT_TIMEOUT_MS,
|
|
255
|
-
`
|
|
238
|
+
`SurrealDB connect (${this.config.url})`,
|
|
256
239
|
)
|
|
257
240
|
|
|
258
241
|
this.isConnected = true
|
|
@@ -278,7 +261,7 @@ export class SurrealDBService {
|
|
|
278
261
|
}
|
|
279
262
|
}
|
|
280
263
|
|
|
281
|
-
this.toSurrealError(lastError)
|
|
264
|
+
return this.toSurrealError(lastError)
|
|
282
265
|
})()
|
|
283
266
|
|
|
284
267
|
try {
|
|
@@ -327,8 +310,16 @@ export class SurrealDBService {
|
|
|
327
310
|
|
|
328
311
|
private normalizeRecordId(id: unknown, table: DatabaseTable): ReturnType<typeof ensureRecordId> {
|
|
329
312
|
try {
|
|
330
|
-
|
|
313
|
+
const recordId = ensureRecordId(id as RecordIdInput, table)
|
|
314
|
+
const resolvedTable = String(recordId.table)
|
|
315
|
+
if (resolvedTable !== table) {
|
|
316
|
+
throw new SurrealDBError(`Record id table mismatch: expected "${table}" but got "${resolvedTable}"`)
|
|
317
|
+
}
|
|
318
|
+
return recordId
|
|
331
319
|
} catch (error) {
|
|
320
|
+
if (error instanceof SurrealDBError) {
|
|
321
|
+
throw error
|
|
322
|
+
}
|
|
332
323
|
if (error instanceof Error) {
|
|
333
324
|
throw new SurrealDBError(`Invalid record id for table "${table}": ${error.message}`, undefined, {
|
|
334
325
|
cause: error,
|
|
@@ -338,14 +329,39 @@ export class SurrealDBService {
|
|
|
338
329
|
}
|
|
339
330
|
}
|
|
340
331
|
|
|
341
|
-
private normalizeQueryRows
|
|
332
|
+
private normalizeQueryRows(statement: unknown, schema?: z.ZodTypeAny): unknown[] {
|
|
342
333
|
if (Array.isArray(statement)) {
|
|
343
|
-
return schema ? statement.map((row) =>
|
|
334
|
+
return schema ? statement.map((row) => this.parseSchema(schema, row)) : (statement as unknown[])
|
|
344
335
|
}
|
|
345
336
|
if (statement === null || statement === undefined) {
|
|
346
337
|
return []
|
|
347
338
|
}
|
|
348
|
-
return schema ? [
|
|
339
|
+
return schema ? [this.parseSchema(schema, statement)] : [statement]
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
private normalizeParseValue(value: unknown): unknown {
|
|
343
|
+
if (
|
|
344
|
+
value instanceof Date ||
|
|
345
|
+
value instanceof RecordId ||
|
|
346
|
+
value instanceof StringRecordId ||
|
|
347
|
+
value instanceof Table
|
|
348
|
+
) {
|
|
349
|
+
return value
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (Array.isArray(value)) {
|
|
353
|
+
return value.map((entry) => this.normalizeParseValue(entry))
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (!isRecord(value)) {
|
|
357
|
+
return value
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, this.normalizeParseValue(entry)]))
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
private parseSchema<TSchema extends z.ZodTypeAny>(schema: TSchema, value: unknown): z.infer<TSchema> {
|
|
364
|
+
return schema.parse(this.normalizeParseValue(value))
|
|
349
365
|
}
|
|
350
366
|
|
|
351
367
|
private buildFilterExpression(filter: Record<string, unknown>): ExprLike | undefined {
|
|
@@ -362,6 +378,33 @@ export class SurrealDBService {
|
|
|
362
378
|
return and(...expressions)
|
|
363
379
|
}
|
|
364
380
|
|
|
381
|
+
private assertValidIdentifier(name: string, context: string): void {
|
|
382
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(name)) {
|
|
383
|
+
throw new SurrealDBError(`Invalid ${context}: "${name}"`)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
private buildBoundFilterClauses(filter: Record<string, unknown>): {
|
|
388
|
+
clause: string
|
|
389
|
+
bindings: Record<string, unknown>
|
|
390
|
+
} {
|
|
391
|
+
const entries = Object.entries(filter)
|
|
392
|
+
if (entries.length === 0) {
|
|
393
|
+
throw new SurrealDBError('Expected a non-empty filter')
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const bindings: Record<string, unknown> = {}
|
|
397
|
+
const clauses = entries.map(([field, value], index) => {
|
|
398
|
+
this.assertValidIdentifier(field, 'filter field')
|
|
399
|
+
|
|
400
|
+
const bindingKey = `filter_${index}`
|
|
401
|
+
bindings[bindingKey] = this.normalizeRuntimeValue(value)
|
|
402
|
+
return `${field} = $${bindingKey}`
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
return { clause: clauses.join(' AND '), bindings }
|
|
406
|
+
}
|
|
407
|
+
|
|
365
408
|
private normalizeBoundQuery(query: BoundQuery | BoundQueryLike): BoundQuery {
|
|
366
409
|
if (!(query instanceof BoundQuery) && !isBoundQueryLike(query)) {
|
|
367
410
|
throw new SurrealDBError('Invalid query object: expected a BoundQuery-like value')
|
|
@@ -392,11 +435,11 @@ export class SurrealDBService {
|
|
|
392
435
|
return value.map((entry) => this.normalizeRuntimeValue(entry))
|
|
393
436
|
}
|
|
394
437
|
|
|
395
|
-
if (!
|
|
438
|
+
if (!isRecord(value)) {
|
|
396
439
|
return value
|
|
397
440
|
}
|
|
398
441
|
|
|
399
|
-
if ('tb' in value && 'id' in value) {
|
|
442
|
+
if ('tb' in value && 'id' in value && Object.keys(value).length === 2) {
|
|
400
443
|
return ensureRecordId(value as RecordIdInput)
|
|
401
444
|
}
|
|
402
445
|
|
|
@@ -413,6 +456,8 @@ export class SurrealDBService {
|
|
|
413
456
|
return Object.fromEntries(entries.map(([key, entryValue]) => [key, this.normalizeRuntimeValue(entryValue)]))
|
|
414
457
|
}
|
|
415
458
|
|
|
459
|
+
// Cast is safe: normalizeRuntimeValue preserves Record shape when input is a Record
|
|
460
|
+
// (non-array objects are mapped entry-by-entry and returned as Object.fromEntries)
|
|
416
461
|
private normalizeBindings(bindings?: Record<string, unknown>): Record<string, unknown> | undefined {
|
|
417
462
|
if (!bindings) {
|
|
418
463
|
return undefined
|
|
@@ -421,24 +466,19 @@ export class SurrealDBService {
|
|
|
421
466
|
return this.normalizeRuntimeValue(bindings) as Record<string, unknown>
|
|
422
467
|
}
|
|
423
468
|
|
|
424
|
-
private
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* SurrealDB v3 `option<T>` fields expect `NONE` (key omitted), not `null`.
|
|
431
|
-
* Strip top-level `null` values so they are omitted from the serialized payload,
|
|
432
|
-
* which the driver interprets as NONE.
|
|
433
|
-
*/
|
|
434
|
-
private stripNullValues(data: Record<string, unknown>): Record<string, unknown> {
|
|
435
|
-
const result: Record<string, unknown> = {}
|
|
436
|
-
for (const [key, value] of Object.entries(data)) {
|
|
437
|
-
if (value !== null) {
|
|
438
|
-
result[key] = value
|
|
439
|
-
}
|
|
469
|
+
private normalizeMutationFieldValue(value: unknown): unknown {
|
|
470
|
+
if (value === null || value === undefined) {
|
|
471
|
+
return undefined
|
|
440
472
|
}
|
|
441
|
-
|
|
473
|
+
|
|
474
|
+
return this.normalizeRuntimeValue(value)
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Cast is safe: normalizeRuntimeValue preserves Record shape when input is a Record
|
|
478
|
+
private normalizeMutationData(data: Record<string, unknown>): Record<string, unknown> {
|
|
479
|
+
return Object.fromEntries(
|
|
480
|
+
Object.entries(data).map(([key, value]) => [key, this.normalizeMutationFieldValue(value)]),
|
|
481
|
+
) as Record<string, unknown>
|
|
442
482
|
}
|
|
443
483
|
|
|
444
484
|
private normalizeTableValue(value: unknown): Table {
|
|
@@ -469,7 +509,7 @@ export class SurrealDBService {
|
|
|
469
509
|
|
|
470
510
|
if (value && typeof value === 'object') {
|
|
471
511
|
const record = value as { tb?: unknown; id?: unknown }
|
|
472
|
-
if (typeof record.tb === 'string' && record.id !== undefined) {
|
|
512
|
+
if (typeof record.tb === 'string' && record.id !== undefined && Object.keys(value).length === 2) {
|
|
473
513
|
return true
|
|
474
514
|
}
|
|
475
515
|
|
|
@@ -495,36 +535,59 @@ export class SurrealDBService {
|
|
|
495
535
|
content: (data) => this.wrapMutationBuilder(builder.content(this.normalizeMutationData(data))),
|
|
496
536
|
replace: (data) => this.wrapMutationBuilder(builder.replace(this.normalizeMutationData(data))),
|
|
497
537
|
merge: (data) => this.wrapMutationBuilder(builder.merge(this.normalizeMutationData(data))),
|
|
498
|
-
output: (mode) => builder.output(mode),
|
|
538
|
+
output: async (mode) => this.normalizeParseValue(await builder.output(mode)),
|
|
499
539
|
}
|
|
500
540
|
}
|
|
501
541
|
|
|
502
542
|
private wrapCreateBuilder(builder: CreateMutationBuilder): CreateMutationBuilder {
|
|
503
543
|
return {
|
|
504
544
|
content: (data) => this.wrapCreateBuilder(builder.content(this.normalizeMutationData(data))),
|
|
505
|
-
output: (mode) => builder.output(mode),
|
|
545
|
+
output: async (mode) => this.normalizeParseValue(await builder.output(mode)),
|
|
506
546
|
}
|
|
507
547
|
}
|
|
508
548
|
|
|
509
549
|
private wrapTransaction(tx: SurrealTransaction): DatabaseTransaction {
|
|
510
550
|
return {
|
|
511
|
-
query: (query: unknown) =>
|
|
551
|
+
query: async (query: unknown) => {
|
|
552
|
+
const boundQuery = this.normalizeBoundQuery(query as BoundQuery | BoundQueryLike)
|
|
553
|
+
const queryText = this.resolveQueryText(boundQuery)
|
|
554
|
+
|
|
555
|
+
try {
|
|
556
|
+
const responses = await tx.query(boundQuery).responses()
|
|
557
|
+
const first = responses.at(0)
|
|
558
|
+
if (!first) {
|
|
559
|
+
return []
|
|
560
|
+
}
|
|
561
|
+
if (!first.success) {
|
|
562
|
+
throw new SurrealDBError(first.error.message, queryText, { cause: first.error })
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return this.normalizeQueryRows(first.result)
|
|
566
|
+
} catch (error) {
|
|
567
|
+
return this.toSurrealError(error, queryText)
|
|
568
|
+
}
|
|
569
|
+
},
|
|
512
570
|
create: (target: unknown) => {
|
|
513
571
|
const normalizedTarget = this.normalizeCreateTarget(target)
|
|
514
572
|
const builder = normalizedTarget instanceof Table ? tx.create(normalizedTarget) : tx.create(normalizedTarget)
|
|
573
|
+
// Cast needed: SurrealDB SDK transaction builder type differs nominally from internal CreateMutationBuilder interface
|
|
515
574
|
return this.wrapCreateBuilder(builder as unknown as CreateMutationBuilder)
|
|
516
575
|
},
|
|
517
576
|
update: (target: unknown) =>
|
|
577
|
+
// Cast needed: SurrealDB SDK transaction builder type differs nominally from internal MutationBuilder interface
|
|
518
578
|
this.wrapMutationBuilder(tx.update(ensureRecordId(target as RecordIdInput)) as unknown as MutationBuilder),
|
|
519
|
-
delete: (target: unknown) =>
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
579
|
+
delete: async (target: unknown) =>
|
|
580
|
+
this.normalizeParseValue(await tx.delete(ensureRecordId(target as RecordIdInput))),
|
|
581
|
+
relate: async (from: unknown, edgeTable: unknown, to: unknown, data?: Values<Record<string, unknown>>) =>
|
|
582
|
+
this.normalizeParseValue(
|
|
583
|
+
await tx.relate(
|
|
584
|
+
ensureRecordId(from as RecordIdInput),
|
|
585
|
+
this.normalizeTableValue(edgeTable),
|
|
586
|
+
ensureRecordId(to as RecordIdInput),
|
|
587
|
+
data
|
|
588
|
+
? (this.normalizeMutationData(data as Record<string, unknown>) as Values<Record<string, unknown>>)
|
|
589
|
+
: undefined,
|
|
590
|
+
),
|
|
528
591
|
),
|
|
529
592
|
commit: () => tx.commit(),
|
|
530
593
|
cancel: () => tx.cancel(),
|
|
@@ -540,7 +603,7 @@ export class SurrealDBService {
|
|
|
540
603
|
return statements.at(0) ?? []
|
|
541
604
|
}
|
|
542
605
|
|
|
543
|
-
async queryAll<T>(query: BoundQuery | BoundQueryLike, schema?: z.
|
|
606
|
+
async queryAll<T>(query: BoundQuery | BoundQueryLike, schema?: z.ZodTypeAny): Promise<T[][]> {
|
|
544
607
|
const client = await this.ensureConnected()
|
|
545
608
|
const boundQuery = this.normalizeBoundQuery(query)
|
|
546
609
|
const queryText = this.resolveQueryText(boundQuery)
|
|
@@ -553,22 +616,22 @@ export class SurrealDBService {
|
|
|
553
616
|
const failure = response.error
|
|
554
617
|
throw new SurrealDBError(`Statement ${index + 1}: ${failure.message}`, queryText, { cause: failure })
|
|
555
618
|
}
|
|
556
|
-
return this.normalizeQueryRows
|
|
619
|
+
return this.normalizeQueryRows(response.result, schema) as T[]
|
|
557
620
|
})
|
|
558
621
|
} catch (error) {
|
|
559
|
-
this.toSurrealError(error, queryText)
|
|
622
|
+
return this.toSurrealError(error, queryText)
|
|
560
623
|
}
|
|
561
624
|
}
|
|
562
625
|
|
|
563
|
-
async queryOne<T extends z.
|
|
626
|
+
async queryOne<T extends z.ZodTypeAny>(query: BoundQuery | BoundQueryLike, schema: T): Promise<z.infer<T> | null> {
|
|
564
627
|
const results = await this.query<unknown>(query)
|
|
565
628
|
const first = results.at(0)
|
|
566
|
-
return first ?
|
|
629
|
+
return first ? this.parseSchema(schema, first) : null
|
|
567
630
|
}
|
|
568
631
|
|
|
569
|
-
async queryMany<T extends z.
|
|
632
|
+
async queryMany<T extends z.ZodTypeAny>(query: BoundQuery | BoundQueryLike, schema: T): Promise<z.infer<T>[]> {
|
|
570
633
|
const results = await this.query<unknown>(query)
|
|
571
|
-
return results.map((row) =>
|
|
634
|
+
return results.map((row) => this.parseSchema(schema, row))
|
|
572
635
|
}
|
|
573
636
|
|
|
574
637
|
private async runSqlFile(file: Bun.BunFile): Promise<void> {
|
|
@@ -580,13 +643,13 @@ export class SurrealDBService {
|
|
|
580
643
|
await this.queryAll<unknown>(new BoundQuery(sql))
|
|
581
644
|
}
|
|
582
645
|
|
|
583
|
-
async
|
|
646
|
+
async applySchema(schemaFiles: readonly Bun.BunFile[]): Promise<void> {
|
|
584
647
|
for (const schemaFile of schemaFiles) {
|
|
585
648
|
await this.runSqlFile(schemaFile)
|
|
586
649
|
}
|
|
587
650
|
}
|
|
588
651
|
|
|
589
|
-
async findOne<T extends z.
|
|
652
|
+
async findOne<T extends z.ZodTypeAny>(
|
|
590
653
|
table: DatabaseTable,
|
|
591
654
|
filter: Record<string, unknown>,
|
|
592
655
|
schema: T,
|
|
@@ -602,13 +665,13 @@ export class SurrealDBService {
|
|
|
602
665
|
|
|
603
666
|
const rows = await query.limit(1)
|
|
604
667
|
const first = rows.at(0)
|
|
605
|
-
return first ?
|
|
668
|
+
return first ? this.parseSchema(schema, first) : null
|
|
606
669
|
} catch (error) {
|
|
607
|
-
this.toSurrealError(error, `SELECT * FROM ${table} LIMIT 1`)
|
|
670
|
+
return this.toSurrealError(error, `SELECT * FROM ${table} LIMIT 1`)
|
|
608
671
|
}
|
|
609
672
|
}
|
|
610
673
|
|
|
611
|
-
async findMany<T extends z.
|
|
674
|
+
async findMany<T extends z.ZodTypeAny>(
|
|
612
675
|
table: DatabaseTable,
|
|
613
676
|
filter: Record<string, unknown>,
|
|
614
677
|
schema: T,
|
|
@@ -620,8 +683,15 @@ export class SurrealDBService {
|
|
|
620
683
|
const selection = this.buildFilterExpression(filter)
|
|
621
684
|
const orderBy = options?.orderBy
|
|
622
685
|
|
|
623
|
-
if (
|
|
624
|
-
|
|
686
|
+
if (orderBy !== undefined) {
|
|
687
|
+
this.assertValidIdentifier(orderBy, 'orderBy field')
|
|
688
|
+
this.assertValidIdentifier(table, 'table name')
|
|
689
|
+
for (const key of filterKeys) {
|
|
690
|
+
this.assertValidIdentifier(key, 'filter field')
|
|
691
|
+
}
|
|
692
|
+
const orderDir = options?.orderDir ?? 'ASC'
|
|
693
|
+
const limit = options?.limit
|
|
694
|
+
const offset = options?.offset
|
|
625
695
|
const vars: Record<string, unknown> = this.normalizeMutationData(filter)
|
|
626
696
|
let sql = `SELECT * FROM ${table}`
|
|
627
697
|
if (filterKeys.length > 0) {
|
|
@@ -638,7 +708,7 @@ export class SurrealDBService {
|
|
|
638
708
|
vars.offsetParam = offset
|
|
639
709
|
}
|
|
640
710
|
const rows = await this.query<unknown>(new BoundQuery(sql, vars))
|
|
641
|
-
return rows.map((row) =>
|
|
711
|
+
return rows.map((row) => this.parseSchema(schema, row))
|
|
642
712
|
}
|
|
643
713
|
|
|
644
714
|
try {
|
|
@@ -654,13 +724,13 @@ export class SurrealDBService {
|
|
|
654
724
|
}
|
|
655
725
|
|
|
656
726
|
const rows = await query
|
|
657
|
-
return rows.map((row) =>
|
|
727
|
+
return rows.map((row) => this.parseSchema(schema, row))
|
|
658
728
|
} catch (error) {
|
|
659
|
-
this.toSurrealError(error, `SELECT * FROM ${table}`)
|
|
729
|
+
return this.toSurrealError(error, `SELECT * FROM ${table}`)
|
|
660
730
|
}
|
|
661
731
|
}
|
|
662
732
|
|
|
663
|
-
async create<T extends z.
|
|
733
|
+
async create<T extends z.ZodTypeAny>(
|
|
664
734
|
table: DatabaseTable,
|
|
665
735
|
data: Record<string, unknown>,
|
|
666
736
|
schema: T,
|
|
@@ -683,13 +753,13 @@ export class SurrealDBService {
|
|
|
683
753
|
throw new SurrealDBError(`Failed to create record in ${table}`)
|
|
684
754
|
}
|
|
685
755
|
|
|
686
|
-
return
|
|
756
|
+
return this.parseSchema(schema, first)
|
|
687
757
|
} catch (error) {
|
|
688
|
-
this.toSurrealError(error, `CREATE ${table}`)
|
|
758
|
+
return this.toSurrealError(error, `CREATE ${table}`)
|
|
689
759
|
}
|
|
690
760
|
}
|
|
691
761
|
|
|
692
|
-
async createWithId<T extends z.
|
|
762
|
+
async createWithId<T extends z.ZodTypeAny>(
|
|
693
763
|
table: DatabaseTable,
|
|
694
764
|
id: unknown,
|
|
695
765
|
data: Record<string, unknown>,
|
|
@@ -705,13 +775,13 @@ export class SurrealDBService {
|
|
|
705
775
|
|
|
706
776
|
try {
|
|
707
777
|
const created = await client.create<unknown>(recordId).content(this.normalizeMutationData(data)).output('after')
|
|
708
|
-
return
|
|
778
|
+
return this.parseSchema(schema, created)
|
|
709
779
|
} catch (error) {
|
|
710
|
-
this.toSurrealError(error, `CREATE ${recordId.toString()}`)
|
|
780
|
+
return this.toSurrealError(error, `CREATE ${recordId.toString()}`)
|
|
711
781
|
}
|
|
712
782
|
}
|
|
713
783
|
|
|
714
|
-
async update<T extends z.
|
|
784
|
+
async update<T extends z.ZodTypeAny>(
|
|
715
785
|
table: DatabaseTable,
|
|
716
786
|
id: unknown,
|
|
717
787
|
data: Record<string, unknown>,
|
|
@@ -732,13 +802,13 @@ export class SurrealDBService {
|
|
|
732
802
|
const builder = client.update<unknown>(recordId)
|
|
733
803
|
const configured = configureMutation(builder, mutation, this.normalizeMutationData(data))
|
|
734
804
|
const updated = await configured.output('after')
|
|
735
|
-
return updated ?
|
|
805
|
+
return updated ? this.parseSchema(schema, updated) : null
|
|
736
806
|
} catch (error) {
|
|
737
|
-
this.toSurrealError(error, `UPDATE ${recordId.toString()}`)
|
|
807
|
+
return this.toSurrealError(error, `UPDATE ${recordId.toString()}`)
|
|
738
808
|
}
|
|
739
809
|
}
|
|
740
810
|
|
|
741
|
-
async upsert<T extends z.
|
|
811
|
+
async upsert<T extends z.ZodTypeAny>(
|
|
742
812
|
table: DatabaseTable,
|
|
743
813
|
id: unknown,
|
|
744
814
|
data: Record<string, unknown>,
|
|
@@ -761,55 +831,52 @@ export class SurrealDBService {
|
|
|
761
831
|
if (!upserted) {
|
|
762
832
|
throw new SurrealDBError(`Failed to upsert record in ${table}`)
|
|
763
833
|
}
|
|
764
|
-
return
|
|
834
|
+
return this.parseSchema(schema, upserted)
|
|
765
835
|
} catch (error) {
|
|
766
|
-
this.toSurrealError(error, `UPSERT ${recordId.toString()}`)
|
|
836
|
+
return this.toSurrealError(error, `UPSERT ${recordId.toString()}`)
|
|
767
837
|
}
|
|
768
838
|
}
|
|
769
839
|
|
|
770
840
|
async deleteById(table: DatabaseTable, id: unknown): Promise<boolean> {
|
|
771
841
|
const recordId = this.normalizeRecordId(id, table)
|
|
772
|
-
const client = await this.ensureConnected()
|
|
773
842
|
|
|
774
843
|
try {
|
|
775
|
-
const
|
|
776
|
-
|
|
844
|
+
const result = await this.query<unknown>(new BoundQuery(`DELETE $recordId RETURN BEFORE`, { recordId }))
|
|
845
|
+
return result.length > 0
|
|
846
|
+
} catch (error) {
|
|
847
|
+
if (error instanceof Error && error.message.includes('does not exist')) {
|
|
777
848
|
return false
|
|
778
849
|
}
|
|
779
|
-
|
|
780
|
-
return true
|
|
781
|
-
} catch (error) {
|
|
782
|
-
this.toSurrealError(error, `DELETE ${recordId.toString()}`)
|
|
850
|
+
return this.toSurrealError(error, `DELETE ${recordId.toString()}`)
|
|
783
851
|
}
|
|
784
852
|
}
|
|
785
853
|
|
|
786
854
|
async deleteWhere(table: DatabaseTable, filter: Record<string, unknown>): Promise<number> {
|
|
855
|
+
this.assertValidIdentifier(table, 'table name')
|
|
787
856
|
const filterKeys = Object.keys(filter)
|
|
788
857
|
if (filterKeys.length === 0) {
|
|
789
858
|
throw new SurrealDBError(`Refusing to delete all records in ${table} without a filter`)
|
|
790
859
|
}
|
|
791
|
-
|
|
792
|
-
const selection = this.buildFilterExpression(filter)
|
|
793
|
-
if (!selection) {
|
|
794
|
-
throw new SurrealDBError(`Invalid delete filter for table ${table}`)
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
const client = await this.ensureConnected()
|
|
860
|
+
const { clause, bindings } = this.buildBoundFilterClauses(filter)
|
|
798
861
|
|
|
799
862
|
try {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
863
|
+
return await this.withTransaction(async (tx) => {
|
|
864
|
+
const matched = (await tx.query(new BoundQuery(`SELECT id FROM ${table} WHERE ${clause}`, bindings))) as Array<{
|
|
865
|
+
id: unknown
|
|
866
|
+
}>
|
|
804
867
|
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
868
|
+
if (matched.length === 0) {
|
|
869
|
+
return 0
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
for (const row of matched) {
|
|
873
|
+
await tx.delete(this.normalizeRecordId(row.id, table))
|
|
874
|
+
}
|
|
809
875
|
|
|
810
|
-
|
|
876
|
+
return matched.length
|
|
877
|
+
})
|
|
811
878
|
} catch (error) {
|
|
812
|
-
this.toSurrealError(error, `DELETE ${table} WHERE ...`)
|
|
879
|
+
return this.toSurrealError(error, `DELETE ${table} WHERE ...`)
|
|
813
880
|
}
|
|
814
881
|
}
|
|
815
882
|
|
|
@@ -835,7 +902,7 @@ export class SurrealDBService {
|
|
|
835
902
|
}
|
|
836
903
|
return 1
|
|
837
904
|
} catch (error) {
|
|
838
|
-
this.toSurrealError(error, `UPDATE ${table} WHERE ...`)
|
|
905
|
+
return this.toSurrealError(error, `UPDATE ${table} WHERE ...`)
|
|
839
906
|
}
|
|
840
907
|
}
|
|
841
908
|
|
|
@@ -854,7 +921,7 @@ export class SurrealDBService {
|
|
|
854
921
|
.output('after')
|
|
855
922
|
return inserted as T[]
|
|
856
923
|
} catch (error) {
|
|
857
|
-
this.toSurrealError(error, `INSERT ${table}`)
|
|
924
|
+
return this.toSurrealError(error, `INSERT ${table}`)
|
|
858
925
|
}
|
|
859
926
|
}
|
|
860
927
|
|
|
@@ -884,7 +951,7 @@ export class SurrealDBService {
|
|
|
884
951
|
}
|
|
885
952
|
return related
|
|
886
953
|
} catch (error) {
|
|
887
|
-
this.toSurrealError(error, `RELATE ${fromRef.toString()}->${edgeTable}->${toRef.toString()}`)
|
|
954
|
+
return this.toSurrealError(error, `RELATE ${fromRef.toString()}->${edgeTable}->${toRef.toString()}`)
|
|
888
955
|
}
|
|
889
956
|
}
|
|
890
957
|
|
|
@@ -893,7 +960,7 @@ export class SurrealDBService {
|
|
|
893
960
|
try {
|
|
894
961
|
return this.wrapTransaction(await client.beginTransaction())
|
|
895
962
|
} catch (error) {
|
|
896
|
-
this.toSurrealError(error, 'BEGIN TRANSACTION')
|
|
963
|
+
return this.toSurrealError(error, 'BEGIN TRANSACTION')
|
|
897
964
|
}
|
|
898
965
|
}
|
|
899
966
|
|
|
@@ -942,10 +1009,14 @@ export const databaseService = new Proxy({} as SurrealDBService, {
|
|
|
942
1009
|
return databaseServiceOverrides.get(property)
|
|
943
1010
|
}
|
|
944
1011
|
|
|
945
|
-
const
|
|
946
|
-
const value =
|
|
1012
|
+
const resolved = resolveConfiguredDatabaseService()
|
|
1013
|
+
const value: unknown = Reflect.get(resolved, property)
|
|
947
1014
|
if (typeof value === 'function') {
|
|
948
|
-
|
|
1015
|
+
// SurrealDB SDK type gap — Reflect.get returns unknown, but we know the resolved target shape
|
|
1016
|
+
return bindTargetMethod(
|
|
1017
|
+
resolved as unknown as Record<PropertyKey, unknown>,
|
|
1018
|
+
value as (...args: unknown[]) => unknown,
|
|
1019
|
+
)
|
|
949
1020
|
}
|
|
950
1021
|
|
|
951
1022
|
return value
|
package/src/db/startup.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { recordIdSchema } from '@lota-sdk/shared'
|
|
1
2
|
import { BoundQuery, RecordId } from 'surrealdb'
|
|
2
3
|
import { z } from 'zod'
|
|
3
4
|
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { getErrorMessage } from '../utils/errors'
|
|
6
|
+
import type { SurrealDBService, SurrealDatabaseLogger } from './service'
|
|
7
|
+
import { TABLES } from './tables'
|
|
7
8
|
|
|
8
9
|
const DATABASE_BOOTSTRAP_KEY = 'database-schema-ready'
|
|
9
10
|
const DEFAULT_RETRY_DELAY_MS = 1_000
|
|
@@ -11,11 +12,11 @@ const DEFAULT_MAX_WAIT_MS = 3 * 60 * 1_000
|
|
|
11
12
|
const RETRY_LOG_INTERVAL = 5
|
|
12
13
|
|
|
13
14
|
const RuntimeBootstrapRecordSchema = z.object({
|
|
14
|
-
id:
|
|
15
|
+
id: recordIdSchema,
|
|
15
16
|
key: z.string(),
|
|
16
17
|
schemaFingerprint: z.string(),
|
|
17
|
-
readyAt: z.
|
|
18
|
-
updatedAt: z.
|
|
18
|
+
readyAt: z.coerce.date(),
|
|
19
|
+
updatedAt: z.coerce.date().optional().nullable(),
|
|
19
20
|
})
|
|
20
21
|
|
|
21
22
|
type StartupLogger = Pick<SurrealDatabaseLogger, 'info' | 'warn' | 'error'>
|
|
@@ -62,7 +63,7 @@ export async function connectWithStartupRetry(params: {
|
|
|
62
63
|
async function readDatabaseBootstrapRecord(
|
|
63
64
|
databaseService: SurrealDBService,
|
|
64
65
|
): Promise<z.infer<typeof RuntimeBootstrapRecordSchema> | null> {
|
|
65
|
-
return
|
|
66
|
+
return databaseService.queryOne(
|
|
66
67
|
new BoundQuery(
|
|
67
68
|
`SELECT *
|
|
68
69
|
FROM ${TABLES.RUNTIME_BOOTSTRAP}
|
|
@@ -141,12 +142,7 @@ export async function publishDatabaseBootstrap(params: {
|
|
|
141
142
|
await params.databaseService.upsert(
|
|
142
143
|
TABLES.RUNTIME_BOOTSTRAP,
|
|
143
144
|
new RecordId(TABLES.RUNTIME_BOOTSTRAP, DATABASE_BOOTSTRAP_KEY),
|
|
144
|
-
{
|
|
145
|
-
key: DATABASE_BOOTSTRAP_KEY,
|
|
146
|
-
schemaFingerprint: params.schemaFingerprint,
|
|
147
|
-
readyAt: new Date(),
|
|
148
|
-
updatedAt: new Date(),
|
|
149
|
-
},
|
|
145
|
+
{ key: DATABASE_BOOTSTRAP_KEY, schemaFingerprint: params.schemaFingerprint, readyAt: new Date() },
|
|
150
146
|
RuntimeBootstrapRecordSchema,
|
|
151
147
|
)
|
|
152
148
|
}
|