@ionite/ioredis 0.0.21
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/README.md +85 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +1 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# @ionite/ioredis
|
|
2
|
+
|
|
3
|
+
ioredis adapter and Redis stores for ionite. Install this package and **ioredis**; import the adapter, stores, and **`ionRedis(...)`** helper from here. You do not need to install `@ionite/redis-core` separately.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @ionite/ioredis ioredis
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Secret-backed reactive Redis
|
|
12
|
+
|
|
13
|
+
Use **`ionRedis(source, path, options?)`** when your ioredis connection details come from the default config-backed secret client or a named `ion.secrets` provider and may change over time.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { ionite, envConfig } from '@ionite/server';
|
|
17
|
+
import { createValidators } from '@ionite/zod';
|
|
18
|
+
import {
|
|
19
|
+
ionRedis,
|
|
20
|
+
RedisAccessIndex,
|
|
21
|
+
RedisAccessAssignmentStore,
|
|
22
|
+
RedisAccessCatalogStore,
|
|
23
|
+
RedisSessionStore,
|
|
24
|
+
RedisUserStore,
|
|
25
|
+
} from '@ionite/ioredis';
|
|
26
|
+
|
|
27
|
+
export const ion = ionite(envConfig(), {
|
|
28
|
+
validators: createValidators(),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const redis = ionRedis(ion.secret, `${process.env.ION_VAULT_PATH}/redis`);
|
|
32
|
+
ion.setStores({
|
|
33
|
+
sessionStore: new RedisSessionStore({ redis }),
|
|
34
|
+
userStore: new RedisUserStore({ redis }),
|
|
35
|
+
accessStore: new RedisAccessAssignmentStore({ redis }),
|
|
36
|
+
accessIndex: new RedisAccessIndex({ redis }),
|
|
37
|
+
catalogStore: new RedisAccessCatalogStore({ redis }),
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Notes:
|
|
42
|
+
|
|
43
|
+
- The first argument is a stable `SecretsSource` handle (for example `ion.secret` or `ion.secrets.redis`) and the second is the **secret path**. The default log label is **`redis`**; pass `{ sourceName }` if you want a different label.
|
|
44
|
+
- The secret payload may be either an ioredis options object or `{ redisUrl, ...options }`.
|
|
45
|
+
- The adapter subscribes with async get-then-watch semantics and defaults to fail-fast: a missing Redis secret rejects `ready()` and store operations instead of waiting silently forever. Pass `{ subscription: { mode: 'wait', timeout: 30_000 } }` only when waiting for the secret to appear is intentional.
|
|
46
|
+
- Store operations wait for the first configured connection, and `ready()` remains available when you want an explicit startup or health-check barrier. Later secret version changes reconnect the adapter in place; refresh failures keep the previous Redis client active.
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import Redis from 'ioredis';
|
|
52
|
+
import {
|
|
53
|
+
createIoredisAdapter,
|
|
54
|
+
RedisAccessIndex,
|
|
55
|
+
RedisAccessAssignmentStore,
|
|
56
|
+
RedisAccessCatalogStore,
|
|
57
|
+
RedisUserStore,
|
|
58
|
+
RedisSessionStore,
|
|
59
|
+
RedisMagicLinkStore,
|
|
60
|
+
RedisWorkloadTokenStore,
|
|
61
|
+
RedisTenantStore,
|
|
62
|
+
} from '@ionite/ioredis';
|
|
63
|
+
|
|
64
|
+
const client = new Redis(process.env.REDIS_URL ?? 'redis://localhost:3503');
|
|
65
|
+
const redis = createIoredisAdapter(client);
|
|
66
|
+
|
|
67
|
+
const userStore = new RedisUserStore({ redis });
|
|
68
|
+
const sessionStore = new RedisSessionStore({ redis });
|
|
69
|
+
const magicLinkStore = new RedisMagicLinkStore({ redis });
|
|
70
|
+
const workloadTokenStore = new RedisWorkloadTokenStore({ redis });
|
|
71
|
+
const accessStore = new RedisAccessAssignmentStore({ redis });
|
|
72
|
+
const accessIndex = new RedisAccessIndex({ redis });
|
|
73
|
+
const catalogStore = new RedisAccessCatalogStore({ redis });
|
|
74
|
+
const tenantStore = new RedisTenantStore({ redis });
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Pass these stores into `ionite` (from `@ionite/server`) the same way you would in-memory stores. `keyPrefix` is **required** in store options (no implicit default; use a distinct per-tenant prefix); `logger` is optional (see `@ionite/redis-core` for the adapter interface and key layout). Use one shared `RedisSessionStore` namespace for opaque browser auth sessions so `SessionStore.get(sid)` can resolve the session without tenant-specific lookup context.
|
|
78
|
+
|
|
79
|
+
**Adapter capabilities & key encoding.** `createIoredisAdapter` implements the full `RedisAdapter`: atomic `getdel` (`GETDEL`, **Redis 6.2+**, for single-winner magic-link consume) and server-side `runScript` (`EVAL`, for create-if-absent / unique-index claim / multi-key txn / session compare-and-set). `list()` returns records in a stable, deterministic (lexicographic id) order via `SMEMBERS` + `MGET` — no sorted-set support is needed. `ionRedis(...)` forwards `getdel` and `runScript` from the active client, so those atomic paths apply over secret-backed reactive connections too. Externally-sourced id segments are hash-encoded before they enter a key (§10.2), so a hostile or `:`-bearing id can never split a key or collide with a structural index segment — see `@ionite/redis-core` for the scheme.
|
|
80
|
+
|
|
81
|
+
`RedisAccessAssignmentStore` persists granted `AccessChange` **roots** (with deterministic id, optional resource scope, provenance, and status) for SDK-owned SCIM access mutation routing and OAA full sync — one row per **direct** grant (for example `alice → role:editor`), not per permission. Fast authorization checks read **`RedisAccessIndex`** instead — the derived closure materialized from roots + the catalog — exposing `has(...)`, `hasGroup(...)`, `hasRole(...)`, `hasPermission(...)`, `hasAny(...)`, and `hasAll(...)`. Because the index already accounts for group -> role -> permission expansion, nested groups/roles, and `applyToSubResources` inheritance, these checks reflect effective access, not just direct edges. **Example:** grant Alice roles `editor` and `reviewer` that both include permission `read`; AccessAssignmentStore holds two roots, AccessIndex holds both roles and all permissions (with `read` attributed to each root). Revoke `editor`: remove that root and its index rows by `rootId` — Alice keeps `read` via the reviewer root. The index also implements the subject-disabled marker (`setSubjectDisabled`/`isSubjectDisabled`, set by inbound SCIM `active: false`): while set, every `has*` denies with `metadata.reason: 'subject_disabled'`; it is stored independently of edges, so `clear`/reconcile never wipes it.
|
|
82
|
+
|
|
83
|
+
`RedisAccessCatalogStore` persists **SCIM/IAM-sourced** Authz Catalog entries (rows the IAM tool authors via SCIM Group create, or definitions with a genuine store lifecycle) under namespaced stores: `groups`, `roles`, and `permissions`. Use it as `FrameworkConfig.authz.catalogStore` or `FrameworkStores.catalogStore` so SCIM Groups aggregation and OAA full sync can translate SDK-native catalog entries without app code writing SCIM extension objects. Code-authored definitions belong in the code-declared catalog instead (`defineCatalog(...)` -> `FrameworkConfig.authz.catalog`), consumed in memory on the read path — never seeded here. `set(entity)` returns a ready-aware catalog entry synchronously (no top-level `await`) and writes to Redis in the background; await `entry.ready()` for a durability guarantee or check `entry.isReady()`.
|
|
84
|
+
|
|
85
|
+
`RedisResourceStore` is a Redis-backed writable `ResourceProvider` for apps whose resources are not already in their own database. Wire it as `FrameworkConfig.authz.resources` / `FrameworkStores.resources`; `set(resource)` / `delete(id)` persist and emit the `changed` signal (driving adjacency-cache invalidation, reconcile, and incremental OAA projection), and the optional `deriveEntitlements`/`resolveEntitlement` pair derives requestable per-resource entitlement Groups on demand. Apps with their own resource tables should implement `ResourceProvider` over them directly instead.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { RedisAdapter as RedisAdapter2 } from "@ionite/redis-core";
|
|
2
|
+
import { createReactiveRedis, getKeyPrefix, getLogger, prefixedKey, ReactiveRedisAdapter as ReactiveRedisAdapter2, ReactiveRedisClient, ReactiveRedisOptions, RedisAccessAssignmentStore, RedisAccessCatalogStore, RedisAccessIndex, RedisCustomerStore, RedisCustomerStoreOptions, RedisIamDeltaIdMapStore, RedisIamDeltaIdMapStoreOptions, RedisMagicLinkStore, RedisResourceStore, RedisSessionStore, RedisStoreOptions, RedisTenantStore, RedisUserStore, RedisWorkloadIdentityStore, RedisWorkloadIdentityStoreOptions, RedisWorkloadTokenStore } from "@ionite/redis-core";
|
|
3
|
+
import { RedisAdapter } from "@ionite/redis-core";
|
|
4
|
+
import { Redis } from "ioredis";
|
|
5
|
+
/**
|
|
6
|
+
* Create a RedisAdapter that uses an ioredis Redis client.
|
|
7
|
+
* Implements the full interface including atomic `getdel` (GETDEL) and server-side `runScript` (EVAL).
|
|
8
|
+
*/
|
|
9
|
+
declare function createIoredisAdapter(redis: Redis): RedisAdapter;
|
|
10
|
+
import { SecretsSource } from "@ionite/core";
|
|
11
|
+
import { IonRedisOptions, ReactiveRedisAdapter } from "@ionite/redis-core";
|
|
12
|
+
import { RedisOptions } from "ioredis";
|
|
13
|
+
type IonIoredisSecret = RedisOptions & {
|
|
14
|
+
redisUrl?: string;
|
|
15
|
+
clientOptions?: RedisOptions;
|
|
16
|
+
};
|
|
17
|
+
type IonIoredisOptions = IonRedisOptions;
|
|
18
|
+
declare function ionRedis(source: SecretsSource, path: string, options?: IonIoredisOptions): ReactiveRedisAdapter;
|
|
19
|
+
export { prefixedKey, ionRedis, getLogger, getKeyPrefix, createReactiveRedis, createIoredisAdapter, RedisWorkloadTokenStore, RedisWorkloadIdentityStoreOptions, RedisWorkloadIdentityStore, RedisUserStore, RedisTenantStore, RedisStoreOptions, RedisSessionStore, RedisResourceStore, RedisMagicLinkStore, RedisIamDeltaIdMapStoreOptions, RedisIamDeltaIdMapStore, RedisCustomerStoreOptions, RedisCustomerStore, RedisAdapter2 as RedisAdapter, RedisAccessIndex, RedisAccessCatalogStore, RedisAccessAssignmentStore, ReactiveRedisOptions, ReactiveRedisClient, ReactiveRedisAdapter2 as ReactiveRedisAdapter, IonIoredisSecret, IonIoredisOptions };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createReactiveRedis as U,getKeyPrefix as V,getLogger as C,prefixedKey as E,RedisAccessAssignmentStore as H,RedisAccessCatalogStore as z,RedisAccessIndex as p,RedisCustomerStore as v,RedisIamDeltaIdMapStore as f,RedisMagicLinkStore as g,RedisResourceStore as b,RedisSessionStore as m,RedisTenantStore as u,RedisUserStore as d,RedisWorkloadIdentityStore as h,RedisWorkloadTokenStore as a}from"@ionite/redis-core";import{chunkedMget as G,createEvalshaRunner as J}from"@ionite/redis-core";function K(S){let x=J({eval:(R,I,W)=>S.eval(R,I.length,...I,...W),evalsha:(R,I,W)=>S.evalsha(R,I.length,...I,...W)});return{async get(R){return S.get(R)},async set(R,I){await S.set(R,I)},async setex(R,I,W){await S.setex(R,I,W)},async del(R){await S.del(R)},async ttl(R){return S.ttl(R)},async getdel(R){return S.getdel(R)},async sadd(R,...I){if(I.length>0)await S.sadd(R,...I)},async srem(R,...I){if(I.length>0)await S.srem(R,...I)},async smembers(R){return S.smembers(R)},async mget(R){return G(R,(I)=>S.mget(...I))},runScript:x}}import{createReactiveRedis as L,splitIonRedisSecret as Q}from"@ionite/redis-core";import O from"ioredis";function A(S){let{redisUrl:x,clientOptions:R}=Q(S);return{redisUrl:x,clientOptions:R}}function X(S){let x=A(S);if(x.redisUrl)return x.redisUrl;let R=x.clientOptions.host,I=x.clientOptions.port;if(R)return`${R}:${I??6379}`;return""}async function Y(S){await S.connect()}async function Z(S){try{await S.quit()}catch{S.disconnect()}}function _(S,x,R={}){return L({source:S,path:x,sourceName:R.sourceName,logger:R.logger,secretOptions:R.secretOptions,subscription:R.subscription,endpointOf:(I)=>X(I.data),async createClient(I){let W=A(I.data),M={...W.clientOptions,lazyConnect:!0},D=W.redisUrl?new O(W.redisUrl,M):new O(M);return D.on("error",(B)=>{R.logger?.warn?.("Reactive ioredis client error",{path:x,sourceName:R.sourceName??"redis",error:B.message})}),await Y(D),{adapter:K(D),dispose:async()=>{await Z(D)}}}})}export{E as prefixedKey,_ as ionRedis,C as getLogger,V as getKeyPrefix,U as createReactiveRedis,K as createIoredisAdapter,a as RedisWorkloadTokenStore,h as RedisWorkloadIdentityStore,d as RedisUserStore,u as RedisTenantStore,m as RedisSessionStore,b as RedisResourceStore,g as RedisMagicLinkStore,f as RedisIamDeltaIdMapStore,v as RedisCustomerStore,p as RedisAccessIndex,z as RedisAccessCatalogStore,H as RedisAccessAssignmentStore};
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ionite/ioredis",
|
|
3
|
+
"version": "0.0.21",
|
|
4
|
+
"description": "ioredis adapter and Redis stores for ionite. Single package: install this and ioredis; import adapter and stores from here.",
|
|
5
|
+
"private": false,
|
|
6
|
+
"author": "ionite",
|
|
7
|
+
"license": "proprietary",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=20.6"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"module": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"import": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"./package.json": "./package.json"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@ionite/redis-core": "^0.0.21"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"ioredis": "*"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"ioredis": "^5.11.1",
|
|
34
|
+
"typescript": "^6.0.3"
|
|
35
|
+
}
|
|
36
|
+
}
|