@lota-sdk/core 0.4.10 → 0.4.11
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/package.json +2 -2
- package/src/ai-gateway/ai-gateway.ts +149 -95
- package/src/ai-gateway/index.ts +16 -1
- package/src/config/agent-defaults.ts +4 -120
- package/src/config/logger.ts +18 -34
- package/src/config/thread-defaults.ts +1 -18
- package/src/create-runtime.ts +90 -28
- package/src/db/base.service.ts +30 -38
- package/src/db/service.ts +489 -545
- package/src/effect/index.ts +0 -2
- package/src/effect/layers.ts +6 -13
- package/src/embeddings/provider.ts +2 -7
- package/src/index.ts +4 -5
- package/src/queues/autonomous-job.queue.ts +159 -113
- package/src/queues/context-compaction.queue.ts +39 -25
- package/src/queues/delayed-node-promotion.queue.ts +56 -29
- package/src/queues/document-processor.queue.ts +5 -3
- package/src/queues/index.ts +1 -0
- package/src/queues/memory-consolidation.queue.ts +79 -53
- package/src/queues/organization-learning.queue.ts +63 -39
- package/src/queues/plan-agent-heartbeat.queue.ts +104 -79
- package/src/queues/plan-scheduler.queue.ts +100 -84
- package/src/queues/post-chat-memory.queue.ts +55 -33
- package/src/queues/queue-factory.ts +40 -41
- package/src/queues/queues.service.ts +61 -0
- package/src/queues/title-generation.queue.ts +42 -31
- package/src/redis/org-memory-lock.ts +24 -9
- package/src/redis/redis-lease-lock.ts +8 -1
- package/src/runtime/agent-identity-overrides.ts +7 -3
- package/src/runtime/agent-runtime-policy.ts +9 -4
- package/src/runtime/agent-stream-helpers.ts +9 -4
- package/src/runtime/context-compaction/context-compaction-runtime.ts +28 -32
- package/src/runtime/context-compaction/context-compaction.ts +9 -7
- package/src/runtime/domain-layer.ts +15 -4
- package/src/runtime/execution-plan-visibility.ts +5 -2
- package/src/runtime/graph-designer.ts +0 -22
- package/src/runtime/index.ts +1 -0
- package/src/runtime/indexed-repositories-policy.ts +2 -6
- package/src/runtime/plugin-resolution.ts +29 -12
- package/src/runtime/post-turn-side-effects.ts +139 -141
- package/src/runtime/runtime-config.ts +0 -6
- package/src/runtime/runtime-extensions.ts +0 -54
- package/src/runtime/runtime-lifecycle.ts +4 -4
- package/src/runtime/runtime-services.ts +122 -53
- package/src/runtime/runtime-worker-registry.ts +113 -30
- package/src/runtime/social-chat/social-chat-agent-runner.ts +6 -3
- package/src/runtime/social-chat/social-chat-history.ts +3 -1
- package/src/runtime/social-chat/social-chat.ts +35 -20
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +6 -5
- package/src/runtime/team-consultation/team-consultation-prompts.ts +11 -6
- package/src/runtime/thread-chat-helpers.ts +18 -9
- package/src/runtime/thread-turn-context.ts +7 -47
- package/src/runtime/turn-lifecycle.ts +6 -14
- package/src/services/agent-activity.service.ts +168 -175
- package/src/services/agent-executor.service.ts +35 -16
- package/src/services/attachment.service.ts +4 -70
- package/src/services/autonomous-job.service.ts +53 -61
- package/src/services/context-compaction.service.ts +7 -9
- package/src/services/execution-plan/execution-plan-graph.ts +106 -115
- package/src/services/execution-plan/execution-plan-schedule.ts +1 -15
- package/src/services/execution-plan/execution-plan.service.ts +67 -50
- package/src/services/global-orchestrator.service.ts +18 -7
- package/src/services/graph-full-routing.ts +7 -6
- package/src/services/memory/memory-conversation.ts +10 -5
- package/src/services/memory/memory.service.ts +11 -8
- package/src/services/ownership-dispatcher.service.ts +16 -5
- package/src/services/plan/plan-agent-heartbeat.service.ts +29 -15
- package/src/services/plan/plan-agent-query.service.ts +12 -8
- package/src/services/plan/plan-completion-side-effects.ts +93 -101
- package/src/services/plan/plan-cycle.service.ts +7 -45
- package/src/services/plan/plan-deadline.service.ts +28 -17
- package/src/services/plan/plan-event-delivery.service.ts +47 -40
- package/src/services/plan/plan-executor-context.ts +2 -0
- package/src/services/plan/plan-executor-graph.ts +366 -391
- package/src/services/plan/plan-executor.service.ts +13 -91
- package/src/services/plan/plan-scheduler.service.ts +62 -49
- package/src/services/plan/plan-transaction-events.ts +1 -1
- package/src/services/recent-activity-title.service.ts +6 -2
- package/src/services/thread/thread-bootstrap.ts +11 -9
- package/src/services/thread/thread-message.service.ts +6 -5
- package/src/services/thread/thread-turn-execution.ts +86 -82
- package/src/services/thread/thread-turn-preparation.service.ts +47 -24
- package/src/services/thread/thread-turn-streaming.ts +20 -25
- package/src/services/thread/thread-turn.ts +25 -44
- package/src/services/thread/thread.service.ts +21 -6
- package/src/system-agents/recent-activity-title-refiner.agent.ts +8 -5
- package/src/system-agents/thread-router.agent.ts +23 -20
- package/src/tools/execution-plan.tool.ts +8 -3
- package/src/tools/fetch-webpage.tool.ts +10 -9
- package/src/tools/firecrawl-client.ts +0 -15
- package/src/tools/remember-memory.tool.ts +3 -6
- package/src/tools/research-topic.tool.ts +12 -3
- package/src/tools/search-web.tool.ts +10 -9
- package/src/tools/search.tool.ts +4 -5
- package/src/tools/team-think.tool.ts +139 -121
- package/src/workers/bootstrap.ts +9 -10
- package/src/workers/memory-consolidation.worker.ts +4 -1
- package/src/workers/organization-learning.worker.ts +15 -2
- package/src/workers/regular-chat-memory-digest.helpers.ts +3 -4
- package/src/workers/regular-chat-memory-digest.runner.ts +21 -14
- package/src/workers/skill-extraction.runner.ts +13 -15
- package/src/workers/worker-utils.ts +6 -18
- package/src/effect/awaitable-effect.ts +0 -96
- package/src/effect/runtime-ref.ts +0 -25
- package/src/effect/runtime.ts +0 -46
- package/src/redis/runtime-connection.ts +0 -20
- package/src/runtime/runtime-accessors.ts +0 -92
- package/src/runtime/runtime-token.ts +0 -47
package/src/db/service.ts
CHANGED
|
@@ -5,8 +5,6 @@ import { ZodError } from 'zod'
|
|
|
5
5
|
import type { z } from 'zod'
|
|
6
6
|
|
|
7
7
|
import { serverLogger } from '../config/logger'
|
|
8
|
-
import type { AwaitableEffect } from '../effect/awaitable-effect'
|
|
9
|
-
import { toAwaitableEffect } from '../effect/awaitable-effect'
|
|
10
8
|
import { getErrorMessage } from '../utils/errors'
|
|
11
9
|
import type { RecordIdInput, ensureRecordId } from './record-id'
|
|
12
10
|
import { ensureRecordIdEffect } from './record-id'
|
|
@@ -63,7 +61,7 @@ export type MutationBuilder = {
|
|
|
63
61
|
content: (data: Record<string, unknown>) => MutationBuilder
|
|
64
62
|
replace: (data: Record<string, unknown>) => MutationBuilder
|
|
65
63
|
merge: (data: Record<string, unknown>) => MutationBuilder
|
|
66
|
-
output: (mode: 'after' | 'before') =>
|
|
64
|
+
output: (mode: 'after' | 'before') => Effect.Effect<unknown, SurrealDBError, never>
|
|
67
65
|
}
|
|
68
66
|
|
|
69
67
|
type CreateBuilderSource = {
|
|
@@ -75,22 +73,22 @@ type PendingMutation = { mutation: RecordMutation; data: Record<string, unknown>
|
|
|
75
73
|
|
|
76
74
|
export type CreateMutationBuilder = {
|
|
77
75
|
content: (data: Record<string, unknown>) => CreateMutationBuilder
|
|
78
|
-
output: (mode: 'after' | 'before') =>
|
|
76
|
+
output: (mode: 'after' | 'before') => Effect.Effect<unknown, SurrealDBError, never>
|
|
79
77
|
}
|
|
80
78
|
|
|
81
79
|
export interface DatabaseTransaction {
|
|
82
|
-
query: (query: unknown) =>
|
|
80
|
+
query: (query: unknown) => Effect.Effect<unknown, SurrealDBError, never>
|
|
83
81
|
create: (target: unknown) => CreateMutationBuilder
|
|
84
82
|
update: (target: unknown) => MutationBuilder
|
|
85
|
-
delete: (target: unknown) =>
|
|
83
|
+
delete: (target: unknown) => Effect.Effect<unknown, SurrealDBError, never>
|
|
86
84
|
relate: (
|
|
87
85
|
from: unknown,
|
|
88
86
|
edgeTable: unknown,
|
|
89
87
|
to: unknown,
|
|
90
88
|
data?: Values<Record<string, unknown>>,
|
|
91
|
-
) =>
|
|
92
|
-
commit: () =>
|
|
93
|
-
cancel: () =>
|
|
89
|
+
) => Effect.Effect<unknown, SurrealDBError, never>
|
|
90
|
+
commit: () => Effect.Effect<void, SurrealDBError, never>
|
|
91
|
+
cancel: () => Effect.Effect<void, SurrealDBError, never>
|
|
94
92
|
}
|
|
95
93
|
|
|
96
94
|
const CONNECT_MAX_ATTEMPTS = 5
|
|
@@ -216,22 +214,20 @@ export class SurrealDBService {
|
|
|
216
214
|
)
|
|
217
215
|
}
|
|
218
216
|
|
|
219
|
-
connect():
|
|
220
|
-
return
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
217
|
+
connect(): Effect.Effect<void, SurrealDBError, never> {
|
|
218
|
+
return Effect.gen(
|
|
219
|
+
function* (this: SurrealDBService) {
|
|
220
|
+
// Fast path: already connected. Cheap, runs inside the Effect so callers
|
|
221
|
+
// observe the same memory barrier the slow path relies on.
|
|
222
|
+
if (this.isConnected) {
|
|
223
|
+
return
|
|
224
|
+
}
|
|
228
225
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
),
|
|
226
|
+
// Slow path: serialize concurrent connect attempts behind a single permit
|
|
227
|
+
// so only one client.connect() ever runs. Late waiters double-check
|
|
228
|
+
// isConnected after acquiring the permit and become no-ops.
|
|
229
|
+
yield* this.connectMutex.withPermits(1)(this.connectLockedEffect())
|
|
230
|
+
}.bind(this),
|
|
235
231
|
)
|
|
236
232
|
}
|
|
237
233
|
|
|
@@ -289,38 +285,33 @@ export class SurrealDBService {
|
|
|
289
285
|
)
|
|
290
286
|
}
|
|
291
287
|
|
|
292
|
-
disconnect():
|
|
293
|
-
return
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
288
|
+
disconnect(): Effect.Effect<void, SurrealDBError, never> {
|
|
289
|
+
return Effect.gen(
|
|
290
|
+
function* (this: SurrealDBService) {
|
|
291
|
+
if (!this.isConnected) {
|
|
292
|
+
return
|
|
293
|
+
}
|
|
299
294
|
|
|
300
|
-
|
|
295
|
+
this.isConnected = false
|
|
301
296
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
297
|
+
const client = this.client
|
|
298
|
+
if (!client) {
|
|
299
|
+
this.client = null
|
|
300
|
+
return
|
|
301
|
+
}
|
|
307
302
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
),
|
|
321
|
-
)
|
|
322
|
-
}.bind(this),
|
|
323
|
-
),
|
|
303
|
+
yield* Effect.tryPromise({
|
|
304
|
+
try: () => client.close(),
|
|
305
|
+
catch: (error) =>
|
|
306
|
+
new SurrealDBError({ message: `Failed to close database client: ${getErrorMessage(error)}`, cause: error }),
|
|
307
|
+
}).pipe(
|
|
308
|
+
Effect.ensuring(
|
|
309
|
+
Effect.sync(() => {
|
|
310
|
+
this.client = null
|
|
311
|
+
}),
|
|
312
|
+
),
|
|
313
|
+
)
|
|
314
|
+
}.bind(this),
|
|
324
315
|
)
|
|
325
316
|
}
|
|
326
317
|
|
|
@@ -463,26 +454,24 @@ export class SurrealDBService {
|
|
|
463
454
|
replace: (data) => this.wrapMutationBuilder(builderEffect, { mutation: 'replace', data }),
|
|
464
455
|
merge: (data) => this.wrapMutationBuilder(builderEffect, { mutation: 'merge', data }),
|
|
465
456
|
output: (mode) =>
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
}.bind(this),
|
|
485
|
-
),
|
|
457
|
+
Effect.gen(
|
|
458
|
+
function* (this: SurrealDBService) {
|
|
459
|
+
const builder = yield* builderEffect
|
|
460
|
+
let configuredBuilder = builder
|
|
461
|
+
if (pendingMutation) {
|
|
462
|
+
const normalizedData = yield* this.normalizeMutationDataEffect(pendingMutation.data)
|
|
463
|
+
configuredBuilder = configureMutation(builder, pendingMutation.mutation, normalizedData)
|
|
464
|
+
}
|
|
465
|
+
const value = yield* Effect.tryPromise({
|
|
466
|
+
try: () => Promise.resolve(configuredBuilder.output(mode)),
|
|
467
|
+
catch: (error) =>
|
|
468
|
+
new SurrealDBError({
|
|
469
|
+
message: `Failed to finish mutation output: ${getErrorMessage(error)}`,
|
|
470
|
+
cause: error,
|
|
471
|
+
}),
|
|
472
|
+
})
|
|
473
|
+
return yield* this.normalizeParseValueEffect(value)
|
|
474
|
+
}.bind(this),
|
|
486
475
|
),
|
|
487
476
|
}
|
|
488
477
|
}
|
|
@@ -494,24 +483,22 @@ export class SurrealDBService {
|
|
|
494
483
|
return {
|
|
495
484
|
content: (data) => this.wrapCreateBuilder(builderEffect, data),
|
|
496
485
|
output: (mode) =>
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
}.bind(this),
|
|
514
|
-
),
|
|
486
|
+
Effect.gen(
|
|
487
|
+
function* (this: SurrealDBService) {
|
|
488
|
+
const builder = yield* builderEffect
|
|
489
|
+
const configuredBuilder = pendingData
|
|
490
|
+
? builder.content(yield* this.normalizeMutationDataEffect(pendingData))
|
|
491
|
+
: builder
|
|
492
|
+
const value = yield* Effect.tryPromise({
|
|
493
|
+
try: () => Promise.resolve(configuredBuilder.output(mode)),
|
|
494
|
+
catch: (error) =>
|
|
495
|
+
new SurrealDBError({
|
|
496
|
+
message: `Failed to finish create output: ${getErrorMessage(error)}`,
|
|
497
|
+
cause: error,
|
|
498
|
+
}),
|
|
499
|
+
})
|
|
500
|
+
return yield* this.normalizeParseValueEffect(value)
|
|
501
|
+
}.bind(this),
|
|
515
502
|
),
|
|
516
503
|
}
|
|
517
504
|
}
|
|
@@ -519,31 +506,29 @@ export class SurrealDBService {
|
|
|
519
506
|
private wrapTransaction(tx: SurrealTransaction): DatabaseTransaction {
|
|
520
507
|
return {
|
|
521
508
|
query: (query: unknown) =>
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
509
|
+
Effect.gen(
|
|
510
|
+
function* (this: SurrealDBService) {
|
|
511
|
+
const boundQuery = yield* this.normalizeTransactionQueryEffect(query)
|
|
512
|
+
const queryText = this.resolveQueryText(boundQuery)
|
|
513
|
+
const responses = yield* Effect.tryPromise({
|
|
514
|
+
try: () => tx.query(boundQuery).responses(),
|
|
515
|
+
catch: (error) =>
|
|
516
|
+
new SurrealDBError({
|
|
517
|
+
message: `Failed to run transaction query: ${getErrorMessage(error)}`,
|
|
518
|
+
query: queryText,
|
|
519
|
+
cause: error,
|
|
520
|
+
}),
|
|
521
|
+
})
|
|
522
|
+
const first = responses.at(0)
|
|
523
|
+
if (!first) {
|
|
524
|
+
return []
|
|
525
|
+
}
|
|
526
|
+
if (!first.success) {
|
|
527
|
+
return yield* new SurrealDBError({ message: first.error.message, query: queryText, cause: first.error })
|
|
528
|
+
}
|
|
543
529
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
),
|
|
530
|
+
return yield* this.normalizeQueryRowsEffect(first.result)
|
|
531
|
+
}.bind(this),
|
|
547
532
|
),
|
|
548
533
|
create: (target: unknown) =>
|
|
549
534
|
this.wrapCreateBuilder(
|
|
@@ -564,60 +549,52 @@ export class SurrealDBService {
|
|
|
564
549
|
),
|
|
565
550
|
),
|
|
566
551
|
delete: (target: unknown) =>
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
}.bind(this),
|
|
581
|
-
),
|
|
552
|
+
Effect.gen(
|
|
553
|
+
function* (this: SurrealDBService) {
|
|
554
|
+
const recordId = yield* this.normalizeTransactionRecordIdEffect(target, 'transaction delete')
|
|
555
|
+
const value = yield* Effect.tryPromise({
|
|
556
|
+
try: () => tx.delete(recordId),
|
|
557
|
+
catch: (error) =>
|
|
558
|
+
new SurrealDBError({
|
|
559
|
+
message: `Failed to delete transaction target: ${getErrorMessage(error)}`,
|
|
560
|
+
cause: error,
|
|
561
|
+
}),
|
|
562
|
+
})
|
|
563
|
+
return yield* this.normalizeParseValueEffect(value)
|
|
564
|
+
}.bind(this),
|
|
582
565
|
),
|
|
583
566
|
relate: (from: unknown, edgeTable: unknown, to: unknown, data?: Values<Record<string, unknown>>) =>
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
}.bind(this),
|
|
603
|
-
),
|
|
567
|
+
Effect.gen(
|
|
568
|
+
function* (this: SurrealDBService) {
|
|
569
|
+
const fromRecordId = yield* this.normalizeTransactionRecordIdEffect(from, 'transaction relate source')
|
|
570
|
+
const normalizedEdgeTable = yield* this.normalizeTableValueEffect(edgeTable)
|
|
571
|
+
const toRecordId = yield* this.normalizeTransactionRecordIdEffect(to, 'transaction relate target')
|
|
572
|
+
const normalizedData = data
|
|
573
|
+
? yield* this.normalizeMutationDataEffect(data as Record<string, unknown>)
|
|
574
|
+
: undefined
|
|
575
|
+
const value = yield* Effect.tryPromise({
|
|
576
|
+
try: () => tx.relate(fromRecordId, normalizedEdgeTable, toRecordId, normalizedData),
|
|
577
|
+
catch: (error) =>
|
|
578
|
+
new SurrealDBError({
|
|
579
|
+
message: `Failed to relate transaction records: ${getErrorMessage(error)}`,
|
|
580
|
+
cause: error,
|
|
581
|
+
}),
|
|
582
|
+
})
|
|
583
|
+
return yield* this.normalizeParseValueEffect(value)
|
|
584
|
+
}.bind(this),
|
|
604
585
|
),
|
|
605
586
|
commit: () =>
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
}),
|
|
612
|
-
),
|
|
587
|
+
Effect.tryPromise({
|
|
588
|
+
try: () => tx.commit(),
|
|
589
|
+
catch: (error) =>
|
|
590
|
+
new SurrealDBError({ message: `Failed to commit transaction: ${getErrorMessage(error)}`, cause: error }),
|
|
591
|
+
}),
|
|
613
592
|
cancel: () =>
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
}),
|
|
620
|
-
),
|
|
593
|
+
Effect.tryPromise({
|
|
594
|
+
try: () => tx.cancel(),
|
|
595
|
+
catch: (error) =>
|
|
596
|
+
new SurrealDBError({ message: `Failed to cancel transaction: ${getErrorMessage(error)}`, cause: error }),
|
|
597
|
+
}),
|
|
621
598
|
}
|
|
622
599
|
}
|
|
623
600
|
|
|
@@ -625,76 +602,71 @@ export class SurrealDBService {
|
|
|
625
602
|
return query.query
|
|
626
603
|
}
|
|
627
604
|
|
|
628
|
-
query<T>(query: BoundQuery):
|
|
629
|
-
return
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
}.bind(this),
|
|
636
|
-
),
|
|
605
|
+
query<T>(query: BoundQuery): Effect.Effect<T[], SurrealDBError, never> {
|
|
606
|
+
return Effect.gen(
|
|
607
|
+
function* (this: SurrealDBService) {
|
|
608
|
+
const boundQuery = yield* this.normalizeBoundQueryEffect(query)
|
|
609
|
+
const statements = yield* this.queryAll<T>(boundQuery)
|
|
610
|
+
return statements.at(0) ?? []
|
|
611
|
+
}.bind(this),
|
|
637
612
|
)
|
|
638
613
|
}
|
|
639
614
|
|
|
640
|
-
queryAll<T>(query: BoundQuery, schema?: z.ZodTypeAny):
|
|
641
|
-
return
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
}.bind(this),
|
|
672
|
-
),
|
|
615
|
+
queryAll<T>(query: BoundQuery, schema?: z.ZodTypeAny): Effect.Effect<T[][], SurrealDBError, never> {
|
|
616
|
+
return Effect.gen(
|
|
617
|
+
function* (this: SurrealDBService) {
|
|
618
|
+
const boundQuery = yield* this.normalizeBoundQueryEffect(query)
|
|
619
|
+
const queryText = this.resolveQueryText(boundQuery)
|
|
620
|
+
const client = yield* this.ensureConnectedEffect(queryText)
|
|
621
|
+
const responses = yield* Effect.tryPromise({
|
|
622
|
+
try: () => client.query(boundQuery).responses(),
|
|
623
|
+
catch: (error) =>
|
|
624
|
+
new SurrealDBError({
|
|
625
|
+
message: `Failed to run query: ${getErrorMessage(error)}`,
|
|
626
|
+
query: queryText,
|
|
627
|
+
cause: error,
|
|
628
|
+
}),
|
|
629
|
+
})
|
|
630
|
+
return yield* Effect.forEach(responses, (response, index) =>
|
|
631
|
+
Effect.gen(
|
|
632
|
+
function* (this: SurrealDBService) {
|
|
633
|
+
if (!response.success) {
|
|
634
|
+
const failure = response.error
|
|
635
|
+
return yield* new SurrealDBError({
|
|
636
|
+
message: `Statement ${index + 1}: ${failure.message}`,
|
|
637
|
+
query: queryText,
|
|
638
|
+
cause: failure,
|
|
639
|
+
})
|
|
640
|
+
}
|
|
641
|
+
return (yield* this.normalizeQueryRowsEffect(response.result, schema)) as T[]
|
|
642
|
+
}.bind(this),
|
|
643
|
+
),
|
|
644
|
+
)
|
|
645
|
+
}.bind(this),
|
|
673
646
|
)
|
|
674
647
|
}
|
|
675
648
|
|
|
676
|
-
queryOne<T extends z.ZodTypeAny>(
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
649
|
+
queryOne<T extends z.ZodTypeAny>(
|
|
650
|
+
query: BoundQuery,
|
|
651
|
+
schema: T,
|
|
652
|
+
): Effect.Effect<z.infer<T> | null, SurrealDBError, never> {
|
|
653
|
+
return Effect.gen(
|
|
654
|
+
function* (this: SurrealDBService) {
|
|
655
|
+
const boundQuery = yield* this.normalizeBoundQueryEffect(query)
|
|
656
|
+
const results = yield* this.query<unknown>(boundQuery)
|
|
657
|
+
const first = results.at(0)
|
|
658
|
+
return first ? yield* this.parseSchemaEffect(schema, first) : null
|
|
659
|
+
}.bind(this),
|
|
686
660
|
)
|
|
687
661
|
}
|
|
688
662
|
|
|
689
|
-
queryMany<T extends z.ZodTypeAny>(query: BoundQuery, schema: T):
|
|
690
|
-
return
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
}.bind(this),
|
|
697
|
-
),
|
|
663
|
+
queryMany<T extends z.ZodTypeAny>(query: BoundQuery, schema: T): Effect.Effect<z.infer<T>[], SurrealDBError, never> {
|
|
664
|
+
return Effect.gen(
|
|
665
|
+
function* (this: SurrealDBService) {
|
|
666
|
+
const boundQuery = yield* this.normalizeBoundQueryEffect(query)
|
|
667
|
+
const results = yield* this.query<unknown>(boundQuery)
|
|
668
|
+
return yield* Effect.forEach(results, (row) => this.parseSchemaEffect(schema, row))
|
|
669
|
+
}.bind(this),
|
|
698
670
|
)
|
|
699
671
|
}
|
|
700
672
|
|
|
@@ -715,39 +687,35 @@ export class SurrealDBService {
|
|
|
715
687
|
)
|
|
716
688
|
}
|
|
717
689
|
|
|
718
|
-
applySchema(schemaFiles: readonly Bun.BunFile[]):
|
|
719
|
-
return
|
|
720
|
-
Effect.forEach(schemaFiles, (schemaFile) => this.runSqlFile(schemaFile), { discard: true }),
|
|
721
|
-
)
|
|
690
|
+
applySchema(schemaFiles: readonly Bun.BunFile[]): Effect.Effect<void, SurrealDBError, never> {
|
|
691
|
+
return Effect.forEach(schemaFiles, (schemaFile) => this.runSqlFile(schemaFile), { discard: true })
|
|
722
692
|
}
|
|
723
693
|
|
|
724
694
|
findOne<T extends z.ZodTypeAny>(
|
|
725
695
|
table: DatabaseTable,
|
|
726
696
|
filter: Record<string, unknown>,
|
|
727
697
|
schema: T,
|
|
728
|
-
):
|
|
729
|
-
return
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
}
|
|
698
|
+
): Effect.Effect<z.infer<T> | null, SurrealDBError, never> {
|
|
699
|
+
return Effect.gen(
|
|
700
|
+
function* (this: SurrealDBService) {
|
|
701
|
+
const selection = yield* this.buildFilterExpressionEffect(filter)
|
|
702
|
+
const client = yield* this.ensureConnectedEffect(`SELECT * FROM ${table} LIMIT 1`)
|
|
703
|
+
let query = client.select<unknown>(new Table(table))
|
|
704
|
+
if (selection) {
|
|
705
|
+
query = query.where(selection)
|
|
706
|
+
}
|
|
738
707
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
),
|
|
708
|
+
const rows = yield* Effect.tryPromise({
|
|
709
|
+
try: () => query.limit(1),
|
|
710
|
+
catch: (error) =>
|
|
711
|
+
new SurrealDBError({
|
|
712
|
+
message: `Failed to find record in ${table}: ${getErrorMessage(error)}`,
|
|
713
|
+
cause: error,
|
|
714
|
+
}),
|
|
715
|
+
})
|
|
716
|
+
const first = rows.at(0)
|
|
717
|
+
return first ? yield* this.parseSchemaEffect(schema, first) : null
|
|
718
|
+
}.bind(this),
|
|
751
719
|
)
|
|
752
720
|
}
|
|
753
721
|
|
|
@@ -756,71 +724,69 @@ export class SurrealDBService {
|
|
|
756
724
|
filter: Record<string, unknown>,
|
|
757
725
|
schema: T,
|
|
758
726
|
options?: FindManyOptions,
|
|
759
|
-
):
|
|
760
|
-
return
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
const orderBy = options?.orderBy
|
|
766
|
-
|
|
767
|
-
if (orderBy !== undefined) {
|
|
768
|
-
yield* this.assertValidIdentifierEffect(orderBy, 'orderBy field')
|
|
769
|
-
yield* this.assertValidIdentifierEffect(table, 'table name')
|
|
770
|
-
for (const key of filterKeys) {
|
|
771
|
-
yield* this.assertValidIdentifierEffect(key, 'filter field')
|
|
772
|
-
}
|
|
773
|
-
const rawOrderDir: unknown = options?.orderDir
|
|
774
|
-
if (rawOrderDir !== undefined && rawOrderDir !== 'ASC' && rawOrderDir !== 'DESC') {
|
|
775
|
-
return yield* new SurrealDBError({
|
|
776
|
-
message: `Invalid orderDir value: ${describeInvalidValue(rawOrderDir)}`,
|
|
777
|
-
})
|
|
778
|
-
}
|
|
779
|
-
const orderDir = rawOrderDir ?? 'ASC'
|
|
780
|
-
const limit = options?.limit
|
|
781
|
-
const offset = options?.offset
|
|
782
|
-
const vars: Record<string, unknown> = yield* this.normalizeMutationDataEffect(filter)
|
|
783
|
-
let sql = `SELECT * FROM ${table}`
|
|
784
|
-
if (filterKeys.length > 0) {
|
|
785
|
-
const conditions = filterKeys.map((key) => `${key} = $${key}`).join(' AND ')
|
|
786
|
-
sql += ` WHERE ${conditions}`
|
|
787
|
-
}
|
|
788
|
-
sql += ` ORDER BY ${orderBy} ${orderDir}`
|
|
789
|
-
if (limit !== undefined) {
|
|
790
|
-
sql += ' LIMIT $limitParam'
|
|
791
|
-
vars.limitParam = limit
|
|
792
|
-
}
|
|
793
|
-
if (offset !== undefined) {
|
|
794
|
-
sql += ' START $offsetParam'
|
|
795
|
-
vars.offsetParam = offset
|
|
796
|
-
}
|
|
797
|
-
const rows = yield* this.query<unknown>(new BoundQuery(sql, vars))
|
|
798
|
-
return yield* Effect.forEach(rows, (row) => this.parseSchemaEffect(schema, row))
|
|
799
|
-
}
|
|
727
|
+
): Effect.Effect<z.infer<T>[], SurrealDBError, never> {
|
|
728
|
+
return Effect.gen(
|
|
729
|
+
function* (this: SurrealDBService) {
|
|
730
|
+
const filterKeys = Object.keys(filter)
|
|
731
|
+
const selection = yield* this.buildFilterExpressionEffect(filter)
|
|
732
|
+
const orderBy = options?.orderBy
|
|
800
733
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
734
|
+
if (orderBy !== undefined) {
|
|
735
|
+
yield* this.assertValidIdentifierEffect(orderBy, 'orderBy field')
|
|
736
|
+
yield* this.assertValidIdentifierEffect(table, 'table name')
|
|
737
|
+
for (const key of filterKeys) {
|
|
738
|
+
yield* this.assertValidIdentifierEffect(key, 'filter field')
|
|
805
739
|
}
|
|
806
|
-
|
|
807
|
-
|
|
740
|
+
const rawOrderDir: unknown = options?.orderDir
|
|
741
|
+
if (rawOrderDir !== undefined && rawOrderDir !== 'ASC' && rawOrderDir !== 'DESC') {
|
|
742
|
+
return yield* new SurrealDBError({
|
|
743
|
+
message: `Invalid orderDir value: ${describeInvalidValue(rawOrderDir)}`,
|
|
744
|
+
})
|
|
808
745
|
}
|
|
809
|
-
|
|
810
|
-
|
|
746
|
+
const orderDir = rawOrderDir ?? 'ASC'
|
|
747
|
+
const limit = options?.limit
|
|
748
|
+
const offset = options?.offset
|
|
749
|
+
const vars: Record<string, unknown> = yield* this.normalizeMutationDataEffect(filter)
|
|
750
|
+
let sql = `SELECT * FROM ${table}`
|
|
751
|
+
if (filterKeys.length > 0) {
|
|
752
|
+
const conditions = filterKeys.map((key) => `${key} = $${key}`).join(' AND ')
|
|
753
|
+
sql += ` WHERE ${conditions}`
|
|
811
754
|
}
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
}
|
|
755
|
+
sql += ` ORDER BY ${orderBy} ${orderDir}`
|
|
756
|
+
if (limit !== undefined) {
|
|
757
|
+
sql += ' LIMIT $limitParam'
|
|
758
|
+
vars.limitParam = limit
|
|
759
|
+
}
|
|
760
|
+
if (offset !== undefined) {
|
|
761
|
+
sql += ' START $offsetParam'
|
|
762
|
+
vars.offsetParam = offset
|
|
763
|
+
}
|
|
764
|
+
const rows = yield* this.query<unknown>(new BoundQuery(sql, vars))
|
|
821
765
|
return yield* Effect.forEach(rows, (row) => this.parseSchemaEffect(schema, row))
|
|
822
|
-
}
|
|
823
|
-
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const client = yield* this.ensureConnectedEffect(`SELECT * FROM ${table}`)
|
|
769
|
+
let query = client.select<unknown>(new Table(table))
|
|
770
|
+
if (selection) {
|
|
771
|
+
query = query.where(selection)
|
|
772
|
+
}
|
|
773
|
+
if (options?.offset !== undefined) {
|
|
774
|
+
query = query.start(options.offset)
|
|
775
|
+
}
|
|
776
|
+
if (options?.limit !== undefined) {
|
|
777
|
+
query = query.limit(options.limit)
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const rows = yield* Effect.tryPromise({
|
|
781
|
+
try: () => query,
|
|
782
|
+
catch: (error) =>
|
|
783
|
+
new SurrealDBError({
|
|
784
|
+
message: `Failed to run findMany query for ${table}: ${getErrorMessage(error)}`,
|
|
785
|
+
cause: error,
|
|
786
|
+
}),
|
|
787
|
+
})
|
|
788
|
+
return yield* Effect.forEach(rows, (row) => this.parseSchemaEffect(schema, row))
|
|
789
|
+
}.bind(this),
|
|
824
790
|
)
|
|
825
791
|
}
|
|
826
792
|
|
|
@@ -828,34 +794,32 @@ export class SurrealDBService {
|
|
|
828
794
|
table: DatabaseTable,
|
|
829
795
|
data: Record<string, unknown>,
|
|
830
796
|
schema: T,
|
|
831
|
-
):
|
|
832
|
-
return
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
}
|
|
797
|
+
): Effect.Effect<z.infer<T>, SurrealDBError, never> {
|
|
798
|
+
return Effect.gen(
|
|
799
|
+
function* (this: SurrealDBService) {
|
|
800
|
+
const keys = Object.keys(data)
|
|
801
|
+
if (keys.length === 0) {
|
|
802
|
+
return yield* new SurrealDBError({ message: `Cannot create record in ${table} with empty data` })
|
|
803
|
+
}
|
|
839
804
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
805
|
+
const client = yield* this.ensureConnectedEffect(`CREATE ${table}`)
|
|
806
|
+
const normalizedData = yield* this.normalizeMutationDataEffect(data)
|
|
807
|
+
const created = yield* Effect.tryPromise({
|
|
808
|
+
try: () => client.create<unknown>(new Table(table)).content(normalizedData).output('after'),
|
|
809
|
+
catch: (error) =>
|
|
810
|
+
new SurrealDBError({
|
|
811
|
+
message: `Failed to create record in ${table}: ${getErrorMessage(error)}`,
|
|
812
|
+
cause: error,
|
|
813
|
+
}),
|
|
814
|
+
})
|
|
815
|
+
const first = Array.isArray(created) ? created.at(0) : created
|
|
851
816
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
817
|
+
if (!first) {
|
|
818
|
+
return yield* new SurrealDBError({ message: `Failed to create record in ${table}` })
|
|
819
|
+
}
|
|
855
820
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
),
|
|
821
|
+
return yield* this.parseSchemaEffect(schema, first)
|
|
822
|
+
}.bind(this),
|
|
859
823
|
)
|
|
860
824
|
}
|
|
861
825
|
|
|
@@ -864,29 +828,27 @@ export class SurrealDBService {
|
|
|
864
828
|
id: unknown,
|
|
865
829
|
data: Record<string, unknown>,
|
|
866
830
|
schema: T,
|
|
867
|
-
):
|
|
868
|
-
return
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
}
|
|
831
|
+
): Effect.Effect<z.infer<T>, SurrealDBError, never> {
|
|
832
|
+
return Effect.gen(
|
|
833
|
+
function* (this: SurrealDBService) {
|
|
834
|
+
const recordId = yield* this.normalizeRecordIdEffect(id, table)
|
|
835
|
+
const keys = Object.keys(data)
|
|
836
|
+
if (keys.length === 0) {
|
|
837
|
+
return yield* new SurrealDBError({ message: `Cannot create record in ${table} with empty data` })
|
|
838
|
+
}
|
|
876
839
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
),
|
|
840
|
+
const client = yield* this.ensureConnectedEffect(`CREATE ${recordId.toString()}`)
|
|
841
|
+
const normalizedData = yield* this.normalizeMutationDataEffect(data)
|
|
842
|
+
const created = yield* Effect.tryPromise({
|
|
843
|
+
try: () => client.create<unknown>(recordId).content(normalizedData).output('after'),
|
|
844
|
+
catch: (error) =>
|
|
845
|
+
new SurrealDBError({
|
|
846
|
+
message: `Failed to create record in ${table}: ${getErrorMessage(error)}`,
|
|
847
|
+
cause: error,
|
|
848
|
+
}),
|
|
849
|
+
})
|
|
850
|
+
return yield* this.parseSchemaEffect(schema, created)
|
|
851
|
+
}.bind(this),
|
|
890
852
|
)
|
|
891
853
|
}
|
|
892
854
|
|
|
@@ -896,31 +858,29 @@ export class SurrealDBService {
|
|
|
896
858
|
data: Record<string, unknown>,
|
|
897
859
|
schema: T,
|
|
898
860
|
options?: { mutation?: RecordMutation },
|
|
899
|
-
):
|
|
900
|
-
return
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
}
|
|
861
|
+
): Effect.Effect<z.infer<T> | null, SurrealDBError, never> {
|
|
862
|
+
return Effect.gen(
|
|
863
|
+
function* (this: SurrealDBService) {
|
|
864
|
+
const recordId = yield* this.normalizeRecordIdEffect(id, table)
|
|
865
|
+
const keys = Object.keys(data)
|
|
866
|
+
if (keys.length === 0) {
|
|
867
|
+
return yield* new SurrealDBError({ message: 'Cannot update record with empty data' })
|
|
868
|
+
}
|
|
908
869
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
),
|
|
870
|
+
const mutation = options?.mutation ?? 'merge'
|
|
871
|
+
const client = yield* this.ensureConnectedEffect(`UPDATE ${recordId.toString()}`)
|
|
872
|
+
const builder = client.update<unknown>(recordId)
|
|
873
|
+
const normalizedData = yield* this.normalizeMutationDataEffect(data)
|
|
874
|
+
const updated: Record<string, unknown> | null | undefined = yield* Effect.tryPromise({
|
|
875
|
+
try: () => configureMutation(builder, mutation, normalizedData).output('after'),
|
|
876
|
+
catch: (error) =>
|
|
877
|
+
new SurrealDBError({
|
|
878
|
+
message: `Failed to update record in ${table}: ${getErrorMessage(error)}`,
|
|
879
|
+
cause: error,
|
|
880
|
+
}),
|
|
881
|
+
})
|
|
882
|
+
return yield* this.parseOptionalSchemaEffect(schema, updated)
|
|
883
|
+
}.bind(this),
|
|
924
884
|
)
|
|
925
885
|
}
|
|
926
886
|
|
|
@@ -930,90 +890,84 @@ export class SurrealDBService {
|
|
|
930
890
|
data: Record<string, unknown>,
|
|
931
891
|
schema: T,
|
|
932
892
|
options?: { mutation?: RecordMutation },
|
|
933
|
-
):
|
|
934
|
-
return
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
}
|
|
893
|
+
): Effect.Effect<z.infer<T>, SurrealDBError, never> {
|
|
894
|
+
return Effect.gen(
|
|
895
|
+
function* (this: SurrealDBService) {
|
|
896
|
+
const recordId = yield* this.normalizeRecordIdEffect(id, table)
|
|
897
|
+
const keys = Object.keys(data)
|
|
898
|
+
if (keys.length === 0) {
|
|
899
|
+
return yield* new SurrealDBError({ message: 'Cannot upsert record with empty data' })
|
|
900
|
+
}
|
|
942
901
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
),
|
|
902
|
+
const mutation = options?.mutation ?? 'merge'
|
|
903
|
+
const client = yield* this.ensureConnectedEffect(`UPSERT ${recordId.toString()}`)
|
|
904
|
+
const builder = client.upsert<unknown>(recordId)
|
|
905
|
+
const normalizedData = yield* this.normalizeMutationDataEffect(data)
|
|
906
|
+
const upserted: Record<string, unknown> | null | undefined = yield* Effect.tryPromise({
|
|
907
|
+
try: () => configureMutation(builder, mutation, normalizedData).output('after'),
|
|
908
|
+
catch: (error) =>
|
|
909
|
+
new SurrealDBError({
|
|
910
|
+
message: `Failed to upsert record in ${table}: ${getErrorMessage(error)}`,
|
|
911
|
+
cause: error,
|
|
912
|
+
}),
|
|
913
|
+
})
|
|
914
|
+
const parsed = yield* this.parseOptionalSchemaEffect(schema, upserted)
|
|
915
|
+
if (parsed === null) {
|
|
916
|
+
return yield* new SurrealDBError({ message: `Failed to upsert record in ${table}` })
|
|
917
|
+
}
|
|
918
|
+
return parsed
|
|
919
|
+
}.bind(this),
|
|
962
920
|
)
|
|
963
921
|
}
|
|
964
922
|
|
|
965
|
-
deleteById(table: DatabaseTable, id: unknown):
|
|
966
|
-
return
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
}
|
|
923
|
+
deleteById(table: DatabaseTable, id: unknown): Effect.Effect<boolean, SurrealDBError, never> {
|
|
924
|
+
return Effect.gen(
|
|
925
|
+
function* (this: SurrealDBService) {
|
|
926
|
+
const recordId = yield* this.normalizeRecordIdEffect(id, table)
|
|
927
|
+
return yield* this.query<unknown>(new BoundQuery(`DELETE $recordId RETURN BEFORE`, { recordId })).pipe(
|
|
928
|
+
Effect.map((result) => result.length > 0),
|
|
929
|
+
Effect.catchTag('SurrealDBError', (error) => {
|
|
930
|
+
if (error.message.includes('does not exist')) {
|
|
931
|
+
return Effect.succeed(false)
|
|
932
|
+
}
|
|
976
933
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
),
|
|
934
|
+
return Effect.fail(this.toSurrealError(error, `DELETE ${recordId.toString()}`))
|
|
935
|
+
}),
|
|
936
|
+
)
|
|
937
|
+
}.bind(this),
|
|
982
938
|
)
|
|
983
939
|
}
|
|
984
940
|
|
|
985
|
-
deleteWhere(table: DatabaseTable, filter: Record<string, unknown>):
|
|
986
|
-
return
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
}.bind(this),
|
|
1016
|
-
),
|
|
941
|
+
deleteWhere(table: DatabaseTable, filter: Record<string, unknown>): Effect.Effect<number, SurrealDBError, never> {
|
|
942
|
+
return Effect.gen(
|
|
943
|
+
function* (this: SurrealDBService) {
|
|
944
|
+
yield* this.assertValidIdentifierEffect(table, 'table name')
|
|
945
|
+
const filterKeys = Object.keys(filter)
|
|
946
|
+
if (filterKeys.length === 0) {
|
|
947
|
+
return yield* new SurrealDBError({ message: `Refusing to delete all records in ${table} without a filter` })
|
|
948
|
+
}
|
|
949
|
+
const { clause, bindings } = yield* this.buildBoundFilterClausesEffect(filter)
|
|
950
|
+
return yield* this.withTransaction((tx) =>
|
|
951
|
+
Effect.gen(
|
|
952
|
+
function* (this: SurrealDBService) {
|
|
953
|
+
const matchedRows = (yield* tx.query(
|
|
954
|
+
new BoundQuery(`SELECT id FROM ${table} WHERE ${clause}`, bindings),
|
|
955
|
+
)) as Array<{ id: unknown }>
|
|
956
|
+
|
|
957
|
+
if (matchedRows.length === 0) {
|
|
958
|
+
return 0
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
for (const row of matchedRows) {
|
|
962
|
+
const recordId = yield* this.normalizeRecordIdEffect(row.id, table)
|
|
963
|
+
yield* tx.delete(recordId)
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
return matchedRows.length
|
|
967
|
+
}.bind(this),
|
|
968
|
+
),
|
|
969
|
+
)
|
|
970
|
+
}.bind(this),
|
|
1017
971
|
)
|
|
1018
972
|
}
|
|
1019
973
|
|
|
@@ -1021,58 +975,52 @@ export class SurrealDBService {
|
|
|
1021
975
|
table: DatabaseTable,
|
|
1022
976
|
where: ExprLike,
|
|
1023
977
|
data: Record<string, unknown>,
|
|
1024
|
-
):
|
|
1025
|
-
return
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
if (keys.length === 0) {
|
|
1035
|
-
return yield* new SurrealDBError({ message: `Cannot update records in ${table} with empty data` })
|
|
1036
|
-
}
|
|
978
|
+
): Effect.Effect<number, SurrealDBError, never> {
|
|
979
|
+
return Effect.gen(
|
|
980
|
+
function* (this: SurrealDBService) {
|
|
981
|
+
if (!where) {
|
|
982
|
+
return yield* new SurrealDBError({ message: `Refusing to update records in ${table} without a where clause` })
|
|
983
|
+
}
|
|
984
|
+
const keys = Object.keys(data)
|
|
985
|
+
if (keys.length === 0) {
|
|
986
|
+
return yield* new SurrealDBError({ message: `Cannot update records in ${table} with empty data` })
|
|
987
|
+
}
|
|
1037
988
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
),
|
|
989
|
+
const client = yield* this.ensureConnectedEffect(`UPDATE ${table} WHERE ...`)
|
|
990
|
+
const normalizedData = yield* this.normalizeMutationDataEffect(data)
|
|
991
|
+
const updated = yield* Effect.tryPromise({
|
|
992
|
+
try: () => client.update<unknown>(new Table(table)).where(where).merge(normalizedData).output('after'),
|
|
993
|
+
catch: (error) =>
|
|
994
|
+
new SurrealDBError({
|
|
995
|
+
message: `Failed to update records in ${table}: ${getErrorMessage(error)}`,
|
|
996
|
+
cause: error,
|
|
997
|
+
}),
|
|
998
|
+
})
|
|
999
|
+
return Array.isArray(updated) ? updated.length : 1
|
|
1000
|
+
}.bind(this),
|
|
1051
1001
|
)
|
|
1052
1002
|
}
|
|
1053
1003
|
|
|
1054
1004
|
insert<T extends Record<string, unknown>>(
|
|
1055
1005
|
table: DatabaseTable,
|
|
1056
1006
|
data: Values<T> | Array<Values<T>>,
|
|
1057
|
-
):
|
|
1058
|
-
return
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
}.bind(this),
|
|
1075
|
-
),
|
|
1007
|
+
): Effect.Effect<T[], SurrealDBError, never> {
|
|
1008
|
+
return Effect.gen(
|
|
1009
|
+
function* (this: SurrealDBService) {
|
|
1010
|
+
const client = yield* this.ensureConnectedEffect(`INSERT ${table}`)
|
|
1011
|
+
const normalized = Array.isArray(data)
|
|
1012
|
+
? yield* Effect.forEach(data, (item) => this.normalizeMutationDataEffect(item as Record<string, unknown>))
|
|
1013
|
+
: yield* this.normalizeMutationDataEffect(data as Record<string, unknown>)
|
|
1014
|
+
const inserted = yield* Effect.tryPromise({
|
|
1015
|
+
try: () => client.insert<T>(new Table(table), normalized as Values<T> | Array<Values<T>>).output('after'),
|
|
1016
|
+
catch: (error) =>
|
|
1017
|
+
new SurrealDBError({
|
|
1018
|
+
message: `Failed to insert rows into ${table}: ${getErrorMessage(error)}`,
|
|
1019
|
+
cause: error,
|
|
1020
|
+
}),
|
|
1021
|
+
})
|
|
1022
|
+
return inserted as T[]
|
|
1023
|
+
}.bind(this),
|
|
1076
1024
|
)
|
|
1077
1025
|
}
|
|
1078
1026
|
|
|
@@ -1081,51 +1029,47 @@ export class SurrealDBService {
|
|
|
1081
1029
|
edgeTable: DatabaseTable,
|
|
1082
1030
|
to: RecordIdInput,
|
|
1083
1031
|
data?: Values<T>,
|
|
1084
|
-
):
|
|
1085
|
-
return
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
}.bind(this),
|
|
1112
|
-
),
|
|
1032
|
+
): Effect.Effect<T | null, SurrealDBError, never> {
|
|
1033
|
+
return Effect.gen(
|
|
1034
|
+
function* (this: SurrealDBService) {
|
|
1035
|
+
const fromRef = yield* ensureRecordIdEffect(from).pipe(Effect.mapError((error) => this.toSurrealError(error)))
|
|
1036
|
+
const toRef = yield* ensureRecordIdEffect(to).pipe(Effect.mapError((error) => this.toSurrealError(error)))
|
|
1037
|
+
const client = yield* this.ensureConnectedEffect(
|
|
1038
|
+
`RELATE ${fromRef.toString()}->${edgeTable}->${toRef.toString()}`,
|
|
1039
|
+
)
|
|
1040
|
+
const normalizedData = data
|
|
1041
|
+
? ((yield* this.normalizeMutationDataEffect(data as Record<string, unknown>)) as Values<T>)
|
|
1042
|
+
: undefined
|
|
1043
|
+
const related = (yield* Effect.tryPromise({
|
|
1044
|
+
try: () => client.relate<T>(fromRef, new Table(edgeTable), toRef, normalizedData).output('after'),
|
|
1045
|
+
catch: (error) =>
|
|
1046
|
+
new SurrealDBError({
|
|
1047
|
+
message: `Failed to relate records ${fromRef.toString()} -> ${edgeTable} -> ${toRef.toString()}: ${getErrorMessage(error)}`,
|
|
1048
|
+
cause: error,
|
|
1049
|
+
}),
|
|
1050
|
+
})) as T | T[] | null
|
|
1051
|
+
if (related === null) {
|
|
1052
|
+
return null
|
|
1053
|
+
}
|
|
1054
|
+
if (Array.isArray(related)) {
|
|
1055
|
+
return related.at(0) ?? null
|
|
1056
|
+
}
|
|
1057
|
+
return related
|
|
1058
|
+
}.bind(this),
|
|
1113
1059
|
)
|
|
1114
1060
|
}
|
|
1115
1061
|
|
|
1116
|
-
beginTransaction():
|
|
1117
|
-
return
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
}.bind(this),
|
|
1128
|
-
),
|
|
1062
|
+
beginTransaction(): Effect.Effect<DatabaseTransaction, SurrealDBError, never> {
|
|
1063
|
+
return Effect.gen(
|
|
1064
|
+
function* (this: SurrealDBService) {
|
|
1065
|
+
const client = yield* this.ensureConnectedEffect('BEGIN TRANSACTION')
|
|
1066
|
+
const tx = yield* Effect.tryPromise({
|
|
1067
|
+
try: () => client.beginTransaction(),
|
|
1068
|
+
catch: (error) =>
|
|
1069
|
+
new SurrealDBError({ message: `Failed to begin transaction: ${getErrorMessage(error)}`, cause: error }),
|
|
1070
|
+
})
|
|
1071
|
+
return this.wrapTransaction(tx)
|
|
1072
|
+
}.bind(this),
|
|
1129
1073
|
)
|
|
1130
1074
|
}
|
|
1131
1075
|
|