@effect/cluster 0.46.1 → 0.46.3
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/dist/cjs/ClusterCron.js +6 -2
- package/dist/cjs/ClusterCron.js.map +1 -1
- package/dist/cjs/EntityProxy.js +1 -1
- package/dist/cjs/EntityProxy.js.map +1 -1
- package/dist/cjs/EntityProxyServer.js +12 -2
- package/dist/cjs/EntityProxyServer.js.map +1 -1
- package/dist/cjs/SqlMessageStorage.js +273 -236
- package/dist/cjs/SqlMessageStorage.js.map +1 -1
- package/dist/dts/ClusterCron.d.ts.map +1 -1
- package/dist/dts/EntityProxy.d.ts.map +1 -1
- package/dist/dts/EntityProxyServer.d.ts.map +1 -1
- package/dist/dts/SqlMessageStorage.d.ts +3 -7
- package/dist/dts/SqlMessageStorage.d.ts.map +1 -1
- package/dist/esm/ClusterCron.js +6 -2
- package/dist/esm/ClusterCron.js.map +1 -1
- package/dist/esm/EntityProxy.js +1 -1
- package/dist/esm/EntityProxy.js.map +1 -1
- package/dist/esm/EntityProxyServer.js +12 -2
- package/dist/esm/EntityProxyServer.js.map +1 -1
- package/dist/esm/SqlMessageStorage.js +273 -236
- package/dist/esm/SqlMessageStorage.js.map +1 -1
- package/package.json +4 -4
- package/src/ClusterCron.ts +10 -4
- package/src/EntityProxy.ts +4 -1
- package/src/EntityProxyServer.ts +23 -2
- package/src/SqlMessageStorage.ts +315 -267
package/src/ClusterCron.ts
CHANGED
|
@@ -101,11 +101,17 @@ export const make = <E, R>(options: {
|
|
|
101
101
|
options.calculateNextRunFromPrevious ? request.payload.dateTime : now
|
|
102
102
|
))
|
|
103
103
|
const client = makeClient(DateTime.formatIso(next))
|
|
104
|
-
return yield* client.run({ dateTime: next }, { discard: true })
|
|
104
|
+
return yield* client.run({ dateTime: next }, { discard: true }).pipe(
|
|
105
|
+
Effect.sandbox,
|
|
106
|
+
Effect.retry(retryPolicy)
|
|
107
|
+
)
|
|
105
108
|
}).pipe(
|
|
106
|
-
Effect.
|
|
107
|
-
Effect.
|
|
108
|
-
|
|
109
|
+
Effect.catchAllCause(Effect.logWarning),
|
|
110
|
+
Effect.annotateLogs({
|
|
111
|
+
module: "ClusterCron",
|
|
112
|
+
name: options.name,
|
|
113
|
+
dateTime: request.payload.dateTime
|
|
114
|
+
})
|
|
109
115
|
)
|
|
110
116
|
)
|
|
111
117
|
}
|
package/src/EntityProxy.ts
CHANGED
|
@@ -172,7 +172,10 @@ export const toHttpApiGroup = <const Name extends string, Type extends string, R
|
|
|
172
172
|
.addSuccess(parentRpc.successSchema)
|
|
173
173
|
.addError(Schema.Union(parentRpc.errorSchema, ...clientErrors))
|
|
174
174
|
.annotateContext(parentRpc.annotations)
|
|
175
|
-
const endpointDiscard = HttpApiEndpoint.post(
|
|
175
|
+
const endpointDiscard = HttpApiEndpoint.post(
|
|
176
|
+
`${parentRpc._tag}Discard`,
|
|
177
|
+
`/${tagToPath(parentRpc._tag)}/:entityId/discard`
|
|
178
|
+
)
|
|
176
179
|
.setPath(entityIdPath)
|
|
177
180
|
.setPayload(parentRpc.payloadSchema)
|
|
178
181
|
.addError(Schema.Union(...clientErrors))
|
package/src/EntityProxyServer.ts
CHANGED
|
@@ -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](
|
|
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
|
|
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>
|
package/src/SqlMessageStorage.ts
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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,
|