@bgord/bun 1.23.9 → 1.23.11

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 (89) hide show
  1. package/dist/cron-scheduler.adapter.d.ts.map +1 -1
  2. package/dist/cron-scheduler.adapter.js +2 -1
  3. package/dist/cron-scheduler.adapter.js.map +1 -1
  4. package/dist/cron-task-handler-bare.strategy.d.ts.map +1 -1
  5. package/dist/cron-task-handler-bare.strategy.js +4 -2
  6. package/dist/cron-task-handler-bare.strategy.js.map +1 -1
  7. package/dist/csv-formula-sanitizer.service.d.ts +5 -0
  8. package/dist/csv-formula-sanitizer.service.d.ts.map +1 -0
  9. package/dist/csv-formula-sanitizer.service.js +8 -0
  10. package/dist/csv-formula-sanitizer.service.js.map +1 -0
  11. package/dist/environment-loader-process-safe.adapter.d.ts +2 -12
  12. package/dist/environment-loader-process-safe.adapter.d.ts.map +1 -1
  13. package/dist/environment-loader-process-safe.adapter.js +2 -10
  14. package/dist/environment-loader-process-safe.adapter.js.map +1 -1
  15. package/dist/environment-loader-with-memo.adapter.d.ts +12 -0
  16. package/dist/environment-loader-with-memo.adapter.d.ts.map +1 -0
  17. package/dist/environment-loader-with-memo.adapter.js +17 -0
  18. package/dist/environment-loader-with-memo.adapter.js.map +1 -0
  19. package/dist/event-store-dispatching.adapter.d.ts.map +1 -1
  20. package/dist/event-store-dispatching.adapter.js +2 -1
  21. package/dist/event-store-dispatching.adapter.js.map +1 -1
  22. package/dist/hash-bytes-noop.strategy.d.ts +6 -0
  23. package/dist/hash-bytes-noop.strategy.d.ts.map +1 -0
  24. package/dist/hash-bytes-noop.strategy.js +7 -0
  25. package/dist/hash-bytes-noop.strategy.js.map +1 -0
  26. package/dist/hash-bytes-sha256.strategy.d.ts +6 -0
  27. package/dist/hash-bytes-sha256.strategy.d.ts.map +1 -0
  28. package/dist/hash-bytes-sha256.strategy.js +8 -0
  29. package/dist/hash-bytes-sha256.strategy.js.map +1 -0
  30. package/dist/hash-bytes.strategy.d.ts +5 -0
  31. package/dist/hash-bytes.strategy.d.ts.map +1 -0
  32. package/dist/hash-bytes.strategy.js +2 -0
  33. package/dist/hash-bytes.strategy.js.map +1 -0
  34. package/dist/hash-file-sha256.adapter.d.ts +4 -4
  35. package/dist/hash-file-sha256.adapter.d.ts.map +1 -1
  36. package/dist/hash-file-sha256.adapter.js +2 -2
  37. package/dist/hash-file-sha256.adapter.js.map +1 -1
  38. package/dist/idempotency-store-cache.adapter.d.ts.map +1 -1
  39. package/dist/idempotency-store-cache.adapter.js +1 -0
  40. package/dist/idempotency-store-cache.adapter.js.map +1 -1
  41. package/dist/index.d.ts +5 -0
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +5 -0
  44. package/dist/index.js.map +1 -1
  45. package/dist/job-queue.adapter.d.ts.map +1 -1
  46. package/dist/job-queue.adapter.js +10 -3
  47. package/dist/job-queue.adapter.js.map +1 -1
  48. package/dist/markdown-generator.adapter.d.ts +1 -0
  49. package/dist/markdown-generator.adapter.d.ts.map +1 -1
  50. package/dist/markdown-generator.adapter.js +8 -1
  51. package/dist/markdown-generator.adapter.js.map +1 -1
  52. package/dist/redactor-key.vo.d.ts +8 -0
  53. package/dist/redactor-key.vo.d.ts.map +1 -0
  54. package/dist/redactor-key.vo.js +16 -0
  55. package/dist/redactor-key.vo.js.map +1 -0
  56. package/dist/redactor-mask.strategy.d.ts.map +1 -1
  57. package/dist/redactor-mask.strategy.js +7 -2
  58. package/dist/redactor-mask.strategy.js.map +1 -1
  59. package/dist/redactor-url-query.strategy.d.ts.map +1 -1
  60. package/dist/redactor-url-query.strategy.js +3 -2
  61. package/dist/redactor-url-query.strategy.js.map +1 -1
  62. package/dist/shield-body-limit.strategy.d.ts.map +1 -1
  63. package/dist/shield-body-limit.strategy.js +1 -0
  64. package/dist/shield-body-limit.strategy.js.map +1 -1
  65. package/dist/trailing-slash.middleware.d.ts +2 -2
  66. package/dist/trailing-slash.middleware.d.ts.map +1 -1
  67. package/dist/trailing-slash.middleware.js +5 -5
  68. package/dist/trailing-slash.middleware.js.map +1 -1
  69. package/package.json +5 -5
  70. package/readme.md +6 -0
  71. package/src/cron-scheduler.adapter.ts +2 -1
  72. package/src/cron-task-handler-bare.strategy.ts +5 -3
  73. package/src/csv-formula-sanitizer.service.ts +9 -0
  74. package/src/environment-loader-process-safe.adapter.ts +2 -22
  75. package/src/environment-loader-with-memo.adapter.ts +21 -0
  76. package/src/event-store-dispatching.adapter.ts +2 -1
  77. package/src/hash-bytes-noop.strategy.ts +8 -0
  78. package/src/hash-bytes-sha256.strategy.ts +10 -0
  79. package/src/hash-bytes.strategy.ts +5 -0
  80. package/src/hash-file-sha256.adapter.ts +6 -6
  81. package/src/idempotency-store-cache.adapter.ts +1 -0
  82. package/src/index.ts +5 -0
  83. package/src/job-queue.adapter.ts +11 -3
  84. package/src/markdown-generator.adapter.ts +10 -1
  85. package/src/redactor-key.vo.ts +15 -0
  86. package/src/redactor-mask.strategy.ts +9 -2
  87. package/src/redactor-url-query.strategy.ts +5 -2
  88. package/src/shield-body-limit.strategy.ts +1 -0
  89. package/src/trailing-slash.middleware.ts +8 -10
@@ -1,5 +1,6 @@
1
1
  import type { MarkdownGeneratorPort } from "./markdown-generator.port";
2
2
  export declare class MarkdownGeneratorAdapter implements MarkdownGeneratorPort {
3
+ private static readonly ALLOWED_SCHEMES;
3
4
  generate(template: string): string;
4
5
  }
5
6
  //# sourceMappingURL=markdown-generator.adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"markdown-generator.adapter.d.ts","sourceRoot":"","sources":["../src/markdown-generator.adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAEvE,qBAAa,wBAAyB,YAAW,qBAAqB;IACpE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEjC;CACF"}
1
+ {"version":3,"file":"markdown-generator.adapter.d.ts","sourceRoot":"","sources":["../src/markdown-generator.adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAEvE,qBAAa,wBAAyB,YAAW,qBAAqB;IACpE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAA+B;IAEtE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CASjC;CACF"}
@@ -1,6 +1,13 @@
1
1
  export class MarkdownGeneratorAdapter {
2
+ static ALLOWED_SCHEMES = ["http", "https", "mailto"];
2
3
  generate(template) {
3
- return Bun.markdown.html(template, { tagFilter: true, noHtmlBlocks: true, noHtmlSpans: true });
4
+ const html = Bun.markdown.html(template, { tagFilter: true, noHtmlBlocks: true, noHtmlSpans: true });
5
+ return html.replace(/ href="([^"]*)"/g, (attribute, url) => {
6
+ const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(url)?.[1]?.toLowerCase();
7
+ if (scheme === undefined || MarkdownGeneratorAdapter.ALLOWED_SCHEMES.includes(scheme))
8
+ return attribute;
9
+ return ' href="#"';
10
+ });
4
11
  }
5
12
  }
6
13
  //# sourceMappingURL=markdown-generator.adapter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"markdown-generator.adapter.js","sourceRoot":"","sources":["../src/markdown-generator.adapter.ts"],"names":[],"mappings":"AAGA,MAAM,OAAO,wBAAwB;IACnC,QAAQ,CAAC,QAAgB;QACvB,OAAO,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;IACjG,CAAC;CACF"}
1
+ {"version":3,"file":"markdown-generator.adapter.js","sourceRoot":"","sources":["../src/markdown-generator.adapter.ts"],"names":[],"mappings":"AAGA,MAAM,OAAO,wBAAwB;IAC3B,MAAM,CAAU,eAAe,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAEtE,QAAQ,CAAC,QAAgB;QACvB,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QAErG,OAAO,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC,SAAS,EAAE,GAAW,EAAE,EAAE;YACjE,MAAM,MAAM,GAAG,wBAAwB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;YAEtE,IAAI,MAAM,KAAK,SAAS,IAAI,wBAAwB,CAAC,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAAE,OAAO,SAAS,CAAC;YACxG,OAAO,WAAW,CAAC;QACrB,CAAC,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,8 @@
1
+ export declare class RedactorKey {
2
+ private readonly value;
3
+ private constructor();
4
+ static fromString(candidate: string): RedactorKey;
5
+ get(): string;
6
+ matches(another: RedactorKey): boolean;
7
+ }
8
+ //# sourceMappingURL=redactor-key.vo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redactor-key.vo.d.ts","sourceRoot":"","sources":["../src/redactor-key.vo.ts"],"names":[],"mappings":"AAAA,qBAAa,WAAW;IACF,OAAO,CAAC,QAAQ,CAAC,KAAK;IAA1C,OAAO,eAA+C;IAEtD,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,CAEhD;IAED,GAAG,IAAI,MAAM,CAEZ;IAED,OAAO,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAErC;CACF"}
@@ -0,0 +1,16 @@
1
+ export class RedactorKey {
2
+ value;
3
+ constructor(value) {
4
+ this.value = value;
5
+ }
6
+ static fromString(candidate) {
7
+ return new RedactorKey(candidate.toLowerCase().replace(/[_-]/g, ""));
8
+ }
9
+ get() {
10
+ return this.value;
11
+ }
12
+ matches(another) {
13
+ return this.value === another.value;
14
+ }
15
+ }
16
+ //# sourceMappingURL=redactor-key.vo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redactor-key.vo.js","sourceRoot":"","sources":["../src/redactor-key.vo.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,WAAW;IACe,KAAK;IAA1C,YAAqC,KAAa;qBAAb,KAAK;IAAW,CAAC;IAEtD,MAAM,CAAC,UAAU,CAAC,SAAiB;QACjC,OAAO,IAAI,WAAW,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IACvE,CAAC;IAED,GAAG;QACD,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,OAAO,CAAC,OAAoB;QAC1B,OAAO,IAAI,CAAC,KAAK,KAAK,OAAO,CAAC,KAAK,CAAC;IACtC,CAAC;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"redactor-mask.strategy.d.ts","sourceRoot":"","sources":["../src/redactor-mask.strategy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,qBAAa,YAAa,YAAW,gBAAgB;IACnD,MAAM,CAAC,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC,CAiBjD;IAEF,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IAEnC,YAAY,IAAI,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,EAEvC;IAED,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAIrB;CACF"}
1
+ {"version":3,"file":"redactor-mask.strategy.d.ts","sourceRoot":"","sources":["../src/redactor-mask.strategy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG5D,qBAAa,YAAa,YAAW,gBAAgB;IACnD,MAAM,CAAC,QAAQ,CAAC,YAAY,EAAE,aAAa,CAAC,MAAM,CAAC,CAqBjD;IAEF,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IAEnC,YAAY,IAAI,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,EAIvC;IAED,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAIrB;CACF"}
@@ -1,30 +1,35 @@
1
1
  /* cspell:disable */
2
2
  import * as tools from "@bgord/tools";
3
+ import { RedactorKey } from "./redactor-key.vo";
3
4
  export class RedactorMask {
4
5
  static DEFAULT_KEYS = [
5
6
  "authorization",
7
+ "proxy-authorization",
6
8
  "cookie",
7
9
  "set-cookie",
8
10
  "x-api-key",
11
+ "x-auth-token",
9
12
  "apikey",
10
13
  "token",
11
14
  "accesstoken",
12
15
  "refreshtoken",
16
+ "idtoken",
13
17
  "password",
14
18
  "currentpassword",
15
19
  "newpassword",
16
20
  "passwordconfirmation",
17
21
  "clientsecret",
22
+ "privatekey",
18
23
  "secret",
19
24
  "otp",
20
25
  "code",
21
26
  ];
22
27
  keys;
23
28
  constructor(keys) {
24
- this.keys = new Set((keys?.length ? keys : RedactorMask.DEFAULT_KEYS).map((key) => key.toLowerCase()));
29
+ this.keys = new Set((keys?.length ? keys : RedactorMask.DEFAULT_KEYS).map((key) => RedactorKey.fromString(key).get()));
25
30
  }
26
31
  redact(input) {
27
- return tools.deepCloneWith(input, (_value, key) => typeof key === "string" && this.keys.has(key.toLowerCase()) ? "***" : undefined);
32
+ return tools.deepCloneWith(input, (_value, key) => typeof key === "string" && this.keys.has(RedactorKey.fromString(key).get()) ? "***" : undefined);
28
33
  }
29
34
  }
30
35
  //# sourceMappingURL=redactor-mask.strategy.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"redactor-mask.strategy.js","sourceRoot":"","sources":["../src/redactor-mask.strategy.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAGtC,MAAM,OAAO,YAAY;IACvB,MAAM,CAAU,YAAY,GAA0B;QACpD,eAAe;QACf,QAAQ;QACR,YAAY;QACZ,WAAW;QACX,QAAQ;QACR,OAAO;QACP,aAAa;QACb,cAAc;QACd,UAAU;QACV,iBAAiB;QACjB,aAAa;QACb,sBAAsB;QACtB,cAAc;QACd,QAAQ;QACR,KAAK;QACL,MAAM;KACP,CAAC;IAEe,IAAI,CAAc;IAEnC,YAAY,IAA4B;QACtC,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACzG,CAAC;IAED,MAAM,CAAI,KAAQ;QAChB,OAAO,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAChD,OAAO,GAAG,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAChF,CAAC;IACJ,CAAC;CACF"}
1
+ {"version":3,"file":"redactor-mask.strategy.js","sourceRoot":"","sources":["../src/redactor-mask.strategy.ts"],"names":[],"mappings":"AAAA,oBAAoB;AACpB,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,OAAO,YAAY;IACvB,MAAM,CAAU,YAAY,GAA0B;QACpD,eAAe;QACf,qBAAqB;QACrB,QAAQ;QACR,YAAY;QACZ,WAAW;QACX,cAAc;QACd,QAAQ;QACR,OAAO;QACP,aAAa;QACb,cAAc;QACd,SAAS;QACT,UAAU;QACV,iBAAiB;QACjB,aAAa;QACb,sBAAsB;QACtB,cAAc;QACd,YAAY;QACZ,QAAQ;QACR,KAAK;QACL,MAAM;KACP,CAAC;IAEe,IAAI,CAAc;IAEnC,YAAY,IAA4B;QACtC,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,CACjB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAClG,CAAC;IACJ,CAAC;IAED,MAAM,CAAI,KAAQ;QAChB,OAAO,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAChD,OAAO,GAAG,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAChG,CAAC;IACJ,CAAC;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"redactor-url-query.strategy.d.ts","sourceRoot":"","sources":["../src/redactor-url-query.strategy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAG5D,qBAAa,gBAAiB,YAAW,gBAAgB;IACvD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IAEnC,YAAY,IAAI,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,EAEvC;IAED,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAKrB;IAED,OAAO,CAAC,IAAI;CAcb"}
1
+ {"version":3,"file":"redactor-url-query.strategy.d.ts","sourceRoot":"","sources":["../src/redactor-url-query.strategy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAI5D,qBAAa,gBAAiB,YAAW,gBAAgB;IACvD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IAEnC,YAAY,IAAI,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,EAIvC;IAED,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAKrB;IAED,OAAO,CAAC,IAAI;CAcb"}
@@ -1,9 +1,10 @@
1
1
  import * as tools from "@bgord/tools";
2
+ import { RedactorKey } from "./redactor-key.vo";
2
3
  import { RedactorMask } from "./redactor-mask.strategy";
3
4
  export class RedactorUrlQuery {
4
5
  keys;
5
6
  constructor(keys) {
6
- this.keys = new Set((keys?.length ? keys : RedactorMask.DEFAULT_KEYS).map((key) => key.toLowerCase()));
7
+ this.keys = new Set((keys?.length ? keys : RedactorMask.DEFAULT_KEYS).map((key) => RedactorKey.fromString(key).get()));
7
8
  }
8
9
  redact(input) {
9
10
  if (!tools.isPlainObject(input))
@@ -18,7 +19,7 @@ export class RedactorUrlQuery {
18
19
  return url;
19
20
  const masked = new URLSearchParams(Array.from(new URLSearchParams(query), ([key, value]) => [
20
21
  key,
21
- this.keys.has(key.toLowerCase()) ? "***" : value,
22
+ this.keys.has(RedactorKey.fromString(key).get()) ? "***" : value,
22
23
  ]));
23
24
  return `${base}?${masked.toString()}`;
24
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"redactor-url-query.strategy.js","sourceRoot":"","sources":["../src/redactor-url-query.strategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,MAAM,OAAO,gBAAgB;IACV,IAAI,CAAc;IAEnC,YAAY,IAA4B;QACtC,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACzG,CAAC;IAED,MAAM,CAAI,KAAQ;QAChB,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAC9C,IAAI,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAEnD,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;IACpD,CAAC;IAEO,IAAI,CAAC,GAAW;QACtB,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAErC,IAAI,CAAC,KAAK;YAAE,OAAO,GAAG,CAAC;QAEvB,MAAM,MAAM,GAAG,IAAI,eAAe,CAChC,KAAK,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;YACvD,GAAG;YACH,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK;SACjD,CAAC,CACH,CAAC;QAEF,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;IACxC,CAAC;CACF"}
1
+ {"version":3,"file":"redactor-url-query.strategy.js","sourceRoot":"","sources":["../src/redactor-url-query.strategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD,MAAM,OAAO,gBAAgB;IACV,IAAI,CAAc;IAEnC,YAAY,IAA4B;QACtC,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,CACjB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAClG,CAAC;IACJ,CAAC;IAED,MAAM,CAAI,KAAQ;QAChB,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAC9C,IAAI,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAEnD,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;IACpD,CAAC;IAEO,IAAI,CAAC,GAAW;QACtB,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAErC,IAAI,CAAC,KAAK;YAAE,OAAO,GAAG,CAAC;QAEvB,MAAM,MAAM,GAAG,IAAI,eAAe,CAChC,KAAK,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;YACvD,GAAG;YACH,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK;SACjE,CAAC,CACH,CAAC;QAEF,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;IACxC,CAAC;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"shield-body-limit.strategy.d.ts","sourceRoot":"","sources":["../src/shield-body-limit.strategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,MAAM,MAAM,qBAAqB,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC,IAAI,CAAA;CAAE,CAAC;AAE5D,eAAO,MAAM,oBAAoB;IAAK,MAAM;CAAgC,CAAC;AAE7E,qBAAa,uBAAuB;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAnC,YAA6B,MAAM,EAAE,qBAAqB,EAAI;IAE9D,QAAQ,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAa3C;CACF"}
1
+ {"version":3,"file":"shield-body-limit.strategy.d.ts","sourceRoot":"","sources":["../src/shield-body-limit.strategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,MAAM,MAAM,qBAAqB,GAAG;IAAE,OAAO,EAAE,KAAK,CAAC,IAAI,CAAA;CAAE,CAAC;AAE5D,eAAO,MAAM,oBAAoB;IAAK,MAAM;CAAgC,CAAC;AAE7E,qBAAa,uBAAuB;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAnC,YAA6B,MAAM,EAAE,qBAAqB,EAAI;IAE9D,QAAQ,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAc3C;CACF"}
@@ -8,6 +8,7 @@ export class ShieldBodyLimitStrategy {
8
8
  }
9
9
  evaluate(context) {
10
10
  const header = context.request.header("content-length");
11
+ // Header-only check: bodies without Content-Length (chunked/streamed) are bounded by Bun.serve's maxRequestBodySize instead.
11
12
  if (header === undefined)
12
13
  return true;
13
14
  const contentLength = v.safeParse(tools.SizeBytes, Number(header));
@@ -1 +1 @@
1
- {"version":3,"file":"shield-body-limit.strategy.js","sourceRoot":"","sources":["../src/shield-body-limit.strategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,CAAC,MAAM,SAAS,CAAC;AAK7B,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,MAAM,EAAE,4BAA4B,EAAE,CAAC;AAE7E,MAAM,OAAO,uBAAuB;IACL,MAAM;IAAnC,YAA6B,MAA6B;sBAA7B,MAAM;IAA0B,CAAC;IAE9D,QAAQ,CAAC,OAAyB;QAChC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAExD,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QAEtC,MAAM,aAAa,GAAG,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QAEnE,IAAI,CAAC,aAAa,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAEzC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAExD,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;YAAE,OAAO,KAAK,CAAC;QAC1D,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
1
+ {"version":3,"file":"shield-body-limit.strategy.js","sourceRoot":"","sources":["../src/shield-body-limit.strategy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,CAAC,MAAM,SAAS,CAAC;AAK7B,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,MAAM,EAAE,4BAA4B,EAAE,CAAC;AAE7E,MAAM,OAAO,uBAAuB;IACL,MAAM;IAAnC,YAA6B,MAA6B;sBAA7B,MAAM;IAA0B,CAAC;IAE9D,QAAQ,CAAC,OAAyB;QAChC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;QAExD,6HAA6H;QAC7H,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QAEtC,MAAM,aAAa,GAAG,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QAEnE,IAAI,CAAC,aAAa,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAEzC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAExD,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC;YAAE,OAAO,KAAK,CAAC;QAC1D,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
@@ -1,4 +1,4 @@
1
- import type { HasRequestPath } from "./request-context.port";
1
+ import type { HasRequestUrl } from "./request-context.port";
2
2
  export type TrailingSlashResult = {
3
3
  redirect: false;
4
4
  } | {
@@ -8,6 +8,6 @@ export type TrailingSlashResult = {
8
8
  };
9
9
  export declare class TrailingSlashMiddleware {
10
10
  static readonly STATUS = 308;
11
- evaluate(context: HasRequestPath): TrailingSlashResult;
11
+ evaluate(context: HasRequestUrl): TrailingSlashResult;
12
12
  }
13
13
  //# sourceMappingURL=trailing-slash.middleware.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"trailing-slash.middleware.d.ts","sourceRoot":"","sources":["../src/trailing-slash.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,MAAM,MAAM,mBAAmB,GAAG;IAAE,QAAQ,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,CAAC;AAK1G,qBAAa,uBAAuB;IAElC,MAAM,CAAC,QAAQ,CAAC,MAAM,OAAO;IAE7B,QAAQ,CAAC,OAAO,EAAE,cAAc,GAAG,mBAAmB,CAUrD;CACF"}
1
+ {"version":3,"file":"trailing-slash.middleware.d.ts","sourceRoot":"","sources":["../src/trailing-slash.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE5D,MAAM,MAAM,mBAAmB,GAAG;IAAE,QAAQ,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,CAAC;AAI1G,qBAAa,uBAAuB;IAElC,MAAM,CAAC,QAAQ,CAAC,MAAM,OAAO;IAE7B,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,mBAAmB,CASpD;CACF"}
@@ -1,14 +1,14 @@
1
- const TRAILING_SLASHES = /\/+$/;
2
- const LEADING_SLASHES = /^\/+/;
1
+ const EDGE_SLASHES = /^\/+|\/+$/g;
3
2
  export class TrailingSlashMiddleware {
4
3
  // RFC 7231 6.4.7 - 308 preserves the request method and body, unlike 301.
5
4
  static STATUS = 308;
6
5
  evaluate(context) {
7
- if (!context.request.path.endsWith("/"))
6
+ const path = new URL(context.request.url()).pathname;
7
+ if (!path.endsWith("/"))
8
8
  return { redirect: false };
9
- if (context.request.path === "/")
9
+ if (path === "/")
10
10
  return { redirect: false };
11
- const pathname = (context.request.path.replace(TRAILING_SLASHES, "") || "/").replace(LEADING_SLASHES, "/");
11
+ const pathname = `/${path.replace(EDGE_SLASHES, "")}`;
12
12
  return { redirect: true, pathname, status: TrailingSlashMiddleware.STATUS };
13
13
  }
14
14
  }
@@ -1 +1 @@
1
- {"version":3,"file":"trailing-slash.middleware.js","sourceRoot":"","sources":["../src/trailing-slash.middleware.ts"],"names":[],"mappings":"AAIA,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAChC,MAAM,eAAe,GAAG,MAAM,CAAC;AAE/B,MAAM,OAAO,uBAAuB;IAClC,0EAA0E;IAC1E,MAAM,CAAU,MAAM,GAAG,GAAG,CAAC;IAE7B,QAAQ,CAAC,OAAuB;QAC9B,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QACpE,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,KAAK,GAAG;YAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QAE7D,MAAM,QAAQ,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,OAAO,CAClF,eAAe,EACf,GAAG,CACJ,CAAC;QAEF,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,uBAAuB,CAAC,MAAM,EAAE,CAAC;IAC9E,CAAC;CACF"}
1
+ {"version":3,"file":"trailing-slash.middleware.js","sourceRoot":"","sources":["../src/trailing-slash.middleware.ts"],"names":[],"mappings":"AAIA,MAAM,YAAY,GAAG,YAAY,CAAC;AAElC,MAAM,OAAO,uBAAuB;IAClC,0EAA0E;IAC1E,MAAM,CAAU,MAAM,GAAG,GAAG,CAAC;IAE7B,QAAQ,CAAC,OAAsB;QAC7B,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC;QAErD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QACpD,IAAI,IAAI,KAAK,GAAG;YAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QAE7C,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,EAAE,CAAC;QAEtD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,uBAAuB,CAAC,MAAM,EAAE,CAAC;IAC9E,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bgord/bun",
3
- "version": "1.23.9",
3
+ "version": "1.23.11",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Bartosz Gordon",
@@ -26,9 +26,9 @@
26
26
  "@stryker-mutator/typescript-checker": "10.0.0",
27
27
  "@types/bun": "1.4.2",
28
28
  "@types/nodemailer": "8.0.2",
29
- "better-auth": "1.7.5",
29
+ "better-auth": "1.7.6",
30
30
  "cspell": "10.3.3",
31
- "fallow": "3.28.0",
31
+ "fallow": "3.30.0",
32
32
  "lefthook": "2.1.14",
33
33
  "lockfile-lint": "5.0.1",
34
34
  "nodemailer": "10.0.10",
@@ -38,12 +38,12 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@bgord/tools": "1.6.14",
41
- "emittery": "2.0.0",
41
+ "emittery": "2.1.0",
42
42
  "hono": "4.13.8",
43
43
  "node-cache": "5.1.2"
44
44
  },
45
45
  "peerDependencies": {
46
- "better-auth": "1.7.5",
46
+ "better-auth": "1.7.6",
47
47
  "nodemailer": "10.0.10"
48
48
  },
49
49
  "peerDependenciesMeta": {
package/readme.md CHANGED
@@ -121,6 +121,7 @@ src/
121
121
  ├── crypto-key-provider-noop.adapter.ts
122
122
  ├── crypto-key-provider-with-memo.adapter.ts
123
123
  ├── crypto-key-provider.port.ts
124
+ ├── csv-formula-sanitizer.service.ts
124
125
  ├── csv-parser-noop.adapter.ts
125
126
  ├── csv-parser.port.ts
126
127
  ├── csv-stringifier-noop.adapter.ts
@@ -145,6 +146,7 @@ src/
145
146
  ├── environment-loader-noop.adapter.ts
146
147
  ├── environment-loader-process-safe.adapter.ts
147
148
  ├── environment-loader-process.adapter.ts
149
+ ├── environment-loader-with-memo.adapter.ts
148
150
  ├── environment-loader.port.ts
149
151
  ├── error-classifier-http-exception-hono.strategy.ts
150
152
  ├── error-classifier-invariant.strategy.ts
@@ -238,6 +240,9 @@ src/
238
240
  ├── handler-with-logger.strategy.ts
239
241
  ├── handler.strategy.ts
240
242
  ├── hash-bucket.vo.ts
243
+ ├── hash-bytes-noop.strategy.ts
244
+ ├── hash-bytes-sha256.strategy.ts
245
+ ├── hash-bytes.strategy.ts
241
246
  ├── hash-content-noop.strategy.ts
242
247
  ├── hash-content-sha256.strategy.ts
243
248
  ├── hash-content.strategy.ts
@@ -495,6 +500,7 @@ src/
495
500
  ├── redactor-composite.strategy.ts
496
501
  ├── redactor-error-cause-depth-limit.strategy.ts
497
502
  ├── redactor-error-stack-hide.strategy.ts
503
+ ├── redactor-key.vo.ts
498
504
  ├── redactor-mask.strategy.ts
499
505
  ├── redactor-metadata-compact-array.strategy.ts
500
506
  ├── redactor-metadata-compact-object.strategy.ts
@@ -6,7 +6,8 @@ export class CronSchedulerAdapter implements CronSchedulerPort {
6
6
  private readonly tasks: Array<Bun.CronJob> = [];
7
7
 
8
8
  schedule(task: CronTask): void {
9
- this.tasks.push(Bun.cron(task.cron, task.handler));
9
+ // Bun.cron turns a rejected run into an unhandledRejection, which GracefulShutdown treats as fatal.
10
+ this.tasks.push(Bun.cron(task.cron, () => task.handler().catch(() => {})));
10
11
  }
11
12
 
12
13
  async verify(): Promise<boolean> {
@@ -11,11 +11,13 @@ export class CronTaskHandlerBareStrategy implements CronTaskHandlerStrategy {
11
11
  constructor(private readonly deps: Dependencies) {}
12
12
 
13
13
  handle(task: CronTask): CronTask {
14
- const correlationId = v.parse(CorrelationId, this.deps.IdProvider.generate());
15
-
16
14
  return {
17
15
  ...task,
18
- handler: async () => CorrelationStorage.run(correlationId, task.handler),
16
+ handler: async () => {
17
+ const correlationId = v.parse(CorrelationId, this.deps.IdProvider.generate());
18
+
19
+ return CorrelationStorage.run(correlationId, task.handler);
20
+ },
19
21
  };
20
22
  }
21
23
  }
@@ -0,0 +1,9 @@
1
+ export class CsvFormulaSanitizer {
2
+ static readonly TRIGGERS = ["=", "+", "-", "@", "\t", "\r"];
3
+
4
+ static sanitize(value: string): string {
5
+ const triggered = CsvFormulaSanitizer.TRIGGERS.some((trigger) => value.startsWith(trigger));
6
+
7
+ return triggered ? `'${value}` : value;
8
+ }
9
+ }
@@ -1,38 +1,18 @@
1
- import { CacheCodecIdentityStrategy } from "./cache-codec-identity.strategy";
2
- import type { CacheResolverStrategy } from "./cache-resolver.strategy";
3
- import type { CacheValueType } from "./cache-value.vo";
4
1
  import type {
5
2
  EnvironmentLoaderConfig,
6
3
  EnvironmentLoaderPort,
7
4
  EnvironmentResultType,
8
5
  } from "./environment-loader.port";
9
- import type { HashContentStrategy } from "./hash-content.strategy";
10
6
  import { StandardSchemaValidator } from "./standard-schema-validator.service";
11
- import { SubjectApplicationResolver } from "./subject-application-resolver.vo";
12
- import { SubjectSegmentFixedStrategy } from "./subject-segment-fixed.strategy";
13
-
14
- type Dependencies = { CacheResolver: CacheResolverStrategy; HashContent: HashContentStrategy };
15
-
16
- export class EnvironmentLoaderProcessSafeAdapter<T extends object & CacheValueType>
17
- implements EnvironmentLoaderPort<T>
18
- {
19
- private readonly codec = new CacheCodecIdentityStrategy<T>();
20
7
 
8
+ export class EnvironmentLoaderProcessSafeAdapter<T extends object> implements EnvironmentLoaderPort<T> {
21
9
  constructor(
22
10
  private env: NodeJS.ProcessEnv,
23
11
  private readonly config: EnvironmentLoaderConfig<T>,
24
- private readonly deps: Dependencies,
25
12
  ) {}
26
13
 
27
14
  async load(): Promise<Readonly<EnvironmentResultType<T>>> {
28
- const resolver = new SubjectApplicationResolver([new SubjectSegmentFixedStrategy("env")], this.deps);
29
- const subject = await resolver.resolve();
30
-
31
- const parsed = await this.deps.CacheResolver.resolve<T>(
32
- subject.hex,
33
- async () => StandardSchemaValidator.validate(this.config.EnvironmentSchema, this.env),
34
- this.codec,
35
- );
15
+ const parsed = StandardSchemaValidator.validate(this.config.EnvironmentSchema, this.env);
36
16
 
37
17
  for (const key of Object.keys(parsed)) {
38
18
  delete this.env[key];
@@ -0,0 +1,21 @@
1
+ import type { EnvironmentLoaderPort, EnvironmentResultType } from "./environment-loader.port";
2
+
3
+ type Config<T extends object> = { inner: EnvironmentLoaderPort<T> };
4
+
5
+ export class EnvironmentLoaderWithMemoAdapter<T extends object> implements EnvironmentLoaderPort<T> {
6
+ private memoized: Promise<Readonly<EnvironmentResultType<T>>> | null = null;
7
+
8
+ constructor(private readonly config: Config<T>) {}
9
+
10
+ async load(): Promise<Readonly<EnvironmentResultType<T>>> {
11
+ if (this.memoized === null) {
12
+ this.memoized = this.config.inner.load().catch((error) => {
13
+ this.memoized = null;
14
+
15
+ throw error;
16
+ });
17
+ }
18
+
19
+ return this.memoized;
20
+ }
21
+ }
@@ -32,7 +32,8 @@ export class EventStoreDispatchingAdapter<Event extends GenericEvent> implements
32
32
  ): Promise<ReadonlyArray<SavedEvent>> {
33
33
  const saved = await this.deps.inner.save(events);
34
34
 
35
- for (const event of saved) this.deps.EventBus.emit(event);
35
+ // Fire-and-forget: events are already persisted, handler errors are logged by the handler strategy.
36
+ for (const event of saved) this.deps.EventBus.emit(event).catch(() => {});
36
37
 
37
38
  return saved;
38
39
  }
@@ -0,0 +1,8 @@
1
+ import { Hash } from "./hash.vo";
2
+ import type { HashBytesStrategy } from "./hash-bytes.strategy";
3
+
4
+ export class HashBytesNoopStrategy implements HashBytesStrategy {
5
+ async hash(_content: ArrayBuffer): Promise<Hash> {
6
+ return Hash.fromString("0".repeat(64));
7
+ }
8
+ }
@@ -0,0 +1,10 @@
1
+ import { Hash } from "./hash.vo";
2
+ import type { HashBytesStrategy } from "./hash-bytes.strategy";
3
+
4
+ export class HashBytesSha256Strategy implements HashBytesStrategy {
5
+ async hash(content: ArrayBuffer): Promise<Hash> {
6
+ const digest = await crypto.subtle.digest("SHA-256", content);
7
+
8
+ return Hash.fromBuffer(new Uint8Array(digest));
9
+ }
10
+ }
@@ -0,0 +1,5 @@
1
+ import type { Hash } from "./hash.vo";
2
+
3
+ export interface HashBytesStrategy {
4
+ hash(content: ArrayBuffer): Promise<Hash>;
5
+ }
@@ -1,12 +1,12 @@
1
1
  import * as tools from "@bgord/tools";
2
2
  import type { FileInspectionPort } from "./file-inspection.port";
3
- import type { FileReaderTextPort } from "./file-reader-text.port";
4
- import type { HashContentStrategy } from "./hash-content.strategy";
3
+ import type { FileReaderRawPort } from "./file-reader-raw.port";
4
+ import type { HashBytesStrategy } from "./hash-bytes.strategy";
5
5
  import type { HashFilePort, HashFileResult } from "./hash-file.port";
6
6
 
7
7
  type Dependencies = {
8
- HashContent: HashContentStrategy;
9
- FileReaderText: FileReaderTextPort;
8
+ HashBytes: HashBytesStrategy;
9
+ FileReaderRaw: FileReaderRawPort;
10
10
  FileInspection: FileInspectionPort;
11
11
  MimeRegistry: tools.MimeRegistry;
12
12
  };
@@ -22,8 +22,8 @@ export class HashFileSha256Adapter implements HashFilePort {
22
22
 
23
23
  const size = await this.deps.FileInspection.size(path);
24
24
  const lastModified = await this.deps.FileInspection.lastModified(path);
25
- const text = await this.deps.FileReaderText.read(path);
25
+ const bytes = await this.deps.FileReaderRaw.read(path);
26
26
 
27
- return { etag: await this.deps.HashContent.hash(text), size, lastModified, mime };
27
+ return { etag: await this.deps.HashBytes.hash(bytes), size, lastModified, mime };
28
28
  }
29
29
  }
@@ -7,6 +7,7 @@ type Dependencies = { CacheRepository: CacheRepositoryPort };
7
7
  export class IdempotencyStoreCacheAdapter implements IdempotencyStorePort {
8
8
  constructor(private readonly deps: Dependencies) {}
9
9
 
10
+ // Non-atomic get-then-set: concurrent claims of the same subject can both succeed
10
11
  async claim(subject: Hash): Promise<boolean> {
11
12
  const processed = await this.deps.CacheRepository.get(subject);
12
13
 
package/src/index.ts CHANGED
@@ -96,6 +96,7 @@ export * from "./crypto-key-provider-memory.adapter";
96
96
  export * from "./crypto-key-provider-noop.adapter";
97
97
  export * from "./crypto-key-provider-with-memo.adapter";
98
98
  export * from "./csv.types";
99
+ export * from "./csv-formula-sanitizer.service";
99
100
  export * from "./csv-parser.port";
100
101
  export * from "./csv-parser-noop.adapter";
101
102
  export * from "./csv-stringifier.port";
@@ -119,6 +120,7 @@ export * from "./environment-loader-encrypted.adapter";
119
120
  export * from "./environment-loader-noop.adapter";
120
121
  export * from "./environment-loader-process.adapter";
121
122
  export * from "./environment-loader-process-safe.adapter";
123
+ export * from "./environment-loader-with-memo.adapter";
122
124
  export * from "./error.handler";
123
125
  export * from "./error-classifier.strategy";
124
126
  export * from "./error-classifier-http-exception-hono.strategy";
@@ -212,6 +214,9 @@ export * from "./handler-with-logger.strategy";
212
214
  export * from "./handler-with-logger-safe.strategy";
213
215
  export * from "./hash.vo";
214
216
  export * from "./hash-bucket.vo";
217
+ export * from "./hash-bytes.strategy";
218
+ export * from "./hash-bytes-noop.strategy";
219
+ export * from "./hash-bytes-sha256.strategy";
215
220
  export * from "./hash-content.strategy";
216
221
  export * from "./hash-content-noop.strategy";
217
222
  export * from "./hash-content-sha256.strategy";
@@ -34,10 +34,18 @@ export class JobQueueAdapter<Job extends GenericJob> implements JobQueuePort<Job
34
34
 
35
35
  async claim(limit: tools.IntegerPositiveType): Promise<ReadonlyArray<Job>> {
36
36
  const jobs = await this.config.claimer.claim(this.config.registry.names, limit);
37
+ const valid: Array<Job> = [];
37
38
 
38
- return jobs
39
- .map((job) => ({ ...job, payload: this.config.serializer.deserialize(job.payload) }))
40
- .map((job) => this.config.registry.validate(job));
39
+ for (const job of jobs) {
40
+ try {
41
+ valid.push(
42
+ this.config.registry.validate({ ...job, payload: this.config.serializer.deserialize(job.payload) }),
43
+ );
44
+ } catch {
45
+ await this.config.failer.fail(job.id);
46
+ }
47
+ }
48
+ return valid;
41
49
  }
42
50
 
43
51
  async complete(id: GenericJob["id"]): Promise<void> {
@@ -2,7 +2,16 @@
2
2
  import type { MarkdownGeneratorPort } from "./markdown-generator.port";
3
3
 
4
4
  export class MarkdownGeneratorAdapter implements MarkdownGeneratorPort {
5
+ private static readonly ALLOWED_SCHEMES = ["http", "https", "mailto"];
6
+
5
7
  generate(template: string): string {
6
- return Bun.markdown.html(template, { tagFilter: true, noHtmlBlocks: true, noHtmlSpans: true });
8
+ const html = Bun.markdown.html(template, { tagFilter: true, noHtmlBlocks: true, noHtmlSpans: true });
9
+
10
+ return html.replace(/ href="([^"]*)"/g, (attribute, url: string) => {
11
+ const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(url)?.[1]?.toLowerCase();
12
+
13
+ if (scheme === undefined || MarkdownGeneratorAdapter.ALLOWED_SCHEMES.includes(scheme)) return attribute;
14
+ return ' href="#"';
15
+ });
7
16
  }
8
17
  }
@@ -0,0 +1,15 @@
1
+ export class RedactorKey {
2
+ private constructor(private readonly value: string) {}
3
+
4
+ static fromString(candidate: string): RedactorKey {
5
+ return new RedactorKey(candidate.toLowerCase().replace(/[_-]/g, ""));
6
+ }
7
+
8
+ get(): string {
9
+ return this.value;
10
+ }
11
+
12
+ matches(another: RedactorKey): boolean {
13
+ return this.value === another.value;
14
+ }
15
+ }
@@ -1,22 +1,27 @@
1
1
  /* cspell:disable */
2
2
  import * as tools from "@bgord/tools";
3
3
  import type { RedactorStrategy } from "./redactor.strategy";
4
+ import { RedactorKey } from "./redactor-key.vo";
4
5
 
5
6
  export class RedactorMask implements RedactorStrategy {
6
7
  static readonly DEFAULT_KEYS: ReadonlyArray<string> = [
7
8
  "authorization",
9
+ "proxy-authorization",
8
10
  "cookie",
9
11
  "set-cookie",
10
12
  "x-api-key",
13
+ "x-auth-token",
11
14
  "apikey",
12
15
  "token",
13
16
  "accesstoken",
14
17
  "refreshtoken",
18
+ "idtoken",
15
19
  "password",
16
20
  "currentpassword",
17
21
  "newpassword",
18
22
  "passwordconfirmation",
19
23
  "clientsecret",
24
+ "privatekey",
20
25
  "secret",
21
26
  "otp",
22
27
  "code",
@@ -25,12 +30,14 @@ export class RedactorMask implements RedactorStrategy {
25
30
  private readonly keys: Set<string>;
26
31
 
27
32
  constructor(keys?: ReadonlyArray<string>) {
28
- this.keys = new Set((keys?.length ? keys : RedactorMask.DEFAULT_KEYS).map((key) => key.toLowerCase()));
33
+ this.keys = new Set(
34
+ (keys?.length ? keys : RedactorMask.DEFAULT_KEYS).map((key) => RedactorKey.fromString(key).get()),
35
+ );
29
36
  }
30
37
 
31
38
  redact<T>(input: T): T {
32
39
  return tools.deepCloneWith(input, (_value, key) =>
33
- typeof key === "string" && this.keys.has(key.toLowerCase()) ? "***" : undefined,
40
+ typeof key === "string" && this.keys.has(RedactorKey.fromString(key).get()) ? "***" : undefined,
34
41
  );
35
42
  }
36
43
  }