@entifix/redis 0.1.4 → 0.2.0

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.
@@ -1,7 +1,7 @@
1
1
  import { EntifixConnError } from '@entifix/core';
2
2
  import { Context, Layer } from 'effect';
3
3
  import { Redis } from 'ioredis';
4
- declare const RedisTag_base: Context.TagClass<RedisTag, "RedisTag", Redis<"legacy">>;
4
+ declare const RedisTag_base: Context.ServiceClass<RedisTag, "RedisTag", Redis<"legacy">>;
5
5
  /** DI tag carrying a connected ioredis client. */
6
6
  export declare class RedisTag extends RedisTag_base {
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"redis-connection.d.ts","sourceRoot":"","sources":["../../src/redis-connection/redis-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,OAAO,EAAU,KAAK,EAAY,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;;AAEhC,kDAAkD;AAClD,qBAAa,QAAS,SAAQ,aAA0C;CAAG;AAE3E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAYD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,aAAa,KACtB,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,gBAAgB,CAgCtC,CAAC"}
1
+ {"version":3,"file":"redis-connection.d.ts","sourceRoot":"","sources":["../../src/redis-connection/redis-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,OAAO,EAAU,KAAK,EAAY,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;;AAEhC,kDAAkD;AAClD,qBAAa,QAAS,SAAQ,aAA8C;CAAG;AAE/E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAYD;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,aAAa,KACtB,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,gBAAgB,CAgCtC,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { EntifixConnError } from '@entifix/core';
2
2
  import { Context, Effect, Layer, Schedule } from 'effect';
3
3
  import { Redis } from 'ioredis';
4
- /** DI tag carrying a connected ioredis client. */ export class RedisTag extends Context.Tag('RedisTag')() {
4
+ /** DI tag carrying a connected ioredis client. */ export class RedisTag extends Context.Service()('RedisTag') {
5
5
  }
6
6
  /**
7
7
  * How long the initial connect keeps trying before the service gives up.
@@ -26,7 +26,7 @@ import { Redis } from 'ioredis';
26
26
  * restarted, pod rescheduled) is re-established for the lifetime of the
27
27
  * process. Without it the client can settle into a terminal state and the
28
28
  * service stays degraded until someone restarts it, even after Redis is back.
29
- */ export const RedisLayer = (settings)=>Layer.scoped(RedisTag, Effect.acquireRelease(Effect.tryPromise({
29
+ */ export const RedisLayer = (settings)=>Layer.effect(RedisTag, Effect.acquireRelease(Effect.tryPromise({
30
30
  try: async ()=>{
31
31
  const redis = new Redis(settings.uri, {
32
32
  lazyConnect: true,
@@ -38,6 +38,8 @@ import { Redis } from 'ioredis';
38
38
  return redis;
39
39
  },
40
40
  catch: (error)=>new EntifixConnError('Failed to connect to Redis', error)
41
- }).pipe(Effect.retry(Schedule.exponential('250 millis').pipe(Schedule.upTo(CONNECT_RETRY_WINDOW)))), (redis)=>Effect.promise(()=>redis.quit().then(()=>undefined).catch(()=>undefined))));
41
+ }).pipe(Effect.retry(Schedule.exponential('250 millis').pipe(Schedule.upTo({
42
+ duration: CONNECT_RETRY_WINDOW
43
+ })))), (redis)=>Effect.promise(()=>redis.quit().then(()=>undefined).catch(()=>undefined))));
42
44
 
43
45
  //# sourceMappingURL=redis-connection.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/redis-connection/redis-connection.ts"],"sourcesContent":["import { EntifixConnError } from '@entifix/core';\nimport { Context, Effect, Layer, Schedule } from 'effect';\nimport { Redis } from 'ioredis';\n\n/** DI tag carrying a connected ioredis client. */\nexport class RedisTag extends Context.Tag('RedisTag')<RedisTag, Redis>() {}\n\nexport interface RedisSettings {\n readonly uri: string;\n}\n\n/**\n * How long the initial connect keeps trying before the service gives up.\n *\n * Boot order is not guaranteed: a service can start while Redis is still\n * rolling out (`ensure-infra` brought the cluster up moments earlier), and\n * dying on the first refused connection turns a five-second wait into a dead\n * process that nothing restarts.\n */\nconst CONNECT_RETRY_WINDOW = '30 seconds';\n\n/**\n * A scoped {@link Layer} that opens an ioredis connection on acquire and quits\n * it on release, mirroring `MongoDatabaseLayer`. `lazyConnect` + an explicit\n * `connect()` surfaces a bad URI/credentials at boot instead of on first command.\n *\n * The reconnect policy is spelled out rather than left to defaults, because two\n * defaults are actively wrong for a service with a readiness endpoint:\n *\n * - **`enableOfflineQueue: false`** — the default queues commands while the\n * connection is down, so a health probe *hangs* instead of failing. Readiness\n * has to answer fastest exactly when a datastore is gone.\n * - **an explicit `retryStrategy`** — so a connection that drops (Redis\n * restarted, pod rescheduled) is re-established for the lifetime of the\n * process. Without it the client can settle into a terminal state and the\n * service stays degraded until someone restarts it, even after Redis is back.\n */\nexport const RedisLayer = (\n settings: RedisSettings,\n): Layer.Layer<RedisTag, EntifixConnError> =>\n Layer.scoped(\n RedisTag,\n Effect.acquireRelease(\n Effect.tryPromise({\n try: async () => {\n const redis = new Redis(settings.uri, {\n lazyConnect: true,\n maxRetriesPerRequest: 1,\n enableOfflineQueue: false,\n retryStrategy: times => Math.min(times * 200, 5_000),\n });\n await redis.connect();\n return redis;\n },\n catch: error =>\n new EntifixConnError('Failed to connect to Redis', error),\n }).pipe(\n Effect.retry(\n Schedule.exponential('250 millis').pipe(\n Schedule.upTo(CONNECT_RETRY_WINDOW),\n ),\n ),\n ),\n redis =>\n Effect.promise(() =>\n redis\n .quit()\n .then(() => undefined)\n .catch(() => undefined),\n ),\n ),\n );\n"],"names":["EntifixConnError","Context","Effect","Layer","Schedule","Redis","RedisTag","Tag","CONNECT_RETRY_WINDOW","RedisLayer","settings","scoped","acquireRelease","tryPromise","try","redis","uri","lazyConnect","maxRetriesPerRequest","enableOfflineQueue","retryStrategy","times","Math","min","connect","catch","error","pipe","retry","exponential","upTo","promise","quit","then","undefined"],"mappings":"AAAA,SAASA,gBAAgB,QAAQ,gBAAgB;AACjD,SAASC,OAAO,EAAEC,MAAM,EAAEC,KAAK,EAAEC,QAAQ,QAAQ,SAAS;AAC1D,SAASC,KAAK,QAAQ,UAAU;AAEhC,gDAAgD,GAChD,OAAO,MAAMC,iBAAiBL,QAAQM,GAAG,CAAC;AAAgC;AAM1E;;;;;;;CAOC,GACD,MAAMC,uBAAuB;AAE7B;;;;;;;;;;;;;;;CAeC,GACD,OAAO,MAAMC,aAAa,CACxBC,WAEAP,MAAMQ,MAAM,CACVL,UACAJ,OAAOU,cAAc,CACnBV,OAAOW,UAAU,CAAC;QAChBC,KAAK;YACH,MAAMC,QAAQ,IAAIV,MAAMK,SAASM,GAAG,EAAE;gBACpCC,aAAa;gBACbC,sBAAsB;gBACtBC,oBAAoB;gBACpBC,eAAeC,CAAAA,QAASC,KAAKC,GAAG,CAACF,QAAQ,KAAK;YAChD;YACA,MAAMN,MAAMS,OAAO;YACnB,OAAOT;QACT;QACAU,OAAOC,CAAAA,QACL,IAAI1B,iBAAiB,8BAA8B0B;IACvD,GAAGC,IAAI,CACLzB,OAAO0B,KAAK,CACVxB,SAASyB,WAAW,CAAC,cAAcF,IAAI,CACrCvB,SAAS0B,IAAI,CAACtB,0BAIpBO,CAAAA,QACEb,OAAO6B,OAAO,CAAC,IACbhB,MACGiB,IAAI,GACJC,IAAI,CAAC,IAAMC,WACXT,KAAK,CAAC,IAAMS,cAGrB"}
1
+ {"version":3,"sources":["../../src/redis-connection/redis-connection.ts"],"sourcesContent":["import { EntifixConnError } from '@entifix/core';\nimport { Context, Effect, Layer, Schedule } from 'effect';\nimport { Redis } from 'ioredis';\n\n/** DI tag carrying a connected ioredis client. */\nexport class RedisTag extends Context.Service<RedisTag, Redis>()('RedisTag') {}\n\nexport interface RedisSettings {\n readonly uri: string;\n}\n\n/**\n * How long the initial connect keeps trying before the service gives up.\n *\n * Boot order is not guaranteed: a service can start while Redis is still\n * rolling out (`ensure-infra` brought the cluster up moments earlier), and\n * dying on the first refused connection turns a five-second wait into a dead\n * process that nothing restarts.\n */\nconst CONNECT_RETRY_WINDOW = '30 seconds';\n\n/**\n * A scoped {@link Layer} that opens an ioredis connection on acquire and quits\n * it on release, mirroring `MongoDatabaseLayer`. `lazyConnect` + an explicit\n * `connect()` surfaces a bad URI/credentials at boot instead of on first command.\n *\n * The reconnect policy is spelled out rather than left to defaults, because two\n * defaults are actively wrong for a service with a readiness endpoint:\n *\n * - **`enableOfflineQueue: false`** — the default queues commands while the\n * connection is down, so a health probe *hangs* instead of failing. Readiness\n * has to answer fastest exactly when a datastore is gone.\n * - **an explicit `retryStrategy`** — so a connection that drops (Redis\n * restarted, pod rescheduled) is re-established for the lifetime of the\n * process. Without it the client can settle into a terminal state and the\n * service stays degraded until someone restarts it, even after Redis is back.\n */\nexport const RedisLayer = (\n settings: RedisSettings,\n): Layer.Layer<RedisTag, EntifixConnError> =>\n Layer.effect(\n RedisTag,\n Effect.acquireRelease(\n Effect.tryPromise({\n try: async () => {\n const redis = new Redis(settings.uri, {\n lazyConnect: true,\n maxRetriesPerRequest: 1,\n enableOfflineQueue: false,\n retryStrategy: times => Math.min(times * 200, 5_000),\n });\n await redis.connect();\n return redis;\n },\n catch: error =>\n new EntifixConnError('Failed to connect to Redis', error),\n }).pipe(\n Effect.retry(\n Schedule.exponential('250 millis').pipe(\n Schedule.upTo({ duration: CONNECT_RETRY_WINDOW }),\n ),\n ),\n ),\n redis =>\n Effect.promise(() =>\n redis\n .quit()\n .then(() => undefined)\n .catch(() => undefined),\n ),\n ),\n );\n"],"names":["EntifixConnError","Context","Effect","Layer","Schedule","Redis","RedisTag","Service","CONNECT_RETRY_WINDOW","RedisLayer","settings","effect","acquireRelease","tryPromise","try","redis","uri","lazyConnect","maxRetriesPerRequest","enableOfflineQueue","retryStrategy","times","Math","min","connect","catch","error","pipe","retry","exponential","upTo","duration","promise","quit","then","undefined"],"mappings":"AAAA,SAASA,gBAAgB,QAAQ,gBAAgB;AACjD,SAASC,OAAO,EAAEC,MAAM,EAAEC,KAAK,EAAEC,QAAQ,QAAQ,SAAS;AAC1D,SAASC,KAAK,QAAQ,UAAU;AAEhC,gDAAgD,GAChD,OAAO,MAAMC,iBAAiBL,QAAQM,OAAO,GAAoB;AAAa;AAM9E;;;;;;;CAOC,GACD,MAAMC,uBAAuB;AAE7B;;;;;;;;;;;;;;;CAeC,GACD,OAAO,MAAMC,aAAa,CACxBC,WAEAP,MAAMQ,MAAM,CACVL,UACAJ,OAAOU,cAAc,CACnBV,OAAOW,UAAU,CAAC;QAChBC,KAAK;YACH,MAAMC,QAAQ,IAAIV,MAAMK,SAASM,GAAG,EAAE;gBACpCC,aAAa;gBACbC,sBAAsB;gBACtBC,oBAAoB;gBACpBC,eAAeC,CAAAA,QAASC,KAAKC,GAAG,CAACF,QAAQ,KAAK;YAChD;YACA,MAAMN,MAAMS,OAAO;YACnB,OAAOT;QACT;QACAU,OAAOC,CAAAA,QACL,IAAI1B,iBAAiB,8BAA8B0B;IACvD,GAAGC,IAAI,CACLzB,OAAO0B,KAAK,CACVxB,SAASyB,WAAW,CAAC,cAAcF,IAAI,CACrCvB,SAAS0B,IAAI,CAAC;QAAEC,UAAUvB;IAAqB,OAIrDO,CAAAA,QACEb,OAAO8B,OAAO,CAAC,IACbjB,MACGkB,IAAI,GACJC,IAAI,CAAC,IAAMC,WACXV,KAAK,CAAC,IAAMU,cAGrB"}
@@ -20,7 +20,7 @@ import { RedisTag } from './redis-connection.js';
20
20
  name: REDIS_PROBE_NAME,
21
21
  kind: 'datastore',
22
22
  targets: stores,
23
- check: Effect.tryPromise(()=>redis.ping()).pipe(Effect.map((reply)=>reply === 'PONG'), Effect.catchAll(()=>Effect.succeed(false)))
23
+ check: Effect.tryPromise(()=>redis.ping()).pipe(Effect.map((reply)=>reply === 'PONG'), Effect.catch(()=>Effect.succeed(false)))
24
24
  });
25
25
  }));
26
26
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/redis-connection/redis-health-probe.ts"],"sourcesContent":["import { HealthRegistryTag } from '@entifix/business';\nimport { Effect, Layer } from 'effect';\n\nimport { RedisTag } from './redis-connection.js';\n\n/** Probe name reported by `/api/health/ready` when Redis is unreachable. */\nexport const REDIS_PROBE_NAME = 'redis';\n\n/**\n * Registers a readiness probe for the Redis connection: `PING`.\n *\n * Sessions live here, so a service that cannot reach Redis can still answer\n * liveness (its process is fine) while readiness reports the truth — which is\n * exactly the split Kubernetes acts on: drain traffic, do not restart.\n *\n * `stores` names the Stores this connection backs, by their register name in\n * `tools/slices/` — `['session']`, `['saga-coordination']`. See\n * {@link MongoHealthProbeLayer} for why the composition root supplies it\n * (ADR 0031).\n */\nexport const RedisHealthProbeLayer = (\n stores: readonly string[],\n): Layer.Layer<never, never, RedisTag | HealthRegistryTag> =>\n Layer.effectDiscard(\n Effect.gen(function* () {\n const redis = yield* RedisTag;\n const registry = yield* HealthRegistryTag;\n\n yield* registry.register({\n name: REDIS_PROBE_NAME,\n kind: 'datastore',\n targets: stores,\n check: Effect.tryPromise(() => redis.ping()).pipe(\n Effect.map(reply => reply === 'PONG'),\n Effect.catchAll(() => Effect.succeed(false)),\n ),\n });\n }),\n );\n"],"names":["HealthRegistryTag","Effect","Layer","RedisTag","REDIS_PROBE_NAME","RedisHealthProbeLayer","stores","effectDiscard","gen","redis","registry","register","name","kind","targets","check","tryPromise","ping","pipe","map","reply","catchAll","succeed"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,oBAAoB;AACtD,SAASC,MAAM,EAAEC,KAAK,QAAQ,SAAS;AAEvC,SAASC,QAAQ,QAAQ,wBAAwB;AAEjD,0EAA0E,GAC1E,OAAO,MAAMC,mBAAmB,QAAQ;AAExC;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,wBAAwB,CACnCC,SAEAJ,MAAMK,aAAa,CACjBN,OAAOO,GAAG,CAAC;QACT,MAAMC,QAAQ,OAAON;QACrB,MAAMO,WAAW,OAAOV;QAExB,OAAOU,SAASC,QAAQ,CAAC;YACvBC,MAAMR;YACNS,MAAM;YACNC,SAASR;YACTS,OAAOd,OAAOe,UAAU,CAAC,IAAMP,MAAMQ,IAAI,IAAIC,IAAI,CAC/CjB,OAAOkB,GAAG,CAACC,CAAAA,QAASA,UAAU,SAC9BnB,OAAOoB,QAAQ,CAAC,IAAMpB,OAAOqB,OAAO,CAAC;QAEzC;IACF,IACA"}
1
+ {"version":3,"sources":["../../src/redis-connection/redis-health-probe.ts"],"sourcesContent":["import { HealthRegistryTag } from '@entifix/business';\nimport { Effect, Layer } from 'effect';\n\nimport { RedisTag } from './redis-connection.js';\n\n/** Probe name reported by `/api/health/ready` when Redis is unreachable. */\nexport const REDIS_PROBE_NAME = 'redis';\n\n/**\n * Registers a readiness probe for the Redis connection: `PING`.\n *\n * Sessions live here, so a service that cannot reach Redis can still answer\n * liveness (its process is fine) while readiness reports the truth — which is\n * exactly the split Kubernetes acts on: drain traffic, do not restart.\n *\n * `stores` names the Stores this connection backs, by their register name in\n * `tools/slices/` — `['session']`, `['saga-coordination']`. See\n * {@link MongoHealthProbeLayer} for why the composition root supplies it\n * (ADR 0031).\n */\nexport const RedisHealthProbeLayer = (\n stores: readonly string[],\n): Layer.Layer<never, never, RedisTag | HealthRegistryTag> =>\n Layer.effectDiscard(\n Effect.gen(function* () {\n const redis = yield* RedisTag;\n const registry = yield* HealthRegistryTag;\n\n yield* registry.register({\n name: REDIS_PROBE_NAME,\n kind: 'datastore',\n targets: stores,\n check: Effect.tryPromise(() => redis.ping()).pipe(\n Effect.map(reply => reply === 'PONG'),\n Effect.catch(() => Effect.succeed(false)),\n ),\n });\n }),\n );\n"],"names":["HealthRegistryTag","Effect","Layer","RedisTag","REDIS_PROBE_NAME","RedisHealthProbeLayer","stores","effectDiscard","gen","redis","registry","register","name","kind","targets","check","tryPromise","ping","pipe","map","reply","catch","succeed"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,oBAAoB;AACtD,SAASC,MAAM,EAAEC,KAAK,QAAQ,SAAS;AAEvC,SAASC,QAAQ,QAAQ,wBAAwB;AAEjD,0EAA0E,GAC1E,OAAO,MAAMC,mBAAmB,QAAQ;AAExC;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,wBAAwB,CACnCC,SAEAJ,MAAMK,aAAa,CACjBN,OAAOO,GAAG,CAAC;QACT,MAAMC,QAAQ,OAAON;QACrB,MAAMO,WAAW,OAAOV;QAExB,OAAOU,SAASC,QAAQ,CAAC;YACvBC,MAAMR;YACNS,MAAM;YACNC,SAASR;YACTS,OAAOd,OAAOe,UAAU,CAAC,IAAMP,MAAMQ,IAAI,IAAIC,IAAI,CAC/CjB,OAAOkB,GAAG,CAACC,CAAAA,QAASA,UAAU,SAC9BnB,OAAOoB,KAAK,CAAC,IAAMpB,OAAOqB,OAAO,CAAC;QAEtC;IACF,IACA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entifix/redis",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "Redis adapters for sessions and one-time tokens, and optionally the lock and sequence services @entifix/transactions needs.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -59,17 +59,17 @@
59
59
  "dependencies": {
60
60
  "@swc/helpers": "0.5.23",
61
61
  "ioredis": "6.0.0",
62
- "@entifix/core": "0.1.4",
63
- "@entifix/business": "0.1.4"
62
+ "@entifix/business": "0.2.0",
63
+ "@entifix/core": "0.2.0"
64
64
  },
65
65
  "devDependencies": {
66
- "effect": "3.22.2",
67
- "@entifix/testing-unit": "0.1.4",
68
- "@entifix/transactions": "0.1.4"
66
+ "effect": "4.0.5",
67
+ "@entifix/transactions": "0.2.0",
68
+ "@entifix/testing-unit": "0.2.0"
69
69
  },
70
70
  "peerDependencies": {
71
- "effect": ">=3.22.2 <4",
72
- "@entifix/transactions": "0.1.4"
71
+ "effect": "^4.0.5",
72
+ "@entifix/transactions": "0.2.0"
73
73
  },
74
74
  "peerDependenciesMeta": {
75
75
  "@entifix/transactions": {