@bgord/bun 1.6.5 → 1.6.7

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 (62) hide show
  1. package/dist/bots.vo.d.ts +1 -1
  2. package/dist/bots.vo.d.ts.map +1 -1
  3. package/dist/bots.vo.js +132 -3
  4. package/dist/bots.vo.js.map +1 -1
  5. package/dist/client.vo.d.ts +5 -0
  6. package/dist/client.vo.d.ts.map +1 -1
  7. package/dist/client.vo.js +16 -1
  8. package/dist/client.vo.js.map +1 -1
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/logger-winston-production.adapter.d.ts +0 -1
  14. package/dist/logger-winston-production.adapter.d.ts.map +1 -1
  15. package/dist/logger-winston-production.adapter.js +1 -7
  16. package/dist/logger-winston-production.adapter.js.map +1 -1
  17. package/dist/modules/system/events/MINUTE_HAS_PASSED_EVENT.d.ts +16 -0
  18. package/dist/modules/system/events/MINUTE_HAS_PASSED_EVENT.d.ts.map +1 -0
  19. package/dist/modules/system/events/MINUTE_HAS_PASSED_EVENT.js +10 -0
  20. package/dist/modules/system/events/MINUTE_HAS_PASSED_EVENT.js.map +1 -0
  21. package/dist/modules/system/events/index.d.ts +1 -0
  22. package/dist/modules/system/events/index.d.ts.map +1 -1
  23. package/dist/modules/system/events/index.js +1 -0
  24. package/dist/modules/system/events/index.js.map +1 -1
  25. package/dist/modules/system/index.d.ts +1 -0
  26. package/dist/modules/system/index.d.ts.map +1 -1
  27. package/dist/modules/system/index.js +1 -0
  28. package/dist/modules/system/index.js.map +1 -1
  29. package/dist/modules/system/services/index.d.ts +3 -0
  30. package/dist/modules/system/services/index.d.ts.map +1 -0
  31. package/dist/modules/system/services/index.js +3 -0
  32. package/dist/modules/system/services/index.js.map +1 -0
  33. package/dist/modules/system/services/passage-of-time-hourly.service.d.ts +19 -0
  34. package/dist/modules/system/services/passage-of-time-hourly.service.d.ts.map +1 -0
  35. package/dist/modules/system/services/passage-of-time-hourly.service.js +21 -0
  36. package/dist/modules/system/services/passage-of-time-hourly.service.js.map +1 -0
  37. package/dist/modules/system/services/passage-of-time-minute.service.d.ts +19 -0
  38. package/dist/modules/system/services/passage-of-time-minute.service.d.ts.map +1 -0
  39. package/dist/modules/system/services/passage-of-time-minute.service.js +21 -0
  40. package/dist/modules/system/services/passage-of-time-minute.service.js.map +1 -0
  41. package/dist/setup.service.d.ts.map +1 -1
  42. package/dist/setup.service.js +2 -3
  43. package/dist/setup.service.js.map +1 -1
  44. package/dist/shield-user-agent-blocker.adapter.d.ts +7 -0
  45. package/dist/shield-user-agent-blocker.adapter.d.ts.map +1 -0
  46. package/dist/shield-user-agent-blocker.adapter.js +15 -0
  47. package/dist/shield-user-agent-blocker.adapter.js.map +1 -0
  48. package/dist/tsconfig.tsbuildinfo +1 -1
  49. package/package.json +1 -5
  50. package/readme.md +5 -0
  51. package/src/bots.vo.ts +132 -3
  52. package/src/client.vo.ts +21 -1
  53. package/src/index.ts +1 -0
  54. package/src/logger-winston-production.adapter.ts +1 -8
  55. package/src/modules/system/events/MINUTE_HAS_PASSED_EVENT.ts +13 -0
  56. package/src/modules/system/events/index.ts +1 -0
  57. package/src/modules/system/index.ts +1 -0
  58. package/src/modules/system/services/index.ts +2 -0
  59. package/src/modules/system/services/passage-of-time-hourly.service.ts +37 -0
  60. package/src/modules/system/services/passage-of-time-minute.service.ts +37 -0
  61. package/src/setup.service.ts +2 -3
  62. package/src/shield-user-agent-blocker.adapter.ts +18 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bgord/bun",
3
- "version": "1.6.5",
3
+ "version": "1.6.7",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "author": "Bartosz Gordon",
@@ -38,18 +38,14 @@
38
38
  "zod": "4.2.1"
39
39
  },
40
40
  "dependencies": {
41
- "@axiomhq/winston": "1.3.1",
42
41
  "@bgord/tools": "1.2.3",
43
- "@hono/ua-blocker": "0.1.22",
44
42
  "better-auth": "1.4.7",
45
43
  "croner": "9.1.0",
46
44
  "csv": "6.4.1",
47
45
  "dotenv": "17.2.3",
48
46
  "hcaptcha": "0.2.0",
49
47
  "hono": "4.11.1",
50
- "isomorphic-dompurify": "2.34.0",
51
48
  "lodash": "4.17.21",
52
- "marked": "17.0.1",
53
49
  "node-cache": "5.1.2",
54
50
  "nodemailer": "7.0.11",
55
51
  "winston": "3.19.0",
package/readme.md CHANGED
@@ -215,6 +215,10 @@ src/
215
215
  │   └── system
216
216
  │   ├── events
217
217
  │   │   ├── HOUR_HAS_PASSED_EVENT.ts
218
+ │   │   ├── MINUTE_HAS_PASSED_EVENT.ts
219
+ │   └── services
220
+ │   ├── passage-of-time-hourly.service.ts
221
+ │   └── passage-of-time-minute.service.ts
218
222
  ├── node-env.vo.ts
219
223
  ├── pdf-generator-noop.adapter.ts
220
224
  ├── pdf-generator.port.ts
@@ -269,6 +273,7 @@ src/
269
273
  ├── shield-noop.adapter.ts
270
274
  ├── shield-rate-limit.adapter.ts
271
275
  ├── shield-timeout.adapter.ts
276
+ ├── shield-user-agent-blocker.adapter.ts
272
277
  ├── shield.port.ts
273
278
  ├── simulated-error.middleware.ts
274
279
  ├── slower.middleware.ts
package/src/bots.vo.ts CHANGED
@@ -1,3 +1,132 @@
1
- export const BOTS_REGEX =
2
- // cspell:disable-next-line
3
- /(AI2BOT|AI2BOT-DOLMA|AIHITBOT|AMAZONBOT|ANDIBOT|ANTHROPIC-AI|APPLEBOT|APPLEBOT-EXTENDED|BEDROCKBOT|BRIGHTBOT 1.0|BYTESPIDER|CCBOT|CHATGPT-USER|CLAUDE-SEARCHBOT|CLAUDE-USER|CLAUDE-WEB|CLAUDEBOT|COHERE-AI|COHERE-TRAINING-DATA-CRAWLER|COTOYOGI|CRAWLSPACE|DIFFBOT|DUCKASSISTBOT|ECHOBOXBOT|FACEBOOKBOT|FACEBOOKEXTERNALHIT|FACTSET_SPYDERBOT|FIRECRAWLAGENT|FRIENDLYCRAWLER|GOOGLE-CLOUDVERTEXBOT|GOOGLE-EXTENDED|GOOGLEOTHER|GOOGLEOTHER-IMAGE|GOOGLEOTHER-VIDEO|GPTBOT|IASKSPIDER\/2.0|ICC-CRAWLER|IMAGESIFTBOT|IMG2DATASET|ISSCYBERRISKCRAWLER|KANGAROO BOT|META-EXTERNALAGENT|META-EXTERNALAGENT|META-EXTERNALFETCHER|META-EXTERNALFETCHER|MISTRALAI-USER\/1.0|MYCENTRALAISCRAPERBOT|NOVAACT|OAI-SEARCHBOT|OMGILI|OMGILIBOT|OPERATOR|PANGUBOT|PANSCIENT|PANSCIENT.COM|PERPLEXITY-USER|PERPLEXITYBOT|PETALBOT|PHINDBOT|POSEIDON RESEARCH CRAWLER|QUALIFIEDBOT|QUILLBOT|QUILLBOT.COM|SBINTUITIONSBOT|SCRAPY|SEMRUSHBOT|SEMRUSHBOT-BA|SEMRUSHBOT-CT|SEMRUSHBOT-OCOB|SEMRUSHBOT-SI|SEMRUSHBOT-SWA|SIDETRADE INDEXER BOT|TIKTOKSPIDER|TIMPIBOT|VELENPUBLICWEBCRAWLER|WEBZIO-EXTENDED|WPBOT|YANDEXADDITIONAL|YANDEXADDITIONALBOT|YOUBOT)/;
1
+ // Source: https://github.com/honojs/middleware/blob/main/packages/ua-blocker/src/generated.ts
2
+ export const ALL_BOTS = [
3
+ "AddSearchBot",
4
+ "AI2Bot",
5
+ "AI2Bot-DeepResearchEval",
6
+ "Ai2Bot-Dolma",
7
+ "aiHitBot",
8
+ "amazon-kendra",
9
+ "Amazonbot",
10
+ "AmazonBuyForMe",
11
+ "Andibot",
12
+ "Anomura",
13
+ "anthropic-ai",
14
+ "Applebot",
15
+ "Applebot-Extended",
16
+ "atlassian-bot",
17
+ "Awario",
18
+ "bedrockbot",
19
+ "bigsur.ai",
20
+ "Bravebot",
21
+ "Brightbot 1.0",
22
+ "BuddyBot",
23
+ "Bytespider",
24
+ "CCBot",
25
+ "Channel3Bot",
26
+ "ChatGLM-Spider",
27
+ "ChatGPT Agent",
28
+ "ChatGPT-User",
29
+ "Claude-SearchBot",
30
+ "Claude-User",
31
+ "Claude-Web",
32
+ "ClaudeBot",
33
+ "Cloudflare-AutoRAG",
34
+ "CloudVertexBot",
35
+ "cohere-ai",
36
+ "cohere-training-data-crawler",
37
+ "Cotoyogi",
38
+ "Crawl4AI",
39
+ "Crawlspace",
40
+ "Datenbank Crawler",
41
+ "DeepSeekBot",
42
+ "Devin",
43
+ "Diffbot",
44
+ "DuckAssistBot",
45
+ "Echobot Bot",
46
+ "EchoboxBot",
47
+ "FacebookBot",
48
+ "facebookexternalhit",
49
+ "Factset_spyderbot",
50
+ "FirecrawlAgent",
51
+ "FriendlyCrawler",
52
+ "Gemini-Deep-Research",
53
+ "Google-CloudVertexBot",
54
+ "Google-Extended",
55
+ "Google-Firebase",
56
+ "Google-NotebookLM",
57
+ "GoogleAgent-Mariner",
58
+ "GoogleOther",
59
+ "GoogleOther-Image",
60
+ "GoogleOther-Video",
61
+ "GPTBot",
62
+ "iAskBot",
63
+ "iaskspider",
64
+ "iaskspider/2.0",
65
+ "IbouBot",
66
+ "ICC-Crawler",
67
+ "ImagesiftBot",
68
+ "imageSpider",
69
+ "img2dataset",
70
+ "ISSCyberRiskCrawler",
71
+ "Kangaroo Bot",
72
+ "KlaviyoAIBot",
73
+ "KunatoCrawler",
74
+ "laion-huggingface-processor",
75
+ "LAIONDownloader",
76
+ "LCC",
77
+ "LinerBot",
78
+ "Linguee Bot",
79
+ "LinkupBot",
80
+ "Manus-User",
81
+ "meta-externalagent",
82
+ "Meta-ExternalAgent",
83
+ "meta-externalfetcher",
84
+ "Meta-ExternalFetcher",
85
+ "meta-webindexer",
86
+ "MistralAI-User",
87
+ "MistralAI-User/1.0",
88
+ "MyCentralAIScraperBot",
89
+ "netEstate Imprint Crawler",
90
+ "NotebookLM",
91
+ "NovaAct",
92
+ "OAI-SearchBot",
93
+ "omgili",
94
+ "omgilibot",
95
+ "OpenAI",
96
+ "Operator",
97
+ "PanguBot",
98
+ "Panscient",
99
+ "panscient.com",
100
+ "Perplexity-User",
101
+ "PerplexityBot",
102
+ "PetalBot",
103
+ "PhindBot",
104
+ "Poggio-Citations",
105
+ "Poseidon Research Crawler",
106
+ "QualifiedBot",
107
+ "QuillBot",
108
+ "quillbot.com",
109
+ "SBIntuitionsBot",
110
+ "Scrapy",
111
+ "SemrushBot-OCOB",
112
+ "SemrushBot-SWA",
113
+ "ShapBot",
114
+ "Sidetrade indexer bot",
115
+ "Spider",
116
+ "TerraCotta",
117
+ "Thinkbot",
118
+ "TikTokSpider",
119
+ "Timpibot",
120
+ "TwinAgent",
121
+ "VelenPublicWebCrawler",
122
+ "WARDBot",
123
+ "Webzio-Extended",
124
+ "webzio-extended",
125
+ "wpbot",
126
+ "WRTNBot",
127
+ "YaK",
128
+ "YandexAdditional",
129
+ "YandexAdditionalBot",
130
+ "YouBot",
131
+ "ZanistaBot",
132
+ ];
package/src/client.vo.ts CHANGED
@@ -9,7 +9,7 @@ export class Client {
9
9
  private constructor(private readonly value: ClientType) {}
10
10
 
11
11
  static fromParts(ip: ClientIpType | null | undefined, ua: ClientUaType | null | undefined): Client {
12
- return new Client({ ip: ip ?? "anon", ua: ua ?? "anon" });
12
+ return new Client({ ip: ip ?? "anon", ua: (ua ?? "anon").toLowerCase() });
13
13
  }
14
14
 
15
15
  static fromHonoContext(context: Context): Client {
@@ -23,6 +23,26 @@ export class Client {
23
23
  return Client.fromParts(ip, ua);
24
24
  }
25
25
 
26
+ equals(another: Client): boolean {
27
+ return this.value.ip === another.value.ip && this.value.ua === another.value.ua;
28
+ }
29
+
30
+ matchesUa(ua: ClientUaType): boolean {
31
+ return this.value.ua.includes(ua.toLowerCase());
32
+ }
33
+
34
+ matchesIp(ip: ClientIpType): boolean {
35
+ return this.value.ip.includes(ip.toLowerCase());
36
+ }
37
+
38
+ get ip(): ClientIpType {
39
+ return this.value.ip;
40
+ }
41
+
42
+ get ua(): ClientUaType {
43
+ return this.value.ua;
44
+ }
45
+
26
46
  toJSON(): ClientType {
27
47
  return this.value;
28
48
  }
package/src/index.ts CHANGED
@@ -187,6 +187,7 @@ export * from "./shield-captcha-recaptcha.adapter";
187
187
  export * from "./shield-noop.adapter";
188
188
  export * from "./shield-rate-limit.adapter";
189
189
  export * from "./shield-timeout.adapter";
190
+ export * from "./shield-user-agent-blocker.adapter";
190
191
  export * from "./simulated-error.middleware";
191
192
  export * from "./slower.middleware";
192
193
  export * from "./static-files.service";
@@ -1,4 +1,3 @@
1
- import { WinstonTransport as AxiomTransport } from "@axiomhq/winston";
2
1
  import * as tools from "@bgord/tools";
3
2
  import * as winston from "winston";
4
3
  import type { LogAppType, LoggerPort, LogLevelEnum } from "./logger.port";
@@ -8,7 +7,6 @@ import type { RedactorPort } from "./redactor.port";
8
7
 
9
8
  type LoggerWinstonProductionAdapterConfigType = {
10
9
  app: LogAppType;
11
- AXIOM_API_TOKEN?: string;
12
10
  redactor: RedactorPort;
13
11
  };
14
12
 
@@ -31,12 +29,7 @@ export class LoggerWinstonProductionAdapter {
31
29
  app: this.config.app,
32
30
  environment: NodeEnvironmentEnum.production,
33
31
  level,
34
- transports: [
35
- file,
36
- this.config.AXIOM_API_TOKEN
37
- ? new AxiomTransport({ token: this.config.AXIOM_API_TOKEN, dataset: this.config.app })
38
- : undefined,
39
- ].filter((adapter) => adapter !== undefined),
32
+ transports: [file],
40
33
  redactor: this.config.redactor,
41
34
  filePath,
42
35
  });
@@ -0,0 +1,13 @@
1
+ import * as tools from "@bgord/tools";
2
+ import { z } from "zod/v4";
3
+ import { EventEnvelopeSchema } from "../../../event-envelope";
4
+
5
+ export const MINUTE_HAS_PASSED_EVENT = "MINUTE_HAS_PASSED_EVENT";
6
+
7
+ export const MinuteHasPassedEvent = z.object({
8
+ ...EventEnvelopeSchema,
9
+ name: z.literal(MINUTE_HAS_PASSED_EVENT),
10
+ payload: z.object({ timestamp: tools.TimestampValue }),
11
+ });
12
+
13
+ export type MinuteHasPassedEventType = z.infer<typeof MinuteHasPassedEvent>;
@@ -1 +1,2 @@
1
1
  export * from "./HOUR_HAS_PASSED_EVENT";
2
+ export * from "./MINUTE_HAS_PASSED_EVENT";
@@ -1 +1,2 @@
1
1
  export * as Events from "./events";
2
+ export * as Services from "./services";
@@ -0,0 +1,2 @@
1
+ export * from "./passage-of-time-hourly.service";
2
+ export * from "./passage-of-time-minute.service";
@@ -0,0 +1,37 @@
1
+ import type { ClockPort } from "../../../clock.port";
2
+ import { createEventEnvelope } from "../../../event-envelope";
3
+ import type { EventStoreLike } from "../../../event-store-like.types";
4
+ import type { IdProviderPort } from "../../../id-provider.port";
5
+ import type { UnitOfWork } from "../../../job-handler.port";
6
+ import { Jobs } from "../../../jobs.service";
7
+ import {
8
+ HOUR_HAS_PASSED_EVENT,
9
+ HourHasPassedEvent,
10
+ type HourHasPassedEventType,
11
+ } from "../events/HOUR_HAS_PASSED_EVENT";
12
+
13
+ type Dependencies = {
14
+ EventStore: EventStoreLike<HourHasPassedEventType>;
15
+ Clock: ClockPort;
16
+ IdProvider: IdProviderPort;
17
+ };
18
+
19
+ export class PassageOfTimeHourly implements UnitOfWork {
20
+ constructor(private readonly deps: Dependencies) {}
21
+
22
+ static cron = Jobs.SCHEDULES.EVERY_HOUR;
23
+
24
+ label = "PassageOfTime";
25
+
26
+ async process() {
27
+ const timestamp = this.deps.Clock.nowMs();
28
+
29
+ const event = HourHasPassedEvent.parse({
30
+ ...createEventEnvelope("passage_of_time", this.deps),
31
+ name: HOUR_HAS_PASSED_EVENT,
32
+ payload: { timestamp },
33
+ } satisfies HourHasPassedEventType);
34
+
35
+ await this.deps.EventStore.save([event]);
36
+ }
37
+ }
@@ -0,0 +1,37 @@
1
+ import type { ClockPort } from "../../../clock.port";
2
+ import { createEventEnvelope } from "../../../event-envelope";
3
+ import type { EventStoreLike } from "../../../event-store-like.types";
4
+ import type { IdProviderPort } from "../../../id-provider.port";
5
+ import type { UnitOfWork } from "../../../job-handler.port";
6
+ import { Jobs } from "../../../jobs.service";
7
+ import {
8
+ MINUTE_HAS_PASSED_EVENT,
9
+ MinuteHasPassedEvent,
10
+ type MinuteHasPassedEventType,
11
+ } from "../events/MINUTE_HAS_PASSED_EVENT";
12
+
13
+ type Dependencies = {
14
+ EventStore: EventStoreLike<MinuteHasPassedEventType>;
15
+ Clock: ClockPort;
16
+ IdProvider: IdProviderPort;
17
+ };
18
+
19
+ export class PassageOfTimeMinute implements UnitOfWork {
20
+ constructor(private readonly deps: Dependencies) {}
21
+
22
+ static cron = Jobs.SCHEDULES.EVERY_MINUTE;
23
+
24
+ label = "PassageOfTime";
25
+
26
+ async process() {
27
+ const timestamp = this.deps.Clock.nowMs();
28
+
29
+ const event = MinuteHasPassedEvent.parse({
30
+ ...createEventEnvelope("passage_of_time", this.deps),
31
+ name: MINUTE_HAS_PASSED_EVENT,
32
+ payload: { timestamp },
33
+ } satisfies MinuteHasPassedEventType);
34
+
35
+ await this.deps.EventStore.save([event]);
36
+ }
37
+ }
@@ -1,5 +1,4 @@
1
1
  import * as tools from "@bgord/tools";
2
- import { uaBlocker } from "@hono/ua-blocker";
3
2
  import { bodyLimit } from "hono/body-limit";
4
3
  import { cors } from "hono/cors";
5
4
  import { languageDetector } from "hono/language";
@@ -7,7 +6,6 @@ import { requestId } from "hono/request-id";
7
6
  import { secureHeaders } from "hono/secure-headers";
8
7
  import { timing } from "hono/timing";
9
8
  import { ApiVersion } from "./api-version.middleware";
10
- import { BOTS_REGEX } from "./bots.vo";
11
9
  import type { ClockPort } from "./clock.port";
12
10
  import { Context } from "./context.middleware";
13
11
  import { CorrelationStorage } from "./correlation-storage.service";
@@ -17,6 +15,7 @@ import type { I18nConfigType } from "./i18n.service";
17
15
  import type { IdProviderPort } from "./id-provider.port";
18
16
  import type { JsonFileReaderPort } from "./json-file-reader.port";
19
17
  import type { LoggerPort } from "./logger.port";
18
+ import { ShieldUserAgentBlockerAdapter } from "./shield-user-agent-blocker.adapter";
20
19
  import { TimeZoneOffset } from "./time-zone-offset.middleware";
21
20
  import { WeakETagExtractor } from "./weak-etag-extractor.middleware";
22
21
 
@@ -44,7 +43,7 @@ export class Setup {
44
43
  return [
45
44
  secureHeaders(secureHeadersOptions),
46
45
  bodyLimit({ maxSize: BODY_LIMIT_MAX_SIZE }),
47
- uaBlocker({ blocklist: BOTS_REGEX }),
46
+ new ShieldUserAgentBlockerAdapter().verify,
48
47
  ApiVersion.build({ Clock: deps.Clock, JsonFileReader: deps.JsonFileReader }),
49
48
  cors(corsOptions),
50
49
  languageDetector({
@@ -0,0 +1,18 @@
1
+ import { createMiddleware } from "hono/factory";
2
+ import { HTTPException } from "hono/http-exception";
3
+ import { ALL_BOTS } from "./bots.vo";
4
+ import { Client } from "./client.vo";
5
+ import type { ShieldPort } from "./shield.port";
6
+
7
+ export const UserAgentBlockedError = new HTTPException(403, { message: "app.user.agent.blocked.error" });
8
+
9
+ export class ShieldUserAgentBlockerAdapter implements ShieldPort {
10
+ verify = createMiddleware(async (context, next) => {
11
+ const client = Client.fromHonoContext(context);
12
+
13
+ const detection = ALL_BOTS.some((bot) => client.matchesUa(bot));
14
+
15
+ if (!detection) return next();
16
+ throw UserAgentBlockedError;
17
+ });
18
+ }