@lota-sdk/core 0.4.9 → 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/embedding-cache.ts +3 -1
- package/src/ai-gateway/ai-gateway.ts +164 -82
- package/src/ai-gateway/index.ts +16 -1
- package/src/config/agent-defaults.ts +4 -107
- package/src/config/agent-types.ts +1 -1
- package/src/config/background-processing.ts +1 -1
- package/src/config/index.ts +0 -1
- package/src/config/logger.ts +22 -25
- package/src/config/thread-defaults.ts +1 -10
- package/src/create-runtime.ts +145 -670
- package/src/db/base.service.ts +30 -38
- package/src/db/memory-query-builder.ts +2 -1
- package/src/db/memory-store.ts +29 -20
- package/src/db/memory.ts +188 -195
- package/src/db/service-normalization.ts +97 -64
- package/src/db/service.ts +496 -384
- package/src/db/startup.ts +30 -19
- package/src/effect/helpers.ts +30 -5
- package/src/effect/index.ts +7 -7
- package/src/effect/layers.ts +75 -72
- package/src/effect/services.ts +15 -11
- package/src/embeddings/provider.ts +65 -71
- package/src/index.ts +13 -12
- package/src/queues/autonomous-job.queue.ts +177 -143
- package/src/queues/context-compaction.queue.ts +41 -39
- package/src/queues/delayed-node-promotion.queue.ts +61 -42
- 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 +70 -33
- package/src/queues/plan-agent-heartbeat.queue.ts +111 -83
- package/src/queues/plan-scheduler.queue.ts +101 -97
- package/src/queues/post-chat-memory.queue.ts +56 -46
- package/src/queues/queue-factory.ts +146 -69
- package/src/queues/queues.service.ts +61 -0
- package/src/queues/title-generation.queue.ts +44 -44
- package/src/redis/connection.ts +181 -164
- package/src/redis/org-memory-lock.ts +24 -9
- package/src/redis/redis-lease-lock.ts +8 -1
- package/src/redis/stream-context.ts +17 -9
- package/src/runtime/agent-identity-overrides.ts +7 -3
- package/src/runtime/agent-runtime-policy.ts +10 -5
- package/src/runtime/agent-stream-helpers.ts +24 -15
- package/src/runtime/chat-run-orchestration.ts +1 -1
- package/src/runtime/context-compaction/context-compaction-runtime.ts +28 -32
- package/src/runtime/context-compaction/context-compaction.ts +131 -85
- package/src/runtime/domain-layer.ts +203 -0
- package/src/runtime/execution-plan-visibility.ts +5 -2
- package/src/runtime/graph-designer.ts +0 -14
- package/src/runtime/helper-model.ts +8 -4
- package/src/runtime/index.ts +1 -1
- package/src/runtime/indexed-repositories-policy.ts +2 -6
- package/src/runtime/memory/memory-block.ts +19 -9
- package/src/runtime/memory/memory-pipeline.ts +53 -66
- package/src/runtime/memory/memory-scope.ts +33 -29
- package/src/runtime/plugin-resolution.ts +58 -62
- package/src/runtime/post-turn-side-effects.ts +139 -161
- package/src/runtime/retrieval-adapters.ts +4 -4
- package/src/runtime/runtime-config.ts +3 -9
- package/src/runtime/runtime-extensions.ts +0 -43
- package/src/runtime/runtime-lifecycle.ts +124 -0
- package/src/runtime/runtime-services.ts +455 -0
- package/src/runtime/runtime-worker-registry.ts +113 -30
- package/src/runtime/social-chat/social-chat-agent-runner.ts +13 -8
- package/src/runtime/social-chat/social-chat-history.ts +24 -13
- package/src/runtime/social-chat/social-chat.ts +420 -369
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +64 -57
- 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 +28 -74
- package/src/runtime/turn-lifecycle.ts +6 -14
- package/src/services/agent-activity.service.ts +169 -176
- package/src/services/agent-executor.service.ts +207 -196
- package/src/services/artifact.service.ts +10 -5
- package/src/services/attachment.service.ts +16 -48
- package/src/services/autonomous-job.service.ts +81 -87
- package/src/services/background-work.service.ts +54 -0
- package/src/services/chat-run-registry.service.ts +3 -1
- package/src/services/context-compaction.service.ts +8 -10
- package/src/services/document-chunk.service.ts +8 -17
- package/src/services/execution-plan/execution-plan-graph.ts +122 -109
- package/src/services/execution-plan/execution-plan-schedule.ts +1 -15
- package/src/services/execution-plan/execution-plan.service.ts +68 -51
- package/src/services/feedback-loop.service.ts +1 -1
- package/src/services/global-orchestrator.service.ts +49 -15
- package/src/services/graph-full-routing.ts +49 -37
- package/src/services/index.ts +1 -0
- package/src/services/institutional-memory.service.ts +8 -17
- package/src/services/learned-skill.service.ts +38 -35
- package/src/services/memory/memory-conversation.ts +10 -5
- package/src/services/memory/memory-errors.ts +27 -0
- package/src/services/memory/memory-org-memory.ts +14 -3
- package/src/services/memory/memory-preseeded.ts +10 -4
- package/src/services/memory/memory-utils.ts +2 -1
- package/src/services/memory/memory.service.ts +37 -52
- package/src/services/memory/rerank.service.ts +3 -11
- package/src/services/monitoring-window.service.ts +1 -1
- package/src/services/mutating-approval.service.ts +1 -1
- package/src/services/node-workspace.service.ts +2 -2
- package/src/services/notification.service.ts +16 -4
- package/src/services/organization-member.service.ts +1 -1
- package/src/services/organization.service.ts +34 -51
- package/src/services/ownership-dispatcher.service.ts +148 -95
- package/src/services/plan/plan-agent-heartbeat.service.ts +30 -16
- package/src/services/plan/plan-agent-query.service.ts +13 -9
- package/src/services/plan/plan-approval.service.ts +52 -48
- package/src/services/plan/plan-artifact.service.ts +2 -2
- package/src/services/plan/plan-builder.service.ts +2 -2
- package/src/services/plan/plan-checkpoint.service.ts +1 -1
- package/src/services/plan/plan-compiler.service.ts +1 -1
- package/src/services/plan/plan-completion-side-effects.ts +99 -113
- package/src/services/plan/plan-coordination.service.ts +1 -1
- package/src/services/plan/plan-cycle.service.ts +171 -202
- package/src/services/plan/plan-deadline.service.ts +304 -307
- package/src/services/plan/plan-event-delivery.service.ts +84 -72
- package/src/services/plan/plan-executor-context.ts +2 -0
- package/src/services/plan/plan-executor-graph.ts +375 -353
- package/src/services/plan/plan-executor-helpers.ts +60 -75
- package/src/services/plan/plan-executor.service.ts +494 -489
- package/src/services/plan/plan-run.service.ts +12 -19
- package/src/services/plan/plan-scheduler.service.ts +89 -82
- package/src/services/plan/plan-template.service.ts +1 -1
- package/src/services/plan/plan-transaction-events.ts +8 -5
- package/src/services/plan/plan-validator.service.ts +1 -1
- package/src/services/plan/plan-workspace.service.ts +17 -11
- package/src/services/plugin-executor.service.ts +26 -21
- package/src/services/quality-metrics.service.ts +1 -1
- package/src/services/queue-job.service.ts +8 -17
- package/src/services/recent-activity-title.service.ts +22 -10
- package/src/services/recent-activity.service.ts +1 -1
- package/src/services/skill-resolver.service.ts +1 -1
- package/src/services/social-chat-history.service.ts +37 -20
- package/src/services/system-executor.service.ts +25 -20
- package/src/services/thread/thread-bootstrap.ts +37 -19
- package/src/services/thread/thread-listing.ts +2 -1
- package/src/services/thread/thread-memory-block.ts +18 -5
- package/src/services/thread/thread-message.service.ts +30 -13
- package/src/services/thread/thread-title.service.ts +1 -1
- package/src/services/thread/thread-turn-execution.ts +87 -83
- package/src/services/thread/thread-turn-preparation.service.ts +65 -40
- package/src/services/thread/thread-turn-streaming.ts +32 -36
- package/src/services/thread/thread-turn.ts +43 -29
- package/src/services/thread/thread.service.ts +32 -8
- package/src/services/user.service.ts +1 -1
- package/src/services/write-intent-validator.service.ts +1 -1
- package/src/storage/attachment-storage.service.ts +7 -4
- package/src/storage/generated-document-storage.service.ts +1 -1
- package/src/system-agents/context-compaction.agent.ts +1 -1
- package/src/system-agents/helper-agent-options.ts +1 -1
- 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 +9 -6
- package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
- package/src/system-agents/skill-extractor.agent.ts +1 -1
- package/src/system-agents/skill-manager.agent.ts +1 -1
- package/src/system-agents/thread-router.agent.ts +23 -20
- package/src/system-agents/title-generator.agent.ts +1 -1
- package/src/tools/execution-plan.tool.ts +36 -20
- package/src/tools/fetch-webpage.tool.ts +30 -22
- package/src/tools/firecrawl-client.ts +1 -6
- package/src/tools/plan-approval.tool.ts +9 -1
- 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 +26 -18
- package/src/tools/search.tool.ts +4 -5
- package/src/tools/team-think.tool.ts +139 -121
- package/src/utils/async.ts +15 -6
- package/src/utils/errors.ts +27 -15
- package/src/workers/bootstrap.ts +34 -58
- package/src/workers/memory-consolidation.worker.ts +4 -1
- package/src/workers/organization-learning.worker.ts +16 -3
- package/src/workers/regular-chat-memory-digest.helpers.ts +3 -4
- package/src/workers/regular-chat-memory-digest.runner.ts +46 -29
- package/src/workers/skill-extraction.runner.ts +13 -15
- package/src/workers/worker-utils.ts +14 -8
- package/src/config/search.ts +0 -3
- package/src/effect/awaitable-effect.ts +0 -87
- package/src/effect/runtime-ref.ts +0 -25
- package/src/effect/runtime.ts +0 -31
- package/src/redis/runtime-connection.ts +0 -10
- package/src/runtime/agent-types.ts +0 -1
package/src/db/service.ts
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import { Duration, Effect, Schedule } from 'effect'
|
|
1
|
+
import { Duration, Effect, Schedule, Semaphore } from 'effect'
|
|
2
2
|
import { BoundQuery, ServerError, Surreal, Table, createRemoteEngines } from 'surrealdb'
|
|
3
3
|
import type { ExprLike, SurrealTransaction, Values } from 'surrealdb'
|
|
4
4
|
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
|
-
import type { RecordIdInput } from './record-id'
|
|
12
|
-
import {
|
|
9
|
+
import type { RecordIdInput, ensureRecordId } from './record-id'
|
|
10
|
+
import { ensureRecordIdEffect } from './record-id'
|
|
13
11
|
import {
|
|
14
12
|
assertValidIdentifier,
|
|
15
13
|
buildBoundFilterClauses,
|
|
@@ -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 = {
|
|
@@ -71,24 +69,26 @@ type CreateBuilderSource = {
|
|
|
71
69
|
output: (mode: 'after' | 'before') => unknown
|
|
72
70
|
}
|
|
73
71
|
|
|
72
|
+
type PendingMutation = { mutation: RecordMutation; data: Record<string, unknown> }
|
|
73
|
+
|
|
74
74
|
export type CreateMutationBuilder = {
|
|
75
75
|
content: (data: Record<string, unknown>) => CreateMutationBuilder
|
|
76
|
-
output: (mode: 'after' | 'before') =>
|
|
76
|
+
output: (mode: 'after' | 'before') => Effect.Effect<unknown, SurrealDBError, never>
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
export interface DatabaseTransaction {
|
|
80
|
-
query: (query: unknown) =>
|
|
80
|
+
query: (query: unknown) => Effect.Effect<unknown, SurrealDBError, never>
|
|
81
81
|
create: (target: unknown) => CreateMutationBuilder
|
|
82
82
|
update: (target: unknown) => MutationBuilder
|
|
83
|
-
delete: (target: unknown) =>
|
|
83
|
+
delete: (target: unknown) => Effect.Effect<unknown, SurrealDBError, never>
|
|
84
84
|
relate: (
|
|
85
85
|
from: unknown,
|
|
86
86
|
edgeTable: unknown,
|
|
87
87
|
to: unknown,
|
|
88
88
|
data?: Values<Record<string, unknown>>,
|
|
89
|
-
) =>
|
|
90
|
-
commit: () =>
|
|
91
|
-
cancel: () =>
|
|
89
|
+
) => Effect.Effect<unknown, SurrealDBError, never>
|
|
90
|
+
commit: () => Effect.Effect<void, SurrealDBError, never>
|
|
91
|
+
cancel: () => Effect.Effect<void, SurrealDBError, never>
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
const CONNECT_MAX_ATTEMPTS = 5
|
|
@@ -106,22 +106,29 @@ function isRetriableConnectError(error: unknown): boolean {
|
|
|
106
106
|
)
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
function isSurrealDBError(error: unknown): error is SurrealDBError {
|
|
110
|
+
return typeof error === 'object' && error !== null && (error as { _tag?: unknown })._tag === 'SurrealDBError'
|
|
111
|
+
}
|
|
112
|
+
|
|
109
113
|
export class SurrealDBService {
|
|
110
114
|
private client: Surreal | null = null
|
|
111
115
|
private isConnected = false
|
|
116
|
+
// Single-permit semaphore acts as a mutex so only one connect attempt runs at a time.
|
|
117
|
+
// Subsequent waiters re-check `isConnected` after acquiring the permit and become no-ops.
|
|
118
|
+
private readonly connectMutex = Semaphore.makeUnsafe(1)
|
|
112
119
|
|
|
113
120
|
constructor(
|
|
114
121
|
private readonly config: SurrealDatabaseConfig,
|
|
115
122
|
private readonly logger?: SurrealDatabaseLogger,
|
|
116
123
|
) {}
|
|
117
124
|
|
|
118
|
-
private toSurrealError(error: unknown, query?: string): SurrealDBError
|
|
119
|
-
if (error
|
|
125
|
+
private toSurrealError(error: unknown, query?: string): SurrealDBError {
|
|
126
|
+
if (isSurrealDBError(error)) {
|
|
120
127
|
return error
|
|
121
128
|
}
|
|
122
129
|
|
|
123
130
|
if (error instanceof ZodError) {
|
|
124
|
-
return error
|
|
131
|
+
return new SurrealDBError({ message: error.message, query: query, cause: error })
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
if (error instanceof ServerError) {
|
|
@@ -151,33 +158,34 @@ export class SurrealDBService {
|
|
|
151
158
|
|
|
152
159
|
const codecOptions = { useNativeDates: true }
|
|
153
160
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
161
|
+
return Effect.gen(
|
|
162
|
+
function* (this: SurrealDBService) {
|
|
163
|
+
if (this.client) {
|
|
164
|
+
return this.client
|
|
165
|
+
}
|
|
159
166
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
167
|
+
if (this.isEmbeddedEngine(this.config.url)) {
|
|
168
|
+
const { createNodeEngines } = yield* Effect.tryPromise({
|
|
169
|
+
try: () => import('@surrealdb/node'),
|
|
170
|
+
catch: (error) =>
|
|
171
|
+
new SurrealDBError({
|
|
172
|
+
message: `Failed to load embedded SurrealDB engine: ${getErrorMessage(error)}`,
|
|
173
|
+
cause: error,
|
|
174
|
+
}),
|
|
175
|
+
})
|
|
176
|
+
this.client = new Surreal({
|
|
177
|
+
engines: { ...createRemoteEngines(), ...createNodeEngines() } as NonNullable<
|
|
178
|
+
ConstructorParameters<typeof Surreal>[0]
|
|
179
|
+
>['engines'],
|
|
180
|
+
codecOptions,
|
|
181
|
+
})
|
|
182
|
+
return this.client
|
|
183
|
+
}
|
|
177
184
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
185
|
+
this.client = new Surreal({ engines: createRemoteEngines(), codecOptions })
|
|
186
|
+
return this.client
|
|
187
|
+
}.bind(this),
|
|
188
|
+
)
|
|
181
189
|
}
|
|
182
190
|
|
|
183
191
|
private resetClient(): Effect.Effect<void, never> {
|
|
@@ -206,69 +214,89 @@ export class SurrealDBService {
|
|
|
206
214
|
)
|
|
207
215
|
}
|
|
208
216
|
|
|
209
|
-
connect():
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
+
}
|
|
213
225
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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),
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private connectLockedEffect(): Effect.Effect<void, SurrealDBError> {
|
|
235
|
+
return Effect.gen(
|
|
236
|
+
function* (this: SurrealDBService) {
|
|
237
|
+
if (this.isConnected) {
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const connectEffect = this.getOrCreateClient().pipe(
|
|
242
|
+
Effect.flatMap((client) =>
|
|
243
|
+
Effect.tryPromise({
|
|
244
|
+
try: () =>
|
|
245
|
+
client.connect(this.config.url, {
|
|
246
|
+
namespace: this.config.namespace,
|
|
247
|
+
database: this.config.database,
|
|
248
|
+
authentication: this.isEmbeddedEngine(this.config.url)
|
|
249
|
+
? undefined
|
|
250
|
+
: { username: this.config.username ?? '', password: this.config.password ?? '' },
|
|
251
|
+
}),
|
|
252
|
+
catch: (error) =>
|
|
253
|
+
new SurrealDBError({
|
|
254
|
+
message: `Failed to connect to SurrealDB (${this.config.url}): ${getErrorMessage(error)}`,
|
|
255
|
+
cause: error,
|
|
256
|
+
}),
|
|
257
|
+
}).pipe(
|
|
258
|
+
Effect.timeout(Duration.millis(CONNECT_ATTEMPT_TIMEOUT_MS)),
|
|
259
|
+
Effect.catchTag('TimeoutError', () =>
|
|
260
|
+
Effect.fail(new SurrealDBError({ message: `Timed out connecting to SurrealDB (${this.config.url})` })),
|
|
261
|
+
),
|
|
262
|
+
),
|
|
263
|
+
),
|
|
264
|
+
Effect.tap(() =>
|
|
265
|
+
Effect.sync(() => {
|
|
266
|
+
this.isConnected = true
|
|
267
|
+
this.logger?.info?.('Connected to SurrealDB')
|
|
229
268
|
}),
|
|
230
|
-
}).pipe(
|
|
231
|
-
Effect.timeout(Duration.millis(CONNECT_ATTEMPT_TIMEOUT_MS)),
|
|
232
|
-
Effect.catchTag('TimeoutError', () =>
|
|
233
|
-
Effect.fail(new SurrealDBError({ message: `Timed out connecting to SurrealDB (${this.config.url})` })),
|
|
234
269
|
),
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
),
|
|
248
|
-
Effect.retry({
|
|
249
|
-
times: CONNECT_MAX_ATTEMPTS - 1,
|
|
250
|
-
schedule: Schedule.jittered(Schedule.exponential(Duration.millis(CONNECT_RETRY_BASE_DELAY_MS), 2)),
|
|
251
|
-
while: isRetriableConnectError,
|
|
252
|
-
}),
|
|
253
|
-
Effect.asVoid,
|
|
254
|
-
)
|
|
270
|
+
Effect.tapError(() =>
|
|
271
|
+
Effect.sync(() => {
|
|
272
|
+
this.isConnected = false
|
|
273
|
+
}).pipe(Effect.andThen(this.resetClient())),
|
|
274
|
+
),
|
|
275
|
+
Effect.retry({
|
|
276
|
+
times: CONNECT_MAX_ATTEMPTS - 1,
|
|
277
|
+
schedule: Schedule.jittered(Schedule.exponential(Duration.millis(CONNECT_RETRY_BASE_DELAY_MS), 2)),
|
|
278
|
+
while: isRetriableConnectError,
|
|
279
|
+
}),
|
|
280
|
+
Effect.asVoid,
|
|
281
|
+
)
|
|
255
282
|
|
|
256
|
-
|
|
283
|
+
yield* connectEffect
|
|
284
|
+
}.bind(this),
|
|
285
|
+
)
|
|
257
286
|
}
|
|
258
287
|
|
|
259
|
-
disconnect():
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
if (!self.isConnected) {
|
|
288
|
+
disconnect(): Effect.Effect<void, SurrealDBError, never> {
|
|
289
|
+
return Effect.gen(
|
|
290
|
+
function* (this: SurrealDBService) {
|
|
291
|
+
if (!this.isConnected) {
|
|
264
292
|
return
|
|
265
293
|
}
|
|
266
294
|
|
|
267
|
-
|
|
295
|
+
this.isConnected = false
|
|
268
296
|
|
|
269
|
-
const client =
|
|
297
|
+
const client = this.client
|
|
270
298
|
if (!client) {
|
|
271
|
-
|
|
299
|
+
this.client = null
|
|
272
300
|
return
|
|
273
301
|
}
|
|
274
302
|
|
|
@@ -279,11 +307,11 @@ export class SurrealDBService {
|
|
|
279
307
|
}).pipe(
|
|
280
308
|
Effect.ensuring(
|
|
281
309
|
Effect.sync(() => {
|
|
282
|
-
|
|
310
|
+
this.client = null
|
|
283
311
|
}),
|
|
284
312
|
),
|
|
285
313
|
)
|
|
286
|
-
}),
|
|
314
|
+
}.bind(this),
|
|
287
315
|
)
|
|
288
316
|
}
|
|
289
317
|
|
|
@@ -292,7 +320,7 @@ export class SurrealDBService {
|
|
|
292
320
|
return this.connect().pipe(
|
|
293
321
|
Effect.flatMap(() => this.getOrCreateClient()),
|
|
294
322
|
Effect.mapError((error) =>
|
|
295
|
-
error
|
|
323
|
+
isSurrealDBError(error)
|
|
296
324
|
? error
|
|
297
325
|
: new SurrealDBError({ message: 'Database not connected', query, cause: error }),
|
|
298
326
|
),
|
|
@@ -302,7 +330,10 @@ export class SurrealDBService {
|
|
|
302
330
|
return Effect.succeed(this.client)
|
|
303
331
|
}
|
|
304
332
|
|
|
305
|
-
private
|
|
333
|
+
private normalizeRecordIdEffect(
|
|
334
|
+
id: unknown,
|
|
335
|
+
table: DatabaseTable,
|
|
336
|
+
): Effect.Effect<ReturnType<typeof ensureRecordId>, SurrealDBError> {
|
|
306
337
|
return normalizeRecordIdForTable(id, table)
|
|
307
338
|
}
|
|
308
339
|
|
|
@@ -310,14 +341,35 @@ export class SurrealDBService {
|
|
|
310
341
|
return normalizeQueryRows(statement, schema, (schemaValue, value) => this.parseSchema(schemaValue, value))
|
|
311
342
|
}
|
|
312
343
|
|
|
344
|
+
private normalizeQueryRowsEffect(
|
|
345
|
+
statement: unknown,
|
|
346
|
+
schema?: z.ZodTypeAny,
|
|
347
|
+
): Effect.Effect<unknown[], SurrealDBError> {
|
|
348
|
+
return Effect.try({
|
|
349
|
+
try: () => this.normalizeQueryRows(statement, schema),
|
|
350
|
+
catch: (error) => this.toSurrealError(error),
|
|
351
|
+
})
|
|
352
|
+
}
|
|
353
|
+
|
|
313
354
|
private normalizeParseValue(value: unknown): unknown {
|
|
314
355
|
return normalizeSurrealValue(value)
|
|
315
356
|
}
|
|
316
357
|
|
|
358
|
+
private normalizeParseValueEffect(value: unknown): Effect.Effect<unknown, SurrealDBError> {
|
|
359
|
+
return Effect.try({ try: () => this.normalizeParseValue(value), catch: (error) => this.toSurrealError(error) })
|
|
360
|
+
}
|
|
361
|
+
|
|
317
362
|
private parseSchema<TSchema extends z.ZodTypeAny>(schema: TSchema, value: unknown): z.infer<TSchema> {
|
|
318
363
|
return schema.parse(this.normalizeParseValue(value))
|
|
319
364
|
}
|
|
320
365
|
|
|
366
|
+
private parseSchemaEffect<TSchema extends z.ZodTypeAny>(
|
|
367
|
+
schema: TSchema,
|
|
368
|
+
value: unknown,
|
|
369
|
+
): Effect.Effect<z.infer<TSchema>, SurrealDBError> {
|
|
370
|
+
return Effect.try({ try: () => this.parseSchema(schema, value), catch: (error) => this.toSurrealError(error) })
|
|
371
|
+
}
|
|
372
|
+
|
|
321
373
|
private parseOptionalSchema<TSchema extends z.ZodTypeAny>(schema: TSchema, value: unknown): z.infer<TSchema> | null {
|
|
322
374
|
if (value === null || value === undefined) {
|
|
323
375
|
return null
|
|
@@ -326,22 +378,43 @@ export class SurrealDBService {
|
|
|
326
378
|
return this.parseSchema(schema, value)
|
|
327
379
|
}
|
|
328
380
|
|
|
381
|
+
private parseOptionalSchemaEffect<TSchema extends z.ZodTypeAny>(
|
|
382
|
+
schema: TSchema,
|
|
383
|
+
value: unknown,
|
|
384
|
+
): Effect.Effect<z.infer<TSchema> | null, SurrealDBError> {
|
|
385
|
+
return Effect.try({
|
|
386
|
+
try: () => this.parseOptionalSchema(schema, value),
|
|
387
|
+
catch: (error) => this.toSurrealError(error),
|
|
388
|
+
})
|
|
389
|
+
}
|
|
390
|
+
|
|
329
391
|
private buildFilterExpression(filter: Record<string, unknown>): ExprLike | undefined {
|
|
330
392
|
return buildFilterExpression(filter)
|
|
331
393
|
}
|
|
332
394
|
|
|
333
|
-
private
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
395
|
+
private buildFilterExpressionEffect(
|
|
396
|
+
filter: Record<string, unknown>,
|
|
397
|
+
): Effect.Effect<ExprLike | undefined, SurrealDBError> {
|
|
398
|
+
return Effect.try({ try: () => this.buildFilterExpression(filter), catch: (error) => this.toSurrealError(error) })
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
private buildBoundFilterClausesEffect(
|
|
402
|
+
filter: Record<string, unknown>,
|
|
403
|
+
): Effect.Effect<{ clause: string; bindings: Record<string, unknown> }, SurrealDBError> {
|
|
337
404
|
return buildBoundFilterClauses(filter)
|
|
338
405
|
}
|
|
339
406
|
|
|
340
|
-
private
|
|
407
|
+
private assertValidIdentifierEffect(name: string, context: string): Effect.Effect<void, SurrealDBError> {
|
|
408
|
+
return assertValidIdentifier(name, context)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
private normalizeBoundQueryEffect<T extends unknown[] = unknown[]>(
|
|
412
|
+
query: BoundQuery<T>,
|
|
413
|
+
): Effect.Effect<BoundQuery<T>, SurrealDBError> {
|
|
341
414
|
return normalizeBoundQuery(query)
|
|
342
415
|
}
|
|
343
416
|
|
|
344
|
-
private
|
|
417
|
+
private normalizeTransactionQueryEffect(query: unknown): Effect.Effect<BoundQuery, SurrealDBError> {
|
|
345
418
|
return normalizeTransactionQuery(query)
|
|
346
419
|
}
|
|
347
420
|
|
|
@@ -349,58 +422,94 @@ export class SurrealDBService {
|
|
|
349
422
|
return normalizeMutationData(data)
|
|
350
423
|
}
|
|
351
424
|
|
|
352
|
-
private
|
|
425
|
+
private normalizeMutationDataEffect(
|
|
426
|
+
data: Record<string, unknown>,
|
|
427
|
+
): Effect.Effect<Record<string, unknown>, SurrealDBError> {
|
|
428
|
+
return Effect.try({ try: () => this.normalizeMutationData(data), catch: (error) => this.toSurrealError(error) })
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private normalizeTableValueEffect(value: unknown): Effect.Effect<Table<string>, SurrealDBError> {
|
|
353
432
|
return normalizeTableValue(value)
|
|
354
433
|
}
|
|
355
434
|
|
|
356
|
-
private
|
|
435
|
+
private normalizeCreateTargetEffect(
|
|
436
|
+
value: unknown,
|
|
437
|
+
): Effect.Effect<Table<string> | ReturnType<typeof ensureRecordId>, SurrealDBError> {
|
|
357
438
|
return normalizeCreateTarget(value)
|
|
358
439
|
}
|
|
359
440
|
|
|
360
|
-
private
|
|
441
|
+
private normalizeTransactionRecordIdEffect(
|
|
442
|
+
value: unknown,
|
|
443
|
+
context: string,
|
|
444
|
+
): Effect.Effect<ReturnType<typeof ensureRecordId>, SurrealDBError> {
|
|
445
|
+
return normalizeTransactionRecordId(value, context)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private wrapMutationBuilder(
|
|
449
|
+
builderEffect: Effect.Effect<MutationBuilderSource, SurrealDBError>,
|
|
450
|
+
pendingMutation?: PendingMutation,
|
|
451
|
+
): MutationBuilder {
|
|
361
452
|
return {
|
|
362
|
-
content: (data) => this.wrapMutationBuilder(
|
|
363
|
-
replace: (data) => this.wrapMutationBuilder(
|
|
364
|
-
merge: (data) => this.wrapMutationBuilder(
|
|
453
|
+
content: (data) => this.wrapMutationBuilder(builderEffect, { mutation: 'content', data }),
|
|
454
|
+
replace: (data) => this.wrapMutationBuilder(builderEffect, { mutation: 'replace', data }),
|
|
455
|
+
merge: (data) => this.wrapMutationBuilder(builderEffect, { mutation: 'merge', data }),
|
|
365
456
|
output: (mode) =>
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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),
|
|
375
475
|
),
|
|
376
476
|
}
|
|
377
477
|
}
|
|
378
478
|
|
|
379
|
-
private wrapCreateBuilder(
|
|
479
|
+
private wrapCreateBuilder(
|
|
480
|
+
builderEffect: Effect.Effect<CreateBuilderSource, SurrealDBError>,
|
|
481
|
+
pendingData?: Record<string, unknown>,
|
|
482
|
+
): CreateMutationBuilder {
|
|
380
483
|
return {
|
|
381
|
-
content: (data) => this.wrapCreateBuilder(
|
|
484
|
+
content: (data) => this.wrapCreateBuilder(builderEffect, data),
|
|
382
485
|
output: (mode) =>
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
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),
|
|
392
502
|
),
|
|
393
503
|
}
|
|
394
504
|
}
|
|
395
505
|
|
|
396
506
|
private wrapTransaction(tx: SurrealTransaction): DatabaseTransaction {
|
|
397
|
-
const self = this
|
|
398
507
|
return {
|
|
399
|
-
query: (query: unknown) =>
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
508
|
+
query: (query: unknown) =>
|
|
509
|
+
Effect.gen(
|
|
510
|
+
function* (this: SurrealDBService) {
|
|
511
|
+
const boundQuery = yield* this.normalizeTransactionQueryEffect(query)
|
|
512
|
+
const queryText = this.resolveQueryText(boundQuery)
|
|
404
513
|
const responses = yield* Effect.tryPromise({
|
|
405
514
|
try: () => tx.query(boundQuery).responses(),
|
|
406
515
|
catch: (error) =>
|
|
@@ -418,62 +527,74 @@ export class SurrealDBService {
|
|
|
418
527
|
return yield* new SurrealDBError({ message: first.error.message, query: queryText, cause: first.error })
|
|
419
528
|
}
|
|
420
529
|
|
|
421
|
-
return
|
|
422
|
-
}),
|
|
423
|
-
)
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
530
|
+
return yield* this.normalizeQueryRowsEffect(first.result)
|
|
531
|
+
}.bind(this),
|
|
532
|
+
),
|
|
533
|
+
create: (target: unknown) =>
|
|
534
|
+
this.wrapCreateBuilder(
|
|
535
|
+
Effect.gen(
|
|
536
|
+
function* (this: SurrealDBService) {
|
|
537
|
+
const normalizedTarget = yield* this.normalizeCreateTargetEffect(target)
|
|
538
|
+
return normalizedTarget instanceof Table ? tx.create(normalizedTarget) : tx.create(normalizedTarget)
|
|
539
|
+
}.bind(this),
|
|
540
|
+
),
|
|
541
|
+
),
|
|
431
542
|
update: (target: unknown) =>
|
|
432
|
-
|
|
543
|
+
this.wrapMutationBuilder(
|
|
544
|
+
Effect.gen(
|
|
545
|
+
function* (this: SurrealDBService) {
|
|
546
|
+
const recordId = yield* this.normalizeTransactionRecordIdEffect(target, 'transaction update')
|
|
547
|
+
return tx.update(recordId)
|
|
548
|
+
}.bind(this),
|
|
549
|
+
),
|
|
550
|
+
),
|
|
433
551
|
delete: (target: unknown) =>
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
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),
|
|
443
565
|
),
|
|
444
566
|
relate: (from: unknown, edgeTable: unknown, to: unknown, data?: Values<Record<string, unknown>>) =>
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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),
|
|
460
585
|
),
|
|
461
586
|
commit: () =>
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}),
|
|
468
|
-
),
|
|
587
|
+
Effect.tryPromise({
|
|
588
|
+
try: () => tx.commit(),
|
|
589
|
+
catch: (error) =>
|
|
590
|
+
new SurrealDBError({ message: `Failed to commit transaction: ${getErrorMessage(error)}`, cause: error }),
|
|
591
|
+
}),
|
|
469
592
|
cancel: () =>
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}),
|
|
476
|
-
),
|
|
593
|
+
Effect.tryPromise({
|
|
594
|
+
try: () => tx.cancel(),
|
|
595
|
+
catch: (error) =>
|
|
596
|
+
new SurrealDBError({ message: `Failed to cancel transaction: ${getErrorMessage(error)}`, cause: error }),
|
|
597
|
+
}),
|
|
477
598
|
}
|
|
478
599
|
}
|
|
479
600
|
|
|
@@ -481,24 +602,22 @@ export class SurrealDBService {
|
|
|
481
602
|
return query.query
|
|
482
603
|
}
|
|
483
604
|
|
|
484
|
-
query<T>(query: BoundQuery):
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
const
|
|
489
|
-
const statements = yield* self.queryAll<T>(boundQuery)
|
|
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)
|
|
490
610
|
return statements.at(0) ?? []
|
|
491
|
-
}),
|
|
611
|
+
}.bind(this),
|
|
492
612
|
)
|
|
493
613
|
}
|
|
494
614
|
|
|
495
|
-
queryAll<T>(query: BoundQuery, schema?: z.ZodTypeAny):
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
const
|
|
501
|
-
const client = yield* self.ensureConnectedEffect(queryText)
|
|
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)
|
|
502
621
|
const responses = yield* Effect.tryPromise({
|
|
503
622
|
try: () => client.query(boundQuery).responses(),
|
|
504
623
|
catch: (error) =>
|
|
@@ -509,77 +628,78 @@ export class SurrealDBService {
|
|
|
509
628
|
}),
|
|
510
629
|
})
|
|
511
630
|
return yield* Effect.forEach(responses, (response, index) =>
|
|
512
|
-
Effect.gen(
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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
|
+
),
|
|
523
644
|
)
|
|
524
|
-
}),
|
|
645
|
+
}.bind(this),
|
|
525
646
|
)
|
|
526
647
|
}
|
|
527
648
|
|
|
528
|
-
queryOne<T extends z.ZodTypeAny>(
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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)
|
|
534
657
|
const first = results.at(0)
|
|
535
|
-
return first ?
|
|
536
|
-
}),
|
|
658
|
+
return first ? yield* this.parseSchemaEffect(schema, first) : null
|
|
659
|
+
}.bind(this),
|
|
537
660
|
)
|
|
538
661
|
}
|
|
539
662
|
|
|
540
|
-
queryMany<T extends z.ZodTypeAny>(query: BoundQuery, schema: T):
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
}),
|
|
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),
|
|
548
670
|
)
|
|
549
671
|
}
|
|
550
672
|
|
|
551
673
|
private runSqlFile(file: Bun.BunFile): Effect.Effect<void, SurrealDBError> {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
yield* self.queryAll<unknown>(new BoundQuery(sql))
|
|
564
|
-
})
|
|
565
|
-
}
|
|
674
|
+
return Effect.gen(
|
|
675
|
+
function* (this: SurrealDBService) {
|
|
676
|
+
const sql = (yield* Effect.tryPromise({
|
|
677
|
+
try: () => file.text(),
|
|
678
|
+
catch: (error) =>
|
|
679
|
+
new SurrealDBError({ message: `Failed to read schema file: ${getErrorMessage(error)}`, cause: error }),
|
|
680
|
+
})).trim()
|
|
681
|
+
if (!sql) {
|
|
682
|
+
return
|
|
683
|
+
}
|
|
566
684
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
Effect.forEach(schemaFiles, (schemaFile) => this.runSqlFile(schemaFile), { discard: true }),
|
|
685
|
+
yield* this.queryAll<unknown>(new BoundQuery(sql))
|
|
686
|
+
}.bind(this),
|
|
570
687
|
)
|
|
571
688
|
}
|
|
572
689
|
|
|
690
|
+
applySchema(schemaFiles: readonly Bun.BunFile[]): Effect.Effect<void, SurrealDBError, never> {
|
|
691
|
+
return Effect.forEach(schemaFiles, (schemaFile) => this.runSqlFile(schemaFile), { discard: true })
|
|
692
|
+
}
|
|
693
|
+
|
|
573
694
|
findOne<T extends z.ZodTypeAny>(
|
|
574
695
|
table: DatabaseTable,
|
|
575
696
|
filter: Record<string, unknown>,
|
|
576
697
|
schema: T,
|
|
577
|
-
):
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
const
|
|
582
|
-
const client = yield* self.ensureConnectedEffect(`SELECT * FROM ${table} LIMIT 1`)
|
|
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`)
|
|
583
703
|
let query = client.select<unknown>(new Table(table))
|
|
584
704
|
if (selection) {
|
|
585
705
|
query = query.where(selection)
|
|
@@ -594,8 +714,8 @@ export class SurrealDBService {
|
|
|
594
714
|
}),
|
|
595
715
|
})
|
|
596
716
|
const first = rows.at(0)
|
|
597
|
-
return first ?
|
|
598
|
-
}),
|
|
717
|
+
return first ? yield* this.parseSchemaEffect(schema, first) : null
|
|
718
|
+
}.bind(this),
|
|
599
719
|
)
|
|
600
720
|
}
|
|
601
721
|
|
|
@@ -604,19 +724,18 @@ export class SurrealDBService {
|
|
|
604
724
|
filter: Record<string, unknown>,
|
|
605
725
|
schema: T,
|
|
606
726
|
options?: FindManyOptions,
|
|
607
|
-
):
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
Effect.gen(function* () {
|
|
727
|
+
): Effect.Effect<z.infer<T>[], SurrealDBError, never> {
|
|
728
|
+
return Effect.gen(
|
|
729
|
+
function* (this: SurrealDBService) {
|
|
611
730
|
const filterKeys = Object.keys(filter)
|
|
612
|
-
const selection =
|
|
731
|
+
const selection = yield* this.buildFilterExpressionEffect(filter)
|
|
613
732
|
const orderBy = options?.orderBy
|
|
614
733
|
|
|
615
734
|
if (orderBy !== undefined) {
|
|
616
|
-
|
|
617
|
-
|
|
735
|
+
yield* this.assertValidIdentifierEffect(orderBy, 'orderBy field')
|
|
736
|
+
yield* this.assertValidIdentifierEffect(table, 'table name')
|
|
618
737
|
for (const key of filterKeys) {
|
|
619
|
-
|
|
738
|
+
yield* this.assertValidIdentifierEffect(key, 'filter field')
|
|
620
739
|
}
|
|
621
740
|
const rawOrderDir: unknown = options?.orderDir
|
|
622
741
|
if (rawOrderDir !== undefined && rawOrderDir !== 'ASC' && rawOrderDir !== 'DESC') {
|
|
@@ -627,7 +746,7 @@ export class SurrealDBService {
|
|
|
627
746
|
const orderDir = rawOrderDir ?? 'ASC'
|
|
628
747
|
const limit = options?.limit
|
|
629
748
|
const offset = options?.offset
|
|
630
|
-
const vars: Record<string, unknown> =
|
|
749
|
+
const vars: Record<string, unknown> = yield* this.normalizeMutationDataEffect(filter)
|
|
631
750
|
let sql = `SELECT * FROM ${table}`
|
|
632
751
|
if (filterKeys.length > 0) {
|
|
633
752
|
const conditions = filterKeys.map((key) => `${key} = $${key}`).join(' AND ')
|
|
@@ -642,11 +761,11 @@ export class SurrealDBService {
|
|
|
642
761
|
sql += ' START $offsetParam'
|
|
643
762
|
vars.offsetParam = offset
|
|
644
763
|
}
|
|
645
|
-
const rows = yield*
|
|
646
|
-
return
|
|
764
|
+
const rows = yield* this.query<unknown>(new BoundQuery(sql, vars))
|
|
765
|
+
return yield* Effect.forEach(rows, (row) => this.parseSchemaEffect(schema, row))
|
|
647
766
|
}
|
|
648
767
|
|
|
649
|
-
const client = yield*
|
|
768
|
+
const client = yield* this.ensureConnectedEffect(`SELECT * FROM ${table}`)
|
|
650
769
|
let query = client.select<unknown>(new Table(table))
|
|
651
770
|
if (selection) {
|
|
652
771
|
query = query.where(selection)
|
|
@@ -666,8 +785,8 @@ export class SurrealDBService {
|
|
|
666
785
|
cause: error,
|
|
667
786
|
}),
|
|
668
787
|
})
|
|
669
|
-
return
|
|
670
|
-
}),
|
|
788
|
+
return yield* Effect.forEach(rows, (row) => this.parseSchemaEffect(schema, row))
|
|
789
|
+
}.bind(this),
|
|
671
790
|
)
|
|
672
791
|
}
|
|
673
792
|
|
|
@@ -675,18 +794,18 @@ export class SurrealDBService {
|
|
|
675
794
|
table: DatabaseTable,
|
|
676
795
|
data: Record<string, unknown>,
|
|
677
796
|
schema: T,
|
|
678
|
-
):
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
Effect.gen(function* () {
|
|
797
|
+
): Effect.Effect<z.infer<T>, SurrealDBError, never> {
|
|
798
|
+
return Effect.gen(
|
|
799
|
+
function* (this: SurrealDBService) {
|
|
682
800
|
const keys = Object.keys(data)
|
|
683
801
|
if (keys.length === 0) {
|
|
684
802
|
return yield* new SurrealDBError({ message: `Cannot create record in ${table} with empty data` })
|
|
685
803
|
}
|
|
686
804
|
|
|
687
|
-
const client = yield*
|
|
805
|
+
const client = yield* this.ensureConnectedEffect(`CREATE ${table}`)
|
|
806
|
+
const normalizedData = yield* this.normalizeMutationDataEffect(data)
|
|
688
807
|
const created = yield* Effect.tryPromise({
|
|
689
|
-
try: () => client.create<unknown>(new Table(table)).content(
|
|
808
|
+
try: () => client.create<unknown>(new Table(table)).content(normalizedData).output('after'),
|
|
690
809
|
catch: (error) =>
|
|
691
810
|
new SurrealDBError({
|
|
692
811
|
message: `Failed to create record in ${table}: ${getErrorMessage(error)}`,
|
|
@@ -699,8 +818,8 @@ export class SurrealDBService {
|
|
|
699
818
|
return yield* new SurrealDBError({ message: `Failed to create record in ${table}` })
|
|
700
819
|
}
|
|
701
820
|
|
|
702
|
-
return
|
|
703
|
-
}),
|
|
821
|
+
return yield* this.parseSchemaEffect(schema, first)
|
|
822
|
+
}.bind(this),
|
|
704
823
|
)
|
|
705
824
|
}
|
|
706
825
|
|
|
@@ -709,27 +828,27 @@ export class SurrealDBService {
|
|
|
709
828
|
id: unknown,
|
|
710
829
|
data: Record<string, unknown>,
|
|
711
830
|
schema: T,
|
|
712
|
-
):
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
const recordId = self.normalizeRecordId(id, table)
|
|
831
|
+
): Effect.Effect<z.infer<T>, SurrealDBError, never> {
|
|
832
|
+
return Effect.gen(
|
|
833
|
+
function* (this: SurrealDBService) {
|
|
834
|
+
const recordId = yield* this.normalizeRecordIdEffect(id, table)
|
|
717
835
|
const keys = Object.keys(data)
|
|
718
836
|
if (keys.length === 0) {
|
|
719
837
|
return yield* new SurrealDBError({ message: `Cannot create record in ${table} with empty data` })
|
|
720
838
|
}
|
|
721
839
|
|
|
722
|
-
const client = yield*
|
|
840
|
+
const client = yield* this.ensureConnectedEffect(`CREATE ${recordId.toString()}`)
|
|
841
|
+
const normalizedData = yield* this.normalizeMutationDataEffect(data)
|
|
723
842
|
const created = yield* Effect.tryPromise({
|
|
724
|
-
try: () => client.create<unknown>(recordId).content(
|
|
843
|
+
try: () => client.create<unknown>(recordId).content(normalizedData).output('after'),
|
|
725
844
|
catch: (error) =>
|
|
726
845
|
new SurrealDBError({
|
|
727
846
|
message: `Failed to create record in ${table}: ${getErrorMessage(error)}`,
|
|
728
847
|
cause: error,
|
|
729
848
|
}),
|
|
730
849
|
})
|
|
731
|
-
return
|
|
732
|
-
}),
|
|
850
|
+
return yield* this.parseSchemaEffect(schema, created)
|
|
851
|
+
}.bind(this),
|
|
733
852
|
)
|
|
734
853
|
}
|
|
735
854
|
|
|
@@ -739,29 +858,29 @@ export class SurrealDBService {
|
|
|
739
858
|
data: Record<string, unknown>,
|
|
740
859
|
schema: T,
|
|
741
860
|
options?: { mutation?: RecordMutation },
|
|
742
|
-
):
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
const recordId = self.normalizeRecordId(id, table)
|
|
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)
|
|
747
865
|
const keys = Object.keys(data)
|
|
748
866
|
if (keys.length === 0) {
|
|
749
867
|
return yield* new SurrealDBError({ message: 'Cannot update record with empty data' })
|
|
750
868
|
}
|
|
751
869
|
|
|
752
870
|
const mutation = options?.mutation ?? 'merge'
|
|
753
|
-
const client = yield*
|
|
871
|
+
const client = yield* this.ensureConnectedEffect(`UPDATE ${recordId.toString()}`)
|
|
754
872
|
const builder = client.update<unknown>(recordId)
|
|
873
|
+
const normalizedData = yield* this.normalizeMutationDataEffect(data)
|
|
755
874
|
const updated: Record<string, unknown> | null | undefined = yield* Effect.tryPromise({
|
|
756
|
-
try: () => configureMutation(builder, mutation,
|
|
875
|
+
try: () => configureMutation(builder, mutation, normalizedData).output('after'),
|
|
757
876
|
catch: (error) =>
|
|
758
877
|
new SurrealDBError({
|
|
759
878
|
message: `Failed to update record in ${table}: ${getErrorMessage(error)}`,
|
|
760
879
|
cause: error,
|
|
761
880
|
}),
|
|
762
881
|
})
|
|
763
|
-
return
|
|
764
|
-
}),
|
|
882
|
+
return yield* this.parseOptionalSchemaEffect(schema, updated)
|
|
883
|
+
}.bind(this),
|
|
765
884
|
)
|
|
766
885
|
}
|
|
767
886
|
|
|
@@ -771,83 +890,84 @@ export class SurrealDBService {
|
|
|
771
890
|
data: Record<string, unknown>,
|
|
772
891
|
schema: T,
|
|
773
892
|
options?: { mutation?: RecordMutation },
|
|
774
|
-
):
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
const recordId = self.normalizeRecordId(id, table)
|
|
893
|
+
): Effect.Effect<z.infer<T>, SurrealDBError, never> {
|
|
894
|
+
return Effect.gen(
|
|
895
|
+
function* (this: SurrealDBService) {
|
|
896
|
+
const recordId = yield* this.normalizeRecordIdEffect(id, table)
|
|
779
897
|
const keys = Object.keys(data)
|
|
780
898
|
if (keys.length === 0) {
|
|
781
899
|
return yield* new SurrealDBError({ message: 'Cannot upsert record with empty data' })
|
|
782
900
|
}
|
|
783
901
|
|
|
784
902
|
const mutation = options?.mutation ?? 'merge'
|
|
785
|
-
const client = yield*
|
|
903
|
+
const client = yield* this.ensureConnectedEffect(`UPSERT ${recordId.toString()}`)
|
|
786
904
|
const builder = client.upsert<unknown>(recordId)
|
|
905
|
+
const normalizedData = yield* this.normalizeMutationDataEffect(data)
|
|
787
906
|
const upserted: Record<string, unknown> | null | undefined = yield* Effect.tryPromise({
|
|
788
|
-
try: () => configureMutation(builder, mutation,
|
|
907
|
+
try: () => configureMutation(builder, mutation, normalizedData).output('after'),
|
|
789
908
|
catch: (error) =>
|
|
790
909
|
new SurrealDBError({
|
|
791
910
|
message: `Failed to upsert record in ${table}: ${getErrorMessage(error)}`,
|
|
792
911
|
cause: error,
|
|
793
912
|
}),
|
|
794
913
|
})
|
|
795
|
-
const parsed =
|
|
914
|
+
const parsed = yield* this.parseOptionalSchemaEffect(schema, upserted)
|
|
796
915
|
if (parsed === null) {
|
|
797
916
|
return yield* new SurrealDBError({ message: `Failed to upsert record in ${table}` })
|
|
798
917
|
}
|
|
799
918
|
return parsed
|
|
800
|
-
}),
|
|
919
|
+
}.bind(this),
|
|
801
920
|
)
|
|
802
921
|
}
|
|
803
922
|
|
|
804
|
-
deleteById(table: DatabaseTable, id: unknown):
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
return yield* self.query<unknown>(new BoundQuery(`DELETE $recordId RETURN BEFORE`, { recordId })).pipe(
|
|
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(
|
|
810
928
|
Effect.map((result) => result.length > 0),
|
|
811
929
|
Effect.catchTag('SurrealDBError', (error) => {
|
|
812
930
|
if (error.message.includes('does not exist')) {
|
|
813
931
|
return Effect.succeed(false)
|
|
814
932
|
}
|
|
815
933
|
|
|
816
|
-
return Effect.fail(
|
|
934
|
+
return Effect.fail(this.toSurrealError(error, `DELETE ${recordId.toString()}`))
|
|
817
935
|
}),
|
|
818
936
|
)
|
|
819
|
-
}),
|
|
937
|
+
}.bind(this),
|
|
820
938
|
)
|
|
821
939
|
}
|
|
822
940
|
|
|
823
|
-
deleteWhere(table: DatabaseTable, filter: Record<string, unknown>):
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
assertValidIdentifier(table, 'table name')
|
|
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')
|
|
828
945
|
const filterKeys = Object.keys(filter)
|
|
829
946
|
if (filterKeys.length === 0) {
|
|
830
947
|
return yield* new SurrealDBError({ message: `Refusing to delete all records in ${table} without a filter` })
|
|
831
948
|
}
|
|
832
|
-
const { clause, bindings } =
|
|
833
|
-
return yield*
|
|
834
|
-
Effect.gen(
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
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
|
+
),
|
|
849
969
|
)
|
|
850
|
-
}),
|
|
970
|
+
}.bind(this),
|
|
851
971
|
)
|
|
852
972
|
}
|
|
853
973
|
|
|
@@ -855,10 +975,9 @@ export class SurrealDBService {
|
|
|
855
975
|
table: DatabaseTable,
|
|
856
976
|
where: ExprLike,
|
|
857
977
|
data: Record<string, unknown>,
|
|
858
|
-
):
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
Effect.gen(function* () {
|
|
978
|
+
): Effect.Effect<number, SurrealDBError, never> {
|
|
979
|
+
return Effect.gen(
|
|
980
|
+
function* (this: SurrealDBService) {
|
|
862
981
|
if (!where) {
|
|
863
982
|
return yield* new SurrealDBError({ message: `Refusing to update records in ${table} without a where clause` })
|
|
864
983
|
}
|
|
@@ -867,14 +986,10 @@ export class SurrealDBService {
|
|
|
867
986
|
return yield* new SurrealDBError({ message: `Cannot update records in ${table} with empty data` })
|
|
868
987
|
}
|
|
869
988
|
|
|
870
|
-
const client = yield*
|
|
989
|
+
const client = yield* this.ensureConnectedEffect(`UPDATE ${table} WHERE ...`)
|
|
990
|
+
const normalizedData = yield* this.normalizeMutationDataEffect(data)
|
|
871
991
|
const updated = yield* Effect.tryPromise({
|
|
872
|
-
try: () =>
|
|
873
|
-
client
|
|
874
|
-
.update<unknown>(new Table(table))
|
|
875
|
-
.where(where)
|
|
876
|
-
.merge(self.normalizeMutationData(data))
|
|
877
|
-
.output('after'),
|
|
992
|
+
try: () => client.update<unknown>(new Table(table)).where(where).merge(normalizedData).output('after'),
|
|
878
993
|
catch: (error) =>
|
|
879
994
|
new SurrealDBError({
|
|
880
995
|
message: `Failed to update records in ${table}: ${getErrorMessage(error)}`,
|
|
@@ -882,21 +997,20 @@ export class SurrealDBService {
|
|
|
882
997
|
}),
|
|
883
998
|
})
|
|
884
999
|
return Array.isArray(updated) ? updated.length : 1
|
|
885
|
-
}),
|
|
1000
|
+
}.bind(this),
|
|
886
1001
|
)
|
|
887
1002
|
}
|
|
888
1003
|
|
|
889
1004
|
insert<T extends Record<string, unknown>>(
|
|
890
1005
|
table: DatabaseTable,
|
|
891
1006
|
data: Values<T> | Array<Values<T>>,
|
|
892
|
-
):
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
const client = yield* self.ensureConnectedEffect(`INSERT ${table}`)
|
|
1007
|
+
): Effect.Effect<T[], SurrealDBError, never> {
|
|
1008
|
+
return Effect.gen(
|
|
1009
|
+
function* (this: SurrealDBService) {
|
|
1010
|
+
const client = yield* this.ensureConnectedEffect(`INSERT ${table}`)
|
|
897
1011
|
const normalized = Array.isArray(data)
|
|
898
|
-
?
|
|
899
|
-
:
|
|
1012
|
+
? yield* Effect.forEach(data, (item) => this.normalizeMutationDataEffect(item as Record<string, unknown>))
|
|
1013
|
+
: yield* this.normalizeMutationDataEffect(data as Record<string, unknown>)
|
|
900
1014
|
const inserted = yield* Effect.tryPromise({
|
|
901
1015
|
try: () => client.insert<T>(new Table(table), normalized as Values<T> | Array<Values<T>>).output('after'),
|
|
902
1016
|
catch: (error) =>
|
|
@@ -906,7 +1020,7 @@ export class SurrealDBService {
|
|
|
906
1020
|
}),
|
|
907
1021
|
})
|
|
908
1022
|
return inserted as T[]
|
|
909
|
-
}),
|
|
1023
|
+
}.bind(this),
|
|
910
1024
|
)
|
|
911
1025
|
}
|
|
912
1026
|
|
|
@@ -915,18 +1029,16 @@ export class SurrealDBService {
|
|
|
915
1029
|
edgeTable: DatabaseTable,
|
|
916
1030
|
to: RecordIdInput,
|
|
917
1031
|
data?: Values<T>,
|
|
918
|
-
):
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
Effect.gen(function* () {
|
|
925
|
-
const client = yield* self.ensureConnectedEffect(
|
|
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(
|
|
926
1038
|
`RELATE ${fromRef.toString()}->${edgeTable}->${toRef.toString()}`,
|
|
927
1039
|
)
|
|
928
1040
|
const normalizedData = data
|
|
929
|
-
? (
|
|
1041
|
+
? ((yield* this.normalizeMutationDataEffect(data as Record<string, unknown>)) as Values<T>)
|
|
930
1042
|
: undefined
|
|
931
1043
|
const related = (yield* Effect.tryPromise({
|
|
932
1044
|
try: () => client.relate<T>(fromRef, new Table(edgeTable), toRef, normalizedData).output('after'),
|
|
@@ -943,45 +1055,45 @@ export class SurrealDBService {
|
|
|
943
1055
|
return related.at(0) ?? null
|
|
944
1056
|
}
|
|
945
1057
|
return related
|
|
946
|
-
}),
|
|
1058
|
+
}.bind(this),
|
|
947
1059
|
)
|
|
948
1060
|
}
|
|
949
1061
|
|
|
950
|
-
beginTransaction():
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
const client = yield* self.ensureConnectedEffect('BEGIN TRANSACTION')
|
|
1062
|
+
beginTransaction(): Effect.Effect<DatabaseTransaction, SurrealDBError, never> {
|
|
1063
|
+
return Effect.gen(
|
|
1064
|
+
function* (this: SurrealDBService) {
|
|
1065
|
+
const client = yield* this.ensureConnectedEffect('BEGIN TRANSACTION')
|
|
955
1066
|
const tx = yield* Effect.tryPromise({
|
|
956
1067
|
try: () => client.beginTransaction(),
|
|
957
1068
|
catch: (error) =>
|
|
958
1069
|
new SurrealDBError({ message: `Failed to begin transaction: ${getErrorMessage(error)}`, cause: error }),
|
|
959
1070
|
})
|
|
960
|
-
return
|
|
961
|
-
}),
|
|
1071
|
+
return this.wrapTransaction(tx)
|
|
1072
|
+
}.bind(this),
|
|
962
1073
|
)
|
|
963
1074
|
}
|
|
964
1075
|
|
|
965
1076
|
withTransaction<T, E, R>(
|
|
966
1077
|
work: (tx: DatabaseTransaction) => Effect.Effect<T, E, R>,
|
|
967
1078
|
): Effect.Effect<T, SurrealDBError | E, R> {
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1079
|
+
return Effect.gen(
|
|
1080
|
+
function* (this: SurrealDBService) {
|
|
1081
|
+
const tx = yield* this.beginTransaction()
|
|
1082
|
+
const cancelEffect = tx.cancel().pipe(
|
|
1083
|
+
Effect.catch((cancelError: SurrealDBError) =>
|
|
1084
|
+
Effect.sync(() => {
|
|
1085
|
+
this.logger?.warn?.(`Failed to cancel transaction after error: ${cancelError.message}`)
|
|
1086
|
+
}),
|
|
1087
|
+
),
|
|
1088
|
+
Effect.asVoid,
|
|
1089
|
+
)
|
|
979
1090
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1091
|
+
return yield* Effect.gen(function* () {
|
|
1092
|
+
const result = yield* work(tx)
|
|
1093
|
+
yield* tx.commit()
|
|
1094
|
+
return result
|
|
1095
|
+
}).pipe(Effect.onError(() => cancelEffect))
|
|
1096
|
+
}.bind(this),
|
|
1097
|
+
)
|
|
986
1098
|
}
|
|
987
1099
|
}
|