@pthm/melange 0.4.0 → 0.6.4

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 (48) hide show
  1. package/README.md +294 -18
  2. package/dist/adapters/index.d.ts +10 -0
  3. package/dist/adapters/index.d.ts.map +1 -0
  4. package/dist/adapters/index.js +8 -0
  5. package/dist/adapters/index.js.map +1 -0
  6. package/dist/adapters/pg.d.ts +42 -0
  7. package/dist/adapters/pg.d.ts.map +1 -0
  8. package/dist/adapters/pg.js +38 -0
  9. package/dist/adapters/pg.js.map +1 -0
  10. package/dist/adapters/postgres.d.ts +37 -0
  11. package/dist/adapters/postgres.d.ts.map +1 -0
  12. package/dist/adapters/postgres.js +35 -0
  13. package/dist/adapters/postgres.js.map +1 -0
  14. package/dist/cache.d.ts +90 -0
  15. package/dist/cache.d.ts.map +1 -0
  16. package/dist/cache.js +93 -0
  17. package/dist/cache.js.map +1 -0
  18. package/dist/cache.test.d.ts +5 -0
  19. package/dist/cache.test.d.ts.map +1 -0
  20. package/dist/cache.test.js +127 -0
  21. package/dist/cache.test.js.map +1 -0
  22. package/dist/checker.d.ts +169 -7
  23. package/dist/checker.d.ts.map +1 -1
  24. package/dist/checker.js +214 -9
  25. package/dist/checker.js.map +1 -1
  26. package/dist/database.d.ts +45 -0
  27. package/dist/database.d.ts.map +1 -0
  28. package/dist/database.js +8 -0
  29. package/dist/database.js.map +1 -0
  30. package/dist/errors.d.ts +45 -0
  31. package/dist/errors.d.ts.map +1 -0
  32. package/dist/errors.js +58 -0
  33. package/dist/errors.js.map +1 -0
  34. package/dist/index.d.ts +8 -2
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +6 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/types.d.ts +37 -0
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/validator.d.ts +28 -0
  41. package/dist/validator.d.ts.map +1 -0
  42. package/dist/validator.js +60 -0
  43. package/dist/validator.js.map +1 -0
  44. package/dist/validator.test.d.ts +5 -0
  45. package/dist/validator.test.d.ts.map +1 -0
  46. package/dist/validator.test.js +88 -0
  47. package/dist/validator.test.js.map +1 -0
  48. package/package.json +24 -8
package/README.md CHANGED
@@ -2,43 +2,319 @@
2
2
 
3
3
  TypeScript client for Melange PostgreSQL authorization.
4
4
 
5
- > **Note:** This package is a placeholder. The TypeScript runtime is not yet implemented.
6
- > Use the Go runtime (`github.com/pthm/melange/melange`) for production workloads.
5
+ Melange is an OpenFGA-compatible authorization library that runs entirely in PostgreSQL. This TypeScript client provides type-safe access to the authorization system.
7
6
 
8
7
  ## Installation
9
8
 
10
9
  ```bash
11
- npm install @pthm/melange
10
+ npm install @pthm/melange pg
11
+ # or
12
+ yarn add @pthm/melange pg
13
+ # or
14
+ pnpm add @pthm/melange pg
12
15
  ```
13
16
 
14
- ## Status
17
+ ## Quick Start
15
18
 
16
- This package provides:
17
- - Type definitions for authorization objects
18
- - Placeholder Checker class (throws "not implemented")
19
+ ```typescript
20
+ import { Checker } from '@pthm/melange';
21
+ import { Pool } from 'pg';
19
22
 
20
- The full implementation will include:
21
- - `Checker` class for permission checks
22
- - `Cache` for caching decisions
23
- - Connection pooling support
23
+ // Create a PostgreSQL connection pool
24
+ const pool = new Pool({
25
+ connectionString: process.env.DATABASE_URL,
26
+ });
27
+
28
+ // Create a checker instance
29
+ const checker = new Checker(pool);
30
+
31
+ // Perform a permission check
32
+ const decision = await checker.check(
33
+ { type: 'user', id: '123' },
34
+ 'can_read',
35
+ { type: 'repository', id: '456' }
36
+ );
37
+
38
+ if (decision.allowed) {
39
+ console.log('Access granted!');
40
+ } else {
41
+ console.log('Access denied.');
42
+ }
43
+ ```
44
+
45
+ ## Features
46
+
47
+ ### Permission Checks
48
+
49
+ ```typescript
50
+ // Check if a user can read a repository
51
+ const decision = await checker.check(
52
+ { type: 'user', id: '123' },
53
+ 'can_read',
54
+ { type: 'repository', id: '456' }
55
+ );
56
+ ```
57
+
58
+ ### List Operations
59
+
60
+ ```typescript
61
+ // List all repositories a user can read
62
+ const result = await checker.listObjects(
63
+ { type: 'user', id: '123' },
64
+ 'can_read',
65
+ 'repository',
66
+ { limit: 100 }
67
+ );
68
+
69
+ for (const repoId of result.items) {
70
+ console.log(`Repository: ${repoId}`);
71
+ }
72
+
73
+ // List all users who can read a repository
74
+ const users = await checker.listSubjects(
75
+ 'user',
76
+ 'can_read',
77
+ { type: 'repository', id: '456' },
78
+ { limit: 100 }
79
+ );
80
+ ```
81
+
82
+ ### Caching
83
+
84
+ ```typescript
85
+ import { Checker, MemoryCache } from '@pthm/melange';
86
+
87
+ // Create a checker with caching
88
+ const cache = new MemoryCache(60000); // 60 second TTL
89
+ const checker = new Checker(pool, { cache });
90
+
91
+ // First check hits the database
92
+ await checker.check(user, 'can_read', repo);
93
+
94
+ // Second check within 60s uses the cache
95
+ await checker.check(user, 'can_read', repo); // cached
96
+ ```
97
+
98
+ ### Decision Overrides for Testing
99
+
100
+ ```typescript
101
+ import { Checker, DecisionAllow, DecisionDeny } from '@pthm/melange';
102
+
103
+ // Test authorized paths
104
+ const allowChecker = new Checker(pool, { decision: DecisionAllow });
105
+ await allowChecker.check(user, 'can_read', repo); // always returns { allowed: true }
106
+
107
+ // Test unauthorized paths
108
+ const denyChecker = new Checker(pool, { decision: DecisionDeny });
109
+ await denyChecker.check(user, 'can_read', repo); // always returns { allowed: false }
110
+ ```
111
+
112
+ ### Contextual Tuples
113
+
114
+ ```typescript
115
+ // Check with temporary permissions
116
+ const decision = await checker.checkWithContextualTuples(
117
+ { type: 'user', id: '123' },
118
+ 'can_read',
119
+ { type: 'document', id: '789' },
120
+ [
121
+ {
122
+ subject: { type: 'user', id: '123' },
123
+ relation: 'temp_access',
124
+ object: { type: 'document', id: '789' }
125
+ }
126
+ ]
127
+ );
128
+ ```
129
+
130
+ ## Database Adapters
131
+
132
+ The runtime works with any PostgreSQL client that implements the `Queryable` interface:
133
+
134
+ ```typescript
135
+ interface Queryable {
136
+ query<T>(text: string, params?: any[]): Promise<{ rows: T[] }>;
137
+ }
138
+ ```
139
+
140
+ ### node-postgres (pg)
141
+
142
+ node-postgres Pool and Client already implement `Queryable` and can be used directly:
143
+
144
+ ```typescript
145
+ import { Checker } from '@pthm/melange';
146
+ import { Pool } from 'pg';
147
+
148
+ const pool = new Pool({ connectionString: process.env.DATABASE_URL });
149
+ const checker = new Checker(pool); // Works directly
150
+ ```
151
+
152
+ ### postgres.js
153
+
154
+ Use the `postgresAdapter` to wrap a postgres.js instance:
155
+
156
+ ```typescript
157
+ import postgres from 'postgres';
158
+ import { Checker, postgresAdapter } from '@pthm/melange';
159
+
160
+ const sql = postgres(process.env.DATABASE_URL);
161
+ const checker = new Checker(postgresAdapter(sql));
162
+ ```
24
163
 
25
164
  ## Generated Client Code
26
165
 
27
- You can generate type-safe client code from your schema using the melange CLI:
166
+ Generate type-safe constants and factory functions from your schema:
28
167
 
29
168
  ```bash
30
169
  melange generate client --runtime typescript --schema schema.fga --output ./src/authz/
31
170
  ```
32
171
 
33
- This generates:
34
- - Type constants (`TYPE_USER`, `TYPE_REPOSITORY`)
35
- - Relation constants (`REL_CAN_READ`, `REL_OWNER`)
36
- - Factory functions (`user()`, `repository()`)
172
+ This generates three files:
37
173
 
38
- ## Contributing
174
+ ### types.ts
39
175
 
40
- See the [main repository](https://github.com/pthm/melange) for contribution guidelines.
176
+ ```typescript
177
+ export const ObjectTypes = {
178
+ User: "user",
179
+ Repository: "repository",
180
+ } as const;
181
+
182
+ export type ObjectType = (typeof ObjectTypes)[keyof typeof ObjectTypes];
183
+
184
+ export const Relations = {
185
+ CanRead: "can_read",
186
+ Owner: "owner",
187
+ } as const;
188
+
189
+ export type Relation = (typeof Relations)[keyof typeof Relations];
190
+ ```
191
+
192
+ ### schema.ts
193
+
194
+ ```typescript
195
+ import type { MelangeObject } from '@pthm/melange';
196
+ import { ObjectTypes } from './types.js';
197
+
198
+ export function user(id: string): MelangeObject {
199
+ return { type: ObjectTypes.User, id };
200
+ }
201
+
202
+ export function repository(id: string): MelangeObject {
203
+ return { type: ObjectTypes.Repository, id };
204
+ }
205
+
206
+ export function anyUser(): MelangeObject {
207
+ return { type: ObjectTypes.User, id: '*' };
208
+ }
209
+ ```
210
+
211
+ ### Usage
212
+
213
+ ```typescript
214
+ import { Checker } from '@pthm/melange';
215
+ import { user, repository, Relations } from './authz/index.js';
216
+
217
+ const decision = await checker.check(
218
+ user('123'),
219
+ Relations.CanRead,
220
+ repository('456')
221
+ );
222
+ ```
223
+
224
+ ## API Reference
225
+
226
+ ### Checker
227
+
228
+ ```typescript
229
+ class Checker {
230
+ constructor(db: Queryable, options?: CheckerOptions);
231
+
232
+ check(
233
+ subject: MelangeObject,
234
+ relation: Relation,
235
+ object: MelangeObject,
236
+ contextualTuples?: ContextualTuple[]
237
+ ): Promise<Decision>;
238
+
239
+ listObjects(
240
+ subject: MelangeObject,
241
+ relation: Relation,
242
+ objectType: ObjectType,
243
+ options?: PageOptions
244
+ ): Promise<ListResult<string>>;
245
+
246
+ listSubjects(
247
+ subjectType: ObjectType,
248
+ relation: Relation,
249
+ object: MelangeObject,
250
+ options?: PageOptions
251
+ ): Promise<ListResult<string>>;
252
+
253
+ checkWithContextualTuples(
254
+ subject: MelangeObject,
255
+ relation: Relation,
256
+ object: MelangeObject,
257
+ contextualTuples: ContextualTuple[]
258
+ ): Promise<Decision>;
259
+ }
260
+ ```
261
+
262
+ ### CheckerOptions
263
+
264
+ ```typescript
265
+ interface CheckerOptions {
266
+ cache?: Cache; // Default: NoopCache
267
+ decision?: Decision; // For testing only
268
+ validateRequest?: boolean; // Default: true
269
+ validateUserset?: boolean; // Default: true
270
+ }
271
+ ```
272
+
273
+ ### Cache
274
+
275
+ ```typescript
276
+ interface Cache {
277
+ get(key: string): Promise<Decision | undefined>;
278
+ set(key: string, value: Decision): Promise<void>;
279
+ clear(): Promise<void>;
280
+ }
281
+
282
+ class NoopCache implements Cache { } // No caching
283
+ class MemoryCache implements Cache { // In-memory with TTL
284
+ constructor(ttlMs?: number);
285
+ }
286
+ ```
287
+
288
+ ## Error Handling
289
+
290
+ ```typescript
291
+ import { MelangeError, ValidationError, NotFoundError } from '@pthm/melange';
292
+
293
+ try {
294
+ await checker.check(user, 'can_read', repo);
295
+ } catch (err) {
296
+ if (err instanceof ValidationError) {
297
+ console.error('Invalid input:', err.message);
298
+ } else if (err instanceof NotFoundError) {
299
+ console.error('Resource not found:', err.message);
300
+ } else if (err instanceof MelangeError) {
301
+ console.error('Melange error:', err.message);
302
+ } else {
303
+ console.error('Unknown error:', err);
304
+ }
305
+ }
306
+ ```
307
+
308
+ ## Requirements
309
+
310
+ - Node.js 18 or higher
311
+ - PostgreSQL 14 or higher
312
+ - Melange schema and functions installed in your database
41
313
 
42
314
  ## License
43
315
 
44
316
  MIT
317
+
318
+ ## Contributing
319
+
320
+ See the [main repository](https://github.com/pthm/melange) for contribution guidelines.
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Database adapters for Melange.
3
+ *
4
+ * This module provides adapters for popular PostgreSQL clients.
5
+ */
6
+ export { pgAdapter } from './pg.js';
7
+ export type { PgQueryable } from './pg.js';
8
+ export { postgresAdapter } from './postgres.js';
9
+ export type { PostgresSql } from './postgres.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Database adapters for Melange.
3
+ *
4
+ * This module provides adapters for popular PostgreSQL clients.
5
+ */
6
+ export { pgAdapter } from './pg.js';
7
+ export { postgresAdapter } from './postgres.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * node-postgres (pg) adapter for Melange.
3
+ *
4
+ * This module provides an adapter to use node-postgres Pool or Client
5
+ * with the Melange Checker.
6
+ */
7
+ import type { Queryable } from '../database.js';
8
+ /**
9
+ * PgQueryable represents a node-postgres client that can execute queries.
10
+ *
11
+ * This interface matches the query signature of both Pool and Client from 'pg'.
12
+ */
13
+ export interface PgQueryable {
14
+ query<T = any>(text: string, params?: any[]): Promise<{
15
+ rows: T[];
16
+ }>;
17
+ }
18
+ /**
19
+ * pgAdapter wraps a node-postgres Pool or Client for use with Checker.
20
+ *
21
+ * Note: In most cases, you don't need this adapter. The pg Pool and Client
22
+ * already implement the Queryable interface and can be used directly.
23
+ *
24
+ * This adapter is provided for explicit type conversion if needed.
25
+ *
26
+ * @param client - A pg Pool or Client
27
+ * @returns A Queryable instance
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * import { Pool } from 'pg';
32
+ * import { Checker, pgAdapter } from '@pthm/melange';
33
+ *
34
+ * const pool = new Pool({ connectionString: process.env.DATABASE_URL });
35
+ * const checker = new Checker(pgAdapter(pool));
36
+ *
37
+ * // Or use the pool directly (preferred):
38
+ * const checker = new Checker(pool);
39
+ * ```
40
+ */
41
+ export declare function pgAdapter(client: PgQueryable): Queryable;
42
+ //# sourceMappingURL=pg.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pg.d.ts","sourceRoot":"","sources":["../../src/adapters/pg.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,gBAAgB,CAAC;AAE7D;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,CAAC,EAAE,CAAA;KAAE,CAAC,CAAC;CACtE;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAOxD"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * node-postgres (pg) adapter for Melange.
3
+ *
4
+ * This module provides an adapter to use node-postgres Pool or Client
5
+ * with the Melange Checker.
6
+ */
7
+ /**
8
+ * pgAdapter wraps a node-postgres Pool or Client for use with Checker.
9
+ *
10
+ * Note: In most cases, you don't need this adapter. The pg Pool and Client
11
+ * already implement the Queryable interface and can be used directly.
12
+ *
13
+ * This adapter is provided for explicit type conversion if needed.
14
+ *
15
+ * @param client - A pg Pool or Client
16
+ * @returns A Queryable instance
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * import { Pool } from 'pg';
21
+ * import { Checker, pgAdapter } from '@pthm/melange';
22
+ *
23
+ * const pool = new Pool({ connectionString: process.env.DATABASE_URL });
24
+ * const checker = new Checker(pgAdapter(pool));
25
+ *
26
+ * // Or use the pool directly (preferred):
27
+ * const checker = new Checker(pool);
28
+ * ```
29
+ */
30
+ export function pgAdapter(client) {
31
+ return {
32
+ async query(text, params) {
33
+ const result = await client.query(text, params);
34
+ return { rows: result.rows };
35
+ },
36
+ };
37
+ }
38
+ //# sourceMappingURL=pg.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pg.js","sourceRoot":"","sources":["../../src/adapters/pg.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAaH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,SAAS,CAAC,MAAmB;IAC3C,OAAO;QACL,KAAK,CAAC,KAAK,CAAU,IAAY,EAAE,MAAc;YAC/C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAI,IAAI,EAAE,MAAM,CAAC,CAAC;YACnD,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;QAC/B,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * postgres.js adapter for Melange.
3
+ *
4
+ * This module provides an adapter to use postgres.js with the Melange Checker.
5
+ */
6
+ import type { Queryable } from '../database.js';
7
+ /**
8
+ * PostgresSql represents a postgres.js Sql instance.
9
+ *
10
+ * This is a minimal interface matching postgres.js's unsafe method.
11
+ */
12
+ export interface PostgresSql {
13
+ unsafe<T = any>(text: string, params?: any[]): Promise<T[]>;
14
+ }
15
+ /**
16
+ * postgresAdapter wraps a postgres.js Sql instance for use with Checker.
17
+ *
18
+ * @param sql - A postgres.js Sql instance
19
+ * @returns A Queryable instance
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * import postgres from 'postgres';
24
+ * import { Checker, postgresAdapter } from '@pthm/melange';
25
+ *
26
+ * const sql = postgres(process.env.DATABASE_URL);
27
+ * const checker = new Checker(postgresAdapter(sql));
28
+ *
29
+ * const decision = await checker.check(
30
+ * { type: 'user', id: '123' },
31
+ * 'can_read',
32
+ * { type: 'repository', id: '456' }
33
+ * );
34
+ * ```
35
+ */
36
+ export declare function postgresAdapter(sql: PostgresSql): Queryable;
37
+ //# sourceMappingURL=postgres.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/adapters/postgres.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,gBAAgB,CAAC;AAE7D;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;CAC7D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,WAAW,GAAG,SAAS,CAO3D"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * postgres.js adapter for Melange.
3
+ *
4
+ * This module provides an adapter to use postgres.js with the Melange Checker.
5
+ */
6
+ /**
7
+ * postgresAdapter wraps a postgres.js Sql instance for use with Checker.
8
+ *
9
+ * @param sql - A postgres.js Sql instance
10
+ * @returns A Queryable instance
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import postgres from 'postgres';
15
+ * import { Checker, postgresAdapter } from '@pthm/melange';
16
+ *
17
+ * const sql = postgres(process.env.DATABASE_URL);
18
+ * const checker = new Checker(postgresAdapter(sql));
19
+ *
20
+ * const decision = await checker.check(
21
+ * { type: 'user', id: '123' },
22
+ * 'can_read',
23
+ * { type: 'repository', id: '456' }
24
+ * );
25
+ * ```
26
+ */
27
+ export function postgresAdapter(sql) {
28
+ return {
29
+ async query(text, params = []) {
30
+ const rows = await sql.unsafe(text, params);
31
+ return { rows };
32
+ },
33
+ };
34
+ }
35
+ //# sourceMappingURL=postgres.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../src/adapters/postgres.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAaH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,eAAe,CAAC,GAAgB;IAC9C,OAAO;QACL,KAAK,CAAC,KAAK,CAAU,IAAY,EAAE,SAAgB,EAAE;YACnD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,MAAM,CAAI,IAAI,EAAE,MAAM,CAAC,CAAC;YAC/C,OAAO,EAAE,IAAI,EAAE,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Caching for Melange authorization checks.
3
+ *
4
+ * This module provides cache interfaces and implementations for storing
5
+ * permission check results to reduce database load.
6
+ */
7
+ import type { Decision } from './types.js';
8
+ /**
9
+ * Cache stores permission check results.
10
+ *
11
+ * Implementations should be safe for concurrent access if the Checker
12
+ * is shared across requests. For request-scoped caching, create a new
13
+ * Checker per request with a request-scoped cache.
14
+ */
15
+ export interface Cache {
16
+ /**
17
+ * Get a cached decision.
18
+ *
19
+ * @param key - Cache key
20
+ * @returns Cached decision, or undefined if not found or expired
21
+ */
22
+ get(key: string): Promise<Decision | undefined>;
23
+ /**
24
+ * Store a decision in the cache.
25
+ *
26
+ * @param key - Cache key
27
+ * @param value - Decision to cache
28
+ */
29
+ set(key: string, value: Decision): Promise<void>;
30
+ /**
31
+ * Clear all cached entries.
32
+ */
33
+ clear(): Promise<void>;
34
+ }
35
+ /**
36
+ * NoopCache is a no-op cache that never stores anything.
37
+ *
38
+ * This is the default cache implementation, suitable for applications
39
+ * that don't want caching overhead or have other caching strategies.
40
+ */
41
+ export declare class NoopCache implements Cache {
42
+ get(_key: string): Promise<Decision | undefined>;
43
+ set(_key: string, _value: Decision): Promise<void>;
44
+ clear(): Promise<void>;
45
+ }
46
+ /**
47
+ * MemoryCache is a simple in-memory cache with TTL support.
48
+ *
49
+ * This cache stores decisions in a Map with time-based expiration.
50
+ * It's suitable for single-instance applications or request-scoped caching.
51
+ *
52
+ * For multi-instance deployments, consider using a distributed cache
53
+ * like Redis with a custom Cache implementation.
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * import { Checker, MemoryCache } from '@pthm/melange';
58
+ * import { Pool } from 'pg';
59
+ *
60
+ * const pool = new Pool({ connectionString: process.env.DATABASE_URL });
61
+ * const cache = new MemoryCache(60000); // 60 second TTL
62
+ * const checker = new Checker(pool, { cache });
63
+ *
64
+ * // First check hits database
65
+ * await checker.check(user, 'can_read', repo);
66
+ *
67
+ * // Second check within 60s uses cache
68
+ * await checker.check(user, 'can_read', repo); // cached
69
+ * ```
70
+ */
71
+ export declare class MemoryCache implements Cache {
72
+ private readonly cache;
73
+ private readonly ttlMs;
74
+ /**
75
+ * Create a new MemoryCache.
76
+ *
77
+ * @param ttlMs - Time to live in milliseconds (default: 60000 = 1 minute)
78
+ */
79
+ constructor(ttlMs?: number);
80
+ get(key: string): Promise<Decision | undefined>;
81
+ set(key: string, value: Decision): Promise<void>;
82
+ clear(): Promise<void>;
83
+ /**
84
+ * Get the number of entries in the cache.
85
+ *
86
+ * Note: This includes expired entries that haven't been accessed yet.
87
+ */
88
+ get size(): number;
89
+ }
90
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;;GAMG;AACH,MAAM,WAAW,KAAK;IACpB;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;IAEhD;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjD;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;;;GAKG;AACH,qBAAa,SAAU,YAAW,KAAK;IAC/B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAIhD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,WAAY,YAAW,KAAK;IACvC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAE/B;;;;OAIG;gBACS,KAAK,GAAE,MAAc;IAO3B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAe/C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAOhD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;;;OAIG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}