@effect/cluster 0.46.2 → 0.46.4

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.
@@ -40,12 +40,33 @@ export const layerHttpApi = <
40
40
  .handle(
41
41
  parentRpc._tag as any,
42
42
  (({ path, payload }: { path: { entityId: string }; payload: any }) =>
43
- (client(path.entityId) as any)[parentRpc._tag](payload)) as any
43
+ (client(path.entityId) as any as Record<string, (p: any) => Effect.Effect<any>>)[parentRpc._tag](
44
+ payload
45
+ ).pipe(
46
+ Effect.tapDefect(Effect.logError),
47
+ Effect.annotateLogs({
48
+ module: "EntityProxyServer",
49
+ entity: entity.type,
50
+ entityId: path.entityId,
51
+ method: parentRpc._tag
52
+ })
53
+ )) as any
44
54
  )
45
55
  .handle(
46
56
  `${parentRpc._tag}Discard` as any,
47
57
  (({ path, payload }: { path: { entityId: string }; payload: any }) =>
48
- (client(path.entityId) as any)[parentRpc._tag](payload, { discard: true })) as any
58
+ (client(path.entityId) as any as Record<string, (p: any, o: {}) => Effect.Effect<any>>)[parentRpc._tag](
59
+ payload,
60
+ { discard: true }
61
+ ).pipe(
62
+ Effect.tapDefect(Effect.logError),
63
+ Effect.annotateLogs({
64
+ module: "EntityProxyServer",
65
+ entity: entity.type,
66
+ entityId: path.entityId,
67
+ method: `${parentRpc._tag}Discard`
68
+ })
69
+ )) as any
49
70
  ) as any
50
71
  }
51
72
  return handlers as HttpApiBuilder.Handlers<never, never, never>
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @since 1.0.0
3
3
  */
4
+ import * as Migrator from "@effect/sql/Migrator"
4
5
  import * as SqlClient from "@effect/sql/SqlClient"
5
6
  import type { Row } from "@effect/sql/SqlConnection"
6
7
  import type { SqlError } from "@effect/sql/SqlError"
@@ -32,6 +33,13 @@ export const make = Effect.fnUntraced(function*(options?: {
32
33
  const prefix = options?.prefix ?? "cluster"
33
34
  const table = (name: string) => `${prefix}_${name}`
34
35
 
36
+ yield* Effect.orDie(
37
+ Migrator.make({})({
38
+ loader: migrations(options),
39
+ table: table("migrations")
40
+ })
41
+ )
42
+
35
43
  const messageKindAckChunk = sql.literal(String(messageKind.AckChunk))
36
44
  const replyKindWithExit = sql.literal(String(replyKind.WithExit))
37
45
 
@@ -41,271 +49,6 @@ export const make = Effect.fnUntraced(function*(options?: {
41
49
  const repliesTable = table("replies")
42
50
  const repliesTableSql = sql(repliesTable)
43
51
 
44
- yield* sql.onDialectOrElse({
45
- mssql: () =>
46
- sql`
47
- IF OBJECT_ID(N'${messagesTableSql}', N'U') IS NULL
48
- CREATE TABLE ${messagesTableSql} (
49
- id BIGINT PRIMARY KEY,
50
- rowid BIGINT IDENTITY(1,1),
51
- message_id VARCHAR(255),
52
- shard_id VARCHAR(50) NOT NULL,
53
- entity_type VARCHAR(50) NOT NULL,
54
- entity_id VARCHAR(255) NOT NULL,
55
- kind INT NOT NULL,
56
- tag VARCHAR(50),
57
- payload TEXT,
58
- headers TEXT,
59
- trace_id VARCHAR(32),
60
- span_id VARCHAR(16),
61
- sampled BIT,
62
- processed BIT NOT NULL DEFAULT 0,
63
- request_id BIGINT NOT NULL,
64
- reply_id BIGINT,
65
- last_reply_id BIGINT,
66
- last_read DATETIME,
67
- deliver_at BIGINT,
68
- UNIQUE (message_id),
69
- FOREIGN KEY (request_id) REFERENCES ${messagesTableSql} (id) ON DELETE CASCADE
70
- )
71
- `,
72
- mysql: () =>
73
- sql`
74
- CREATE TABLE IF NOT EXISTS ${messagesTableSql} (
75
- id BIGINT NOT NULL,
76
- rowid BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
77
- message_id VARCHAR(255),
78
- shard_id VARCHAR(50) NOT NULL,
79
- entity_type VARCHAR(50) NOT NULL,
80
- entity_id VARCHAR(255) NOT NULL,
81
- kind INT NOT NULL,
82
- tag VARCHAR(50),
83
- payload TEXT,
84
- headers TEXT,
85
- trace_id VARCHAR(32),
86
- span_id VARCHAR(16),
87
- sampled BOOLEAN,
88
- processed BOOLEAN NOT NULL DEFAULT FALSE,
89
- request_id BIGINT NOT NULL,
90
- reply_id BIGINT,
91
- last_reply_id BIGINT,
92
- last_read DATETIME,
93
- deliver_at BIGINT,
94
- UNIQUE (id),
95
- UNIQUE (message_id),
96
- FOREIGN KEY (request_id) REFERENCES ${messagesTableSql} (id) ON DELETE CASCADE
97
- )
98
- `,
99
- pg: () =>
100
- sql`
101
- CREATE TABLE IF NOT EXISTS ${messagesTableSql} (
102
- id BIGINT PRIMARY KEY,
103
- rowid BIGSERIAL,
104
- message_id VARCHAR(255),
105
- shard_id VARCHAR(50) NOT NULL,
106
- entity_type VARCHAR(50) NOT NULL,
107
- entity_id VARCHAR(255) NOT NULL,
108
- kind INT NOT NULL,
109
- tag VARCHAR(50),
110
- payload TEXT,
111
- headers TEXT,
112
- trace_id VARCHAR(32),
113
- span_id VARCHAR(16),
114
- sampled BOOLEAN,
115
- processed BOOLEAN NOT NULL DEFAULT FALSE,
116
- request_id BIGINT NOT NULL,
117
- reply_id BIGINT,
118
- last_reply_id BIGINT,
119
- last_read TIMESTAMP,
120
- deliver_at BIGINT,
121
- UNIQUE (message_id),
122
- FOREIGN KEY (request_id) REFERENCES ${messagesTableSql} (id) ON DELETE CASCADE
123
- )
124
- `.pipe(Effect.ignore),
125
- orElse: () =>
126
- // sqlite
127
- sql`
128
- CREATE TABLE IF NOT EXISTS ${messagesTableSql} (
129
- id INTEGER PRIMARY KEY,
130
- message_id TEXT,
131
- shard_id TEXT NOT NULL,
132
- entity_type TEXT NOT NULL,
133
- entity_id TEXT NOT NULL,
134
- kind INTEGER NOT NULL,
135
- tag TEXT,
136
- payload TEXT,
137
- headers TEXT,
138
- trace_id TEXT,
139
- span_id TEXT,
140
- sampled BOOLEAN,
141
- processed BOOLEAN NOT NULL DEFAULT FALSE,
142
- request_id INTEGER NOT NULL,
143
- reply_id INTEGER,
144
- last_reply_id INTEGER,
145
- last_read TEXT,
146
- deliver_at INTEGER,
147
- UNIQUE (message_id),
148
- FOREIGN KEY (request_id) REFERENCES ${messagesTableSql} (id) ON DELETE CASCADE
149
- )
150
- `
151
- })
152
-
153
- // Add message indexes optimized for the specific query patterns
154
- const shardLookupIndex = `${messagesTable}_shard_idx`
155
- const requestIdLookupIndex = `${messagesTable}_request_id_idx`
156
- yield* sql.onDialectOrElse({
157
- mssql: () =>
158
- sql`
159
- IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = ${shardLookupIndex})
160
- CREATE INDEX ${sql(shardLookupIndex)}
161
- ON ${messagesTableSql} (shard_id, processed, last_read, deliver_at);
162
-
163
- IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = ${requestIdLookupIndex})
164
- CREATE INDEX ${sql(requestIdLookupIndex)}
165
- ON ${messagesTableSql} (request_id);
166
- `,
167
- mysql: () =>
168
- sql`
169
- CREATE INDEX ${sql(shardLookupIndex)}
170
- ON ${messagesTableSql} (shard_id, processed, last_read, deliver_at);
171
-
172
- CREATE INDEX ${sql(requestIdLookupIndex)}
173
- ON ${messagesTableSql} (request_id);
174
- `.unprepared.pipe(Effect.ignore),
175
- pg: () =>
176
- sql`
177
- CREATE INDEX IF NOT EXISTS ${sql(shardLookupIndex)}
178
- ON ${messagesTableSql} (shard_id, processed, last_read, deliver_at);
179
-
180
- CREATE INDEX IF NOT EXISTS ${sql(requestIdLookupIndex)}
181
- ON ${messagesTableSql} (request_id);
182
- `.pipe(
183
- Effect.tapDefect((error) =>
184
- Effect.annotateLogs(Effect.logDebug("Failed to create indexes", error), {
185
- package: "@effect/cluster",
186
- module: "SqlMessageStorage"
187
- })
188
- ),
189
- Effect.retry({
190
- schedule: Schedule.spaced(1000)
191
- })
192
- ),
193
- orElse: () =>
194
- // sqlite
195
- Effect.all([
196
- sql`
197
- CREATE INDEX IF NOT EXISTS ${sql(shardLookupIndex)}
198
- ON ${messagesTableSql} (shard_id, processed, last_read, deliver_at)
199
- `,
200
- sql`
201
- CREATE INDEX IF NOT EXISTS ${sql(requestIdLookupIndex)}
202
- ON ${messagesTableSql} (request_id)
203
- `
204
- ]).pipe(sql.withTransaction)
205
- })
206
-
207
- yield* sql.onDialectOrElse({
208
- mssql: () =>
209
- sql`
210
- IF OBJECT_ID(N'${repliesTableSql}', N'U') IS NULL
211
- CREATE TABLE ${repliesTableSql} (
212
- id BIGINT PRIMARY KEY,
213
- rowid BIGINT IDENTITY(1,1),
214
- kind INT,
215
- request_id BIGINT NOT NULL,
216
- payload TEXT NOT NULL,
217
- sequence INT,
218
- acked BIT NOT NULL DEFAULT 0,
219
- CONSTRAINT ${sql(repliesTable + "_one_exit")} UNIQUE (request_id, kind),
220
- CONSTRAINT ${sql(repliesTable + "_sequence")} UNIQUE (request_id, sequence),
221
- FOREIGN KEY (request_id) REFERENCES ${messagesTableSql} (id) ON DELETE CASCADE
222
- )
223
- `,
224
- mysql: () =>
225
- sql`
226
- CREATE TABLE IF NOT EXISTS ${repliesTableSql} (
227
- id BIGINT NOT NULL,
228
- rowid BIGINT AUTO_INCREMENT PRIMARY KEY,
229
- kind INT,
230
- request_id BIGINT NOT NULL,
231
- payload TEXT NOT NULL,
232
- sequence INT,
233
- acked BOOLEAN NOT NULL DEFAULT FALSE,
234
- UNIQUE (id),
235
- UNIQUE (request_id, kind),
236
- UNIQUE (request_id, sequence),
237
- FOREIGN KEY (request_id) REFERENCES ${messagesTableSql} (id) ON DELETE CASCADE
238
- )
239
- `,
240
- pg: () =>
241
- sql`
242
- CREATE TABLE IF NOT EXISTS ${repliesTableSql} (
243
- id BIGINT PRIMARY KEY,
244
- rowid BIGSERIAL,
245
- kind INT,
246
- request_id BIGINT NOT NULL,
247
- payload TEXT NOT NULL,
248
- sequence INT,
249
- acked BOOLEAN NOT NULL DEFAULT FALSE,
250
- UNIQUE (request_id, kind),
251
- UNIQUE (request_id, sequence),
252
- FOREIGN KEY (request_id) REFERENCES ${messagesTableSql} (id) ON DELETE CASCADE
253
- )
254
- `,
255
- orElse: () =>
256
- // sqlite
257
- sql`
258
- CREATE TABLE IF NOT EXISTS ${repliesTableSql} (
259
- id INTEGER PRIMARY KEY,
260
- kind INTEGER,
261
- request_id INTEGER NOT NULL,
262
- payload TEXT NOT NULL,
263
- sequence INTEGER,
264
- acked BOOLEAN NOT NULL DEFAULT FALSE,
265
- UNIQUE (request_id, kind),
266
- UNIQUE (request_id, sequence),
267
- FOREIGN KEY (request_id) REFERENCES ${messagesTableSql} (id) ON DELETE CASCADE
268
- )
269
- `
270
- })
271
-
272
- // Add reply indexes optimized for request_id lookups
273
- const replyLookupIndex = `${repliesTable}_request_lookup_idx`
274
- yield* sql.onDialectOrElse({
275
- mssql: () =>
276
- sql`
277
- IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = ${replyLookupIndex})
278
- CREATE INDEX ${sql(replyLookupIndex)}
279
- ON ${repliesTableSql} (request_id, kind, acked);
280
- `,
281
- mysql: () =>
282
- sql`
283
- CREATE INDEX ${sql(replyLookupIndex)}
284
- ON ${repliesTableSql} (request_id, kind, acked);
285
- `.unprepared.pipe(Effect.ignore),
286
- pg: () =>
287
- sql`
288
- CREATE INDEX IF NOT EXISTS ${sql(replyLookupIndex)}
289
- ON ${repliesTableSql} (request_id, kind, acked);
290
- `.pipe(
291
- Effect.tapDefect((error) =>
292
- Effect.annotateLogs(Effect.logDebug("Failed to create indexes", error), {
293
- package: "@effect/cluster",
294
- module: "SqlMessageStorage"
295
- })
296
- ),
297
- Effect.retry({
298
- schedule: Schedule.spaced(1000)
299
- })
300
- ),
301
- orElse: () =>
302
- // sqlite
303
- sql`
304
- CREATE INDEX IF NOT EXISTS ${sql(replyLookupIndex)}
305
- ON ${repliesTableSql} (request_id, kind, acked);
306
- `
307
- })
308
-
309
52
  const envelopeToRow = (
310
53
  envelope: Envelope.Envelope.Encoded,
311
54
  message_id: string | null,
@@ -835,7 +578,7 @@ export const make = Effect.fnUntraced(function*(options?: {
835
578
  */
836
579
  export const layer: Layer.Layer<
837
580
  MessageStorage.MessageStorage,
838
- SqlError,
581
+ never,
839
582
  SqlClient.SqlClient | ShardingConfig
840
583
  > = Layer.scoped(MessageStorage.MessageStorage, make()).pipe(
841
584
  Layer.provide(Snowflake.layerGenerator)
@@ -848,7 +591,7 @@ export const layer: Layer.Layer<
848
591
  export const layerWith = (options: {
849
592
  readonly prefix?: string | undefined
850
593
  readonly replyPollInterval?: DurationInput | undefined
851
- }): Layer.Layer<MessageStorage.MessageStorage, SqlError, SqlClient.SqlClient | ShardingConfig> =>
594
+ }): Layer.Layer<MessageStorage.MessageStorage, never, SqlClient.SqlClient | ShardingConfig> =>
852
595
  Layer.scoped(MessageStorage.MessageStorage, make(options)).pipe(
853
596
  Layer.provide(Snowflake.layerGenerator)
854
597
  )
@@ -857,6 +600,311 @@ export const layerWith = (options: {
857
600
  // internal
858
601
  // -------------------------------------------------------------------------------------------------
859
602
 
603
+ const migrations = (options?: {
604
+ readonly prefix?: string | undefined
605
+ }) => {
606
+ const prefix = options?.prefix ?? "cluster"
607
+ const table = (name: string) => `${prefix}_${name}`
608
+ const messagesTable = table("messages")
609
+ const repliesTable = table("replies")
610
+
611
+ return Migrator.fromRecord({
612
+ "0001_create_tables": Effect.gen(function*() {
613
+ const sql = (yield* SqlClient.SqlClient).withoutTransforms()
614
+ const messagesTableSql = sql(messagesTable)
615
+ const repliesTableSql = sql(repliesTable)
616
+
617
+ yield* sql.onDialectOrElse({
618
+ mssql: () =>
619
+ sql`
620
+ IF OBJECT_ID(N'${messagesTableSql}', N'U') IS NULL
621
+ CREATE TABLE ${messagesTableSql} (
622
+ id BIGINT PRIMARY KEY,
623
+ rowid BIGINT IDENTITY(1,1),
624
+ message_id VARCHAR(255),
625
+ shard_id VARCHAR(50) NOT NULL,
626
+ entity_type VARCHAR(150) NOT NULL,
627
+ entity_id VARCHAR(255) NOT NULL,
628
+ kind INT NOT NULL,
629
+ tag VARCHAR(50),
630
+ payload TEXT,
631
+ headers TEXT,
632
+ trace_id VARCHAR(32),
633
+ span_id VARCHAR(16),
634
+ sampled BIT,
635
+ processed BIT NOT NULL DEFAULT 0,
636
+ request_id BIGINT NOT NULL,
637
+ reply_id BIGINT,
638
+ last_reply_id BIGINT,
639
+ last_read DATETIME,
640
+ deliver_at BIGINT,
641
+ UNIQUE (message_id),
642
+ FOREIGN KEY (request_id) REFERENCES ${messagesTableSql} (id) ON DELETE CASCADE
643
+ )
644
+ `,
645
+ mysql: () =>
646
+ sql`
647
+ CREATE TABLE IF NOT EXISTS ${messagesTableSql} (
648
+ id BIGINT NOT NULL,
649
+ rowid BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
650
+ message_id VARCHAR(255),
651
+ shard_id VARCHAR(50) NOT NULL,
652
+ entity_type VARCHAR(150) NOT NULL,
653
+ entity_id VARCHAR(255) NOT NULL,
654
+ kind INT NOT NULL,
655
+ tag VARCHAR(50),
656
+ payload TEXT,
657
+ headers TEXT,
658
+ trace_id VARCHAR(32),
659
+ span_id VARCHAR(16),
660
+ sampled BOOLEAN,
661
+ processed BOOLEAN NOT NULL DEFAULT FALSE,
662
+ request_id BIGINT NOT NULL,
663
+ reply_id BIGINT,
664
+ last_reply_id BIGINT,
665
+ last_read DATETIME,
666
+ deliver_at BIGINT,
667
+ UNIQUE (id),
668
+ UNIQUE (message_id),
669
+ FOREIGN KEY (request_id) REFERENCES ${messagesTableSql} (id) ON DELETE CASCADE
670
+ )
671
+ `,
672
+ pg: () =>
673
+ sql`
674
+ CREATE TABLE IF NOT EXISTS ${messagesTableSql} (
675
+ id BIGINT PRIMARY KEY,
676
+ rowid BIGSERIAL,
677
+ message_id VARCHAR(255),
678
+ shard_id VARCHAR(50) NOT NULL,
679
+ entity_type VARCHAR(150) NOT NULL,
680
+ entity_id VARCHAR(255) NOT NULL,
681
+ kind INT NOT NULL,
682
+ tag VARCHAR(50),
683
+ payload TEXT,
684
+ headers TEXT,
685
+ trace_id VARCHAR(32),
686
+ span_id VARCHAR(16),
687
+ sampled BOOLEAN,
688
+ processed BOOLEAN NOT NULL DEFAULT FALSE,
689
+ request_id BIGINT NOT NULL,
690
+ reply_id BIGINT,
691
+ last_reply_id BIGINT,
692
+ last_read TIMESTAMP,
693
+ deliver_at BIGINT,
694
+ UNIQUE (message_id),
695
+ FOREIGN KEY (request_id) REFERENCES ${messagesTableSql} (id) ON DELETE CASCADE
696
+ )
697
+ `.pipe(Effect.ignore),
698
+ orElse: () =>
699
+ // sqlite
700
+ sql`
701
+ CREATE TABLE IF NOT EXISTS ${messagesTableSql} (
702
+ id INTEGER PRIMARY KEY,
703
+ message_id TEXT,
704
+ shard_id TEXT NOT NULL,
705
+ entity_type TEXT NOT NULL,
706
+ entity_id TEXT NOT NULL,
707
+ kind INTEGER NOT NULL,
708
+ tag TEXT,
709
+ payload TEXT,
710
+ headers TEXT,
711
+ trace_id TEXT,
712
+ span_id TEXT,
713
+ sampled BOOLEAN,
714
+ processed BOOLEAN NOT NULL DEFAULT FALSE,
715
+ request_id INTEGER NOT NULL,
716
+ reply_id INTEGER,
717
+ last_reply_id INTEGER,
718
+ last_read TEXT,
719
+ deliver_at INTEGER,
720
+ UNIQUE (message_id),
721
+ FOREIGN KEY (request_id) REFERENCES ${messagesTableSql} (id) ON DELETE CASCADE
722
+ )
723
+ `
724
+ })
725
+
726
+ // Add message indexes optimized for the specific query patterns
727
+ const shardLookupIndex = `${messagesTable}_shard_idx`
728
+ const requestIdLookupIndex = `${messagesTable}_request_id_idx`
729
+ yield* sql.onDialectOrElse({
730
+ mssql: () =>
731
+ sql`
732
+ IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = ${shardLookupIndex})
733
+ CREATE INDEX ${sql(shardLookupIndex)}
734
+ ON ${messagesTableSql} (shard_id, processed, last_read, deliver_at);
735
+
736
+ IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = ${requestIdLookupIndex})
737
+ CREATE INDEX ${sql(requestIdLookupIndex)}
738
+ ON ${messagesTableSql} (request_id);
739
+ `,
740
+ mysql: () =>
741
+ sql`
742
+ CREATE INDEX ${sql(shardLookupIndex)}
743
+ ON ${messagesTableSql} (shard_id, processed, last_read, deliver_at);
744
+
745
+ CREATE INDEX ${sql(requestIdLookupIndex)}
746
+ ON ${messagesTableSql} (request_id);
747
+ `.unprepared.pipe(Effect.ignore),
748
+ pg: () =>
749
+ sql`
750
+ CREATE INDEX IF NOT EXISTS ${sql(shardLookupIndex)}
751
+ ON ${messagesTableSql} (shard_id, processed, last_read, deliver_at);
752
+
753
+ CREATE INDEX IF NOT EXISTS ${sql(requestIdLookupIndex)}
754
+ ON ${messagesTableSql} (request_id);
755
+ `.pipe(
756
+ Effect.tapDefect((error) =>
757
+ Effect.annotateLogs(Effect.logDebug("Failed to create indexes", error), {
758
+ package: "@effect/cluster",
759
+ module: "SqlMessageStorage"
760
+ })
761
+ ),
762
+ Effect.retry({
763
+ schedule: Schedule.spaced(1000)
764
+ })
765
+ ),
766
+ orElse: () =>
767
+ // sqlite
768
+ Effect.all([
769
+ sql`
770
+ CREATE INDEX IF NOT EXISTS ${sql(shardLookupIndex)}
771
+ ON ${messagesTableSql} (shard_id, processed, last_read, deliver_at)
772
+ `,
773
+ sql`
774
+ CREATE INDEX IF NOT EXISTS ${sql(requestIdLookupIndex)}
775
+ ON ${messagesTableSql} (request_id)
776
+ `
777
+ ]).pipe(sql.withTransaction)
778
+ })
779
+
780
+ yield* sql.onDialectOrElse({
781
+ mssql: () =>
782
+ sql`
783
+ IF OBJECT_ID(N'${repliesTableSql}', N'U') IS NULL
784
+ CREATE TABLE ${repliesTableSql} (
785
+ id BIGINT PRIMARY KEY,
786
+ rowid BIGINT IDENTITY(1,1),
787
+ kind INT,
788
+ request_id BIGINT NOT NULL,
789
+ payload TEXT NOT NULL,
790
+ sequence INT,
791
+ acked BIT NOT NULL DEFAULT 0,
792
+ CONSTRAINT ${sql(repliesTable + "_one_exit")} UNIQUE (request_id, kind),
793
+ CONSTRAINT ${sql(repliesTable + "_sequence")} UNIQUE (request_id, sequence),
794
+ FOREIGN KEY (request_id) REFERENCES ${messagesTableSql} (id) ON DELETE CASCADE
795
+ )
796
+ `,
797
+ mysql: () =>
798
+ sql`
799
+ CREATE TABLE IF NOT EXISTS ${repliesTableSql} (
800
+ id BIGINT NOT NULL,
801
+ rowid BIGINT AUTO_INCREMENT PRIMARY KEY,
802
+ kind INT,
803
+ request_id BIGINT NOT NULL,
804
+ payload TEXT NOT NULL,
805
+ sequence INT,
806
+ acked BOOLEAN NOT NULL DEFAULT FALSE,
807
+ UNIQUE (id),
808
+ UNIQUE (request_id, kind),
809
+ UNIQUE (request_id, sequence),
810
+ FOREIGN KEY (request_id) REFERENCES ${messagesTableSql} (id) ON DELETE CASCADE
811
+ )
812
+ `,
813
+ pg: () =>
814
+ sql`
815
+ CREATE TABLE IF NOT EXISTS ${repliesTableSql} (
816
+ id BIGINT PRIMARY KEY,
817
+ rowid BIGSERIAL,
818
+ kind INT,
819
+ request_id BIGINT NOT NULL,
820
+ payload TEXT NOT NULL,
821
+ sequence INT,
822
+ acked BOOLEAN NOT NULL DEFAULT FALSE,
823
+ UNIQUE (request_id, kind),
824
+ UNIQUE (request_id, sequence),
825
+ FOREIGN KEY (request_id) REFERENCES ${messagesTableSql} (id) ON DELETE CASCADE
826
+ )
827
+ `,
828
+ orElse: () =>
829
+ // sqlite
830
+ sql`
831
+ CREATE TABLE IF NOT EXISTS ${repliesTableSql} (
832
+ id INTEGER PRIMARY KEY,
833
+ kind INTEGER,
834
+ request_id INTEGER NOT NULL,
835
+ payload TEXT NOT NULL,
836
+ sequence INTEGER,
837
+ acked BOOLEAN NOT NULL DEFAULT FALSE,
838
+ UNIQUE (request_id, kind),
839
+ UNIQUE (request_id, sequence),
840
+ FOREIGN KEY (request_id) REFERENCES ${messagesTableSql} (id) ON DELETE CASCADE
841
+ )
842
+ `
843
+ })
844
+
845
+ // Add reply indexes optimized for request_id lookups
846
+ const replyLookupIndex = `${repliesTable}_request_lookup_idx`
847
+ yield* sql.onDialectOrElse({
848
+ mssql: () =>
849
+ sql`
850
+ IF NOT EXISTS (SELECT * FROM sys.indexes WHERE name = ${replyLookupIndex})
851
+ CREATE INDEX ${sql(replyLookupIndex)}
852
+ ON ${repliesTableSql} (request_id, kind, acked);
853
+ `,
854
+ mysql: () =>
855
+ sql`
856
+ CREATE INDEX ${sql(replyLookupIndex)}
857
+ ON ${repliesTableSql} (request_id, kind, acked);
858
+ `.unprepared.pipe(Effect.ignore),
859
+ pg: () =>
860
+ sql`
861
+ CREATE INDEX IF NOT EXISTS ${sql(replyLookupIndex)}
862
+ ON ${repliesTableSql} (request_id, kind, acked);
863
+ `.pipe(
864
+ Effect.tapDefect((error) =>
865
+ Effect.annotateLogs(Effect.logDebug("Failed to create indexes", error), {
866
+ package: "@effect/cluster",
867
+ module: "SqlMessageStorage"
868
+ })
869
+ ),
870
+ Effect.retry({
871
+ schedule: Schedule.spaced(1000)
872
+ })
873
+ ),
874
+ orElse: () =>
875
+ // sqlite
876
+ sql`
877
+ CREATE INDEX IF NOT EXISTS ${sql(replyLookupIndex)}
878
+ ON ${repliesTableSql} (request_id, kind, acked);
879
+ `
880
+ })
881
+ }),
882
+ "0002_entity_type_size": Effect.gen(function*() {
883
+ const sql = (yield* SqlClient.SqlClient).withoutTransforms()
884
+ const messagesTableSql = sql(messagesTable)
885
+
886
+ // resize entity_type to 150 characters
887
+ yield* sql.onDialectOrElse({
888
+ mssql: () =>
889
+ sql`
890
+ ALTER TABLE ${messagesTableSql} ALTER COLUMN entity_type VARCHAR(150) NOT NULL;
891
+ `,
892
+ mysql: () =>
893
+ sql`
894
+ ALTER TABLE ${messagesTableSql} MODIFY entity_type VARCHAR(150) NOT NULL;
895
+ `.unprepared.pipe(Effect.ignore),
896
+ pg: () =>
897
+ sql`
898
+ ALTER TABLE ${messagesTableSql} ALTER COLUMN entity_type TYPE VARCHAR(150);
899
+ `,
900
+ orElse: () =>
901
+ // sqlite
902
+ Effect.void
903
+ })
904
+ })
905
+ })
906
+ }
907
+
860
908
  const messageKind = {
861
909
  "Request": 0,
862
910
  "AckChunk": 1,