@beechcms/core 0.8.1 → 0.9.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.
Files changed (163) hide show
  1. package/dist/antivirus/noop-antivirus.provider.d.ts.map +1 -1
  2. package/dist/antivirus/virustotal-antivirus.provider.d.ts.map +1 -1
  3. package/dist/auth/account-email.d.ts +3 -0
  4. package/dist/auth/account-email.d.ts.map +1 -0
  5. package/dist/auth/account-email.js +9 -0
  6. package/dist/auth/password-reset-token.repository.d.ts +13 -2
  7. package/dist/auth/password-reset-token.repository.d.ts.map +1 -1
  8. package/dist/auth/user.repository.d.ts +69 -4
  9. package/dist/auth/user.repository.d.ts.map +1 -1
  10. package/dist/common/canonical-json.d.ts +2 -1
  11. package/dist/common/canonical-json.d.ts.map +1 -1
  12. package/dist/common/canonical-json.js +5 -3
  13. package/dist/content/content-scan.repository.d.ts +1 -1
  14. package/dist/content/content.repository.d.ts +156 -5
  15. package/dist/content/content.repository.d.ts.map +1 -1
  16. package/dist/content/content.repository.js +62 -0
  17. package/dist/content/seed-media-purge.repository.d.ts +34 -0
  18. package/dist/content/seed-media-purge.repository.d.ts.map +1 -0
  19. package/dist/content/seed-media-purge.repository.js +3 -0
  20. package/dist/content/seed.repository.d.ts +14 -0
  21. package/dist/content/seed.repository.d.ts.map +1 -1
  22. package/dist/dashboard-layout/content-view.d.ts +757 -0
  23. package/dist/dashboard-layout/content-view.d.ts.map +1 -0
  24. package/dist/dashboard-layout/content-view.js +285 -0
  25. package/dist/dashboard-layout/content-view.repository.d.ts +39 -0
  26. package/dist/dashboard-layout/content-view.repository.d.ts.map +1 -0
  27. package/dist/dashboard-layout/content-view.repository.js +3 -0
  28. package/dist/dashboard-layout/dashboard-layout.d.ts +5 -0
  29. package/dist/dashboard-layout/dashboard-layout.d.ts.map +1 -1
  30. package/dist/dashboard-layout/dashboard-layout.js +17 -9
  31. package/dist/dashboard-layout/kanban/kanban.d.ts +32 -18
  32. package/dist/dashboard-layout/kanban/kanban.d.ts.map +1 -1
  33. package/dist/dashboard-layout/kanban/kanban.js +45 -21
  34. package/dist/dashboard-layout/seed-layout.d.ts +22 -31
  35. package/dist/dashboard-layout/seed-layout.d.ts.map +1 -1
  36. package/dist/dashboard-layout/seed-layout.js +93 -18
  37. package/dist/dashboard-layout/seed-layout.repository.d.ts +1 -5
  38. package/dist/dashboard-layout/seed-layout.repository.d.ts.map +1 -1
  39. package/dist/engine/ddl/ddl.d.ts +31 -0
  40. package/dist/engine/ddl/ddl.d.ts.map +1 -1
  41. package/dist/engine/ddl/ddl.js +92 -13
  42. package/dist/engine/ddl/schema-mutator.d.ts +3 -0
  43. package/dist/engine/ddl/schema-mutator.d.ts.map +1 -1
  44. package/dist/engine/ddl/seed-purge-guards.d.ts +7 -0
  45. package/dist/engine/ddl/seed-purge-guards.d.ts.map +1 -0
  46. package/dist/engine/ddl/seed-purge-guards.js +19 -0
  47. package/dist/engine/engine.d.ts +4 -3
  48. package/dist/engine/engine.d.ts.map +1 -1
  49. package/dist/engine/engine.js +4 -3
  50. package/dist/engine/introspection/relation-audit.d.ts +46 -0
  51. package/dist/engine/introspection/relation-audit.d.ts.map +1 -0
  52. package/dist/engine/introspection/relation-audit.js +103 -0
  53. package/dist/engine/introspection/schema-fingerprint.d.ts +2 -1
  54. package/dist/engine/introspection/schema-fingerprint.d.ts.map +1 -1
  55. package/dist/engine/introspection/schema-fingerprint.js +8 -3
  56. package/dist/engine/privacy/policies.d.ts +1 -1
  57. package/dist/engine/privacy/policies.d.ts.map +1 -1
  58. package/dist/engine/privacy/policies.js +12 -6
  59. package/dist/engine/privacy/privacy.service.d.ts +13 -0
  60. package/dist/engine/privacy/privacy.service.d.ts.map +1 -1
  61. package/dist/engine/privacy/privacy.service.js +24 -0
  62. package/dist/engine/query/active-clause.d.ts +19 -0
  63. package/dist/engine/query/active-clause.d.ts.map +1 -0
  64. package/dist/engine/query/active-clause.js +27 -0
  65. package/dist/engine/query/query.d.ts +14 -1
  66. package/dist/engine/query/query.d.ts.map +1 -1
  67. package/dist/engine/query/query.js +105 -13
  68. package/dist/engine/seeds/candidate-ids.d.ts +14 -0
  69. package/dist/engine/seeds/candidate-ids.d.ts.map +1 -0
  70. package/dist/engine/seeds/candidate-ids.js +40 -0
  71. package/dist/engine/seeds/seed-definition-diff.d.ts +38 -0
  72. package/dist/engine/seeds/seed-definition-diff.d.ts.map +1 -0
  73. package/dist/engine/seeds/seed-definition-diff.js +51 -0
  74. package/dist/engine/seeds/seed-registry.d.ts +17 -0
  75. package/dist/engine/seeds/seed-registry.d.ts.map +1 -1
  76. package/dist/engine/seeds/seed-registry.js +27 -0
  77. package/dist/engine/seeds/seed-types-generator.d.ts.map +1 -1
  78. package/dist/engine/seeds/seed-types-generator.js +4 -5
  79. package/dist/engine/seeds/seed-validation.d.ts +5 -0
  80. package/dist/engine/seeds/seed-validation.d.ts.map +1 -1
  81. package/dist/engine/seeds/seed-validation.js +39 -19
  82. package/dist/engine/seeds/tag-migration.d.ts +73 -0
  83. package/dist/engine/seeds/tag-migration.d.ts.map +1 -0
  84. package/dist/engine/seeds/tag-migration.js +212 -0
  85. package/dist/engine/seeds/tag-options.d.ts +15 -0
  86. package/dist/engine/seeds/tag-options.d.ts.map +1 -0
  87. package/dist/engine/seeds/tag-options.js +111 -0
  88. package/dist/engine/seeds/tag-value.d.ts +31 -0
  89. package/dist/engine/seeds/tag-value.d.ts.map +1 -0
  90. package/dist/engine/seeds/tag-value.js +51 -0
  91. package/dist/engine/types.d.ts +59 -7
  92. package/dist/engine/types.d.ts.map +1 -1
  93. package/dist/engine/validation/cache.d.ts.map +1 -1
  94. package/dist/engine/validation/cache.js +20 -4
  95. package/dist/engine/validation/index.d.ts +12 -1
  96. package/dist/engine/validation/index.d.ts.map +1 -1
  97. package/dist/engine/validation/index.js +29 -33
  98. package/dist/engine/validation/primitives.d.ts +12 -0
  99. package/dist/engine/validation/primitives.d.ts.map +1 -1
  100. package/dist/engine/validation/primitives.js +22 -0
  101. package/dist/engine/validation/richtext-sanitizer.d.ts +9 -1
  102. package/dist/engine/validation/richtext-sanitizer.d.ts.map +1 -1
  103. package/dist/engine/validation/richtext-sanitizer.js +36 -7
  104. package/dist/engine/validation/schema-builders.d.ts +16 -0
  105. package/dist/engine/validation/schema-builders.d.ts.map +1 -1
  106. package/dist/engine/validation/schema-builders.js +125 -18
  107. package/dist/index.d.ts +34 -11
  108. package/dist/index.d.ts.map +1 -1
  109. package/dist/index.js +34 -11
  110. package/dist/media/file-types.d.ts +12 -0
  111. package/dist/media/file-types.d.ts.map +1 -1
  112. package/dist/media/file-types.js +24 -4
  113. package/dist/media/magic-bytes.d.ts.map +1 -1
  114. package/dist/media/magic-bytes.js +19 -9
  115. package/dist/media/media-transform.d.ts +8 -4
  116. package/dist/media/media-transform.d.ts.map +1 -1
  117. package/dist/media/media-transform.js +10 -6
  118. package/dist/media/media.repository.d.ts +5 -0
  119. package/dist/media/media.repository.d.ts.map +1 -1
  120. package/dist/notifications/notification-service.d.ts +1 -1
  121. package/dist/notifications/notification.repository.d.ts +11 -2
  122. package/dist/notifications/notification.repository.d.ts.map +1 -1
  123. package/dist/oauth/authorization-code.repository.d.ts +10 -5
  124. package/dist/oauth/authorization-code.repository.d.ts.map +1 -1
  125. package/dist/oauth/pkce.d.ts +1 -1
  126. package/dist/oauth/role-guard.d.ts +30 -11
  127. package/dist/oauth/role-guard.d.ts.map +1 -1
  128. package/dist/oauth/role-guard.js +27 -5
  129. package/dist/oauth/scopes.d.ts +1 -1
  130. package/dist/oauth/scopes.d.ts.map +1 -1
  131. package/dist/oauth/token.repository.d.ts +2 -0
  132. package/dist/oauth/token.repository.d.ts.map +1 -1
  133. package/dist/queue/queue.interface.d.ts +9 -1
  134. package/dist/queue/queue.interface.d.ts.map +1 -1
  135. package/dist/queue/queue.stub.d.ts +1 -1
  136. package/dist/queue/queue.stub.d.ts.map +1 -1
  137. package/dist/queue/queue.stub.js +2 -3
  138. package/dist/queue/scheduler.stub.d.ts.map +1 -1
  139. package/dist/rate-limit/token-bucket-rate-limiter.d.ts +7 -0
  140. package/dist/rate-limit/token-bucket-rate-limiter.d.ts.map +1 -1
  141. package/dist/rate-limit/token-bucket-rate-limiter.js +66 -0
  142. package/dist/rbac/invitation.repository.d.ts +28 -10
  143. package/dist/rbac/invitation.repository.d.ts.map +1 -1
  144. package/dist/rbac/types.d.ts +40 -28
  145. package/dist/rbac/types.d.ts.map +1 -1
  146. package/dist/rbac/types.js +2 -1
  147. package/dist/schema/manifest-seeds.js +3 -3
  148. package/dist/search/search.repository.d.ts +5 -4
  149. package/dist/search/search.repository.d.ts.map +1 -1
  150. package/dist/search/vector-extractor.d.ts.map +1 -1
  151. package/dist/search/vector-extractor.js +23 -2
  152. package/dist/search/vector.repository.d.ts +2 -1
  153. package/dist/search/vector.repository.d.ts.map +1 -1
  154. package/dist/security/time-trap-token.repository.d.ts +5 -3
  155. package/dist/security/time-trap-token.repository.d.ts.map +1 -1
  156. package/dist/settings/site-settings.repository.d.ts +8 -0
  157. package/dist/settings/site-settings.repository.d.ts.map +1 -1
  158. package/dist/transfer/flat-seed.d.ts.map +1 -1
  159. package/dist/transfer/flat-seed.js +6 -1
  160. package/dist/transfer/row-mapping.d.ts.map +1 -1
  161. package/dist/transfer/row-mapping.js +9 -2
  162. package/package.json +8 -3
  163. package/dist/.tsbuildinfo +0 -1
@@ -61,6 +61,7 @@ const OLE_DOCUMENT_DEFINITION = {
61
61
  isBinary: true,
62
62
  magicBytes: [...OLE_MAGIC],
63
63
  matchSignature: matchOleSignature,
64
+ signatureFamily: 'OLE2 container',
64
65
  };
65
66
  /** Common shared definition for OpenXML Office files. */
66
67
  const OPENXML_DOCUMENT_DEFINITION = {
@@ -68,6 +69,7 @@ const OPENXML_DOCUMENT_DEFINITION = {
68
69
  isBinary: true,
69
70
  magicBytes: [0x50, 0x4B, 0x03, 0x04],
70
71
  matchSignature: matchZipSignature,
72
+ signatureFamily: 'ZIP container',
71
73
  };
72
74
  /**
73
75
  * Single source of truth registry for all supported file types in BeechCMS.
@@ -97,6 +99,7 @@ export const SUPPORTED_FILE_TYPES = {
97
99
  category: 'image',
98
100
  isBinary: true,
99
101
  magicBytes: [0x47, 0x49, 0x46, 0x38],
102
+ minSignatureLength: 6,
100
103
  matchSignature: (bytes) => bytes.length >= 6 &&
101
104
  bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x38 &&
102
105
  (bytes[4] === 0x37 || bytes[4] === 0x39) && bytes[5] === 0x61,
@@ -108,6 +111,7 @@ export const SUPPORTED_FILE_TYPES = {
108
111
  category: 'image',
109
112
  isBinary: true,
110
113
  magicBytes: [0x52, 0x49, 0x46, 0x46],
114
+ minSignatureLength: 12,
111
115
  matchSignature: (bytes) => bytes.length >= 12 &&
112
116
  bytes[0] === 0x52 && bytes[1] === 0x49 && bytes[2] === 0x46 && bytes[3] === 0x46 &&
113
117
  bytes[8] === 0x57 && bytes[9] === 0x45 && bytes[10] === 0x42 && bytes[11] === 0x50,
@@ -119,6 +123,7 @@ export const SUPPORTED_FILE_TYPES = {
119
123
  category: 'image',
120
124
  isBinary: true,
121
125
  magicBytes: [0x66, 0x74, 0x79, 0x70],
126
+ minSignatureLength: 12,
122
127
  matchSignature: (bytes) => {
123
128
  if (bytes.length < 12)
124
129
  return false;
@@ -222,6 +227,7 @@ export const SUPPORTED_FILE_TYPES = {
222
227
  isBinary: true,
223
228
  magicBytes: [0x50, 0x4B, 0x03, 0x04],
224
229
  matchSignature: matchZipSignature,
230
+ signatureFamily: 'ZIP container',
225
231
  },
226
232
  '7z': {
227
233
  extension: '7z',
@@ -238,6 +244,7 @@ export const SUPPORTED_FILE_TYPES = {
238
244
  category: 'archive',
239
245
  isBinary: true,
240
246
  magicBytes: [0x75, 0x73, 0x74, 0x61, 0x72], // 'ustar'
247
+ minSignatureLength: 512,
241
248
  matchSignature: (bytes) => {
242
249
  if (bytes.length < 512)
243
250
  return false;
@@ -297,6 +304,8 @@ export function getFileTypeByExtension(ext) {
297
304
  if (!ext)
298
305
  return undefined;
299
306
  const normalised = ext.replace(/^\./, '').toLowerCase().trim();
307
+ if (!Object.hasOwn(SUPPORTED_FILE_TYPES, normalised))
308
+ return undefined;
300
309
  return SUPPORTED_FILE_TYPES[normalised];
301
310
  }
302
311
  /**
@@ -311,6 +320,17 @@ export function getFileTypeByMime(mime) {
311
320
  const normalised = mime.split(';')[0].trim().toLowerCase();
312
321
  return Object.values(SUPPORTED_FILE_TYPES).find((def) => def.mimeTypes.some((m) => m.toLowerCase() === normalised));
313
322
  }
323
+ /**
324
+ * Canonicalizes a declared MIME type to its registered primary form (e.g. 'image/jpg' -> 'image/jpeg').
325
+ * Unrecognized MIME types pass through unchanged (lowercased, parameters stripped).
326
+ *
327
+ * @param mime - The declared MIME string (e.g. 'image/jpg' or 'image/png; charset=utf-8').
328
+ * @returns The canonical primary MIME type, or the normalised input if unrecognized.
329
+ */
330
+ export function canonicalizeMime(mime) {
331
+ const normalised = mime.split(';')[0].trim().toLowerCase();
332
+ return getFileTypeByMime(normalised)?.primaryMime ?? normalised;
333
+ }
314
334
  /**
315
335
  * Extracts a lowercase file extension from a URL string or query parameters.
316
336
  *
@@ -320,10 +340,10 @@ export function getFileTypeByMime(mime) {
320
340
  export function extensionFromUrl(url) {
321
341
  try {
322
342
  const parsed = new URL(url);
323
- const path = parsed.pathname;
324
- const dot = path.lastIndexOf('.');
325
- if (dot >= 0 && dot < path.length - 1) {
326
- return path.slice(dot + 1).toLowerCase();
343
+ const segment = parsed.pathname.slice(parsed.pathname.lastIndexOf('/') + 1);
344
+ const dot = segment.lastIndexOf('.');
345
+ if (dot >= 0 && dot < segment.length - 1) {
346
+ return segment.slice(dot + 1).toLowerCase();
327
347
  }
328
348
  const param = parsed.searchParams.get('fm') || parsed.searchParams.get('format') || parsed.searchParams.get('ext');
329
349
  if (param && /^[a-z0-9]+$/i.test(param)) {
@@ -1 +1 @@
1
- {"version":3,"file":"magic-bytes.d.ts","sourceRoot":"","sources":["../../src/media/magic-bytes.ts"],"names":[],"mappings":"AAWA;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAA;IACd,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAiBD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,WAAW,GAAG,UAAU,EAChC,YAAY,EAAE,MAAM,GACnB,0BAA0B,CAgD5B"}
1
+ {"version":3,"file":"magic-bytes.d.ts","sourceRoot":"","sources":["../../src/media/magic-bytes.ts"],"names":[],"mappings":"AAWA;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC,qEAAqE;IACrE,KAAK,EAAE,OAAO,CAAA;IACd,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAkBD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,WAAW,GAAG,UAAU,EAChC,YAAY,EAAE,MAAM,GACnB,0BAA0B,CA0D5B"}
@@ -2,18 +2,19 @@
2
2
  // Copyright (c) 2024–2026 Flavio De Musso
3
3
  import { SUPPORTED_FILE_TYPES, getFileTypeByMime, BLOCKED_IMAGE_MIME_TYPES, matchesFileSignature, } from './file-types.js';
4
4
  /**
5
- * Iterates across supported binary definitions to find any matching file signature.
5
+ * Finds the binary formats matching the buffer, one per distinct MIME type.
6
6
  *
7
7
  * @param bytes - The raw byte buffer to analyze.
8
- * @returns The matching FileTypeDefinition if recognized, or undefined.
8
+ * @returns Matching definitions (empty if unrecognized).
9
9
  */
10
- function detectBinaryType(bytes) {
10
+ function detectBinaryTypes(bytes) {
11
+ const matches = new Map();
11
12
  for (const def of Object.values(SUPPORTED_FILE_TYPES)) {
12
- if (def.isBinary && matchesFileSignature(def, bytes)) {
13
- return def;
13
+ if (def.isBinary && !matches.has(def.primaryMime) && matchesFileSignature(def, bytes)) {
14
+ matches.set(def.primaryMime, def);
14
15
  }
15
16
  }
16
- return undefined;
17
+ return [...matches.values()];
17
18
  }
18
19
  /**
19
20
  * Validates that an uploaded file's binary content matches its declared MIME type,
@@ -45,7 +46,7 @@ export function verifyMagicBytes(buffer, declaredMime) {
45
46
  return { valid: true, detectedMime: declaredDef.primaryMime };
46
47
  }
47
48
  // If buffer is smaller than the minimum bytes needed for initial signature inspection (typically 4, or 2-3 for short signatures)
48
- const minInspectionLength = Math.min(4, declaredDef.magicBytes?.length ?? 4);
49
+ const minInspectionLength = declaredDef.minSignatureLength ?? Math.min(4, declaredDef.magicBytes?.length ?? 4);
49
50
  if (bytes.length < minInspectionLength) {
50
51
  return { valid: false, error: 'File buffer too small for signature inspection' };
51
52
  }
@@ -54,13 +55,22 @@ export function verifyMagicBytes(buffer, declaredMime) {
54
55
  return { valid: true, detectedMime: declaredDef.primaryMime };
55
56
  }
56
57
  // Signature mismatch: check if it matches another known binary signature
57
- const detectedDef = detectBinaryType(bytes);
58
- if (detectedDef) {
58
+ const detected = detectBinaryTypes(bytes);
59
+ if (detected.length === 1) {
60
+ const [detectedDef] = detected;
59
61
  return {
60
62
  valid: false,
61
63
  detectedMime: detectedDef.primaryMime,
62
64
  error: `Signature mismatch: file is ${detectedDef.extension.toUpperCase()} but declared as ${declaredMime}`,
63
65
  };
64
66
  }
67
+ if (detected.length > 1) {
68
+ // Shared signature: exact format unknowable
69
+ const family = detected[0].signatureFamily ?? 'shared-signature format';
70
+ return {
71
+ valid: false,
72
+ error: `Signature mismatch: file is a ${family} but declared as ${declaredMime}`,
73
+ };
74
+ }
65
75
  return { valid: false, error: `Unrecognized file signature for declared MIME ${declaredMime}` };
66
76
  }
@@ -71,18 +71,22 @@ export declare function isTransformableMime(mime: string | null | undefined): bo
71
71
  * side would exceed `maxDimension` — the pathological-aspect-ratio guard of brief §4. Never truncates.
72
72
  */
73
73
  export declare function deriveScaleOutput(presetWidth: number, source: MediaDimensions, maxDimension: number): MediaDimensions | null;
74
- /** Identity of a preset's *definition*, so redefining a name never reuses an old variant. */
75
- export declare function mediaPresetSignature(preset: MediaPreset): string;
74
+ /**
75
+ * Identity of a preset's *definition*, so redefining a name never reuses an old variant.
76
+ * A scale preset's validity depends on the effective dimension ceiling (its height derives from the
77
+ * source), so the ceiling is part of its identity; a crop preset is validated at catalog build.
78
+ */
79
+ export declare function mediaPresetSignature(preset: MediaPreset, maxDimension: number): string;
76
80
  /** Precondition: `isTransformableMime(sourceMime)` is true. */
77
81
  export declare function buildImageTransformSpec(preset: MediaPreset, request: MediaTransformRequest, sourceMime: string): ImageTransformSpec;
78
82
  /**
79
83
  * Strong ETag of a variant: source identity (key + size — keys are never overwritten) plus the
80
- * canonical request plus the preset definition. Format: `"mv1-<32 hex>"`.
84
+ * canonical request plus the preset definition (and the ceiling for scale presets). Format: `"mv1-<32 hex>"`.
81
85
  */
82
86
  export declare function computeMediaVariantEtag(source: {
83
87
  readonly key: string;
84
88
  readonly size: number;
85
- }, request: MediaTransformRequest, preset: MediaPreset): Promise<string>;
89
+ }, request: MediaTransformRequest, preset: MediaPreset, maxDimension: number): Promise<string>;
86
90
  /** RFC 9110 §13.1.2: `*` matches; otherwise weak comparison against a comma-separated list. */
87
91
  export declare function ifNoneMatchMatches(header: string | null | undefined, etag: string | null | undefined): boolean;
88
92
  //# sourceMappingURL=media-transform.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"media-transform.d.ts","sourceRoot":"","sources":["../../src/media/media-transform.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"media-transform.d.ts","sourceRoot":"","sources":["../../src/media/media-transform.ts"],"names":[],"mappings":"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"}
@@ -157,9 +157,13 @@ export function deriveScaleOutput(presetWidth, source, maxDimension) {
157
157
  : { width: presetWidth, height: Math.max(1, Math.round((source.height * presetWidth) / source.width)) };
158
158
  return output.width > maxDimension || output.height > maxDimension ? null : output;
159
159
  }
160
- /** Identity of a preset's *definition*, so redefining a name never reuses an old variant. */
161
- export function mediaPresetSignature(preset) {
162
- return preset.kind === 'crop' ? `crop:${preset.width}x${preset.height}` : `scale:${preset.width}`;
160
+ /**
161
+ * Identity of a preset's *definition*, so redefining a name never reuses an old variant.
162
+ * A scale preset's validity depends on the effective dimension ceiling (its height derives from the
163
+ * source), so the ceiling is part of its identity; a crop preset is validated at catalog build.
164
+ */
165
+ export function mediaPresetSignature(preset, maxDimension) {
166
+ return preset.kind === 'crop' ? `crop:${preset.width}x${preset.height}` : `scale:${preset.width}@${maxDimension}`;
163
167
  }
164
168
  /** Precondition: `isTransformableMime(sourceMime)` is true. */
165
169
  export function buildImageTransformSpec(preset, request, sourceMime) {
@@ -175,10 +179,10 @@ export function buildImageTransformSpec(preset, request, sourceMime) {
175
179
  }
176
180
  /**
177
181
  * Strong ETag of a variant: source identity (key + size — keys are never overwritten) plus the
178
- * canonical request plus the preset definition. Format: `"mv1-<32 hex>"`.
182
+ * canonical request plus the preset definition (and the ceiling for scale presets). Format: `"mv1-<32 hex>"`.
179
183
  */
180
- export async function computeMediaVariantEtag(source, request, preset) {
181
- const digest = await sha256hex(`${source.key}\n${source.size}\n${canonicalMediaTransformQuery(request)}\n${mediaPresetSignature(preset)}`);
184
+ export async function computeMediaVariantEtag(source, request, preset, maxDimension) {
185
+ const digest = await sha256hex(`${source.key}\n${source.size}\n${canonicalMediaTransformQuery(request)}\n${mediaPresetSignature(preset, maxDimension)}`);
182
186
  return `"mv1-${digest.slice(0, 32)}"`;
183
187
  }
184
188
  /** RFC 9110 §13.1.2: `*` matches; otherwise weak comparison against a comma-separated list. */
@@ -14,6 +14,11 @@ export interface MediaRepository {
14
14
  * Tracks a new file upload in the database.
15
15
  */
16
16
  trackUpload(media: Omit<MediaObject, 'created_at'>): Promise<void>;
17
+ /**
18
+ * Registers a file and counts its bytes toward total storage in one atomic step.
19
+ * Returns false, counting nothing, when the key is already registered.
20
+ */
21
+ registerUpload(media: Omit<MediaObject, 'created_at'>): Promise<boolean>;
17
22
  /**
18
23
  * Retrieves tracking info for a file by its key.
19
24
  */
@@ -1 +1 @@
1
- {"version":3,"file":"media.repository.d.ts","sourceRoot":"","sources":["../../src/media/media.repository.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnE;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAEnD;;OAEG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpC;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,WAAW,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEnG;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/C;;OAEG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/C;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC;;OAEG;IACH,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACpC"}
1
+ {"version":3,"file":"media.repository.d.ts","sourceRoot":"","sources":["../../src/media/media.repository.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnE;;;OAGG;IACH,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEzE;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAEnD;;OAEG;IACH,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpC;;OAEG;IACH,IAAI,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,WAAW,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEnG;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/C;;OAEG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE/C;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC;;OAEG;IACH,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACpC"}
@@ -13,9 +13,9 @@ export declare const createNotificationSchema: z.ZodObject<{
13
13
  title: z.ZodString;
14
14
  message: z.ZodString;
15
15
  type: z.ZodOptional<z.ZodEnum<{
16
+ success: "success";
16
17
  error: "error";
17
18
  info: "info";
18
- success: "success";
19
19
  warning: "warning";
20
20
  }>>;
21
21
  }, z.core.$strip>;
@@ -16,6 +16,14 @@ export interface NotificationRecord {
16
16
  isRead: boolean;
17
17
  createdAt: number;
18
18
  }
19
+ /** Input of {@link INotificationRepository.create}. */
20
+ export type CreateNotificationRecord = Omit<NotificationRecord, 'id' | 'createdAt' | 'isRead'> & {
21
+ /**
22
+ * Stable identifier of the upstream event (e.g. a QStash message id). Creating twice with the
23
+ * same key stores one notification, so at-least-once delivery cannot inflate the inbox.
24
+ */
25
+ dedupeKey?: string;
26
+ };
19
27
  export interface NotificationStats {
20
28
  totalCount: number;
21
29
  latestCreatedAt: number;
@@ -40,9 +48,10 @@ export interface INotificationRepository {
40
48
  /**
41
49
  * Insert a new notification and return its generated id so callers (e.g.
42
50
  * the public form submission flow) can correlate the notification with the
43
- * triggering request.
51
+ * triggering request. With `dedupeKey`, a repeated key stores nothing and returns the id of the
52
+ * notification the first call created.
44
53
  */
45
- create(record: Omit<NotificationRecord, 'id' | 'createdAt' | 'isRead'>): Promise<string>;
54
+ create(record: CreateNotificationRecord): Promise<string>;
46
55
  markRead(notificationId: string): Promise<void>;
47
56
  markUnread(notificationId: string): Promise<void>;
48
57
  markAllRead(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"notification.repository.d.ts","sourceRoot":"","sources":["../../src/notifications/notification.repository.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;AAEvE,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,gBAAgB,CAAA;IACtB,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAA;IAElD;;;;;;OAMG;IACH,KAAK,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAEnC;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,GAAG,WAAW,GAAG,QAAQ,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAExF,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjD,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9C"}
1
+ {"version":3,"file":"notification.repository.d.ts","sourceRoot":"","sources":["../../src/notifications/notification.repository.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AAEH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;AAEvE,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,gBAAgB,CAAA;IACtB,MAAM,EAAE,OAAO,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,uDAAuD;AACvD,MAAM,MAAM,wBAAwB,GAAG,IAAI,CAAC,kBAAkB,EAAE,IAAI,GAAG,WAAW,GAAG,QAAQ,CAAC,GAAG;IAC/F;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAA;IAElD;;;;;;OAMG;IACH,KAAK,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAEnC;;;;;OAKG;IACH,MAAM,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAEzD,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjD,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5B,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9C"}
@@ -19,15 +19,20 @@ export interface IOAuthAuthorizationCodeRepository {
19
19
  /** Persists a new single-use authorization code. Hash only, never plaintext. */
20
20
  save(record: NewAuthorizationCode): Promise<void>;
21
21
  /**
22
- * Returns the code regardless of its consumed state, provided it is unexpired.
23
- * Callers MUST inspect `consumedAt`: a non-null value means replay, which
24
- * requires cascade revocation via IOAuthTokenRepository.revokeByAuthorizationCode.
22
+ * Returns the code regardless of its consumed OR expired state — replay must be
23
+ * detectable even after the 60s TTL, since tokens derived from the code outlive it
24
+ * by 30 days. Callers MUST inspect `consumedAt` (replay signal, requires cascade
25
+ * revocation via IOAuthTokenRepository.revokeByAuthorizationCode) and `expiresAt`
26
+ * separately (an unconsumed-but-expired code is simply invalid, nothing to revoke).
25
27
  */
26
- findByHash(codeHash: string, nowTimestamp: number): Promise<AuthorizationCodeRecord | null>;
28
+ findByHash(codeHash: string): Promise<AuthorizationCodeRecord | null>;
27
29
  /**
28
30
  * Atomically marks the code consumed. Returns true only for the first caller;
29
- * every subsequent call returns false, which is the replay signal.
31
+ * every subsequent call returns false, which is the replay signal. Expiry is
32
+ * enforced here — redemption, unlike replay detection, must respect the TTL.
30
33
  */
31
34
  consumeByHash(codeHash: string, nowTimestamp: number): Promise<boolean>;
35
+ /** Invalidates every unconsumed code issued to a (client, user) pair, e.g. on consent revoke. */
36
+ invalidateByClientAndUser(clientId: string, userId: string): Promise<number>;
32
37
  }
33
38
  //# sourceMappingURL=authorization-code.repository.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"authorization-code.repository.d.ts","sourceRoot":"","sources":["../../src/oauth/authorization-code.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;IACrB,wEAAwE;IACxE,mBAAmB,EAAE,MAAM,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,uBAAwB,SAAQ,oBAAoB;IACnE,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,iCAAiC;IAChD,gFAAgF;IAChF,IAAI,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEjD;;;;OAIG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAAA;IAE3F;;;OAGG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACxE"}
1
+ {"version":3,"file":"authorization-code.repository.d.ts","sourceRoot":"","sources":["../../src/oauth/authorization-code.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;IACrB,wEAAwE;IACxE,mBAAmB,EAAE,MAAM,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,uBAAwB,SAAQ,oBAAoB;IACnE,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1B;AAED,MAAM,WAAW,iCAAiC;IAChD,gFAAgF;IAChF,IAAI,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEjD;;;;;;OAMG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAAA;IAErE;;;;OAIG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAEvE,iGAAiG;IACjG,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CAC7E"}
@@ -3,7 +3,7 @@
3
3
  * per OAuth 2.1, so no weak fallback configuration can ever exist.
4
4
  */
5
5
  /** The only accepted code challenge method. */
6
- export declare const PKCE_CHALLENGE_METHOD: 'S256';
6
+ export declare const PKCE_CHALLENGE_METHOD: "S256";
7
7
  export type PkceChallengeMethod = typeof PKCE_CHALLENGE_METHOD;
8
8
  /** Minimum / maximum code_verifier length, RFC 7636 §4.1. */
9
9
  export declare const PKCE_VERIFIER_MIN_LENGTH = 43;
@@ -1,32 +1,51 @@
1
1
  import type { OAuthScope } from './scopes.js';
2
+ import type { EffectivePermissions } from '../rbac/types.js';
2
3
  /** Outcome of a role arbitration. `deniedScopes` is empty when fully granted. */
3
4
  export interface ScopeGrantDecision {
4
5
  grantedScopes: OAuthScope[];
5
6
  deniedScopes: OAuthScope[];
6
7
  }
7
8
  /**
8
- * Arbitrates which of the requested scopes a given user role may grant.
9
+ * Arbitrates which of the requested scopes a given caller may grant.
9
10
  *
10
11
  * This is the ONLY place role-based authorization may live in the OAuth flow.
11
- * `/oauth/authorize` and `/oauth/token` must never branch on `role` themselves,
12
- * so introducing a real role system later requires swapping the implementation
13
- * bound in repositoryMiddleware and nothing else.
12
+ * `/oauth/authorize` and `/oauth/token` must never branch on authority themselves.
13
+ *
14
+ * The parameter is the caller's RESOLVED authority, not a role string: the role
15
+ * string is the pre-RBAC vocabulary and is on its way out.
14
16
  */
15
17
  export interface IRoleGuard {
16
18
  /**
17
- * @param role - The resource owner's role claim, or undefined when absent.
19
+ * @param effective - The resource owner's effective permissions, already folded.
18
20
  * @param requestedScopes - Scopes the client asked for, already validated.
19
21
  */
20
- arbitrate(role: string | undefined, requestedScopes: readonly OAuthScope[]): Promise<ScopeGrantDecision>;
22
+ arbitrate(effective: EffectivePermissions, requestedScopes: readonly OAuthScope[]): Promise<ScopeGrantDecision>;
21
23
  }
22
24
  /**
23
- * Stub guard for the pre-roles world: grants every requested scope to every role.
25
+ * Stub guard: grants every requested scope to every caller.
24
26
  *
25
- * This permissiveness is EXPLICIT and directly tested, not an accidental default.
26
- * When the roles feature lands, replace this binding with a real adapter; the
27
- * behaviour change will then be visible as a failing test here, by design.
27
+ * Retained as the explicit, directly-tested permissive baseline and as the test
28
+ * double for suites that are not exercising arbitration. It is NO LONGER the
29
+ * production binding — `repositoryMiddleware` binds {@link PermissionRoleGuard}.
28
30
  */
29
31
  export declare class AllowAllRoleGuard implements IRoleGuard {
30
- arbitrate(_role: string | undefined, requestedScopes: readonly OAuthScope[]): Promise<ScopeGrantDecision>;
32
+ arbitrate(_effective: EffectivePermissions, requestedScopes: readonly OAuthScope[]): Promise<ScopeGrantDecision>;
33
+ }
34
+ /**
35
+ * Production guard: only a platform-wide administrator may delegate authority to an
36
+ * OAuth/MCP client.
37
+ *
38
+ * Every OAuth scope in this system (`schema:read`, `schema:write`) drives seed-schema
39
+ * tooling, which brief §2 keeps OUT of the dashboard permission axis entirely — there is
40
+ * deliberately no `manage_seeds` permission to map onto. So the question this guard
41
+ * answers is not "which seed?" but "may this account hand platform authority to a
42
+ * client at all?", and the marker for that is holding `manage_users` at
43
+ * {@link GLOBAL_SCOPE} — the same SuperAdmin marker the last-global-admin guard uses.
44
+ *
45
+ * A seed-scoped collaborator therefore cannot mint an MCP token, no matter which
46
+ * scopes the client requests. Denial is all-or-nothing: there is no partial grant.
47
+ */
48
+ export declare class PermissionRoleGuard implements IRoleGuard {
49
+ arbitrate(effective: EffectivePermissions, requestedScopes: readonly OAuthScope[]): Promise<ScopeGrantDecision>;
31
50
  }
32
51
  //# sourceMappingURL=role-guard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"role-guard.d.ts","sourceRoot":"","sources":["../../src/oauth/role-guard.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,UAAU,EAAE,CAAA;IAC3B,YAAY,EAAE,UAAU,EAAE,CAAA;CAC3B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,eAAe,EAAE,SAAS,UAAU,EAAE,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAA;CACzG;AAED;;;;;;GAMG;AACH,qBAAa,iBAAkB,YAAW,UAAU;IAC5C,SAAS,CACb,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,eAAe,EAAE,SAAS,UAAU,EAAE,GACrC,OAAO,CAAC,kBAAkB,CAAC,CAE7B;CACF"}
1
+ {"version":3,"file":"role-guard.d.ts","sourceRoot":"","sources":["../../src/oauth/role-guard.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAE5D,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,UAAU,EAAE,CAAA;IAC3B,YAAY,EAAE,UAAU,EAAE,CAAA;CAC3B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,SAAS,CACP,SAAS,EAAE,oBAAoB,EAC/B,eAAe,EAAE,SAAS,UAAU,EAAE,GACrC,OAAO,CAAC,kBAAkB,CAAC,CAAA;CAC/B;AAED;;;;;;GAMG;AACH,qBAAa,iBAAkB,YAAW,UAAU;IAC5C,SAAS,CACb,UAAU,EAAE,oBAAoB,EAChC,eAAe,EAAE,SAAS,UAAU,EAAE,GACrC,OAAO,CAAC,kBAAkB,CAAC;CAG/B;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,mBAAoB,YAAW,UAAU;IAC9C,SAAS,CACb,SAAS,EAAE,oBAAoB,EAC/B,eAAe,EAAE,SAAS,UAAU,EAAE,GACrC,OAAO,CAAC,kBAAkB,CAAC;CAM/B"}
@@ -1,14 +1,36 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Copyright (c) 2024–2026 Flavio De Musso
3
3
  /**
4
- * Stub guard for the pre-roles world: grants every requested scope to every role.
4
+ * Stub guard: grants every requested scope to every caller.
5
5
  *
6
- * This permissiveness is EXPLICIT and directly tested, not an accidental default.
7
- * When the roles feature lands, replace this binding with a real adapter; the
8
- * behaviour change will then be visible as a failing test here, by design.
6
+ * Retained as the explicit, directly-tested permissive baseline and as the test
7
+ * double for suites that are not exercising arbitration. It is NO LONGER the
8
+ * production binding — `repositoryMiddleware` binds {@link PermissionRoleGuard}.
9
9
  */
10
10
  export class AllowAllRoleGuard {
11
- async arbitrate(_role, requestedScopes) {
11
+ async arbitrate(_effective, requestedScopes) {
12
+ return { grantedScopes: [...requestedScopes], deniedScopes: [] };
13
+ }
14
+ }
15
+ /**
16
+ * Production guard: only a platform-wide administrator may delegate authority to an
17
+ * OAuth/MCP client.
18
+ *
19
+ * Every OAuth scope in this system (`schema:read`, `schema:write`) drives seed-schema
20
+ * tooling, which brief §2 keeps OUT of the dashboard permission axis entirely — there is
21
+ * deliberately no `manage_seeds` permission to map onto. So the question this guard
22
+ * answers is not "which seed?" but "may this account hand platform authority to a
23
+ * client at all?", and the marker for that is holding `manage_users` at
24
+ * {@link GLOBAL_SCOPE} — the same SuperAdmin marker the last-global-admin guard uses.
25
+ *
26
+ * A seed-scoped collaborator therefore cannot mint an MCP token, no matter which
27
+ * scopes the client requests. Denial is all-or-nothing: there is no partial grant.
28
+ */
29
+ export class PermissionRoleGuard {
30
+ async arbitrate(effective, requestedScopes) {
31
+ if (!effective.global.has('manage_users')) {
32
+ return { grantedScopes: [], deniedScopes: [...requestedScopes] };
33
+ }
12
34
  return { grantedScopes: [...requestedScopes], deniedScopes: [] };
13
35
  }
14
36
  }
@@ -9,7 +9,7 @@
9
9
  * `beech_schema_plan` is deliberately classified as READ: despite the imperative
10
10
  * name it is a dry-run that computes a migration plan and mutates nothing.
11
11
  */
12
- export declare const OAUTH_SCOPES: readonly ['schema:read', 'schema:write'];
12
+ export declare const OAUTH_SCOPES: readonly ["schema:read", "schema:write"];
13
13
  export type OAuthScope = (typeof OAUTH_SCOPES)[number];
14
14
  /** Narrows an arbitrary string to a known scope. */
15
15
  export declare function isOAuthScope(value: string): value is OAuthScope;
@@ -1 +1 @@
1
- {"version":3,"file":"scopes.d.ts","sourceRoot":"","sources":["../../src/oauth/scopes.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,YAAI,aAAa,EAAE,cAAc,CAAU,CAAA;AAEpE,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAA;AAEtD,oDAAoD;AACpD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,UAAU,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,GAAG,IAAI,CAMjE;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM,CAElE;AAED,oEAAoE;AACpE,wBAAgB,aAAa,CAC3B,SAAS,EAAE,SAAS,UAAU,EAAE,EAChC,OAAO,EAAE,SAAS,UAAU,EAAE,GAC7B,OAAO,CAET"}
1
+ {"version":3,"file":"scopes.d.ts","sourceRoot":"","sources":["../../src/oauth/scopes.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,0CAA2C,CAAA;AAEpE,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAA;AAEtD,oDAAoD;AACpD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,UAAU,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,EAAE,GAAG,IAAI,CAMjE;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM,CAElE;AAED,oEAAoE;AACpE,wBAAgB,aAAa,CAC3B,SAAS,EAAE,SAAS,UAAU,EAAE,EAChC,OAAO,EAAE,SAAS,UAAU,EAAE,GAC7B,OAAO,CAET"}
@@ -27,6 +27,8 @@ export interface IOAuthTokenRepository {
27
27
  save(record: NewOAuthToken): Promise<void>;
28
28
  /** Finds an unexpired, unrevoked token by hash and type. */
29
29
  findActiveByHash(tokenHash: string, tokenType: OAuthTokenType, nowTimestamp: number): Promise<OAuthTokenRecord | null>;
30
+ /** Finds a token by hash and type in any state (expired or revoked included). */
31
+ findByHash(tokenHash: string, tokenType: OAuthTokenType): Promise<OAuthTokenRecord | null>;
30
32
  /** Revokes a single token. False when already revoked or absent. */
31
33
  revokeByHash(tokenHash: string, nowTimestamp: number): Promise<boolean>;
32
34
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"token.repository.d.ts","sourceRoot":"","sources":["../../src/oauth/token.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAA;AAEjD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,cAAc,CAAA;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,uFAAuF;IACvF,qBAAqB,EAAE,MAAM,CAAA;IAC7B,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE1C,4DAA4D;IAC5D,gBAAgB,CACd,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,cAAc,EACzB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;IAEnC,oEAAoE;IACpE,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAEvE;;;;OAIG;IACH,yBAAyB,CAAC,qBAAqB,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAE/F;;;OAGG;IACH,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAElG,qFAAqF;IACrF,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAAA;CACvG"}
1
+ {"version":3,"file":"token.repository.d.ts","sourceRoot":"","sources":["../../src/oauth/token.repository.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,SAAS,CAAA;AAEjD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,cAAc,CAAA;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,uFAAuF;IACvF,qBAAqB,EAAE,MAAM,CAAA;IAC7B,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,UAAU,EAAE,CAAA;IACnB,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE1C,4DAA4D;IAC5D,gBAAgB,CACd,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,cAAc,EACzB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;IAEnC,iFAAiF;IACjF,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;IAE1F,oEAAoE;IACpE,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAEvE;;;;OAIG;IACH,yBAAyB,CAAC,qBAAqB,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAE/F;;;OAGG;IACH,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAElG,qFAAqF;IACrF,4BAA4B,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAAA;CACvG"}
@@ -12,7 +12,8 @@ export interface QueueMessage<T = unknown> {
12
12
  }
13
13
  /**
14
14
  * Execution context handed to every job. Botanical Invariant: jobs receive the
15
- * engine-mediated repository, NEVER a raw D1Database. `env` is a read-only,
15
+ * engine-mediated repository, NEVER a raw D1Database, and the engine-mediated
16
+ * queue port, NEVER a raw transport binding. `env` is a read-only,
16
17
  * stringly-typed view of bindings/secrets for things like fetch targets — it
17
18
  * deliberately does not expose `DB`.
18
19
  */
@@ -21,6 +22,13 @@ export interface JobContext {
21
22
  bucket: BeechBucket;
22
23
  clock: IClock;
23
24
  idGenerator: IIdGenerator;
25
+ /**
26
+ * Producer port, so a job can schedule its own successor. Required for cursor-style
27
+ * jobs that process a bounded slice per invocation, persist their offset and re-enqueue
28
+ * (bulk import): without it a handler would have to reach into `env` for the raw QUEUE
29
+ * binding, which is exactly the transport coupling `repository` exists to prevent for D1.
30
+ */
31
+ queue: IQueueService;
24
32
  env: Record<string, string | undefined>;
25
33
  }
26
34
  /** A single background worker. Receives the decoded payload + context. */
@@ -1 +1 @@
1
- {"version":3,"file":"queue.interface.d.ts","sourceRoot":"","sources":["../../src/queue/queue.interface.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAA;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,CAAC,CAAA;CACX;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,iBAAiB,CAAA;IAC7B,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,YAAY,CAAA;IACzB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CACxC;AAED,0EAA0E;AAC1E,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,CACpC,OAAO,EAAE,CAAC,EACV,OAAO,EAAE,UAAU,KAChB,OAAO,CAAC,IAAI,CAAC,CAAA;AAElB,0DAA0D;AAC1D,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAA;AAEzD;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACvD"}
1
+ {"version":3,"file":"queue.interface.d.ts","sourceRoot":"","sources":["../../src/queue/queue.interface.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAA;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,CAAC,CAAA;CACX;AAED;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,iBAAiB,CAAA;IAC7B,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,YAAY,CAAA;IACzB;;;;;OAKG;IACH,KAAK,EAAE,aAAa,CAAA;IACpB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CACxC;AAED,0EAA0E;AAC1E,MAAM,MAAM,UAAU,CAAC,CAAC,GAAG,OAAO,IAAI,CACpC,OAAO,EAAE,CAAC,EACV,OAAO,EAAE,UAAU,KAChB,OAAO,CAAC,IAAI,CAAC,CAAA;AAElB,0DAA0D;AAC1D,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAA;AAEzD;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACvD"}
@@ -1,5 +1,5 @@
1
1
  import type { IQueueService } from './queue.interface.js';
2
- /** Safe no-op producer (e.g. unit tests that don't assert on enqueue). */
2
+ /** Drops messages without a transport. */
3
3
  export declare class NoOpQueueService implements IQueueService {
4
4
  enqueue<T>(_name: string, _payload: T): Promise<boolean>;
5
5
  }
@@ -1 +1 @@
1
- {"version":3,"file":"queue.stub.d.ts","sourceRoot":"","sources":["../../src/queue/queue.stub.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEzD,0EAA0E;AAC1E,qBAAa,gBAAiB,YAAW,aAAa;IAC9C,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAG7D;CACF"}
1
+ {"version":3,"file":"queue.stub.d.ts","sourceRoot":"","sources":["../../src/queue/queue.stub.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAEzD,0CAA0C;AAC1C,qBAAa,gBAAiB,YAAW,aAAa;IAC9C,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;CAG/D"}
@@ -1,9 +1,8 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  // Copyright (c) 2024–2026 Flavio De Musso
3
- /** Safe no-op producer (e.g. unit tests that don't assert on enqueue). */
3
+ /** Drops messages without a transport. */
4
4
  export class NoOpQueueService {
5
5
  async enqueue(_name, _payload) {
6
- // intentionally empty; reports success since there is nothing to fail
7
- return true;
6
+ return false;
8
7
  }
9
8
  }
@@ -1 +1 @@
1
- {"version":3,"file":"scheduler.stub.d.ts","sourceRoot":"","sources":["../../src/queue/scheduler.stub.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,qBAAa,aAAc,YAAW,UAAU;IAC9C,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAE1C;CACF"}
1
+ {"version":3,"file":"scheduler.stub.d.ts","sourceRoot":"","sources":["../../src/queue/scheduler.stub.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,qBAAa,aAAc,YAAW,UAAU;IAC9C,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI;CAG5C"}
@@ -5,6 +5,8 @@ export interface TokenBucketOptions {
5
5
  refillRatePerSecond?: number;
6
6
  clock?: IClock;
7
7
  maxIdleTimeSeconds?: number;
8
+ maxBuckets?: number;
9
+ pruneIntervalSeconds?: number;
8
10
  }
9
11
  export declare class TokenBucketRateLimiter implements IRateLimiter {
10
12
  private readonly buckets;
@@ -12,8 +14,13 @@ export declare class TokenBucketRateLimiter implements IRateLimiter {
12
14
  private readonly refillRatePerSecond;
13
15
  private readonly clock;
14
16
  private readonly maxIdleTimeSeconds;
17
+ private readonly maxBuckets;
18
+ private readonly pruneIntervalSeconds;
19
+ private lastPruneTimestamp;
15
20
  constructor(options?: TokenBucketOptions);
21
+ private static readonly MAX_EVICTION_SCAN;
16
22
  private pruneExpiredBuckets;
23
+ private evictRefilledBucket;
17
24
  checkLimit(key: string): Promise<RateLimitResult>;
18
25
  reset(): void;
19
26
  }
@@ -1 +1 @@
1
- {"version":3,"file":"token-bucket-rate-limiter.d.ts","sourceRoot":"","sources":["../../src/rate-limit/token-bucket-rate-limiter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACtE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAQhD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED,qBAAa,sBAAuB,YAAW,YAAY;IACzD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAQ;IAC5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAQ;IAE3C,YAAY,OAAO,CAAC,EAAE,kBAAkB,EAMvC;IAED,OAAO,CAAC,mBAAmB;IASrB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAoCtD;IAED,KAAK,IAAI,IAAI,CAEZ;CACF"}
1
+ {"version":3,"file":"token-bucket-rate-limiter.d.ts","sourceRoot":"","sources":["../../src/rate-limit/token-bucket-rate-limiter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACtE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAQhD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAC9B;AAED,qBAAa,sBAAuB,YAAW,YAAY;IACzD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAQ;IAC5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAQ;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAQ;IAC7C,OAAO,CAAC,kBAAkB,CAAI;gBAElB,OAAO,CAAC,EAAE,kBAAkB;IAcxC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAK;IAE9C,OAAO,CAAC,mBAAmB;IA4B3B,OAAO,CAAC,mBAAmB;IAsBrB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAqDvD,KAAK,IAAI,IAAI;CAId"}