@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.
Files changed (108) hide show
  1. package/package.json +2 -2
  2. package/src/ai-gateway/ai-gateway.ts +149 -95
  3. package/src/ai-gateway/index.ts +16 -1
  4. package/src/config/agent-defaults.ts +4 -120
  5. package/src/config/logger.ts +18 -34
  6. package/src/config/thread-defaults.ts +1 -18
  7. package/src/create-runtime.ts +90 -28
  8. package/src/db/base.service.ts +30 -38
  9. package/src/db/service.ts +489 -545
  10. package/src/effect/index.ts +0 -2
  11. package/src/effect/layers.ts +6 -13
  12. package/src/embeddings/provider.ts +2 -7
  13. package/src/index.ts +4 -5
  14. package/src/queues/autonomous-job.queue.ts +159 -113
  15. package/src/queues/context-compaction.queue.ts +39 -25
  16. package/src/queues/delayed-node-promotion.queue.ts +56 -29
  17. package/src/queues/document-processor.queue.ts +5 -3
  18. package/src/queues/index.ts +1 -0
  19. package/src/queues/memory-consolidation.queue.ts +79 -53
  20. package/src/queues/organization-learning.queue.ts +63 -39
  21. package/src/queues/plan-agent-heartbeat.queue.ts +104 -79
  22. package/src/queues/plan-scheduler.queue.ts +100 -84
  23. package/src/queues/post-chat-memory.queue.ts +55 -33
  24. package/src/queues/queue-factory.ts +40 -41
  25. package/src/queues/queues.service.ts +61 -0
  26. package/src/queues/title-generation.queue.ts +42 -31
  27. package/src/redis/org-memory-lock.ts +24 -9
  28. package/src/redis/redis-lease-lock.ts +8 -1
  29. package/src/runtime/agent-identity-overrides.ts +7 -3
  30. package/src/runtime/agent-runtime-policy.ts +9 -4
  31. package/src/runtime/agent-stream-helpers.ts +9 -4
  32. package/src/runtime/context-compaction/context-compaction-runtime.ts +28 -32
  33. package/src/runtime/context-compaction/context-compaction.ts +9 -7
  34. package/src/runtime/domain-layer.ts +15 -4
  35. package/src/runtime/execution-plan-visibility.ts +5 -2
  36. package/src/runtime/graph-designer.ts +0 -22
  37. package/src/runtime/index.ts +1 -0
  38. package/src/runtime/indexed-repositories-policy.ts +2 -6
  39. package/src/runtime/plugin-resolution.ts +29 -12
  40. package/src/runtime/post-turn-side-effects.ts +139 -141
  41. package/src/runtime/runtime-config.ts +0 -6
  42. package/src/runtime/runtime-extensions.ts +0 -54
  43. package/src/runtime/runtime-lifecycle.ts +4 -4
  44. package/src/runtime/runtime-services.ts +122 -53
  45. package/src/runtime/runtime-worker-registry.ts +113 -30
  46. package/src/runtime/social-chat/social-chat-agent-runner.ts +6 -3
  47. package/src/runtime/social-chat/social-chat-history.ts +3 -1
  48. package/src/runtime/social-chat/social-chat.ts +35 -20
  49. package/src/runtime/team-consultation/team-consultation-orchestrator.ts +6 -5
  50. package/src/runtime/team-consultation/team-consultation-prompts.ts +11 -6
  51. package/src/runtime/thread-chat-helpers.ts +18 -9
  52. package/src/runtime/thread-turn-context.ts +7 -47
  53. package/src/runtime/turn-lifecycle.ts +6 -14
  54. package/src/services/agent-activity.service.ts +168 -175
  55. package/src/services/agent-executor.service.ts +35 -16
  56. package/src/services/attachment.service.ts +4 -70
  57. package/src/services/autonomous-job.service.ts +53 -61
  58. package/src/services/context-compaction.service.ts +7 -9
  59. package/src/services/execution-plan/execution-plan-graph.ts +106 -115
  60. package/src/services/execution-plan/execution-plan-schedule.ts +1 -15
  61. package/src/services/execution-plan/execution-plan.service.ts +67 -50
  62. package/src/services/global-orchestrator.service.ts +18 -7
  63. package/src/services/graph-full-routing.ts +7 -6
  64. package/src/services/memory/memory-conversation.ts +10 -5
  65. package/src/services/memory/memory.service.ts +11 -8
  66. package/src/services/ownership-dispatcher.service.ts +16 -5
  67. package/src/services/plan/plan-agent-heartbeat.service.ts +29 -15
  68. package/src/services/plan/plan-agent-query.service.ts +12 -8
  69. package/src/services/plan/plan-completion-side-effects.ts +93 -101
  70. package/src/services/plan/plan-cycle.service.ts +7 -45
  71. package/src/services/plan/plan-deadline.service.ts +28 -17
  72. package/src/services/plan/plan-event-delivery.service.ts +47 -40
  73. package/src/services/plan/plan-executor-context.ts +2 -0
  74. package/src/services/plan/plan-executor-graph.ts +366 -391
  75. package/src/services/plan/plan-executor.service.ts +13 -91
  76. package/src/services/plan/plan-scheduler.service.ts +62 -49
  77. package/src/services/plan/plan-transaction-events.ts +1 -1
  78. package/src/services/recent-activity-title.service.ts +6 -2
  79. package/src/services/thread/thread-bootstrap.ts +11 -9
  80. package/src/services/thread/thread-message.service.ts +6 -5
  81. package/src/services/thread/thread-turn-execution.ts +86 -82
  82. package/src/services/thread/thread-turn-preparation.service.ts +47 -24
  83. package/src/services/thread/thread-turn-streaming.ts +20 -25
  84. package/src/services/thread/thread-turn.ts +25 -44
  85. package/src/services/thread/thread.service.ts +21 -6
  86. package/src/system-agents/recent-activity-title-refiner.agent.ts +8 -5
  87. package/src/system-agents/thread-router.agent.ts +23 -20
  88. package/src/tools/execution-plan.tool.ts +8 -3
  89. package/src/tools/fetch-webpage.tool.ts +10 -9
  90. package/src/tools/firecrawl-client.ts +0 -15
  91. package/src/tools/remember-memory.tool.ts +3 -6
  92. package/src/tools/research-topic.tool.ts +12 -3
  93. package/src/tools/search-web.tool.ts +10 -9
  94. package/src/tools/search.tool.ts +4 -5
  95. package/src/tools/team-think.tool.ts +139 -121
  96. package/src/workers/bootstrap.ts +9 -10
  97. package/src/workers/memory-consolidation.worker.ts +4 -1
  98. package/src/workers/organization-learning.worker.ts +15 -2
  99. package/src/workers/regular-chat-memory-digest.helpers.ts +3 -4
  100. package/src/workers/regular-chat-memory-digest.runner.ts +21 -14
  101. package/src/workers/skill-extraction.runner.ts +13 -15
  102. package/src/workers/worker-utils.ts +6 -18
  103. package/src/effect/awaitable-effect.ts +0 -96
  104. package/src/effect/runtime-ref.ts +0 -25
  105. package/src/effect/runtime.ts +0 -46
  106. package/src/redis/runtime-connection.ts +0 -20
  107. package/src/runtime/runtime-accessors.ts +0 -92
  108. 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') => AwaitableEffect<unknown, SurrealDBError>
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') => AwaitableEffect<unknown, SurrealDBError>
76
+ output: (mode: 'after' | 'before') => Effect.Effect<unknown, SurrealDBError, never>
79
77
  }
80
78
 
81
79
  export interface DatabaseTransaction {
82
- query: (query: unknown) => AwaitableEffect<unknown, SurrealDBError>
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) => AwaitableEffect<unknown, SurrealDBError>
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
- ) => AwaitableEffect<unknown, SurrealDBError>
92
- commit: () => AwaitableEffect<void, SurrealDBError>
93
- cancel: () => AwaitableEffect<void, SurrealDBError>
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(): AwaitableEffect<void, SurrealDBError> {
220
- return toAwaitableEffect(
221
- Effect.gen(
222
- function* (this: SurrealDBService) {
223
- // Fast path: already connected. Cheap, runs inside the Effect so callers
224
- // observe the same memory barrier the slow path relies on.
225
- if (this.isConnected) {
226
- return
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
- // Slow path: serialize concurrent connect attempts behind a single permit
230
- // so only one client.connect() ever runs. Late waiters double-check
231
- // isConnected after acquiring the permit and become no-ops.
232
- yield* this.connectMutex.withPermits(1)(this.connectLockedEffect())
233
- }.bind(this),
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(): AwaitableEffect<void, SurrealDBError> {
293
- return toAwaitableEffect(
294
- Effect.gen(
295
- function* (this: SurrealDBService) {
296
- if (!this.isConnected) {
297
- return
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
- this.isConnected = false
295
+ this.isConnected = false
301
296
 
302
- const client = this.client
303
- if (!client) {
304
- this.client = null
305
- return
306
- }
297
+ const client = this.client
298
+ if (!client) {
299
+ this.client = null
300
+ return
301
+ }
307
302
 
308
- yield* Effect.tryPromise({
309
- try: () => client.close(),
310
- catch: (error) =>
311
- new SurrealDBError({
312
- message: `Failed to close database client: ${getErrorMessage(error)}`,
313
- cause: error,
314
- }),
315
- }).pipe(
316
- Effect.ensuring(
317
- Effect.sync(() => {
318
- this.client = null
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
- toAwaitableEffect(
467
- Effect.gen(
468
- function* (this: SurrealDBService) {
469
- const builder = yield* builderEffect
470
- let configuredBuilder = builder
471
- if (pendingMutation) {
472
- const normalizedData = yield* this.normalizeMutationDataEffect(pendingMutation.data)
473
- configuredBuilder = configureMutation(builder, pendingMutation.mutation, normalizedData)
474
- }
475
- const value = yield* Effect.tryPromise({
476
- try: () => Promise.resolve(configuredBuilder.output(mode)),
477
- catch: (error) =>
478
- new SurrealDBError({
479
- message: `Failed to finish mutation output: ${getErrorMessage(error)}`,
480
- cause: error,
481
- }),
482
- })
483
- return yield* this.normalizeParseValueEffect(value)
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
- toAwaitableEffect(
498
- Effect.gen(
499
- function* (this: SurrealDBService) {
500
- const builder = yield* builderEffect
501
- const configuredBuilder = pendingData
502
- ? builder.content(yield* this.normalizeMutationDataEffect(pendingData))
503
- : builder
504
- const value = yield* Effect.tryPromise({
505
- try: () => Promise.resolve(configuredBuilder.output(mode)),
506
- catch: (error) =>
507
- new SurrealDBError({
508
- message: `Failed to finish create output: ${getErrorMessage(error)}`,
509
- cause: error,
510
- }),
511
- })
512
- return yield* this.normalizeParseValueEffect(value)
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
- toAwaitableEffect(
523
- Effect.gen(
524
- function* (this: SurrealDBService) {
525
- const boundQuery = yield* this.normalizeTransactionQueryEffect(query)
526
- const queryText = this.resolveQueryText(boundQuery)
527
- const responses = yield* Effect.tryPromise({
528
- try: () => tx.query(boundQuery).responses(),
529
- catch: (error) =>
530
- new SurrealDBError({
531
- message: `Failed to run transaction query: ${getErrorMessage(error)}`,
532
- query: queryText,
533
- cause: error,
534
- }),
535
- })
536
- const first = responses.at(0)
537
- if (!first) {
538
- return []
539
- }
540
- if (!first.success) {
541
- return yield* new SurrealDBError({ message: first.error.message, query: queryText, cause: first.error })
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
- return yield* this.normalizeQueryRowsEffect(first.result)
545
- }.bind(this),
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
- toAwaitableEffect(
568
- Effect.gen(
569
- function* (this: SurrealDBService) {
570
- const recordId = yield* this.normalizeTransactionRecordIdEffect(target, 'transaction delete')
571
- const value = yield* Effect.tryPromise({
572
- try: () => tx.delete(recordId),
573
- catch: (error) =>
574
- new SurrealDBError({
575
- message: `Failed to delete transaction target: ${getErrorMessage(error)}`,
576
- cause: error,
577
- }),
578
- })
579
- return yield* this.normalizeParseValueEffect(value)
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
- toAwaitableEffect(
585
- Effect.gen(
586
- function* (this: SurrealDBService) {
587
- const fromRecordId = yield* this.normalizeTransactionRecordIdEffect(from, 'transaction relate source')
588
- const normalizedEdgeTable = yield* this.normalizeTableValueEffect(edgeTable)
589
- const toRecordId = yield* this.normalizeTransactionRecordIdEffect(to, 'transaction relate target')
590
- const normalizedData = data
591
- ? yield* this.normalizeMutationDataEffect(data as Record<string, unknown>)
592
- : undefined
593
- const value = yield* Effect.tryPromise({
594
- try: () => tx.relate(fromRecordId, normalizedEdgeTable, toRecordId, normalizedData),
595
- catch: (error) =>
596
- new SurrealDBError({
597
- message: `Failed to relate transaction records: ${getErrorMessage(error)}`,
598
- cause: error,
599
- }),
600
- })
601
- return yield* this.normalizeParseValueEffect(value)
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
- toAwaitableEffect(
607
- Effect.tryPromise({
608
- try: () => tx.commit(),
609
- catch: (error) =>
610
- new SurrealDBError({ message: `Failed to commit transaction: ${getErrorMessage(error)}`, cause: error }),
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
- toAwaitableEffect(
615
- Effect.tryPromise({
616
- try: () => tx.cancel(),
617
- catch: (error) =>
618
- new SurrealDBError({ message: `Failed to cancel transaction: ${getErrorMessage(error)}`, cause: error }),
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): AwaitableEffect<T[], SurrealDBError> {
629
- return toAwaitableEffect(
630
- Effect.gen(
631
- function* (this: SurrealDBService) {
632
- const boundQuery = yield* this.normalizeBoundQueryEffect(query)
633
- const statements = yield* this.queryAll<T>(boundQuery)
634
- return statements.at(0) ?? []
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): AwaitableEffect<T[][], SurrealDBError> {
641
- return toAwaitableEffect(
642
- Effect.gen(
643
- function* (this: SurrealDBService) {
644
- const boundQuery = yield* this.normalizeBoundQueryEffect(query)
645
- const queryText = this.resolveQueryText(boundQuery)
646
- const client = yield* this.ensureConnectedEffect(queryText)
647
- const responses = yield* Effect.tryPromise({
648
- try: () => client.query(boundQuery).responses(),
649
- catch: (error) =>
650
- new SurrealDBError({
651
- message: `Failed to run query: ${getErrorMessage(error)}`,
652
- query: queryText,
653
- cause: error,
654
- }),
655
- })
656
- return yield* Effect.forEach(responses, (response, index) =>
657
- Effect.gen(
658
- function* (this: SurrealDBService) {
659
- if (!response.success) {
660
- const failure = response.error
661
- return yield* new SurrealDBError({
662
- message: `Statement ${index + 1}: ${failure.message}`,
663
- query: queryText,
664
- cause: failure,
665
- })
666
- }
667
- return (yield* this.normalizeQueryRowsEffect(response.result, schema)) as T[]
668
- }.bind(this),
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>(query: BoundQuery, schema: T): AwaitableEffect<z.infer<T> | null, SurrealDBError> {
677
- return toAwaitableEffect(
678
- Effect.gen(
679
- function* (this: SurrealDBService) {
680
- const boundQuery = yield* this.normalizeBoundQueryEffect(query)
681
- const results = yield* this.query<unknown>(boundQuery)
682
- const first = results.at(0)
683
- return first ? yield* this.parseSchemaEffect(schema, first) : null
684
- }.bind(this),
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): AwaitableEffect<z.infer<T>[], SurrealDBError> {
690
- return toAwaitableEffect(
691
- Effect.gen(
692
- function* (this: SurrealDBService) {
693
- const boundQuery = yield* this.normalizeBoundQueryEffect(query)
694
- const results = yield* this.query<unknown>(boundQuery)
695
- return yield* Effect.forEach(results, (row) => this.parseSchemaEffect(schema, row))
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[]): AwaitableEffect<void, SurrealDBError> {
719
- return toAwaitableEffect(
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
- ): AwaitableEffect<z.infer<T> | null, SurrealDBError> {
729
- return toAwaitableEffect(
730
- Effect.gen(
731
- function* (this: SurrealDBService) {
732
- const selection = yield* this.buildFilterExpressionEffect(filter)
733
- const client = yield* this.ensureConnectedEffect(`SELECT * FROM ${table} LIMIT 1`)
734
- let query = client.select<unknown>(new Table(table))
735
- if (selection) {
736
- query = query.where(selection)
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
- const rows = yield* Effect.tryPromise({
740
- try: () => query.limit(1),
741
- catch: (error) =>
742
- new SurrealDBError({
743
- message: `Failed to find record in ${table}: ${getErrorMessage(error)}`,
744
- cause: error,
745
- }),
746
- })
747
- const first = rows.at(0)
748
- return first ? yield* this.parseSchemaEffect(schema, first) : null
749
- }.bind(this),
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
- ): AwaitableEffect<z.infer<T>[], SurrealDBError> {
760
- return toAwaitableEffect(
761
- Effect.gen(
762
- function* (this: SurrealDBService) {
763
- const filterKeys = Object.keys(filter)
764
- const selection = yield* this.buildFilterExpressionEffect(filter)
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
- const client = yield* this.ensureConnectedEffect(`SELECT * FROM ${table}`)
802
- let query = client.select<unknown>(new Table(table))
803
- if (selection) {
804
- query = query.where(selection)
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
- if (options?.offset !== undefined) {
807
- query = query.start(options.offset)
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
- if (options?.limit !== undefined) {
810
- query = query.limit(options.limit)
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
- const rows = yield* Effect.tryPromise({
814
- try: () => query,
815
- catch: (error) =>
816
- new SurrealDBError({
817
- message: `Failed to run findMany query for ${table}: ${getErrorMessage(error)}`,
818
- cause: error,
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
- }.bind(this),
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
- ): AwaitableEffect<z.infer<T>, SurrealDBError> {
832
- return toAwaitableEffect(
833
- Effect.gen(
834
- function* (this: SurrealDBService) {
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
- }
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
- const client = yield* this.ensureConnectedEffect(`CREATE ${table}`)
841
- const normalizedData = yield* this.normalizeMutationDataEffect(data)
842
- const created = yield* Effect.tryPromise({
843
- try: () => client.create<unknown>(new Table(table)).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
- const first = Array.isArray(created) ? created.at(0) : created
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
- if (!first) {
853
- return yield* new SurrealDBError({ message: `Failed to create record in ${table}` })
854
- }
817
+ if (!first) {
818
+ return yield* new SurrealDBError({ message: `Failed to create record in ${table}` })
819
+ }
855
820
 
856
- return yield* this.parseSchemaEffect(schema, first)
857
- }.bind(this),
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
- ): AwaitableEffect<z.infer<T>, SurrealDBError> {
868
- return toAwaitableEffect(
869
- Effect.gen(
870
- function* (this: SurrealDBService) {
871
- const recordId = yield* this.normalizeRecordIdEffect(id, table)
872
- const keys = Object.keys(data)
873
- if (keys.length === 0) {
874
- return yield* new SurrealDBError({ message: `Cannot create record in ${table} with empty data` })
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
- const client = yield* this.ensureConnectedEffect(`CREATE ${recordId.toString()}`)
878
- const normalizedData = yield* this.normalizeMutationDataEffect(data)
879
- const created = yield* Effect.tryPromise({
880
- try: () => client.create<unknown>(recordId).content(normalizedData).output('after'),
881
- catch: (error) =>
882
- new SurrealDBError({
883
- message: `Failed to create record in ${table}: ${getErrorMessage(error)}`,
884
- cause: error,
885
- }),
886
- })
887
- return yield* this.parseSchemaEffect(schema, created)
888
- }.bind(this),
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
- ): AwaitableEffect<z.infer<T> | null, SurrealDBError> {
900
- return toAwaitableEffect(
901
- Effect.gen(
902
- function* (this: SurrealDBService) {
903
- const recordId = yield* this.normalizeRecordIdEffect(id, table)
904
- const keys = Object.keys(data)
905
- if (keys.length === 0) {
906
- return yield* new SurrealDBError({ message: 'Cannot update record with empty data' })
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
- const mutation = options?.mutation ?? 'merge'
910
- const client = yield* this.ensureConnectedEffect(`UPDATE ${recordId.toString()}`)
911
- const builder = client.update<unknown>(recordId)
912
- const normalizedData = yield* this.normalizeMutationDataEffect(data)
913
- const updated: Record<string, unknown> | null | undefined = yield* Effect.tryPromise({
914
- try: () => configureMutation(builder, mutation, normalizedData).output('after'),
915
- catch: (error) =>
916
- new SurrealDBError({
917
- message: `Failed to update record in ${table}: ${getErrorMessage(error)}`,
918
- cause: error,
919
- }),
920
- })
921
- return yield* this.parseOptionalSchemaEffect(schema, updated)
922
- }.bind(this),
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
- ): AwaitableEffect<z.infer<T>, SurrealDBError> {
934
- return toAwaitableEffect(
935
- Effect.gen(
936
- function* (this: SurrealDBService) {
937
- const recordId = yield* this.normalizeRecordIdEffect(id, table)
938
- const keys = Object.keys(data)
939
- if (keys.length === 0) {
940
- return yield* new SurrealDBError({ message: 'Cannot upsert record with empty data' })
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
- const mutation = options?.mutation ?? 'merge'
944
- const client = yield* this.ensureConnectedEffect(`UPSERT ${recordId.toString()}`)
945
- const builder = client.upsert<unknown>(recordId)
946
- const normalizedData = yield* this.normalizeMutationDataEffect(data)
947
- const upserted: Record<string, unknown> | null | undefined = yield* Effect.tryPromise({
948
- try: () => configureMutation(builder, mutation, normalizedData).output('after'),
949
- catch: (error) =>
950
- new SurrealDBError({
951
- message: `Failed to upsert record in ${table}: ${getErrorMessage(error)}`,
952
- cause: error,
953
- }),
954
- })
955
- const parsed = yield* this.parseOptionalSchemaEffect(schema, upserted)
956
- if (parsed === null) {
957
- return yield* new SurrealDBError({ message: `Failed to upsert record in ${table}` })
958
- }
959
- return parsed
960
- }.bind(this),
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): AwaitableEffect<boolean, SurrealDBError> {
966
- return toAwaitableEffect(
967
- Effect.gen(
968
- function* (this: SurrealDBService) {
969
- const recordId = yield* this.normalizeRecordIdEffect(id, table)
970
- return yield* this.query<unknown>(new BoundQuery(`DELETE $recordId RETURN BEFORE`, { recordId })).pipe(
971
- Effect.map((result) => result.length > 0),
972
- Effect.catchTag('SurrealDBError', (error) => {
973
- if (error.message.includes('does not exist')) {
974
- return Effect.succeed(false)
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
- return Effect.fail(this.toSurrealError(error, `DELETE ${recordId.toString()}`))
978
- }),
979
- )
980
- }.bind(this),
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>): AwaitableEffect<number, SurrealDBError> {
986
- return toAwaitableEffect(
987
- Effect.gen(
988
- function* (this: SurrealDBService) {
989
- yield* this.assertValidIdentifierEffect(table, 'table name')
990
- const filterKeys = Object.keys(filter)
991
- if (filterKeys.length === 0) {
992
- return yield* new SurrealDBError({ message: `Refusing to delete all records in ${table} without a filter` })
993
- }
994
- const { clause, bindings } = yield* this.buildBoundFilterClausesEffect(filter)
995
- return yield* this.withTransaction((tx) =>
996
- Effect.gen(
997
- function* (this: SurrealDBService) {
998
- const matchedRows = (yield* tx.query(
999
- new BoundQuery(`SELECT id FROM ${table} WHERE ${clause}`, bindings),
1000
- )) as Array<{ id: unknown }>
1001
-
1002
- if (matchedRows.length === 0) {
1003
- return 0
1004
- }
1005
-
1006
- for (const row of matchedRows) {
1007
- const recordId = yield* this.normalizeRecordIdEffect(row.id, table)
1008
- yield* tx.delete(recordId)
1009
- }
1010
-
1011
- return matchedRows.length
1012
- }.bind(this),
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
- ): AwaitableEffect<number, SurrealDBError> {
1025
- return toAwaitableEffect(
1026
- Effect.gen(
1027
- function* (this: SurrealDBService) {
1028
- if (!where) {
1029
- return yield* new SurrealDBError({
1030
- message: `Refusing to update records in ${table} without a where clause`,
1031
- })
1032
- }
1033
- const keys = Object.keys(data)
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
- const client = yield* this.ensureConnectedEffect(`UPDATE ${table} WHERE ...`)
1039
- const normalizedData = yield* this.normalizeMutationDataEffect(data)
1040
- const updated = yield* Effect.tryPromise({
1041
- try: () => client.update<unknown>(new Table(table)).where(where).merge(normalizedData).output('after'),
1042
- catch: (error) =>
1043
- new SurrealDBError({
1044
- message: `Failed to update records in ${table}: ${getErrorMessage(error)}`,
1045
- cause: error,
1046
- }),
1047
- })
1048
- return Array.isArray(updated) ? updated.length : 1
1049
- }.bind(this),
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
- ): AwaitableEffect<T[], SurrealDBError> {
1058
- return toAwaitableEffect(
1059
- Effect.gen(
1060
- function* (this: SurrealDBService) {
1061
- const client = yield* this.ensureConnectedEffect(`INSERT ${table}`)
1062
- const normalized = Array.isArray(data)
1063
- ? yield* Effect.forEach(data, (item) => this.normalizeMutationDataEffect(item as Record<string, unknown>))
1064
- : yield* this.normalizeMutationDataEffect(data as Record<string, unknown>)
1065
- const inserted = yield* Effect.tryPromise({
1066
- try: () => client.insert<T>(new Table(table), normalized as Values<T> | Array<Values<T>>).output('after'),
1067
- catch: (error) =>
1068
- new SurrealDBError({
1069
- message: `Failed to insert rows into ${table}: ${getErrorMessage(error)}`,
1070
- cause: error,
1071
- }),
1072
- })
1073
- return inserted as T[]
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
- ): AwaitableEffect<T | null, SurrealDBError> {
1085
- return toAwaitableEffect(
1086
- Effect.gen(
1087
- function* (this: SurrealDBService) {
1088
- const fromRef = yield* ensureRecordIdEffect(from).pipe(Effect.mapError((error) => this.toSurrealError(error)))
1089
- const toRef = yield* ensureRecordIdEffect(to).pipe(Effect.mapError((error) => this.toSurrealError(error)))
1090
- const client = yield* this.ensureConnectedEffect(
1091
- `RELATE ${fromRef.toString()}->${edgeTable}->${toRef.toString()}`,
1092
- )
1093
- const normalizedData = data
1094
- ? ((yield* this.normalizeMutationDataEffect(data as Record<string, unknown>)) as Values<T>)
1095
- : undefined
1096
- const related = (yield* Effect.tryPromise({
1097
- try: () => client.relate<T>(fromRef, new Table(edgeTable), toRef, normalizedData).output('after'),
1098
- catch: (error) =>
1099
- new SurrealDBError({
1100
- message: `Failed to relate records ${fromRef.toString()} -> ${edgeTable} -> ${toRef.toString()}: ${getErrorMessage(error)}`,
1101
- cause: error,
1102
- }),
1103
- })) as T | T[] | null
1104
- if (related === null) {
1105
- return null
1106
- }
1107
- if (Array.isArray(related)) {
1108
- return related.at(0) ?? null
1109
- }
1110
- return related
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(): AwaitableEffect<DatabaseTransaction, SurrealDBError> {
1117
- return toAwaitableEffect(
1118
- Effect.gen(
1119
- function* (this: SurrealDBService) {
1120
- const client = yield* this.ensureConnectedEffect('BEGIN TRANSACTION')
1121
- const tx = yield* Effect.tryPromise({
1122
- try: () => client.beginTransaction(),
1123
- catch: (error) =>
1124
- new SurrealDBError({ message: `Failed to begin transaction: ${getErrorMessage(error)}`, cause: error }),
1125
- })
1126
- return this.wrapTransaction(tx)
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