@toa.io/extensions.exposition 1.0.0-alpha.269 → 1.0.0-alpha.270

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 (81) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/components/exposition.stash/manifest.toa.yaml +13 -4
  3. package/components/identity.passkeys/operations/tsconfig.tsbuildinfo +1 -1
  4. package/documentation/io.md +26 -23
  5. package/documentation/notes/throttling.md +39 -5
  6. package/features/etag.feature +55 -5
  7. package/features/io.throttle.feature +3 -5
  8. package/package.json +5 -5
  9. package/schemas/io/throttle.cos.yaml +2 -3
  10. package/source/Directive.test.ts +3 -0
  11. package/source/Directive.ts +25 -4
  12. package/source/Endpoint.ts +7 -11
  13. package/source/Factory.ts +16 -1
  14. package/source/HTTP/Context.ts +39 -8
  15. package/source/HTTP/Server.ts +16 -11
  16. package/source/HTTP/exceptions.ts +9 -3
  17. package/source/HTTP/messages.ts +31 -0
  18. package/source/RTD/Directives.ts +6 -0
  19. package/source/directives/auth/Authorization.ts +29 -3
  20. package/source/directives/io/Directive.ts +5 -4
  21. package/source/directives/io/IO.ts +15 -10
  22. package/source/directives/io/Input.ts +3 -3
  23. package/source/directives/io/Output.ts +12 -4
  24. package/source/directives/io/Throttle.ts +9 -12
  25. package/source/directives/io/lib/throttle/Configuration.test.ts +1 -2
  26. package/source/directives/io/lib/throttle/Configuration.ts +2 -4
  27. package/source/directives/io/lib/throttle/Quotas.test.ts +233 -148
  28. package/source/directives/io/lib/throttle/Quotas.ts +129 -53
  29. package/source/directives/io/lib/throttle/Sync.test.ts +153 -0
  30. package/source/directives/io/lib/throttle/Sync.ts +99 -0
  31. package/source/directives/io/lib/throttle/index.ts +1 -0
  32. package/source/directives/io/schemas.test.ts +7 -1
  33. package/transpiled/Directive.d.ts +2 -0
  34. package/transpiled/Directive.js +16 -4
  35. package/transpiled/Directive.js.map +1 -1
  36. package/transpiled/Endpoint.js +7 -8
  37. package/transpiled/Endpoint.js.map +1 -1
  38. package/transpiled/Factory.js +14 -1
  39. package/transpiled/Factory.js.map +1 -1
  40. package/transpiled/HTTP/Context.d.ts +1 -1
  41. package/transpiled/HTTP/Context.js +30 -8
  42. package/transpiled/HTTP/Context.js.map +1 -1
  43. package/transpiled/HTTP/Server.js +14 -10
  44. package/transpiled/HTTP/Server.js.map +1 -1
  45. package/transpiled/HTTP/exceptions.d.ts +5 -2
  46. package/transpiled/HTTP/exceptions.js +7 -3
  47. package/transpiled/HTTP/exceptions.js.map +1 -1
  48. package/transpiled/HTTP/messages.d.ts +2 -0
  49. package/transpiled/HTTP/messages.js +21 -0
  50. package/transpiled/HTTP/messages.js.map +1 -1
  51. package/transpiled/RTD/Directives.d.ts +5 -0
  52. package/transpiled/directives/auth/Authorization.d.ts +1 -0
  53. package/transpiled/directives/auth/Authorization.js +21 -2
  54. package/transpiled/directives/auth/Authorization.js.map +1 -1
  55. package/transpiled/directives/io/Directive.d.ts +4 -4
  56. package/transpiled/directives/io/IO.d.ts +9 -4
  57. package/transpiled/directives/io/IO.js +14 -9
  58. package/transpiled/directives/io/IO.js.map +1 -1
  59. package/transpiled/directives/io/Input.d.ts +1 -1
  60. package/transpiled/directives/io/Input.js +3 -3
  61. package/transpiled/directives/io/Input.js.map +1 -1
  62. package/transpiled/directives/io/Output.d.ts +2 -0
  63. package/transpiled/directives/io/Output.js +9 -3
  64. package/transpiled/directives/io/Output.js.map +1 -1
  65. package/transpiled/directives/io/Throttle.d.ts +3 -5
  66. package/transpiled/directives/io/Throttle.js +8 -10
  67. package/transpiled/directives/io/Throttle.js.map +1 -1
  68. package/transpiled/directives/io/lib/throttle/Configuration.d.ts +0 -1
  69. package/transpiled/directives/io/lib/throttle/Configuration.js +2 -3
  70. package/transpiled/directives/io/lib/throttle/Configuration.js.map +1 -1
  71. package/transpiled/directives/io/lib/throttle/Quotas.d.ts +46 -22
  72. package/transpiled/directives/io/lib/throttle/Quotas.js +107 -46
  73. package/transpiled/directives/io/lib/throttle/Quotas.js.map +1 -1
  74. package/transpiled/directives/io/lib/throttle/Sync.d.ts +30 -0
  75. package/transpiled/directives/io/lib/throttle/Sync.js +76 -0
  76. package/transpiled/directives/io/lib/throttle/Sync.js.map +1 -0
  77. package/transpiled/directives/io/lib/throttle/index.d.ts +1 -0
  78. package/transpiled/directives/io/lib/throttle/index.js +3 -1
  79. package/transpiled/directives/io/lib/throttle/index.js.map +1 -1
  80. package/transpiled/tsconfig.tsbuildinfo +1 -1
  81. package/components/exposition.stash/operations/count.js +0 -7
@@ -4,10 +4,12 @@ export declare class Output implements Directive {
4
4
  private readonly disabled;
5
5
  private readonly omitted;
6
6
  private readonly permissions;
7
+ private readonly allowed;
7
8
  constructor(permissions: Permissions);
8
9
  static validate(permissions: unknown): asserts permissions is Permissions;
9
10
  preflight(context: Context): void;
10
11
  private restriction;
12
+ /** Runs per entity of a collection, hence the set and the absence of intermediates. */
11
13
  private fit;
12
14
  }
13
15
  export type Permissions = string[] | boolean;
@@ -31,6 +31,7 @@ class Output {
31
31
  disabled = false;
32
32
  omitted = true;
33
33
  permissions = [];
34
+ allowed;
34
35
  constructor(permissions) {
35
36
  if (typeof permissions === 'boolean')
36
37
  if (permissions)
@@ -39,6 +40,7 @@ class Output {
39
40
  this.omitted = false;
40
41
  else
41
42
  this.permissions = permissions;
43
+ this.allowed = new Set(this.permissions);
42
44
  }
43
45
  static validate(permissions) {
44
46
  schemas.output.validate(permissions, 'Incorrect \'io:output\' format');
@@ -66,10 +68,14 @@ class Output {
66
68
  message.body = this.fit(message.body);
67
69
  };
68
70
  }
71
+ /** Runs per entity of a collection, hence the set and the absence of intermediates. */
69
72
  fit(message) {
70
- const entries = Object.entries(message)
71
- .filter(([key]) => this.permissions.includes(key));
72
- return Object.fromEntries(entries);
73
+ const output = {};
74
+ // the keys of the entity, so that the response keeps the order it was built in
75
+ for (const key of Object.keys(message))
76
+ if (this.allowed.has(key))
77
+ output[key] = message[key];
78
+ return output;
73
79
  }
74
80
  }
75
81
  exports.Output = Output;
@@ -1 +1 @@
1
- {"version":3,"file":"Output.js","sourceRoot":"","sources":["../../../source/directives/io/Output.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAAoC;AACpC,uCAAkC;AAClC,mDAAoC;AAMpC,MAAa,MAAM;IACA,QAAQ,GAAY,KAAK,CAAA;IACzB,OAAO,GAAY,IAAI,CAAA;IACvB,WAAW,GAAa,EAAE,CAAA;IAE3C,YAAoB,WAAwB;QAC1C,IAAI,OAAO,WAAW,KAAK,SAAS;YAClC,IAAI,WAAW;gBACb,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;;gBAEpB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;;YAGtB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAA;IAClC,CAAC;IAEM,MAAM,CAAC,QAAQ,CAAE,WAAoB;QAC1C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,gCAAgC,CAAC,CAAA;IACxE,CAAC;IAEM,SAAS,CAAE,OAAgB;QAChC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAA;IAC5D,CAAC;IAEO,WAAW,CAAE,OAAgB;QACnC,OAAO,CAAC,OAAwB,EAAQ,EAAE;YACxC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,IAAI,GAAG,CAAA;YACnE,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,YAAY,oBAAM,CAAA;YAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,CAAA;YAEhE,IAAI,IAAI,CAAC,QAAQ,IAAI,KAAK,IAAI,MAAM,IAAI,IAAI;gBAC1C,OAAM;YAER,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtE,IAAI,IAAI,CAAC,OAAO;oBACd,kBAAO,CAAC,IAAI,CAAC,4EAA4E,EACvF,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAA;gBAEnC,OAAO,OAAO,CAAC,IAAI,CAAA;gBAEnB,OAAM;YACR,CAAC;YAED,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAU,OAAO,CAAC,IAAI,EAC5C,oEAAoE,CAAC,CAAA;YAEvE,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;gBAC7B,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,MAAiB,CAAC,CAAC,CAAA;;gBAExE,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACzC,CAAC,CAAA;IACH,CAAC;IAEO,GAAG,CAAE,OAAgB;QAC3B,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;aACpC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAA;QAEpD,OAAO,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAA;IACpC,CAAC;CACF;AA3DD,wBA2DC"}
1
+ {"version":3,"file":"Output.js","sourceRoot":"","sources":["../../../source/directives/io/Output.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAAoC;AACpC,uCAAkC;AAClC,mDAAoC;AAMpC,MAAa,MAAM;IACA,QAAQ,GAAY,KAAK,CAAA;IACzB,OAAO,GAAY,IAAI,CAAA;IACvB,WAAW,GAAa,EAAE,CAAA;IAC1B,OAAO,CAAa;IAErC,YAAoB,WAAwB;QAC1C,IAAI,OAAO,WAAW,KAAK,SAAS;YAClC,IAAI,WAAW;gBACb,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;;gBAEpB,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;;YAGtB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAA;QAEhC,IAAI,CAAC,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;IAC1C,CAAC;IAEM,MAAM,CAAC,QAAQ,CAAE,WAAoB;QAC1C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,gCAAgC,CAAC,CAAA;IACxE,CAAC;IAEM,SAAS,CAAE,OAAgB;QAChC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAA;IAC5D,CAAC;IAEO,WAAW,CAAE,OAAgB;QACnC,OAAO,CAAC,OAAwB,EAAQ,EAAE;YACxC,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,IAAI,GAAG,CAAA;YACnE,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,YAAY,oBAAM,CAAA;YAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,CAAA;YAEhE,IAAI,IAAI,CAAC,QAAQ,IAAI,KAAK,IAAI,MAAM,IAAI,IAAI;gBAC1C,OAAM;YAER,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACtE,IAAI,IAAI,CAAC,OAAO;oBACd,kBAAO,CAAC,IAAI,CAAC,4EAA4E,EACvF,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAA;gBAEnC,OAAO,OAAO,CAAC,IAAI,CAAA;gBAEnB,OAAM;YACR,CAAC;YAED,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAU,OAAO,CAAC,IAAI,EAC5C,oEAAoE,CAAC,CAAA;YAEvE,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;gBAC7B,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,MAAiB,CAAC,CAAC,CAAA;;gBAExE,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACzC,CAAC,CAAA;IACH,CAAC;IAED,uFAAuF;IAC/E,GAAG,CAAE,OAAgB;QAC3B,MAAM,MAAM,GAAY,EAAE,CAAA;QAE1B,+EAA+E;QAC/E,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;YACpC,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;gBACvB,MAAM,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;QAE9B,OAAO,MAAM,CAAA;IACf,CAAC;CACF;AAnED,wBAmEC"}
@@ -1,13 +1,11 @@
1
- import { type Declaration } from './lib/throttle';
2
- import type { Remote } from '@toa.io/core';
1
+ import { type Declaration, type Sync } from './lib/throttle';
3
2
  import type * as http from '../../HTTP';
4
3
  import type { Parameter } from '../../RTD';
5
4
  import type { Directive } from './Directive';
6
5
  export declare class Throttle implements Directive {
7
6
  private readonly quotas;
8
- constructor(declaration: Declaration, counter: Promise<Remote>, route: string);
7
+ constructor(declaration: Declaration, sync: Sync, route: string);
9
8
  static validate(declaration: unknown): asserts declaration is Declaration;
10
9
  preflight(context: http.Context, parameters: Parameter[]): void;
11
- settle(context: http.Context, output: http.OutgoingMessage): Promise<void>;
12
- dispose(): void;
10
+ settle(context: http.Context, output: http.OutgoingMessage): void;
13
11
  }
@@ -29,22 +29,20 @@ const schemas = __importStar(require("./schemas"));
29
29
  const throttle_1 = require("./lib/throttle");
30
30
  class Throttle {
31
31
  quotas;
32
- constructor(declaration, counter, route) {
33
- const configuration = (0, throttle_1.parse)(declaration);
34
- this.quotas = throttle_1.Quotas.create(configuration, counter, route);
32
+ constructor(declaration, sync, route) {
33
+ this.quotas = throttle_1.Quotas.create((0, throttle_1.parse)(declaration), route);
34
+ sync.register(this.quotas);
35
35
  }
36
36
  static validate(declaration) {
37
37
  schemas.throttle.validate(declaration, 'Incorrect \'io:throttle\' format');
38
38
  }
39
39
  preflight(context, parameters) {
40
- if (!this.quotas.ok(context, parameters))
41
- throw new HTTP_1.TooManyRequests();
40
+ const retry = this.quotas.check(context, parameters);
41
+ if (retry > 0)
42
+ throw new HTTP_1.TooManyRequests(retry);
42
43
  }
43
- async settle(context, output) {
44
- await this.quotas.use(context, output);
45
- }
46
- dispose() {
47
- this.quotas.dispose();
44
+ settle(context, output) {
45
+ this.quotas.use(context, output);
48
46
  }
49
47
  }
50
48
  exports.Throttle = Throttle;
@@ -1 +1 @@
1
- {"version":3,"file":"Throttle.js","sourceRoot":"","sources":["../../../source/directives/io/Throttle.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qCAA4C;AAC5C,mDAAoC;AACpC,6CAAgE;AAMhE,MAAa,QAAQ;IACF,MAAM,CAAQ;IAE/B,YAAoB,WAAwB,EAAE,OAAwB,EAAE,KAAa;QACnF,MAAM,aAAa,GAAG,IAAA,gBAAK,EAAC,WAAW,CAAC,CAAA;QAExC,IAAI,CAAC,MAAM,GAAG,iBAAM,CAAC,MAAM,CAAC,aAAa,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;IAC5D,CAAC;IAEM,MAAM,CAAC,QAAQ,CAAE,WAAoB;QAC1C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,EAAE,kCAAkC,CAAC,CAAA;IAC5E,CAAC;IAEM,SAAS,CAAE,OAAqB,EAAE,UAAuB;QAC9D,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC;YACtC,MAAM,IAAI,sBAAe,EAAE,CAAA;IAC/B,CAAC;IAEM,KAAK,CAAC,MAAM,CAAE,OAAqB,EAAE,MAA4B;QACtE,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IACxC,CAAC;IAEM,OAAO;QACZ,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAA;IACvB,CAAC;CACF;AAzBD,4BAyBC"}
1
+ {"version":3,"file":"Throttle.js","sourceRoot":"","sources":["../../../source/directives/io/Throttle.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qCAA4C;AAC5C,mDAAoC;AACpC,6CAA2E;AAK3E,MAAa,QAAQ;IACF,MAAM,CAAQ;IAE/B,YAAoB,WAAwB,EAAE,IAAU,EAAE,KAAa;QACrE,IAAI,CAAC,MAAM,GAAG,iBAAM,CAAC,MAAM,CAAC,IAAA,gBAAK,EAAC,WAAW,CAAC,EAAE,KAAK,CAAC,CAAA;QAEtD,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC5B,CAAC;IAEM,MAAM,CAAC,QAAQ,CAAE,WAAoB;QAC1C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,EAAE,kCAAkC,CAAC,CAAA;IAC5E,CAAC;IAEM,SAAS,CAAE,OAAqB,EAAE,UAAuB;QAC9D,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,UAAU,CAAC,CAAA;QAEpD,IAAI,KAAK,GAAG,CAAC;YACX,MAAM,IAAI,sBAAe,CAAC,KAAK,CAAC,CAAA;IACpC,CAAC;IAEM,MAAM,CAAE,OAAqB,EAAE,MAA4B;QAChE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAClC,CAAC;CACF;AAvBD,4BAuBC"}
@@ -3,7 +3,6 @@ export interface Configuration {
3
3
  condition?: KeyCondition[];
4
4
  requests: number;
5
5
  interval: number;
6
- cooldown: number;
7
6
  }
8
7
  interface Rule<T, K = unknown> {
9
8
  method: T;
@@ -2,13 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parse = void 0;
4
4
  function parse(declaration) {
5
- const { key, condition, requests, interval, cooldown } = declaration;
5
+ const { key, condition, requests, interval } = declaration;
6
6
  return {
7
7
  key: mapKey(key),
8
8
  condition: mapCondition(condition),
9
9
  requests,
10
- interval: interval * 1000,
11
- cooldown: cooldown * 1000
10
+ interval: interval * 1000
12
11
  };
13
12
  }
14
13
  exports.parse = parse;
@@ -1 +1 @@
1
- {"version":3,"file":"Configuration.js","sourceRoot":"","sources":["../../../../../source/directives/io/lib/throttle/Configuration.ts"],"names":[],"mappings":";;;AAgCA,SAAgB,KAAK,CAAE,WAAwB;IAC7C,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,WAAW,CAAA;IAEpE,OAAO;QACL,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC;QAChB,SAAS,EAAE,YAAY,CAAC,SAAS,CAAC;QAClC,QAAQ;QACR,QAAQ,EAAE,QAAQ,GAAG,IAAI;QACzB,QAAQ,EAAE,QAAQ,GAAG,IAAI;KAC1B,CAAA;AACH,CAAC;AAVD,sBAUC;AAED,SAAS,MAAM,CAAE,WAA2B;IAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;IAExE,qEAAqE;IACrE,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAkB,EAAE,CAC/C,OAAO,KAAK,KAAK,QAAQ;QACvB,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QACrB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,CAChD,CAAC,EAAE,MAAM,EAAE,MAA4B,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;AAC7D,CAAC;AAED,SAAS,YAAY,CAAE,WAAkC;IACvD,IAAI,WAAW,KAAK,SAAS;QAC3B,OAAM;IAER,uDAAuD;IACvD,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;IAC3E,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;IAEpF,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAmB,CAAA;AACnG,CAAC"}
1
+ {"version":3,"file":"Configuration.js","sourceRoot":"","sources":["../../../../../source/directives/io/lib/throttle/Configuration.ts"],"names":[],"mappings":";;;AA+BA,SAAgB,KAAK,CAAE,WAAwB;IAC7C,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,WAAW,CAAA;IAE1D,OAAO;QACL,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC;QAChB,SAAS,EAAE,YAAY,CAAC,SAAS,CAAC;QAClC,QAAQ;QACR,QAAQ,EAAE,QAAQ,GAAG,IAAI;KAC1B,CAAA;AACH,CAAC;AATD,sBASC;AAED,SAAS,MAAM,CAAE,WAA2B;IAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;IAExE,qEAAqE;IACrE,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAkB,EAAE,CAC/C,OAAO,KAAK,KAAK,QAAQ;QACvB,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QACrB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,CAChD,CAAC,EAAE,MAAM,EAAE,MAA4B,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;AAC7D,CAAC;AAED,SAAS,YAAY,CAAE,WAAkC;IACvD,IAAI,WAAW,KAAK,SAAS;QAC3B,OAAM;IAER,uDAAuD;IACvD,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;IAC3E,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;IAEpF,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAmB,CAAA;AACnG,CAAC"}
@@ -1,45 +1,69 @@
1
1
  import { Keys } from './Keys';
2
- import type { Remote } from '@toa.io/core';
2
+ import type { Batch } from './Sync';
3
3
  import type { Configuration } from './Configuration';
4
4
  import type { Parameter } from '../../../../RTD';
5
5
  import type { Input as Context, Output } from '../../../../io';
6
6
  /**
7
- * Quota usage counted across every gateway process, and blocking decided in this one.
7
+ * A quota per key, metered by GCRA, decided in this process and shared with the others.
8
8
  *
9
- * The count comes from the `exposition.stash` component, which counts through Redis
10
- * and hands back a lower bound on what the whole group has reached. Blocking stays
11
- * local: every process sees the same number and reaches the same conclusion on its
12
- * own, and a process that cannot reach Redis still enforces what it has seen itself.
9
+ * The state of a key is one theoretical arrival time: the moment the key would be back
10
+ * at zero if nothing more arrived. An admitted request pushes it `emission` further out,
11
+ * time drags it back, and a request is admitted while it stays within `capacity` of now.
12
+ * So `requests` is what a key may spend at once, and `requests / interval` what it earns
13
+ * back — no window to burst across the edge of, and no lockout to time out of.
14
+ *
15
+ * Deciding needs nothing but the local map, which is what keeps the request path free of
16
+ * I/O. What the other gateways have spent arrives on {@link Sync}'s tick, as a debt in
17
+ * milliseconds — a duration, so it carries across processes without their clocks having
18
+ * to agree, and additive, which is what lets each process report only its own increments.
13
19
  */
14
20
  export declare class Quotas {
15
- private readonly requests;
16
- private readonly interval;
17
- private readonly cooldown;
21
+ /** How often the debt is worth reconciling with the other gateways, in milliseconds. */
22
+ readonly period: number;
23
+ /** Milliseconds of debt an admitted request adds, `interval / requests`. */
24
+ private readonly emission;
25
+ /** Debt a key may carry before it is refused, `interval` — a burst of `requests`. */
26
+ private readonly capacity;
18
27
  private readonly keys;
19
- private readonly counter;
20
- private readonly blocked;
21
- private readonly timers;
28
+ private readonly prefix;
29
+ private readonly conditional;
30
+ private readonly entries;
22
31
  /**
23
- * The key computed at preflight, for settle to count against.
32
+ * The key computed at preflight, for settle to charge against.
24
33
  *
25
34
  * `segment` reads the route parameters, which settle is not given, and `identity`
26
35
  * can be refreshed between the two — so recomputing there would key a request
27
- * differently than it was checked, and blocking would quietly stop working.
36
+ * differently than it was checked, and throttling would quietly stop working.
28
37
  */
29
38
  private readonly keyed;
30
- private remote;
31
39
  constructor(options: Options);
32
- static create(configuration: Configuration, counter: Promise<Remote>, route?: string): Quotas;
33
- ok(context: Context, parameters: Parameter[]): boolean;
34
- use(input: Context, output: Output): Promise<void>;
35
- dispose(): void;
36
- private block;
40
+ static create(configuration: Configuration, route?: string): Quotas;
41
+ /**
42
+ * Seconds to wait before the request would be admitted, or zero when it is.
43
+ *
44
+ * Charges here when nothing conditions the count, so that the request is metered by
45
+ * the same call that admits it; otherwise only the response can tell whether it counts,
46
+ * and {@link use} charges once it can.
47
+ */
48
+ check(context: Context, parameters: Parameter[]): number;
49
+ /** Charges what the condition accepts, once the response can be matched against it. */
50
+ use(input: Context, output: Output): void;
51
+ /**
52
+ * Adds what is worth reporting to the batch, and drops what has gone quiet.
53
+ *
54
+ * The debt is not cleared here but in {@link settled}, so a tick that fails to reach
55
+ * Redis leaves it to be reported by the next one instead of losing it.
56
+ */
57
+ flush(now: number, batch: Batch[]): void;
58
+ /** Takes the group's debt back, and clears what this process contributed to it. */
59
+ settled(reported: Batch, debt: number, now: number): void;
60
+ name(key: string): string;
61
+ private charge;
37
62
  }
38
63
  interface Options {
39
64
  keys: Keys;
40
65
  requests: number;
41
66
  interval: number;
42
- cooldown: number;
43
- counter: Promise<Remote>;
67
+ conditional: boolean;
44
68
  }
45
69
  export {};
@@ -1,77 +1,138 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Quotas = void 0;
4
- const openspan_1 = require("openspan");
5
4
  const Keys_1 = require("./Keys");
6
5
  /**
7
- * Quota usage counted across every gateway process, and blocking decided in this one.
6
+ * A quota per key, metered by GCRA, decided in this process and shared with the others.
8
7
  *
9
- * The count comes from the `exposition.stash` component, which counts through Redis
10
- * and hands back a lower bound on what the whole group has reached. Blocking stays
11
- * local: every process sees the same number and reaches the same conclusion on its
12
- * own, and a process that cannot reach Redis still enforces what it has seen itself.
8
+ * The state of a key is one theoretical arrival time: the moment the key would be back
9
+ * at zero if nothing more arrived. An admitted request pushes it `emission` further out,
10
+ * time drags it back, and a request is admitted while it stays within `capacity` of now.
11
+ * So `requests` is what a key may spend at once, and `requests / interval` what it earns
12
+ * back — no window to burst across the edge of, and no lockout to time out of.
13
+ *
14
+ * Deciding needs nothing but the local map, which is what keeps the request path free of
15
+ * I/O. What the other gateways have spent arrives on {@link Sync}'s tick, as a debt in
16
+ * milliseconds — a duration, so it carries across processes without their clocks having
17
+ * to agree, and additive, which is what lets each process report only its own increments.
13
18
  */
14
19
  class Quotas {
15
- requests;
16
- interval;
17
- cooldown;
20
+ /** How often the debt is worth reconciling with the other gateways, in milliseconds. */
21
+ period;
22
+ /** Milliseconds of debt an admitted request adds, `interval / requests`. */
23
+ emission;
24
+ /** Debt a key may carry before it is refused, `interval` — a burst of `requests`. */
25
+ capacity;
18
26
  keys;
19
- counter;
20
- blocked = {};
21
- timers = new Set();
27
+ prefix;
28
+ conditional;
29
+ entries = new Map();
22
30
  /**
23
- * The key computed at preflight, for settle to count against.
31
+ * The key computed at preflight, for settle to charge against.
24
32
  *
25
33
  * `segment` reads the route parameters, which settle is not given, and `identity`
26
34
  * can be refreshed between the two — so recomputing there would key a request
27
- * differently than it was checked, and blocking would quietly stop working.
35
+ * differently than it was checked, and throttling would quietly stop working.
28
36
  */
29
37
  keyed = new WeakMap();
30
- remote = null;
31
38
  constructor(options) {
32
- this.requests = options.requests;
33
- this.interval = options.interval;
34
- this.cooldown = options.cooldown;
35
39
  this.keys = options.keys;
36
- this.counter = options.counter;
40
+ this.conditional = options.conditional;
41
+ this.emission = options.interval / options.requests;
42
+ this.capacity = options.interval;
43
+ this.period = period(options.interval);
44
+ // a key means one budget, so quotas that do not share one must not share a key
45
+ this.prefix = `t:${options.requests}:${options.interval}:`;
37
46
  }
38
- static create(configuration, counter, route = '') {
39
- const { requests, interval, cooldown } = configuration;
40
- const keys = Keys_1.Keys.create(configuration.key, configuration.condition, route);
41
- return new this({ requests, interval, cooldown, keys, counter });
47
+ static create(configuration, route = '') {
48
+ const { requests, interval, condition } = configuration;
49
+ const keys = Keys_1.Keys.create(configuration.key, condition, route);
50
+ return new this({ keys, requests, interval, conditional: condition !== undefined });
42
51
  }
43
- ok(context, parameters) {
52
+ /**
53
+ * Seconds to wait before the request would be admitted, or zero when it is.
54
+ *
55
+ * Charges here when nothing conditions the count, so that the request is metered by
56
+ * the same call that admits it; otherwise only the response can tell whether it counts,
57
+ * and {@link use} charges once it can.
58
+ */
59
+ check(context, parameters) {
44
60
  const key = this.keys.get(context, parameters);
45
61
  this.keyed.set(context, key);
46
- return this.blocked[key] !== true;
62
+ const now = Date.now();
63
+ const entry = this.entries.get(key);
64
+ const tat = Math.max(entry?.tat ?? now, now);
65
+ const admit = tat + this.emission - this.capacity;
66
+ if (admit > now)
67
+ return Math.ceil((admit - now) / 1000);
68
+ if (!this.conditional)
69
+ this.charge(key, entry, tat);
70
+ return 0;
47
71
  }
48
- async use(input, output) {
49
- if (!this.keys.matches(input, output))
72
+ /** Charges what the condition accepts, once the response can be matched against it. */
73
+ use(input, output) {
74
+ if (!this.conditional || !this.keys.matches(input, output))
50
75
  return;
51
- // preflight always runs first, and a request it throws on never reaches settle
76
+ // preflight always runs first, and a request it refuses never reaches settle
52
77
  const key = this.keyed.get(input) ?? this.keys.get(input);
53
- this.remote ??= await this.counter;
54
- const count = await this.remote.invoke('count', { input: { name: key, interval: this.interval, amount: 1 } });
55
- if (count >= this.requests)
56
- this.block(key);
78
+ const now = Date.now();
79
+ const entry = this.entries.get(key);
80
+ const tat = Math.max(entry?.tat ?? now, now);
81
+ this.charge(key, entry, tat);
57
82
  }
58
- dispose() {
59
- for (const timer of this.timers)
60
- clearTimeout(timer);
61
- this.timers.clear();
83
+ /**
84
+ * Adds what is worth reporting to the batch, and drops what has gone quiet.
85
+ *
86
+ * The debt is not cleared here but in {@link settled}, so a tick that fails to reach
87
+ * Redis leaves it to be reported by the next one instead of losing it.
88
+ */
89
+ flush(now, batch) {
90
+ for (const [key, entry] of this.entries) {
91
+ const delta = Math.round(entry.debt);
92
+ if (delta > 0) {
93
+ batch.push({ quotas: this, key, delta });
94
+ continue;
95
+ }
96
+ // out of debt and with nothing left to report, a key says no more than an absent one
97
+ if (entry.tat <= now)
98
+ this.entries.delete(key);
99
+ }
62
100
  }
63
- block(key) {
64
- if (this.blocked[key] === true)
101
+ /** Takes the group's debt back, and clears what this process contributed to it. */
102
+ settled(reported, debt, now) {
103
+ const entry = this.entries.get(reported.key);
104
+ if (entry === undefined) {
105
+ if (debt > 0)
106
+ this.entries.set(reported.key, { tat: now + debt, debt: 0 });
65
107
  return;
66
- this.blocked[key] = true;
67
- const timer = setTimeout(() => {
68
- delete this.blocked[key];
69
- this.timers.delete(timer);
70
- }, this.cooldown);
71
- timer.unref();
72
- this.timers.add(timer);
73
- openspan_1.console.info('Quota exceeded, key blocked', { key, cooldown: this.cooldown });
108
+ }
109
+ entry.debt -= reported.delta;
110
+ // the group has spent at least what this process has, so its debt is the one to keep
111
+ entry.tat = Math.max(entry.tat, now + debt);
112
+ }
113
+ name(key) {
114
+ return this.prefix + key;
115
+ }
116
+ charge(key, entry, tat) {
117
+ if (entry === undefined)
118
+ this.entries.set(key, { tat: tat + this.emission, debt: this.emission });
119
+ else {
120
+ entry.tat = tat + this.emission;
121
+ entry.debt += this.emission;
122
+ }
74
123
  }
75
124
  }
76
125
  exports.Quotas = Quotas;
126
+ /**
127
+ * How often to reconcile, in milliseconds.
128
+ *
129
+ * Reconciling is what bounds the overshoot — between two ticks a gateway is going on
130
+ * what it alone has spent — so it is worth doing an order of magnitude more often than
131
+ * the interval, within reason on either end.
132
+ */
133
+ function period(interval) {
134
+ return Math.min(Math.max(interval / 10, MIN_PERIOD), MAX_PERIOD);
135
+ }
136
+ const MIN_PERIOD = 250;
137
+ const MAX_PERIOD = 2000;
77
138
  //# sourceMappingURL=Quotas.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Quotas.js","sourceRoot":"","sources":["../../../../../source/directives/io/lib/throttle/Quotas.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Quotas.js","sourceRoot":"","sources":["../../../../../source/directives/io/lib/throttle/Quotas.ts"],"names":[],"mappings":";;;AAAA,iCAA6B;AAM7B;;;;;;;;;;;;;GAaG;AACH,MAAa,MAAM;IACjB,wFAAwF;IACxE,MAAM,CAAQ;IAE9B,4EAA4E;IAC3D,QAAQ,CAAQ;IAEjC,qFAAqF;IACpE,QAAQ,CAAQ;IAEhB,IAAI,CAAM;IACV,MAAM,CAAQ;IACd,WAAW,CAAS;IACpB,OAAO,GAAG,IAAI,GAAG,EAAiB,CAAA;IAEnD;;;;;;OAMG;IACc,KAAK,GAAG,IAAI,OAAO,EAAmB,CAAA;IAEvD,YAAoB,OAAgB;QAClC,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAA;QACxB,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAA;QACtC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAA;QACnD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAA;QAChC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;QAEtC,+EAA+E;QAC/E,IAAI,CAAC,MAAM,GAAG,KAAK,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,GAAG,CAAA;IAC5D,CAAC;IAEM,MAAM,CAAC,MAAM,CAAE,aAA4B,EAAE,QAAgB,EAAE;QACpE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,aAAa,CAAA;QACvD,MAAM,IAAI,GAAG,WAAI,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,CAAA;QAE7D,OAAO,IAAI,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,KAAK,SAAS,EAAE,CAAC,CAAA;IACrF,CAAC;IAED;;;;;;OAMG;IACI,KAAK,CAAE,OAAgB,EAAE,UAAuB;QACrD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,CAAA;QAE9C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;QAE5B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,IAAI,GAAG,EAAE,GAAG,CAAC,CAAA;QAC5C,MAAM,KAAK,GAAG,GAAG,GAAG,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAA;QAEjD,IAAI,KAAK,GAAG,GAAG;YACb,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAA;QAExC,IAAI,CAAC,IAAI,CAAC,WAAW;YACnB,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CAAA;QAE9B,OAAO,CAAC,CAAA;IACV,CAAC;IAED,uFAAuF;IAChF,GAAG,CAAE,KAAc,EAAE,MAAc;QACxC,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC;YACxD,OAAM;QAER,6EAA6E;QAC7E,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QACzD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,IAAI,GAAG,EAAE,GAAG,CAAC,CAAA;QAE5C,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CAAA;IAC9B,CAAC;IAED;;;;;OAKG;IACI,KAAK,CAAE,GAAW,EAAE,KAAc;QACvC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACxC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YAEpC,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;gBACd,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAA;gBAExC,SAAQ;YACV,CAAC;YAED,qFAAqF;YACrF,IAAI,KAAK,CAAC,GAAG,IAAI,GAAG;gBAClB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAC5B,CAAC;IACH,CAAC;IAED,mFAAmF;IAC5E,OAAO,CAAE,QAAe,EAAE,IAAY,EAAE,GAAW;QACxD,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;QAE5C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,IAAI,IAAI,GAAG,CAAC;gBACV,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAA;YAE9D,OAAM;QACR,CAAC;QAED,KAAK,CAAC,IAAI,IAAI,QAAQ,CAAC,KAAK,CAAA;QAE5B,qFAAqF;QACrF,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC,CAAA;IAC7C,CAAC;IAEM,IAAI,CAAE,GAAW;QACtB,OAAO,IAAI,CAAC,MAAM,GAAG,GAAG,CAAA;IAC1B,CAAC;IAEO,MAAM,CAAE,GAAW,EAAE,KAAwB,EAAE,GAAW;QAChE,IAAI,KAAK,KAAK,SAAS;YACrB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAA;aACrE,CAAC;YACJ,KAAK,CAAC,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAA;YAC/B,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAA;QAC7B,CAAC;IACH,CAAC;CACF;AArID,wBAqIC;AAED;;;;;;GAMG;AACH,SAAS,MAAM,CAAE,QAAgB;IAC/B,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,EAAE,EAAE,UAAU,CAAC,EAAE,UAAU,CAAC,CAAA;AAClE,CAAC;AAED,MAAM,UAAU,GAAG,GAAG,CAAA;AACtB,MAAM,UAAU,GAAG,IAAI,CAAA"}
@@ -0,0 +1,30 @@
1
+ import type { Quotas } from './Quotas';
2
+ import type { Remote } from '@toa.io/core';
3
+ /**
4
+ * Reconciles every quota of the process with the other gateways, on one tick.
5
+ *
6
+ * Nothing here is on the request path. Each tick takes what the quotas have spent since
7
+ * the last one and sends all of it as a single call, so the cost of staying in step is
8
+ * one round trip a tick — not one per key, and not one per directive. What comes back is
9
+ * the debt the whole group has reached, which the quotas then decide on locally.
10
+ */
11
+ export declare class Sync {
12
+ private readonly stash;
13
+ private readonly quotas;
14
+ private remote;
15
+ private timer;
16
+ private period;
17
+ /** Ticks do not overlap: a slow round trip delays reconciling, it does not double it. */
18
+ private reconciling;
19
+ constructor(stash: Promise<Remote>);
20
+ register(quotas: Quotas): void;
21
+ dispose(): void;
22
+ private start;
23
+ private readonly tick;
24
+ private reconcile;
25
+ }
26
+ export interface Batch {
27
+ quotas: Quotas;
28
+ key: string;
29
+ delta: number;
30
+ }
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Sync = void 0;
4
+ const openspan_1 = require("openspan");
5
+ /**
6
+ * Reconciles every quota of the process with the other gateways, on one tick.
7
+ *
8
+ * Nothing here is on the request path. Each tick takes what the quotas have spent since
9
+ * the last one and sends all of it as a single call, so the cost of staying in step is
10
+ * one round trip a tick — not one per key, and not one per directive. What comes back is
11
+ * the debt the whole group has reached, which the quotas then decide on locally.
12
+ */
13
+ class Sync {
14
+ stash;
15
+ quotas = [];
16
+ remote = null;
17
+ timer = null;
18
+ period = Infinity;
19
+ /** Ticks do not overlap: a slow round trip delays reconciling, it does not double it. */
20
+ reconciling = false;
21
+ constructor(stash) {
22
+ this.stash = stash;
23
+ }
24
+ register(quotas) {
25
+ this.quotas.push(quotas);
26
+ // the shortest interval sets the pace, and reconciling the others more often than
27
+ // they asked for only makes them more accurate — an idle quota contributes nothing
28
+ if (quotas.period < this.period) {
29
+ this.period = quotas.period;
30
+ this.start();
31
+ }
32
+ }
33
+ dispose() {
34
+ if (this.timer !== null) {
35
+ clearInterval(this.timer);
36
+ this.timer = null;
37
+ }
38
+ }
39
+ start() {
40
+ this.dispose();
41
+ this.timer = setInterval(this.tick, this.period);
42
+ this.timer.unref();
43
+ }
44
+ tick = () => {
45
+ if (this.reconciling)
46
+ return;
47
+ const now = Date.now();
48
+ const batch = [];
49
+ for (const quotas of this.quotas)
50
+ quotas.flush(now, batch);
51
+ if (batch.length === 0)
52
+ return;
53
+ this.reconciling = true;
54
+ void this.reconcile(batch).finally(() => {
55
+ this.reconciling = false;
56
+ });
57
+ };
58
+ async reconcile(batch) {
59
+ try {
60
+ this.remote ??= await this.stash;
61
+ const keys = batch.map((entry) => entry.quotas.name(entry.key));
62
+ const deltas = batch.map((entry) => entry.delta);
63
+ const debts = await this.remote.invoke('meter', { input: { keys, deltas } });
64
+ const now = Date.now();
65
+ for (let i = 0; i < batch.length; i++)
66
+ batch[i].quotas.settled(batch[i], debts[i], now);
67
+ }
68
+ catch (error) {
69
+ // what was not reported stays to be reported by the next tick, and until then
70
+ // this gateway keeps throttling on what it alone has seen, and keeps serving
71
+ openspan_1.console.warn('Throttling could not reconcile with the group', error);
72
+ }
73
+ }
74
+ }
75
+ exports.Sync = Sync;
76
+ //# sourceMappingURL=Sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Sync.js","sourceRoot":"","sources":["../../../../../source/directives/io/lib/throttle/Sync.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,3 @@
1
1
  export { Quotas } from './Quotas';
2
+ export { Sync } from './Sync';
2
3
  export { parse, type Configuration, type Declaration } from './Configuration';
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parse = exports.Quotas = void 0;
3
+ exports.parse = exports.Sync = exports.Quotas = void 0;
4
4
  var Quotas_1 = require("./Quotas");
5
5
  Object.defineProperty(exports, "Quotas", { enumerable: true, get: function () { return Quotas_1.Quotas; } });
6
+ var Sync_1 = require("./Sync");
7
+ Object.defineProperty(exports, "Sync", { enumerable: true, get: function () { return Sync_1.Sync; } });
6
8
  var Configuration_1 = require("./Configuration");
7
9
  Object.defineProperty(exports, "parse", { enumerable: true, get: function () { return Configuration_1.parse; } });
8
10
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../source/directives/io/lib/throttle/index.ts"],"names":[],"mappings":";;;AAAA,mCAAiC;AAAxB,gGAAA,MAAM,OAAA;AACf,iDAA6E;AAApE,sGAAA,KAAK,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../source/directives/io/lib/throttle/index.ts"],"names":[],"mappings":";;;AAAA,mCAAiC;AAAxB,gGAAA,MAAM,OAAA;AACf,+BAA6B;AAApB,4FAAA,IAAI,OAAA;AACb,iDAA6E;AAApE,sGAAA,KAAK,OAAA"}