@effect/cluster 0.53.1 → 0.53.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/EntityResource/package.json +6 -0
- package/dist/cjs/Entity.js +1 -0
- package/dist/cjs/Entity.js.map +1 -1
- package/dist/cjs/EntityResource.js +74 -0
- package/dist/cjs/EntityResource.js.map +1 -0
- package/dist/cjs/SqlMessageStorage.js +27 -20
- package/dist/cjs/SqlMessageStorage.js.map +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/internal/entityManager.js +3 -4
- package/dist/cjs/internal/entityManager.js.map +1 -1
- package/dist/cjs/internal/entityReaper.js +1 -1
- package/dist/cjs/internal/entityReaper.js.map +1 -1
- package/dist/dts/Entity.d.ts.map +1 -1
- package/dist/dts/EntityResource.d.ts +58 -0
- package/dist/dts/EntityResource.d.ts.map +1 -0
- package/dist/dts/SqlMessageStorage.d.ts.map +1 -1
- package/dist/dts/index.d.ts +4 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/Entity.js +1 -0
- package/dist/esm/Entity.js.map +1 -1
- package/dist/esm/EntityResource.js +65 -0
- package/dist/esm/EntityResource.js.map +1 -0
- package/dist/esm/SqlMessageStorage.js +27 -20
- package/dist/esm/SqlMessageStorage.js.map +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/entityManager.js +3 -4
- package/dist/esm/internal/entityManager.js.map +1 -1
- package/dist/esm/internal/entityReaper.js +1 -1
- package/dist/esm/internal/entityReaper.js.map +1 -1
- package/package.json +12 -4
- package/src/Entity.ts +1 -0
- package/src/EntityResource.ts +108 -0
- package/src/SqlMessageStorage.ts +27 -20
- package/src/index.ts +5 -0
- package/src/internal/entityManager.ts +3 -4
- package/src/internal/entityReaper.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entityReaper.js","names":["Effect","EntityReaper","Service","scoped","gen","currentResolution","registered","latch","makeLatch","register","options","suspend","Math","max","min","maxIdleTime","push","open","clock","sleep","now","unsafeCurrentTimeMillis","entities","servers","state","values","duration","lastActiveCheck","activeRequests","size","fork","removeIgnore","address","pipe","whenOpen","interruptible","forkScoped"],"sources":["../../../src/internal/entityReaper.ts"],"sourcesContent":[null],"mappings":"AAAA,OAAO,KAAKA,MAAM,MAAM,eAAe;AAOvC;AACA,OAAM,MAAOC,YAAa,sBAAQD,MAAM,CAACE,OAAO,EAAgB,CAAC,8BAA8B,EAAE;EAC/FC,MAAM,eAAEH,MAAM,CAACI,GAAG,CAAC,aAAS;IAC1B,IAAIC,iBAAiB,GAAG,MAAM;IAC9B,MAAMC,UAAU,GAIX,EAAE;IACP,MAAMC,KAAK,GAAG,OAAOP,MAAM,CAACQ,SAAS,EAAE;IAEvC,MAAMC,QAAQ,GAAIC,OAIjB,IACCV,MAAM,CAACW,OAAO,CAAC,MAAK;MAClBN,iBAAiB,GAAGO,IAAI,CAACC,GAAG,CAACD,IAAI,CAACE,GAAG,CAACT,iBAAiB,EAAEK,OAAO,CAACK,WAAW,CAAC,EAAE,IAAI,CAAC;MACpFT,UAAU,CAACU,IAAI,CAACN,OAAO,CAAC;MACxB,OAAOH,KAAK,CAACU,IAAI;IACnB,CAAC,CAAC;IAEJ,MAAMC,KAAK,GAAG,OAAOlB,MAAM,CAACkB,KAAK;IACjC,OAAOlB,MAAM,CAACI,GAAG,CAAC,aAAS;MACzB,OAAO,IAAI,EAAE;QACX,OAAOJ,MAAM,CAACmB,KAAK,CAACd,iBAAiB,CAAC;QACtC,MAAMe,GAAG,GAAGF,KAAK,CAACG,uBAAuB,EAAE;QAC3C,KAAK,MAAM;UAAEC,QAAQ;UAAEP,WAAW;UAAEQ;QAAO,CAAE,IAAIjB,UAAU,EAAE;UAC3D,KAAK,MAAMkB,KAAK,IAAID,OAAO,CAACE,MAAM,EAAE,EAAE;YACpC,MAAMC,QAAQ,GAAGN,GAAG,GAAGI,KAAK,CAACG,eAAe;YAC5C,IAAIH,KAAK,CAACI,cAAc,CAACC,IAAI,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"entityReaper.js","names":["Effect","EntityReaper","Service","scoped","gen","currentResolution","registered","latch","makeLatch","register","options","suspend","Math","max","min","maxIdleTime","push","open","clock","sleep","now","unsafeCurrentTimeMillis","entities","servers","state","values","duration","lastActiveCheck","keepAliveEnabled","activeRequests","size","fork","removeIgnore","address","pipe","whenOpen","interruptible","forkScoped"],"sources":["../../../src/internal/entityReaper.ts"],"sourcesContent":[null],"mappings":"AAAA,OAAO,KAAKA,MAAM,MAAM,eAAe;AAOvC;AACA,OAAM,MAAOC,YAAa,sBAAQD,MAAM,CAACE,OAAO,EAAgB,CAAC,8BAA8B,EAAE;EAC/FC,MAAM,eAAEH,MAAM,CAACI,GAAG,CAAC,aAAS;IAC1B,IAAIC,iBAAiB,GAAG,MAAM;IAC9B,MAAMC,UAAU,GAIX,EAAE;IACP,MAAMC,KAAK,GAAG,OAAOP,MAAM,CAACQ,SAAS,EAAE;IAEvC,MAAMC,QAAQ,GAAIC,OAIjB,IACCV,MAAM,CAACW,OAAO,CAAC,MAAK;MAClBN,iBAAiB,GAAGO,IAAI,CAACC,GAAG,CAACD,IAAI,CAACE,GAAG,CAACT,iBAAiB,EAAEK,OAAO,CAACK,WAAW,CAAC,EAAE,IAAI,CAAC;MACpFT,UAAU,CAACU,IAAI,CAACN,OAAO,CAAC;MACxB,OAAOH,KAAK,CAACU,IAAI;IACnB,CAAC,CAAC;IAEJ,MAAMC,KAAK,GAAG,OAAOlB,MAAM,CAACkB,KAAK;IACjC,OAAOlB,MAAM,CAACI,GAAG,CAAC,aAAS;MACzB,OAAO,IAAI,EAAE;QACX,OAAOJ,MAAM,CAACmB,KAAK,CAACd,iBAAiB,CAAC;QACtC,MAAMe,GAAG,GAAGF,KAAK,CAACG,uBAAuB,EAAE;QAC3C,KAAK,MAAM;UAAEC,QAAQ;UAAEP,WAAW;UAAEQ;QAAO,CAAE,IAAIjB,UAAU,EAAE;UAC3D,KAAK,MAAMkB,KAAK,IAAID,OAAO,CAACE,MAAM,EAAE,EAAE;YACpC,MAAMC,QAAQ,GAAGN,GAAG,GAAGI,KAAK,CAACG,eAAe;YAC5C,IAAIH,KAAK,CAACI,gBAAgB,IAAIJ,KAAK,CAACK,cAAc,CAACC,IAAI,GAAG,CAAC,IAAIJ,QAAQ,GAAGX,WAAW,EAAE;cACrF;YACF;YACA,OAAOf,MAAM,CAAC+B,IAAI,CAACT,QAAQ,CAACU,YAAY,CAACR,KAAK,CAACS,OAAO,CAAC,CAAC;UAC1D;QACF;MACF;IACF,CAAC,CAAC,CAACC,IAAI,CACL3B,KAAK,CAAC4B,QAAQ,EACdnC,MAAM,CAACoC,aAAa,EACpBpC,MAAM,CAACqC,UAAU,CAClB;IAED,OAAO;MAAE5B;IAAQ,CAAW;EAC9B,CAAC;CACF,CAAC","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect/cluster",
|
|
3
|
-
"version": "0.53.
|
|
3
|
+
"version": "0.53.3",
|
|
4
4
|
"description": "Unified interfaces for common cluster-specific services",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
"sideEffects": [],
|
|
12
12
|
"homepage": "https://effect.website",
|
|
13
13
|
"peerDependencies": {
|
|
14
|
-
"@effect/sql": "^0.48.0",
|
|
15
14
|
"@effect/platform": "^0.93.3",
|
|
16
|
-
"@effect/
|
|
15
|
+
"@effect/sql": "^0.48.0",
|
|
17
16
|
"@effect/rpc": "^0.72.2",
|
|
18
|
-
"effect": "^
|
|
17
|
+
"@effect/workflow": "^0.13.0",
|
|
18
|
+
"effect": "^3.19.6"
|
|
19
19
|
},
|
|
20
20
|
"publishConfig": {
|
|
21
21
|
"provenance": true
|
|
@@ -85,6 +85,11 @@
|
|
|
85
85
|
"import": "./dist/esm/EntityProxyServer.js",
|
|
86
86
|
"default": "./dist/cjs/EntityProxyServer.js"
|
|
87
87
|
},
|
|
88
|
+
"./EntityResource": {
|
|
89
|
+
"types": "./dist/dts/EntityResource.d.ts",
|
|
90
|
+
"import": "./dist/esm/EntityResource.js",
|
|
91
|
+
"default": "./dist/cjs/EntityResource.js"
|
|
92
|
+
},
|
|
88
93
|
"./EntityType": {
|
|
89
94
|
"types": "./dist/dts/EntityType.d.ts",
|
|
90
95
|
"import": "./dist/esm/EntityType.js",
|
|
@@ -241,6 +246,9 @@
|
|
|
241
246
|
"EntityProxyServer": [
|
|
242
247
|
"./dist/dts/EntityProxyServer.d.ts"
|
|
243
248
|
],
|
|
249
|
+
"EntityResource": [
|
|
250
|
+
"./dist/dts/EntityResource.d.ts"
|
|
251
|
+
],
|
|
244
252
|
"EntityType": [
|
|
245
253
|
"./dist/dts/EntityType.d.ts"
|
|
246
254
|
],
|
package/src/Entity.ts
CHANGED
|
@@ -609,6 +609,7 @@ export const keepAlive: (
|
|
|
609
609
|
const address = yield* CurrentAddress
|
|
610
610
|
const requestId = yield* sharding.getSnowflake
|
|
611
611
|
const span = yield* Effect.orDie(Effect.currentSpan)
|
|
612
|
+
olatch.value.unsafeClose()
|
|
612
613
|
yield* Effect.orDie(sharding.sendOutgoing(
|
|
613
614
|
new Message.OutgoingRequest({
|
|
614
615
|
rpc: KeepAliveRpc,
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import * as Context from "effect/Context"
|
|
5
|
+
import * as Duration from "effect/Duration"
|
|
6
|
+
import * as Effect from "effect/Effect"
|
|
7
|
+
import { identity } from "effect/Function"
|
|
8
|
+
import * as RcRef from "effect/RcRef"
|
|
9
|
+
import * as Scope from "effect/Scope"
|
|
10
|
+
import * as Entity from "./Entity.js"
|
|
11
|
+
import type { Sharding } from "./Sharding.js"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @since 1.0.0
|
|
15
|
+
* @category Type ids
|
|
16
|
+
*/
|
|
17
|
+
export const TypeId: TypeId = "~@effect/cluster/EntityResource"
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @since 1.0.0
|
|
21
|
+
* @category Type ids
|
|
22
|
+
*/
|
|
23
|
+
export type TypeId = "~@effect/cluster/EntityResource"
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @since 1.0.0
|
|
27
|
+
* @category Models
|
|
28
|
+
*/
|
|
29
|
+
export interface EntityResource<out A, out E = never> {
|
|
30
|
+
readonly [TypeId]: TypeId
|
|
31
|
+
readonly get: Effect.Effect<A, E, Scope.Scope>
|
|
32
|
+
readonly close: Effect.Effect<void>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A `Scope` that is only closed when the resource is explicitly closed.
|
|
37
|
+
*
|
|
38
|
+
* It is not closed during restarts, due to shard movement or node shutdowns.
|
|
39
|
+
*
|
|
40
|
+
* @since 1.0.0
|
|
41
|
+
* @category Scope
|
|
42
|
+
*/
|
|
43
|
+
export class CloseScope extends Context.Tag("@effect/cluster/EntityResource/CloseScope")<
|
|
44
|
+
CloseScope,
|
|
45
|
+
Scope.Scope
|
|
46
|
+
>() {}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* A `EntityResource` is a resource that can be acquired inside a cluster
|
|
50
|
+
* entity, which will keep the entity alive even across restarts.
|
|
51
|
+
*
|
|
52
|
+
* The resource will only be fully released when the idle time to live is
|
|
53
|
+
* reached, or when the `close` effect is called.
|
|
54
|
+
*
|
|
55
|
+
* By default, the `idleTimeToLive` is infinite, meaning the resource will only
|
|
56
|
+
* be released when `close` is called.
|
|
57
|
+
*
|
|
58
|
+
* @since 1.0.0
|
|
59
|
+
* @category Constructors
|
|
60
|
+
*/
|
|
61
|
+
export const make: <A, E, R>(options: {
|
|
62
|
+
readonly acquire: Effect.Effect<A, E, R>
|
|
63
|
+
readonly idleTimeToLive?: Duration.DurationInput | undefined
|
|
64
|
+
}) => Effect.Effect<
|
|
65
|
+
EntityResource<A, E>,
|
|
66
|
+
E,
|
|
67
|
+
Scope.Scope | Exclude<R, CloseScope> | Sharding | Entity.CurrentAddress
|
|
68
|
+
> = Effect.fnUntraced(function*<A, E, R>(options: {
|
|
69
|
+
readonly acquire: Effect.Effect<A, E, R>
|
|
70
|
+
readonly idleTimeToLive?: Duration.DurationInput | undefined
|
|
71
|
+
}) {
|
|
72
|
+
let shuttingDown = false
|
|
73
|
+
|
|
74
|
+
const ref = yield* RcRef.make({
|
|
75
|
+
acquire: Effect.gen(function*() {
|
|
76
|
+
const closeable = yield* Scope.make()
|
|
77
|
+
|
|
78
|
+
yield* Effect.addFinalizer(
|
|
79
|
+
Effect.fnUntraced(function*(exit) {
|
|
80
|
+
if (shuttingDown) return
|
|
81
|
+
yield* Scope.close(closeable, exit)
|
|
82
|
+
yield* Entity.keepAlive(false)
|
|
83
|
+
})
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
yield* Entity.keepAlive(true)
|
|
87
|
+
|
|
88
|
+
return yield* options.acquire.pipe(
|
|
89
|
+
Effect.provideService(CloseScope, closeable)
|
|
90
|
+
)
|
|
91
|
+
}),
|
|
92
|
+
idleTimeToLive: options.idleTimeToLive ?? Duration.infinity
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
yield* Effect.addFinalizer(() => {
|
|
96
|
+
shuttingDown = true
|
|
97
|
+
return Effect.void
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// Initialize the resource
|
|
101
|
+
yield* Effect.scoped(RcRef.get(ref))
|
|
102
|
+
|
|
103
|
+
return identity<EntityResource<A, E>>({
|
|
104
|
+
[TypeId]: TypeId,
|
|
105
|
+
get: RcRef.get(ref),
|
|
106
|
+
close: RcRef.invalidate(ref)
|
|
107
|
+
})
|
|
108
|
+
})
|
package/src/SqlMessageStorage.ts
CHANGED
|
@@ -302,30 +302,36 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
|
302
302
|
mssql: () => (s: string) => `N'${s}'`,
|
|
303
303
|
orElse: () => (s: string) => `'${s}'`
|
|
304
304
|
})
|
|
305
|
+
const forUpdate = sql.onDialectOrElse({
|
|
306
|
+
sqlite: () => sql.literal(""),
|
|
307
|
+
orElse: () => sql.literal("FOR UPDATE")
|
|
308
|
+
})
|
|
305
309
|
|
|
306
310
|
const getUnprocessedMessages = sql.onDialectOrElse({
|
|
307
311
|
pg: () => (shardIds: ReadonlyArray<string>, now: number) =>
|
|
308
312
|
sql<MessageJoinRow>`
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
313
|
+
WITH messages AS (
|
|
314
|
+
UPDATE ${messagesTableSql} m
|
|
315
|
+
SET last_read = ${sqlNow}
|
|
316
|
+
FROM (
|
|
317
|
+
SELECT m.*
|
|
318
|
+
FROM ${messagesTableSql} m
|
|
319
|
+
WHERE m.shard_id IN (${sql.literal(shardIds.map(wrapString).join(","))})
|
|
320
|
+
AND NOT EXISTS (
|
|
321
|
+
SELECT 1 FROM ${repliesTableSql}
|
|
322
|
+
WHERE request_id = m.request_id
|
|
323
|
+
AND (kind = ${replyKindWithExit} OR acked = ${sqlFalse})
|
|
324
|
+
)
|
|
325
|
+
AND m.processed = ${sqlFalse}
|
|
326
|
+
AND (m.last_read IS NULL OR m.last_read < ${tenMinutesAgo})
|
|
327
|
+
AND (m.deliver_at IS NULL OR m.deliver_at <= ${sql.literal(String(now))})
|
|
328
|
+
FOR UPDATE
|
|
329
|
+
) AS ids
|
|
330
|
+
LEFT JOIN ${repliesTableSql} r ON r.id = ids.last_reply_id
|
|
331
|
+
WHERE m.id = ids.id
|
|
332
|
+
RETURNING ids.*, r.id as reply_reply_id, r.kind as reply_kind, r.payload as reply_payload, r.sequence as reply_sequence
|
|
333
|
+
)
|
|
334
|
+
SELECT * FROM messages ORDER BY rowid ASC
|
|
329
335
|
`,
|
|
330
336
|
orElse: () => (shardIds: ReadonlyArray<string>, now: number) =>
|
|
331
337
|
sql<MessageJoinRow>`
|
|
@@ -342,6 +348,7 @@ export const make = Effect.fnUntraced(function*(options?: {
|
|
|
342
348
|
AND (m.last_read IS NULL OR m.last_read < ${tenMinutesAgo})
|
|
343
349
|
AND (m.deliver_at IS NULL OR m.deliver_at <= ${sql.literal(String(now))})
|
|
344
350
|
ORDER BY m.rowid ASC
|
|
351
|
+
${forUpdate}
|
|
345
352
|
`.unprepared.pipe(
|
|
346
353
|
Effect.tap((rows) => {
|
|
347
354
|
if (rows.length === 0) {
|
package/src/index.ts
CHANGED
|
@@ -131,7 +131,7 @@ export const make = Effect.fnUntraced(function*<
|
|
|
131
131
|
|
|
132
132
|
const scope = yield* Effect.scope
|
|
133
133
|
const endLatch = Effect.unsafeMakeLatch()
|
|
134
|
-
const keepAliveLatch = Effect.unsafeMakeLatch()
|
|
134
|
+
const keepAliveLatch = Effect.unsafeMakeLatch(false)
|
|
135
135
|
|
|
136
136
|
// on shutdown, reset the storage for the entity
|
|
137
137
|
yield* Scope.addFinalizerExit(
|
|
@@ -420,7 +420,6 @@ export const make = Effect.fnUntraced(function*<
|
|
|
420
420
|
|
|
421
421
|
if (server.keepAliveEnabled) return reply
|
|
422
422
|
server.keepAliveEnabled = true
|
|
423
|
-
server.keepAliveLatch.unsafeClose()
|
|
424
423
|
return server.keepAliveLatch.whenOpen(Effect.suspend(() => {
|
|
425
424
|
server.keepAliveEnabled = false
|
|
426
425
|
return reply
|
|
@@ -537,7 +536,7 @@ export const make = Effect.fnUntraced(function*<
|
|
|
537
536
|
requestId: message.envelope.requestId,
|
|
538
537
|
exit: Exit.die(new MalformedMessage({ cause }))
|
|
539
538
|
}),
|
|
540
|
-
rpc:
|
|
539
|
+
rpc: entityRpcs.get(message.envelope.tag)!,
|
|
541
540
|
context
|
|
542
541
|
})
|
|
543
542
|
))
|
|
@@ -549,7 +548,7 @@ export const make = Effect.fnUntraced(function*<
|
|
|
549
548
|
)
|
|
550
549
|
}
|
|
551
550
|
const request = message as Message.IncomingRequest<any>
|
|
552
|
-
const rpc =
|
|
551
|
+
const rpc = entityRpcs.get(decoded.envelope.tag)!
|
|
553
552
|
return sendLocal(
|
|
554
553
|
new Message.IncomingRequestLocal({
|
|
555
554
|
envelope: decoded.envelope,
|
|
@@ -35,7 +35,7 @@ export class EntityReaper extends Effect.Service<EntityReaper>()("@effect/cluste
|
|
|
35
35
|
for (const { entities, maxIdleTime, servers } of registered) {
|
|
36
36
|
for (const state of servers.values()) {
|
|
37
37
|
const duration = now - state.lastActiveCheck
|
|
38
|
-
if (state.activeRequests.size > 0 || duration < maxIdleTime) {
|
|
38
|
+
if (state.keepAliveEnabled || state.activeRequests.size > 0 || duration < maxIdleTime) {
|
|
39
39
|
continue
|
|
40
40
|
}
|
|
41
41
|
yield* Effect.fork(entities.removeIgnore(state.address))
|