@effect/cluster 0.53.1 → 0.53.2
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 +55 -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 +66 -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 +117 -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.2",
|
|
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",
|
|
15
|
+
"@effect/sql": "^0.48.0",
|
|
16
16
|
"@effect/workflow": "^0.13.0",
|
|
17
|
-
"
|
|
18
|
-
"effect": "^
|
|
17
|
+
"effect": "^3.19.6",
|
|
18
|
+
"@effect/rpc": "^0.72.2"
|
|
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,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import * as Duration from "effect/Duration"
|
|
5
|
+
import * as Effect from "effect/Effect"
|
|
6
|
+
import { identity } from "effect/Function"
|
|
7
|
+
import * as RcRef from "effect/RcRef"
|
|
8
|
+
import * as Scope from "effect/Scope"
|
|
9
|
+
import * as Entity from "./Entity.js"
|
|
10
|
+
import type { Sharding } from "./Sharding.js"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @since 1.0.0
|
|
14
|
+
* @category Type ids
|
|
15
|
+
*/
|
|
16
|
+
export const TypeId: TypeId = "~@effect/cluster/EntityResource"
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @since 1.0.0
|
|
20
|
+
* @category Type ids
|
|
21
|
+
*/
|
|
22
|
+
export type TypeId = "~@effect/cluster/EntityResource"
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @since 1.0.0
|
|
26
|
+
* @category Models
|
|
27
|
+
*/
|
|
28
|
+
export interface EntityResource<out A, out E = never> {
|
|
29
|
+
readonly [TypeId]: TypeId
|
|
30
|
+
readonly get: Effect.Effect<A, E, Scope.Scope>
|
|
31
|
+
readonly close: Effect.Effect<void>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A `EntityResource` is a resource that can be acquired inside a cluster
|
|
36
|
+
* entity, which will keep the entity alive even across restarts.
|
|
37
|
+
*
|
|
38
|
+
* The resource will only be fully released when the idle time to live is
|
|
39
|
+
* reached, or when the `close` effect is called.
|
|
40
|
+
*
|
|
41
|
+
* By default, the `idleTimeToLive` is infinite, meaning the resource will only
|
|
42
|
+
* be released when `close` is called.
|
|
43
|
+
*
|
|
44
|
+
* @since 1.0.0
|
|
45
|
+
* @category Constructors
|
|
46
|
+
*/
|
|
47
|
+
export const make: <A, E, R>(options: {
|
|
48
|
+
readonly acquire: Effect.Effect<A, E, R>
|
|
49
|
+
readonly idleTimeToLive?: Duration.DurationInput | undefined
|
|
50
|
+
/**
|
|
51
|
+
* When to close the resource Scope.
|
|
52
|
+
*
|
|
53
|
+
* If set to "explicit", the resource will only be cleaned up when either the
|
|
54
|
+
* `idleTimeToLive` is reached, or the .close effect is called.
|
|
55
|
+
*
|
|
56
|
+
* Defaults to "always", which means the resource will be cleaned up when the
|
|
57
|
+
* the parent Scope is closed.
|
|
58
|
+
*/
|
|
59
|
+
readonly shutdownMode?: "explicit" | "always" | undefined
|
|
60
|
+
}) => Effect.Effect<
|
|
61
|
+
EntityResource<A, E>,
|
|
62
|
+
E,
|
|
63
|
+
Scope.Scope | R | Sharding | Entity.CurrentAddress
|
|
64
|
+
> = Effect.fnUntraced(function*<A, E, R>(options: {
|
|
65
|
+
readonly acquire: Effect.Effect<A, E, R>
|
|
66
|
+
readonly idleTimeToLive?: Duration.DurationInput | undefined
|
|
67
|
+
readonly shutdownMode?: "explicit" | "always" | undefined
|
|
68
|
+
}) {
|
|
69
|
+
const shutdownMode = options.shutdownMode ?? "always"
|
|
70
|
+
let shuttingDown = false
|
|
71
|
+
|
|
72
|
+
const ref = yield* RcRef.make({
|
|
73
|
+
acquire: Effect.gen(function*() {
|
|
74
|
+
let scope = yield* Effect.scope
|
|
75
|
+
|
|
76
|
+
if (shutdownMode === "explicit") {
|
|
77
|
+
const closeable = yield* Scope.make()
|
|
78
|
+
const context = yield* Effect.context<Sharding | Entity.CurrentAddress>()
|
|
79
|
+
yield* Scope.addFinalizerExit(
|
|
80
|
+
scope,
|
|
81
|
+
Effect.fnUntraced(function*(exit) {
|
|
82
|
+
if (shuttingDown) return
|
|
83
|
+
yield* Scope.close(closeable, exit)
|
|
84
|
+
yield* Entity.keepAlive(false)
|
|
85
|
+
}, Effect.provide(context))
|
|
86
|
+
)
|
|
87
|
+
scope = closeable
|
|
88
|
+
} else {
|
|
89
|
+
yield* Effect.addFinalizer(() => {
|
|
90
|
+
if (shuttingDown) return Effect.void
|
|
91
|
+
return Entity.keepAlive(false)
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
yield* Entity.keepAlive(true)
|
|
96
|
+
|
|
97
|
+
return yield* options.acquire.pipe(
|
|
98
|
+
Scope.extend(scope)
|
|
99
|
+
)
|
|
100
|
+
}),
|
|
101
|
+
idleTimeToLive: options.idleTimeToLive ?? Duration.infinity
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
yield* Effect.addFinalizer(() => {
|
|
105
|
+
shuttingDown = true
|
|
106
|
+
return Effect.void
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// Initialize the resource
|
|
110
|
+
yield* Effect.scoped(RcRef.get(ref))
|
|
111
|
+
|
|
112
|
+
return identity<EntityResource<A, E>>({
|
|
113
|
+
[TypeId]: TypeId,
|
|
114
|
+
get: RcRef.get(ref),
|
|
115
|
+
close: RcRef.invalidate(ref)
|
|
116
|
+
})
|
|
117
|
+
})
|
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))
|