@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.
Files changed (37) hide show
  1. package/EntityResource/package.json +6 -0
  2. package/dist/cjs/Entity.js +1 -0
  3. package/dist/cjs/Entity.js.map +1 -1
  4. package/dist/cjs/EntityResource.js +74 -0
  5. package/dist/cjs/EntityResource.js.map +1 -0
  6. package/dist/cjs/SqlMessageStorage.js +27 -20
  7. package/dist/cjs/SqlMessageStorage.js.map +1 -1
  8. package/dist/cjs/index.js +3 -1
  9. package/dist/cjs/internal/entityManager.js +3 -4
  10. package/dist/cjs/internal/entityManager.js.map +1 -1
  11. package/dist/cjs/internal/entityReaper.js +1 -1
  12. package/dist/cjs/internal/entityReaper.js.map +1 -1
  13. package/dist/dts/Entity.d.ts.map +1 -1
  14. package/dist/dts/EntityResource.d.ts +55 -0
  15. package/dist/dts/EntityResource.d.ts.map +1 -0
  16. package/dist/dts/SqlMessageStorage.d.ts.map +1 -1
  17. package/dist/dts/index.d.ts +4 -0
  18. package/dist/dts/index.d.ts.map +1 -1
  19. package/dist/esm/Entity.js +1 -0
  20. package/dist/esm/Entity.js.map +1 -1
  21. package/dist/esm/EntityResource.js +66 -0
  22. package/dist/esm/EntityResource.js.map +1 -0
  23. package/dist/esm/SqlMessageStorage.js +27 -20
  24. package/dist/esm/SqlMessageStorage.js.map +1 -1
  25. package/dist/esm/index.js +4 -0
  26. package/dist/esm/index.js.map +1 -1
  27. package/dist/esm/internal/entityManager.js +3 -4
  28. package/dist/esm/internal/entityManager.js.map +1 -1
  29. package/dist/esm/internal/entityReaper.js +1 -1
  30. package/dist/esm/internal/entityReaper.js.map +1 -1
  31. package/package.json +12 -4
  32. package/src/Entity.ts +1 -0
  33. package/src/EntityResource.ts +117 -0
  34. package/src/SqlMessageStorage.ts +27 -20
  35. package/src/index.ts +5 -0
  36. package/src/internal/entityManager.ts +3 -4
  37. 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,IAAIH,QAAQ,GAAGX,WAAW,EAAE;cAC3D;YACF;YACA,OAAOf,MAAM,CAAC8B,IAAI,CAACR,QAAQ,CAACS,YAAY,CAACP,KAAK,CAACQ,OAAO,CAAC,CAAC;UAC1D;QACF;MACF;IACF,CAAC,CAAC,CAACC,IAAI,CACL1B,KAAK,CAAC2B,QAAQ,EACdlC,MAAM,CAACmC,aAAa,EACpBnC,MAAM,CAACoC,UAAU,CAClB;IAED,OAAO;MAAE3B;IAAQ,CAAW;EAC9B,CAAC;CACF,CAAC","ignoreList":[]}
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.1",
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
- "@effect/rpc": "^0.72.2",
18
- "effect": "^3.19.5"
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
+ })
@@ -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
- UPDATE ${messagesTableSql} m
310
- SET last_read = ${sqlNow}
311
- FROM (
312
- SELECT m.*
313
- FROM ${messagesTableSql} m
314
- WHERE m.shard_id IN (${sql.literal(shardIds.map(wrapString).join(","))})
315
- AND NOT EXISTS (
316
- SELECT 1 FROM ${repliesTableSql}
317
- WHERE request_id = m.request_id
318
- AND (kind = ${replyKindWithExit} OR acked = ${sqlFalse})
319
- )
320
- AND m.processed = ${sqlFalse}
321
- AND (m.last_read IS NULL OR m.last_read < ${tenMinutesAgo})
322
- AND (m.deliver_at IS NULL OR m.deliver_at <= ${sql.literal(String(now))})
323
- ORDER BY m.rowid ASC
324
- FOR UPDATE
325
- ) AS ids
326
- LEFT JOIN ${repliesTableSql} r ON r.id = ids.last_reply_id
327
- WHERE m.id = ids.id
328
- RETURNING ids.*, r.id as reply_reply_id, r.kind as reply_kind, r.payload as reply_payload, r.sequence as reply_sequence
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
@@ -53,6 +53,11 @@ export * as EntityProxy from "./EntityProxy.js"
53
53
  */
54
54
  export * as EntityProxyServer from "./EntityProxyServer.js"
55
55
 
56
+ /**
57
+ * @since 1.0.0
58
+ */
59
+ export * as EntityResource from "./EntityResource.js"
60
+
56
61
  /**
57
62
  * @since 1.0.0
58
63
  */
@@ -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: entity.protocol.requests.get(message.envelope.tag)!,
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 = entity.protocol.requests.get(decoded.envelope.tag)!
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))