scrapme-flow 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +126 -2
  3. package/dist/src/commands/commands.models.d.ts +41 -0
  4. package/dist/src/commands/commands.models.js +2 -0
  5. package/dist/src/commands/commands.models.js.map +1 -0
  6. package/dist/src/commands/save-document.command.d.ts +9 -0
  7. package/dist/src/commands/save-document.command.js +33 -0
  8. package/dist/src/commands/save-document.command.js.map +1 -0
  9. package/dist/src/commands/scrape-page.command.d.ts +20 -0
  10. package/dist/src/commands/scrape-page.command.js +93 -0
  11. package/dist/src/commands/scrape-page.command.js.map +1 -0
  12. package/dist/src/commands/send-mail.command.d.ts +11 -0
  13. package/dist/src/commands/send-mail.command.js +39 -0
  14. package/dist/src/commands/send-mail.command.js.map +1 -0
  15. package/dist/src/config/config-builder.d.ts +24 -0
  16. package/dist/src/config/config-builder.js +129 -0
  17. package/dist/src/config/config-builder.js.map +1 -0
  18. package/dist/src/config/config-loader.d.ts +10 -0
  19. package/dist/src/config/config-loader.js +79 -0
  20. package/dist/src/config/config-loader.js.map +1 -0
  21. package/dist/src/config/config.models.d.ts +144 -0
  22. package/dist/src/config/config.models.js +2 -0
  23. package/dist/src/config/config.models.js.map +1 -0
  24. package/dist/src/config/config.schema.d.ts +75 -0
  25. package/dist/src/config/config.schema.js +87 -0
  26. package/dist/src/config/config.schema.js.map +1 -0
  27. package/dist/src/config/dsn-check.d.ts +5 -0
  28. package/dist/src/config/dsn-check.js +12 -0
  29. package/dist/src/config/dsn-check.js.map +1 -0
  30. package/dist/src/di/container.d.ts +13 -0
  31. package/dist/src/di/container.js +67 -0
  32. package/dist/src/di/container.js.map +1 -0
  33. package/dist/src/di/types.d.ts +27 -0
  34. package/dist/src/di/types.js +28 -0
  35. package/dist/src/di/types.js.map +1 -0
  36. package/dist/src/flow/command-executor.d.ts +18 -0
  37. package/dist/src/flow/command-executor.js +87 -0
  38. package/dist/src/flow/command-executor.js.map +1 -0
  39. package/dist/src/flow/command-resolver.d.ts +11 -0
  40. package/dist/src/flow/command-resolver.js +35 -0
  41. package/dist/src/flow/command-resolver.js.map +1 -0
  42. package/dist/src/flow/flow-catalog.d.ts +12 -0
  43. package/dist/src/flow/flow-catalog.js +31 -0
  44. package/dist/src/flow/flow-catalog.js.map +1 -0
  45. package/dist/src/flow/flow-errors.d.ts +66 -0
  46. package/dist/src/flow/flow-errors.js +119 -0
  47. package/dist/src/flow/flow-errors.js.map +1 -0
  48. package/dist/src/flow/flow-job-processor.d.ts +33 -0
  49. package/dist/src/flow/flow-job-processor.js +208 -0
  50. package/dist/src/flow/flow-job-processor.js.map +1 -0
  51. package/dist/src/flow/flow-params.d.ts +9 -0
  52. package/dist/src/flow/flow-params.js +52 -0
  53. package/dist/src/flow/flow-params.js.map +1 -0
  54. package/dist/src/flow/flow-runner.d.ts +36 -0
  55. package/dist/src/flow/flow-runner.js +209 -0
  56. package/dist/src/flow/flow-runner.js.map +1 -0
  57. package/dist/src/flow/flow-scheduler.d.ts +24 -0
  58. package/dist/src/flow/flow-scheduler.js +96 -0
  59. package/dist/src/flow/flow-scheduler.js.map +1 -0
  60. package/dist/src/flow/flow-validator.d.ts +29 -0
  61. package/dist/src/flow/flow-validator.js +141 -0
  62. package/dist/src/flow/flow-validator.js.map +1 -0
  63. package/dist/src/flow/flow.d.ts +32 -0
  64. package/dist/src/flow/flow.js +50 -0
  65. package/dist/src/flow/flow.js.map +1 -0
  66. package/dist/src/flow/flow.models.d.ts +288 -0
  67. package/dist/src/flow/flow.models.js +38 -0
  68. package/dist/src/flow/flow.models.js.map +1 -0
  69. package/dist/src/flow/retry-policy.d.ts +9 -0
  70. package/dist/src/flow/retry-policy.js +15 -0
  71. package/dist/src/flow/retry-policy.js.map +1 -0
  72. package/dist/src/flow/state-path.d.ts +21 -0
  73. package/dist/src/flow/state-path.js +37 -0
  74. package/dist/src/flow/state-path.js.map +1 -0
  75. package/dist/src/flow/step-input-builder.d.ts +15 -0
  76. package/dist/src/flow/step-input-builder.js +63 -0
  77. package/dist/src/flow/step-input-builder.js.map +1 -0
  78. package/dist/src/flows/scrape.flow.d.ts +6 -0
  79. package/dist/src/flows/scrape.flow.js +31 -0
  80. package/dist/src/flows/scrape.flow.js.map +1 -0
  81. package/dist/src/index.d.ts +57 -0
  82. package/dist/src/index.js +50 -0
  83. package/dist/src/index.js.map +1 -0
  84. package/dist/src/logging/logger.d.ts +17 -0
  85. package/dist/src/logging/logger.js +55 -0
  86. package/dist/src/logging/logger.js.map +1 -0
  87. package/dist/src/logging/logger.models.d.ts +25 -0
  88. package/dist/src/logging/logger.models.js +7 -0
  89. package/dist/src/logging/logger.models.js.map +1 -0
  90. package/dist/src/mail/mail.models.d.ts +15 -0
  91. package/dist/src/mail/mail.models.js +2 -0
  92. package/dist/src/mail/mail.models.js.map +1 -0
  93. package/dist/src/mail/smtp-mailer.d.ts +13 -0
  94. package/dist/src/mail/smtp-mailer.js +70 -0
  95. package/dist/src/mail/smtp-mailer.js.map +1 -0
  96. package/dist/src/mail/unconfigured-mailer.d.ts +6 -0
  97. package/dist/src/mail/unconfigured-mailer.js +20 -0
  98. package/dist/src/mail/unconfigured-mailer.js.map +1 -0
  99. package/dist/src/models.d.ts +11 -0
  100. package/dist/src/models.js +10 -0
  101. package/dist/src/models.js.map +1 -0
  102. package/dist/src/monitor/monitor-api.d.ts +18 -0
  103. package/dist/src/monitor/monitor-api.js +62 -0
  104. package/dist/src/monitor/monitor-api.js.map +1 -0
  105. package/dist/src/monitor/monitor-server.d.ts +33 -0
  106. package/dist/src/monitor/monitor-server.js +142 -0
  107. package/dist/src/monitor/monitor-server.js.map +1 -0
  108. package/dist/src/monitor/monitor.models.d.ts +27 -0
  109. package/dist/src/monitor/monitor.models.js +2 -0
  110. package/dist/src/monitor/monitor.models.js.map +1 -0
  111. package/dist/src/queue/pg-boss-flow-queue.d.ts +22 -0
  112. package/dist/src/queue/pg-boss-flow-queue.js +102 -0
  113. package/dist/src/queue/pg-boss-flow-queue.js.map +1 -0
  114. package/dist/src/queue/queue.models.d.ts +38 -0
  115. package/dist/src/queue/queue.models.js +2 -0
  116. package/dist/src/queue/queue.models.js.map +1 -0
  117. package/dist/src/reporting/lighty-error-reporter.d.ts +24 -0
  118. package/dist/src/reporting/lighty-error-reporter.js +96 -0
  119. package/dist/src/reporting/lighty-error-reporter.js.map +1 -0
  120. package/dist/src/reporting/log-error-reporter.d.ts +12 -0
  121. package/dist/src/reporting/log-error-reporter.js +35 -0
  122. package/dist/src/reporting/log-error-reporter.js.map +1 -0
  123. package/dist/src/reporting/reporting.models.d.ts +27 -0
  124. package/dist/src/reporting/reporting.models.js +2 -0
  125. package/dist/src/reporting/reporting.models.js.map +1 -0
  126. package/dist/src/scrapme.d.ts +61 -0
  127. package/dist/src/scrapme.js +133 -0
  128. package/dist/src/scrapme.js.map +1 -0
  129. package/dist/src/scrapme.models.d.ts +18 -0
  130. package/dist/src/scrapme.models.js +2 -0
  131. package/dist/src/scrapme.models.js.map +1 -0
  132. package/dist/src/storage/couch-connection.d.ts +19 -0
  133. package/dist/src/storage/couch-connection.js +67 -0
  134. package/dist/src/storage/couch-connection.js.map +1 -0
  135. package/dist/src/storage/couch-database.d.ts +44 -0
  136. package/dist/src/storage/couch-database.js +118 -0
  137. package/dist/src/storage/couch-database.js.map +1 -0
  138. package/dist/src/storage/couch-document-store.d.ts +14 -0
  139. package/dist/src/storage/couch-document-store.js +36 -0
  140. package/dist/src/storage/couch-document-store.js.map +1 -0
  141. package/dist/src/storage/couch-error.d.ts +12 -0
  142. package/dist/src/storage/couch-error.js +19 -0
  143. package/dist/src/storage/couch-error.js.map +1 -0
  144. package/dist/src/storage/couch-flow-run-store.d.ts +24 -0
  145. package/dist/src/storage/couch-flow-run-store.js +66 -0
  146. package/dist/src/storage/couch-flow-run-store.js.map +1 -0
  147. package/dist/src/storage/couch-run-store.d.ts +15 -0
  148. package/dist/src/storage/couch-run-store.js +52 -0
  149. package/dist/src/storage/couch-run-store.js.map +1 -0
  150. package/dist/src/storage/run-doc-ids.d.ts +9 -0
  151. package/dist/src/storage/run-doc-ids.js +15 -0
  152. package/dist/src/storage/run-doc-ids.js.map +1 -0
  153. package/dist/src/storage/storage.models.d.ts +95 -0
  154. package/dist/src/storage/storage.models.js +5 -0
  155. package/dist/src/storage/storage.models.js.map +1 -0
  156. package/dist/src/storage/unconfigured-document-store.d.ts +8 -0
  157. package/dist/src/storage/unconfigured-document-store.js +26 -0
  158. package/dist/src/storage/unconfigured-document-store.js.map +1 -0
  159. package/dist/src/util/preview.d.ts +10 -0
  160. package/dist/src/util/preview.js +44 -0
  161. package/dist/src/util/preview.js.map +1 -0
  162. package/dist/src/util/text.d.ts +8 -0
  163. package/dist/src/util/text.js +16 -0
  164. package/dist/src/util/text.js.map +1 -0
  165. package/dist/src/util/timing.d.ts +10 -0
  166. package/dist/src/util/timing.js +59 -0
  167. package/dist/src/util/timing.js.map +1 -0
  168. package/dist/src/util/url.d.ts +5 -0
  169. package/dist/src/util/url.js +11 -0
  170. package/dist/src/util/url.js.map +1 -0
  171. package/package.json +75 -4
@@ -0,0 +1,79 @@
1
+ import { z } from 'zod';
2
+ import { ConfigSchema } from './config.schema.js';
3
+ import { Urls } from '../util/url.js';
4
+ export class ConfigError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = 'ConfigError';
8
+ }
9
+ }
10
+ /** The only place that reads environment variables. Empty strings count as "not set" (default applies). */
11
+ export class ConfigLoader {
12
+ static load(env = process.env) {
13
+ const cleaned = {};
14
+ for (const key of Object.keys(ConfigSchema.env.shape)) {
15
+ const value = env[key];
16
+ if (value !== undefined && value.trim() !== '')
17
+ cleaned[key] = value;
18
+ }
19
+ const parsed = ConfigSchema.env.safeParse(cleaned);
20
+ if (!parsed.success)
21
+ throw new ConfigError(`Invalid scrapme configuration:\n${z.prettifyError(parsed.error)}`);
22
+ return ConfigLoader.deepFreeze(ConfigLoader.toConfig(parsed.data));
23
+ }
24
+ static toConfig(e) {
25
+ return {
26
+ logLevel: e.LOG_LEVEL,
27
+ scrapAnything: {
28
+ url: e.SCRAP_ANYTHING_URL,
29
+ secret: e.SCRAP_ANYTHING_SECRET,
30
+ requestTimeoutMs: e.SCRAP_ANYTHING_REQUEST_TIMEOUT_MS,
31
+ pollIntervalMs: e.SCRAP_ANYTHING_POLL_INTERVAL_MS,
32
+ waitTimeoutMs: e.SCRAP_ANYTHING_WAIT_TIMEOUT_MS,
33
+ },
34
+ scrape: { stepTimeoutMs: e.SCRAPE_STEP_TIMEOUT_MS, stepRetries: e.SCRAPE_STEP_RETRIES },
35
+ couch: {
36
+ url: Urls.withoutTrailingSlash(e.COUCHDB_URL),
37
+ publicUrl: Urls.withoutTrailingSlash(e.COUCHDB_PUBLIC_URL || e.COUCHDB_URL),
38
+ user: e.COUCHDB_USER,
39
+ password: e.COUCHDB_PASSWORD,
40
+ db: e.COUCHDB_DB,
41
+ runsDb: e.COUCHDB_RUNS_DB,
42
+ timeoutMs: e.COUCHDB_TIMEOUT_MS,
43
+ retryLimit: e.COUCHDB_RETRY_LIMIT,
44
+ },
45
+ smtp: {
46
+ host: e.SMTP_HOST,
47
+ port: e.SMTP_PORT,
48
+ secure: e.SMTP_SECURE,
49
+ user: e.SMTP_USER,
50
+ password: e.SMTP_PASSWORD,
51
+ timeoutMs: e.SMTP_TIMEOUT_MS,
52
+ },
53
+ mail: { from: e.MAIL_FROM, to: e.MAIL_TO, uiUrl: e.MAIL_UI_URL },
54
+ lighty: {
55
+ dsn: e.LIGHTY_DSN,
56
+ environment: e.LIGHTY_ENVIRONMENT,
57
+ release: e.LIGHTY_RELEASE,
58
+ flushTimeoutMs: e.LIGHTY_FLUSH_TIMEOUT_MS,
59
+ },
60
+ flow: {
61
+ retries: e.FLOW_RETRIES,
62
+ timeoutMs: e.FLOW_TIMEOUT_MS,
63
+ retryDelayMs: e.FLOW_RETRY_DELAY_MS,
64
+ maxRetryDelayMs: e.FLOW_RETRY_MAX_DELAY_MS,
65
+ saveRuns: e.FLOW_SAVE_RUNS,
66
+ maxStoredChars: e.FLOW_MAX_STORED_CHARS,
67
+ },
68
+ };
69
+ }
70
+ static deepFreeze(value) {
71
+ if (value && typeof value === 'object') {
72
+ for (const v of Object.values(value))
73
+ ConfigLoader.deepFreeze(v);
74
+ Object.freeze(value);
75
+ }
76
+ return value;
77
+ }
78
+ }
79
+ //# sourceMappingURL=config-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-loader.js","sourceRoot":"","sources":["../../../src/config/config-loader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,YAAY,EAAkB,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEtC,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,YAAmB,OAAe;QAChC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAED,2GAA2G;AAC3G,MAAM,OAAO,YAAY;IAChB,MAAM,CAAC,IAAI,CAAC,MAA0C,OAAO,CAAC,GAAG;QACtE,MAAM,OAAO,GAA2B,EAAE,CAAC;QAC3C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YACtD,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;YACvB,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;gBAAE,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACvE,CAAC;QACD,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACnD,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,MAAM,IAAI,WAAW,CAAC,mCAAmC,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC/G,OAAO,YAAY,CAAC,UAAU,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IACrE,CAAC;IAEO,MAAM,CAAC,QAAQ,CAAC,CAAY;QAClC,OAAO;YACL,QAAQ,EAAE,CAAC,CAAC,SAAS;YACrB,aAAa,EAAE;gBACb,GAAG,EAAE,CAAC,CAAC,kBAAkB;gBACzB,MAAM,EAAE,CAAC,CAAC,qBAAqB;gBAC/B,gBAAgB,EAAE,CAAC,CAAC,iCAAiC;gBACrD,cAAc,EAAE,CAAC,CAAC,+BAA+B;gBACjD,aAAa,EAAE,CAAC,CAAC,8BAA8B;aAChD;YACD,MAAM,EAAE,EAAE,aAAa,EAAE,CAAC,CAAC,sBAAsB,EAAE,WAAW,EAAE,CAAC,CAAC,mBAAmB,EAAE;YACvF,KAAK,EAAE;gBACL,GAAG,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC,WAAW,CAAC;gBAC7C,SAAS,EAAE,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC,kBAAkB,IAAI,CAAC,CAAC,WAAW,CAAC;gBAC3E,IAAI,EAAE,CAAC,CAAC,YAAY;gBACpB,QAAQ,EAAE,CAAC,CAAC,gBAAgB;gBAC5B,EAAE,EAAE,CAAC,CAAC,UAAU;gBAChB,MAAM,EAAE,CAAC,CAAC,eAAe;gBACzB,SAAS,EAAE,CAAC,CAAC,kBAAkB;gBAC/B,UAAU,EAAE,CAAC,CAAC,mBAAmB;aAClC;YACD,IAAI,EAAE;gBACJ,IAAI,EAAE,CAAC,CAAC,SAAS;gBACjB,IAAI,EAAE,CAAC,CAAC,SAAS;gBACjB,MAAM,EAAE,CAAC,CAAC,WAAW;gBACrB,IAAI,EAAE,CAAC,CAAC,SAAS;gBACjB,QAAQ,EAAE,CAAC,CAAC,aAAa;gBACzB,SAAS,EAAE,CAAC,CAAC,eAAe;aAC7B;YACD,IAAI,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,WAAW,EAAE;YAChE,MAAM,EAAE;gBACN,GAAG,EAAE,CAAC,CAAC,UAAU;gBACjB,WAAW,EAAE,CAAC,CAAC,kBAAkB;gBACjC,OAAO,EAAE,CAAC,CAAC,cAAc;gBACzB,cAAc,EAAE,CAAC,CAAC,uBAAuB;aAC1C;YACD,IAAI,EAAE;gBACJ,OAAO,EAAE,CAAC,CAAC,YAAY;gBACvB,SAAS,EAAE,CAAC,CAAC,eAAe;gBAC5B,YAAY,EAAE,CAAC,CAAC,mBAAmB;gBACnC,eAAe,EAAE,CAAC,CAAC,uBAAuB;gBAC1C,QAAQ,EAAE,CAAC,CAAC,cAAc;gBAC1B,cAAc,EAAE,CAAC,CAAC,qBAAqB;aACxC;SACF,CAAC;IACJ,CAAC;IAEO,MAAM,CAAC,UAAU,CAAI,KAAQ;QACnC,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACvC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;gBAAE,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;YACjE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;CACF"}
@@ -0,0 +1,144 @@
1
+ import type { LogLevel } from '../logging/logger.models.js';
2
+ /** Postgres for the pg-boss flow queue (required). */
3
+ export interface PostgresOptions {
4
+ /** e.g. postgres://user:password@localhost:5434/scrapme */
5
+ connectionString: string;
6
+ /** pg-boss tables live in this schema. Default "scrapme". */
7
+ schema?: string;
8
+ }
9
+ /**
10
+ * The pg-boss queues and their workers: one queue per flow (prefix + flow name), created when the flow is first
11
+ * scheduled. Each flow can override retries / delay / timeout (FlowConfig).
12
+ */
13
+ export interface QueueOptions {
14
+ /** Queue names are prefix + flow name. Default "scrapme_". */
15
+ prefix?: string;
16
+ /** Flow attempts after the first one (each attempt runs the flow from its first step). Default 3. */
17
+ retries?: number;
18
+ /** Default 30. */
19
+ retryDelaySeconds?: number;
20
+ /** Default true. */
21
+ retryBackoff?: boolean;
22
+ /** One flow attempt may run this long (pg-boss expireInSeconds). Default 3 600. */
23
+ timeoutSeconds?: number;
24
+ /** Runs of one flow at the same time in this process. Default 1. */
25
+ concurrency?: number;
26
+ /** How often idle workers look for jobs, in seconds (min 0.5). Default 2. */
27
+ pollingIntervalSeconds?: number;
28
+ /** Finished jobs are removed from Postgres after this (the runs stay in CouchDB). Default 7. */
29
+ retentionDays?: number;
30
+ /** stop() waits this long for running flows. Default 30 000. */
31
+ shutdownTimeoutMs?: number;
32
+ }
33
+ /** scrap-anything — needed by ScrapePageCommand. */
34
+ export interface ScrapAnythingOptions {
35
+ /** e.g. http://localhost:3000 */
36
+ url: string;
37
+ /** API_SECRET of scrap-anything (Bearer for POST /jobs). */
38
+ apiSecret: string;
39
+ /** One HTTP request to the API. Default 30 000. */
40
+ requestTimeoutMs?: number;
41
+ /** How often a waiting step asks for the job status. Default 2 000. */
42
+ pollIntervalMs?: number;
43
+ /** How long one ScrapePage attempt waits for its job (the server retries for up to ~7 min). Default 600 000. */
44
+ waitTimeoutMs?: number;
45
+ /** Timeout of one ScrapePage attempt; must be > waitTimeoutMs. Default waitTimeoutMs + 60 000. */
46
+ stepTimeoutMs?: number;
47
+ /** New server jobs after a failed one (the server already retried). Default 1. */
48
+ stepRetries?: number;
49
+ }
50
+ /** CouchDB — the run documents and the results of SaveDocumentCommand (required). */
51
+ export interface CouchOptions {
52
+ /** e.g. http://localhost:5984 */
53
+ url: string;
54
+ user: string;
55
+ password: string;
56
+ /** Database of SaveDocumentCommand. Default "scrapme". */
57
+ db?: string;
58
+ /** Database of runs and failed attempts. Default "scrapme_runs". */
59
+ runsDb?: string;
60
+ /** CouchDB as seen from a browser (Fauxton links). Default url. */
61
+ publicUrl?: string;
62
+ /** Default 15 000. */
63
+ timeoutMs?: number;
64
+ /** Default 2. */
65
+ retryLimit?: number;
66
+ }
67
+ /** SMTP — needed by SendMailCommand. */
68
+ export interface MailOptions {
69
+ host: string;
70
+ /** Default 587 with secure=false, 465 with secure=true. */
71
+ port?: number;
72
+ /** Default false. */
73
+ secure?: boolean;
74
+ user?: string;
75
+ password?: string;
76
+ /** Default 10 000. */
77
+ timeoutMs?: number;
78
+ /** e.g. "scrapme <scrapme@example.com>" */
79
+ from: string;
80
+ /** Default recipients of SendMailCommand. */
81
+ to?: string[];
82
+ }
83
+ /** LightyErrors (sentry-clone): a step that finally fails is reported there. */
84
+ export interface LightyOptions {
85
+ /** http(s)://<project-key>@<host>/<project> */
86
+ dsn: string;
87
+ /** Default "production". */
88
+ environment?: string;
89
+ /** Default "scrapme". */
90
+ release?: string;
91
+ /** Default 5 000. */
92
+ flushTimeoutMs?: number;
93
+ }
94
+ /** Defaults of every step (a step or the command itself can override them). Command retries run inside a flow attempt. */
95
+ export interface StepDefaults {
96
+ /** Attempts = retries + 1. Default 3. */
97
+ retries?: number;
98
+ /** One attempt. Default 60 000. */
99
+ timeoutMs?: number;
100
+ /** First retry delay, doubled per retry, with jitter. Default 1 000. */
101
+ retryDelayMs?: number;
102
+ /** Default 30 000. */
103
+ maxRetryDelayMs?: number;
104
+ /** Longer strings (HTML!) are cut in stored inputs/outputs. Default 2 000. */
105
+ maxStoredChars?: number;
106
+ }
107
+ /**
108
+ * Everything scrapme connects to, given by the app (scrapme reads no environment variables). `postgres` and `couch`
109
+ * are required: flows run on a pg-boss queue and their progress is kept in CouchDB. The rest is needed only by the
110
+ * commands that use it: no `scrapAnything` = ScrapePageCommand fails, no `mail` = SendMailCommand fails (both with
111
+ * NOT_CONFIGURED); no `lighty` = final errors are only logged.
112
+ */
113
+ export interface ServiceOptions {
114
+ postgres: PostgresOptions;
115
+ couch: CouchOptions;
116
+ queue?: QueueOptions;
117
+ scrapAnything?: ScrapAnythingOptions;
118
+ mail?: MailOptions;
119
+ lighty?: LightyOptions;
120
+ defaults?: StepDefaults;
121
+ /** Default "info"; "silent" turns logging off. */
122
+ logLevel?: LogLevel;
123
+ }
124
+ export type ScrapAnythingConfig = Required<ScrapAnythingOptions>;
125
+ export type CouchConfig = Required<CouchOptions>;
126
+ export type MailConfig = Required<Omit<MailOptions, 'user' | 'password'>> & {
127
+ user: string | null;
128
+ password: string | null;
129
+ };
130
+ export type LightyConfig = Required<LightyOptions>;
131
+ export type StepDefaultsConfig = Required<StepDefaults>;
132
+ export type PostgresConfig = Required<PostgresOptions>;
133
+ export type QueueConfig = Required<QueueOptions>;
134
+ /** Validated, frozen configuration (ConfigBuilder). A missing optional service is null. */
135
+ export interface ScrapmeConfig {
136
+ logLevel: LogLevel;
137
+ flow: StepDefaultsConfig;
138
+ postgres: PostgresConfig;
139
+ queue: QueueConfig;
140
+ scrapAnything: ScrapAnythingConfig | null;
141
+ couch: CouchConfig;
142
+ mail: MailConfig | null;
143
+ lighty: LightyConfig | null;
144
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config.models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.models.js","sourceRoot":"","sources":["../../../src/config/config.models.ts"],"names":[],"mappings":""}
@@ -0,0 +1,75 @@
1
+ import { z } from 'zod';
2
+ /** The zod schema of every setting, with its default. Documented in .env.example. */
3
+ export declare class ConfigSchema {
4
+ /**
5
+ * CouchDB's own rule for database names (docs.couchdb.org, "PUT /{db}"). It is a rule about characters, so a
6
+ * regex is the clearest way to state it.
7
+ */
8
+ private static readonly COUCH_DB_NAME;
9
+ private static readonly TRUE_VALUES;
10
+ private static readonly FALSE_VALUES;
11
+ private static readonly LIST_SEPARATOR;
12
+ private static int;
13
+ private static str;
14
+ private static bool;
15
+ private static couchDbName;
16
+ private static list;
17
+ static readonly env: z.ZodObject<{
18
+ LOG_LEVEL: z.ZodDefault<z.ZodEnum<{
19
+ debug: "debug";
20
+ info: "info";
21
+ warn: "warn";
22
+ error: "error";
23
+ silent: "silent";
24
+ }>>;
25
+ SCRAP_ANYTHING_URL: z.ZodDefault<z.ZodURL>;
26
+ SCRAP_ANYTHING_SECRET: z.ZodDefault<z.ZodString>;
27
+ SCRAP_ANYTHING_REQUEST_TIMEOUT_MS: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
28
+ SCRAP_ANYTHING_POLL_INTERVAL_MS: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
29
+ SCRAP_ANYTHING_WAIT_TIMEOUT_MS: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
30
+ SCRAPE_STEP_TIMEOUT_MS: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
31
+ SCRAPE_STEP_RETRIES: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
32
+ COUCHDB_URL: z.ZodDefault<z.ZodURL>;
33
+ COUCHDB_PUBLIC_URL: z.ZodDefault<z.ZodString>;
34
+ COUCHDB_USER: z.ZodDefault<z.ZodString>;
35
+ COUCHDB_PASSWORD: z.ZodDefault<z.ZodString>;
36
+ COUCHDB_DB: z.ZodDefault<z.ZodString>;
37
+ COUCHDB_RUNS_DB: z.ZodDefault<z.ZodString>;
38
+ COUCHDB_TIMEOUT_MS: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
39
+ COUCHDB_RETRY_LIMIT: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
40
+ SMTP_HOST: z.ZodDefault<z.ZodString>;
41
+ SMTP_PORT: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
42
+ SMTP_SECURE: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
43
+ 0: "0";
44
+ 1: "1";
45
+ true: "true";
46
+ yes: "yes";
47
+ false: "false";
48
+ no: "no";
49
+ }>>, z.ZodTransform<boolean, "0" | "1" | "true" | "yes" | "false" | "no">>;
50
+ SMTP_USER: z.ZodDefault<z.ZodString>;
51
+ SMTP_PASSWORD: z.ZodDefault<z.ZodString>;
52
+ SMTP_TIMEOUT_MS: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
53
+ MAIL_FROM: z.ZodDefault<z.ZodString>;
54
+ MAIL_TO: z.ZodPipe<z.ZodDefault<z.ZodString>, z.ZodTransform<string[], string>>;
55
+ MAIL_UI_URL: z.ZodDefault<z.ZodString>;
56
+ LIGHTY_DSN: z.ZodDefault<z.ZodString>;
57
+ LIGHTY_ENVIRONMENT: z.ZodDefault<z.ZodString>;
58
+ LIGHTY_RELEASE: z.ZodDefault<z.ZodString>;
59
+ LIGHTY_FLUSH_TIMEOUT_MS: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
60
+ FLOW_RETRIES: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
61
+ FLOW_TIMEOUT_MS: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
62
+ FLOW_RETRY_DELAY_MS: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
63
+ FLOW_RETRY_MAX_DELAY_MS: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
64
+ FLOW_SAVE_RUNS: z.ZodPipe<z.ZodDefault<z.ZodEnum<{
65
+ 0: "0";
66
+ 1: "1";
67
+ true: "true";
68
+ yes: "yes";
69
+ false: "false";
70
+ no: "no";
71
+ }>>, z.ZodTransform<boolean, "0" | "1" | "true" | "yes" | "false" | "no">>;
72
+ FLOW_MAX_STORED_CHARS: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
73
+ }, z.core.$strip>;
74
+ }
75
+ export type ParsedEnv = z.infer<typeof ConfigSchema.env>;
@@ -0,0 +1,87 @@
1
+ import { z } from 'zod';
2
+ import { LOG_LEVEL, LOG_LEVELS } from '../logging/logger.models.js';
3
+ import { DsnCheck } from './dsn-check.js';
4
+ /** The zod schema of every setting, with its default. Documented in .env.example. */
5
+ export class ConfigSchema {
6
+ /**
7
+ * CouchDB's own rule for database names (docs.couchdb.org, "PUT /{db}"). It is a rule about characters, so a
8
+ * regex is the clearest way to state it.
9
+ */
10
+ static COUCH_DB_NAME = /^[a-z][a-z0-9_$()+/-]*$/;
11
+ static TRUE_VALUES = ['true', '1', 'yes'];
12
+ static FALSE_VALUES = ['false', '0', 'no'];
13
+ static LIST_SEPARATOR = ',';
14
+ static int(def, min = 0) {
15
+ return z.coerce.number().int().min(min).default(def);
16
+ }
17
+ static str(def) {
18
+ return z.string().trim().default(def);
19
+ }
20
+ static bool(def) {
21
+ const C = ConfigSchema;
22
+ return z
23
+ .enum([...C.TRUE_VALUES, ...C.FALSE_VALUES])
24
+ .default(def ? C.TRUE_VALUES[0] : C.FALSE_VALUES[0])
25
+ .transform((v) => C.TRUE_VALUES.includes(v));
26
+ }
27
+ static couchDbName(def) {
28
+ return z.string().regex(ConfigSchema.COUCH_DB_NAME, 'CouchDB names: lowercase letters, digits and _$()+/-, starting with a letter').default(def);
29
+ }
30
+ static list(def) {
31
+ return z
32
+ .string()
33
+ .default(def)
34
+ .transform((v) => v.split(ConfigSchema.LIST_SEPARATOR).map((s) => s.trim()).filter(Boolean));
35
+ }
36
+ static env = z
37
+ .object({
38
+ LOG_LEVEL: z.enum(LOG_LEVELS).default(LOG_LEVEL.INFO),
39
+ SCRAP_ANYTHING_URL: z.url().default('http://localhost:3000'),
40
+ SCRAP_ANYTHING_SECRET: ConfigSchema.str(''),
41
+ SCRAP_ANYTHING_REQUEST_TIMEOUT_MS: ConfigSchema.int(30_000, 1),
42
+ SCRAP_ANYTHING_POLL_INTERVAL_MS: ConfigSchema.int(2_000, 1),
43
+ SCRAP_ANYTHING_WAIT_TIMEOUT_MS: ConfigSchema.int(600_000, 1),
44
+ SCRAPE_STEP_TIMEOUT_MS: ConfigSchema.int(660_000, 1),
45
+ SCRAPE_STEP_RETRIES: ConfigSchema.int(1),
46
+ COUCHDB_URL: z.url().default('http://localhost:5984'),
47
+ COUCHDB_PUBLIC_URL: ConfigSchema.str(''),
48
+ COUCHDB_USER: ConfigSchema.str('admin'),
49
+ COUCHDB_PASSWORD: ConfigSchema.str('admin'),
50
+ COUCHDB_DB: ConfigSchema.couchDbName('scrapme'),
51
+ COUCHDB_RUNS_DB: ConfigSchema.couchDbName('scrapme_runs'),
52
+ COUCHDB_TIMEOUT_MS: ConfigSchema.int(15_000, 1),
53
+ COUCHDB_RETRY_LIMIT: ConfigSchema.int(2),
54
+ SMTP_HOST: ConfigSchema.str('localhost'),
55
+ SMTP_PORT: ConfigSchema.int(1025, 1),
56
+ SMTP_SECURE: ConfigSchema.bool(false),
57
+ SMTP_USER: ConfigSchema.str(''),
58
+ SMTP_PASSWORD: ConfigSchema.str(''),
59
+ SMTP_TIMEOUT_MS: ConfigSchema.int(10_000, 1),
60
+ MAIL_FROM: ConfigSchema.str('scrapme <scrapme@localhost>'),
61
+ MAIL_TO: ConfigSchema.list('owner@localhost'),
62
+ MAIL_UI_URL: ConfigSchema.str('http://localhost:8025'),
63
+ LIGHTY_DSN: ConfigSchema.str(''),
64
+ LIGHTY_ENVIRONMENT: ConfigSchema.str('development'),
65
+ LIGHTY_RELEASE: ConfigSchema.str('scrapme@0.1.0'),
66
+ LIGHTY_FLUSH_TIMEOUT_MS: ConfigSchema.int(5_000, 1),
67
+ FLOW_RETRIES: ConfigSchema.int(2),
68
+ FLOW_TIMEOUT_MS: ConfigSchema.int(60_000, 1),
69
+ FLOW_RETRY_DELAY_MS: ConfigSchema.int(1_000),
70
+ FLOW_RETRY_MAX_DELAY_MS: ConfigSchema.int(30_000),
71
+ FLOW_SAVE_RUNS: ConfigSchema.bool(true),
72
+ FLOW_MAX_STORED_CHARS: ConfigSchema.int(2_000, 100),
73
+ })
74
+ .refine((e) => e.SCRAPE_STEP_TIMEOUT_MS > e.SCRAP_ANYTHING_WAIT_TIMEOUT_MS, {
75
+ message: 'SCRAPE_STEP_TIMEOUT_MS must be greater than SCRAP_ANYTHING_WAIT_TIMEOUT_MS (the step waits for the server job)',
76
+ path: ['SCRAPE_STEP_TIMEOUT_MS'],
77
+ })
78
+ .refine((e) => e.FLOW_RETRY_MAX_DELAY_MS >= e.FLOW_RETRY_DELAY_MS, {
79
+ message: 'FLOW_RETRY_MAX_DELAY_MS must be >= FLOW_RETRY_DELAY_MS',
80
+ path: ['FLOW_RETRY_MAX_DELAY_MS'],
81
+ })
82
+ .refine((e) => !e.LIGHTY_DSN || DsnCheck.isValid(e.LIGHTY_DSN), {
83
+ message: 'LIGHTY_DSN must look like https://<project-key>@<host>/<project>',
84
+ path: ['LIGHTY_DSN'],
85
+ });
86
+ }
87
+ //# sourceMappingURL=config.schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.schema.js","sourceRoot":"","sources":["../../../src/config/config.schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /** LightyErrors DSN: `http(s)://<project-key>@<host>[:port]/<project>` — checked with URL, not a pattern. */
2
+ export declare class DsnCheck {
3
+ private static readonly PROTOCOLS;
4
+ static isValid(dsn: string): boolean;
5
+ }
@@ -0,0 +1,12 @@
1
+ /** LightyErrors DSN: `http(s)://<project-key>@<host>[:port]/<project>` — checked with URL, not a pattern. */
2
+ export class DsnCheck {
3
+ static PROTOCOLS = new Set(['http:', 'https:']);
4
+ static isValid(dsn) {
5
+ if (!URL.canParse(dsn))
6
+ return false;
7
+ const url = new URL(dsn);
8
+ const project = url.pathname.split('/').filter(Boolean);
9
+ return DsnCheck.PROTOCOLS.has(url.protocol) && url.username !== '' && url.password === '' && project.length > 0;
10
+ }
11
+ }
12
+ //# sourceMappingURL=dsn-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dsn-check.js","sourceRoot":"","sources":["../../../src/config/dsn-check.ts"],"names":[],"mappings":"AAAA,6GAA6G;AAC7G,MAAM,OAAO,QAAQ;IACX,MAAM,CAAU,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE1D,MAAM,CAAC,OAAO,CAAC,GAAW;QAC/B,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACzB,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACxD,OAAO,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,KAAK,EAAE,IAAI,GAAG,CAAC,QAAQ,KAAK,EAAE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;IAClH,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { Container } from 'inversify';
2
+ import type { ScrapmeConfig } from '../config/config.models.js';
3
+ /**
4
+ * Composition root of scrapme's own services (singletons). The container's parent is the app's container (when
5
+ * given): `autobind` resolves any `@injectable()` command class by its class, and its `@inject(...)`s find the
6
+ * app's services through the parent — commands need no bindings. A missing optional service (mail, scrapAnything)
7
+ * gets an "unconfigured" stand-in, so only the command that needs it fails (NOT_CONFIGURED). Replace a scrapme
8
+ * service with `scrapme.container.rebindSync(TYPES.X)` before start() (tests use this for the queue and stores).
9
+ */
10
+ export declare class ContainerBuilder {
11
+ private static readonly APP;
12
+ static build(config: ScrapmeConfig, parent?: Container): Container;
13
+ }
@@ -0,0 +1,67 @@
1
+ import { Container } from 'inversify';
2
+ import { ScrapAnythingClient } from 'scrap-anything-client';
3
+ import { CommandExecutor } from '../flow/command-executor.js';
4
+ import { CommandResolver } from '../flow/command-resolver.js';
5
+ import { FlowJobProcessor } from '../flow/flow-job-processor.js';
6
+ import { FlowParams } from '../flow/flow-params.js';
7
+ import { FlowScheduler } from '../flow/flow-scheduler.js';
8
+ import { StepInputBuilder } from '../flow/step-input-builder.js';
9
+ import { Logger } from '../logging/logger.js';
10
+ import { SmtpMailer } from '../mail/smtp-mailer.js';
11
+ import { UnconfiguredMailer } from '../mail/unconfigured-mailer.js';
12
+ import { PgBossFlowQueue } from '../queue/pg-boss-flow-queue.js';
13
+ import { LightyErrorReporter } from '../reporting/lighty-error-reporter.js';
14
+ import { LogErrorReporter } from '../reporting/log-error-reporter.js';
15
+ import { CouchConnection } from '../storage/couch-connection.js';
16
+ import { CouchDocumentStore } from '../storage/couch-document-store.js';
17
+ import { CouchRunStore } from '../storage/couch-run-store.js';
18
+ import { TYPES } from './types.js';
19
+ /**
20
+ * Composition root of scrapme's own services (singletons). The container's parent is the app's container (when
21
+ * given): `autobind` resolves any `@injectable()` command class by its class, and its `@inject(...)`s find the
22
+ * app's services through the parent — commands need no bindings. A missing optional service (mail, scrapAnything)
23
+ * gets an "unconfigured" stand-in, so only the command that needs it fails (NOT_CONFIGURED). Replace a scrapme
24
+ * service with `scrapme.container.rebindSync(TYPES.X)` before start() (tests use this for the queue and stores).
25
+ */
26
+ export class ContainerBuilder {
27
+ static APP = 'scrapme';
28
+ static build(config, parent) {
29
+ const c = new Container({ defaultScope: 'Singleton', autobind: true, ...(parent ? { parent } : {}) });
30
+ c.bind(TYPES.Config).toConstantValue(config);
31
+ c.bind(TYPES.PostgresConfig).toConstantValue(config.postgres);
32
+ c.bind(TYPES.QueueConfig).toConstantValue(config.queue);
33
+ c.bind(TYPES.CouchConfig).toConstantValue(config.couch);
34
+ c.bind(TYPES.Logger).toConstantValue(new Logger(config.logLevel, { app: ContainerBuilder.APP }));
35
+ c.bind(TYPES.Container).toConstantValue(c);
36
+ c.bind(TYPES.FlowQueue).to(PgBossFlowQueue);
37
+ c.bind(TYPES.FlowScheduler).to(FlowScheduler);
38
+ c.bind(TYPES.FlowJobProcessor).to(FlowJobProcessor);
39
+ c.bind(TYPES.CommandResolver).to(CommandResolver);
40
+ c.bind(TYPES.CommandExecutor).to(CommandExecutor);
41
+ c.bind(TYPES.StepInputBuilder).to(StepInputBuilder);
42
+ c.bind(TYPES.FlowParams).to(FlowParams);
43
+ c.bind(TYPES.CouchConnection).to(CouchConnection);
44
+ c.bind(TYPES.RunStore).to(CouchRunStore);
45
+ c.bind(TYPES.DocumentStore).to(CouchDocumentStore);
46
+ if (config.lighty) {
47
+ c.bind(TYPES.LightyConfig).toConstantValue(config.lighty);
48
+ c.bind(TYPES.ErrorReporter).to(LightyErrorReporter);
49
+ }
50
+ else {
51
+ c.bind(TYPES.ErrorReporter).to(LogErrorReporter);
52
+ }
53
+ if (config.mail) {
54
+ c.bind(TYPES.MailConfig).toConstantValue(config.mail);
55
+ c.bind(TYPES.Mailer).to(SmtpMailer);
56
+ }
57
+ else {
58
+ c.bind(TYPES.Mailer).to(UnconfiguredMailer);
59
+ }
60
+ const sa = config.scrapAnything;
61
+ if (sa) {
62
+ c.bind(TYPES.ScrapAnythingClient).toConstantValue(new ScrapAnythingClient({ baseUrl: sa.url, apiSecret: sa.apiSecret, requestTimeoutMs: sa.requestTimeoutMs, pollIntervalMs: sa.pollIntervalMs, waitTimeoutMs: sa.waitTimeoutMs }));
63
+ }
64
+ return c;
65
+ }
66
+ }
67
+ //# sourceMappingURL=container.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"container.js","sourceRoot":"","sources":["../../../src/di/container.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Inversify service identifiers of scrapme's own services. Commands are resolved by their class (autobind);
3
+ * your services live in your app's container (passed to Scrapme.create), which is the parent of scrapme's.
4
+ */
5
+ export declare const TYPES: {
6
+ readonly Config: symbol;
7
+ readonly PostgresConfig: symbol;
8
+ readonly QueueConfig: symbol;
9
+ readonly CouchConfig: symbol;
10
+ readonly MailConfig: symbol;
11
+ readonly LightyConfig: symbol;
12
+ readonly Logger: symbol;
13
+ readonly Container: symbol;
14
+ readonly FlowScheduler: symbol;
15
+ readonly FlowJobProcessor: symbol;
16
+ readonly FlowQueue: symbol;
17
+ readonly RunStore: symbol;
18
+ readonly CommandResolver: symbol;
19
+ readonly CommandExecutor: symbol;
20
+ readonly StepInputBuilder: symbol;
21
+ readonly FlowParams: symbol;
22
+ readonly ErrorReporter: symbol;
23
+ readonly DocumentStore: symbol;
24
+ readonly CouchConnection: symbol;
25
+ readonly Mailer: symbol;
26
+ readonly ScrapAnythingClient: symbol;
27
+ };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Inversify service identifiers of scrapme's own services. Commands are resolved by their class (autobind);
3
+ * your services live in your app's container (passed to Scrapme.create), which is the parent of scrapme's.
4
+ */
5
+ export const TYPES = {
6
+ Config: Symbol.for('scrapme.Config'),
7
+ PostgresConfig: Symbol.for('scrapme.PostgresConfig'),
8
+ QueueConfig: Symbol.for('scrapme.QueueConfig'),
9
+ CouchConfig: Symbol.for('scrapme.CouchConfig'),
10
+ MailConfig: Symbol.for('scrapme.MailConfig'),
11
+ LightyConfig: Symbol.for('scrapme.LightyConfig'),
12
+ Logger: Symbol.for('scrapme.Logger'),
13
+ Container: Symbol.for('scrapme.Container'),
14
+ FlowScheduler: Symbol.for('scrapme.FlowScheduler'),
15
+ FlowJobProcessor: Symbol.for('scrapme.FlowJobProcessor'),
16
+ FlowQueue: Symbol.for('scrapme.FlowQueue'),
17
+ RunStore: Symbol.for('scrapme.RunStore'),
18
+ CommandResolver: Symbol.for('scrapme.CommandResolver'),
19
+ CommandExecutor: Symbol.for('scrapme.CommandExecutor'),
20
+ StepInputBuilder: Symbol.for('scrapme.StepInputBuilder'),
21
+ FlowParams: Symbol.for('scrapme.FlowParams'),
22
+ ErrorReporter: Symbol.for('scrapme.ErrorReporter'),
23
+ DocumentStore: Symbol.for('scrapme.DocumentStore'),
24
+ CouchConnection: Symbol.for('scrapme.CouchConnection'),
25
+ Mailer: Symbol.for('scrapme.Mailer'),
26
+ ScrapAnythingClient: Symbol.for('scrapme.ScrapAnythingClient'),
27
+ };
28
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/di/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC;IACpC,cAAc,EAAE,MAAM,CAAC,GAAG,CAAC,wBAAwB,CAAC;IACpD,WAAW,EAAE,MAAM,CAAC,GAAG,CAAC,qBAAqB,CAAC;IAC9C,WAAW,EAAE,MAAM,CAAC,GAAG,CAAC,qBAAqB,CAAC;IAC9C,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC;IAC5C,YAAY,EAAE,MAAM,CAAC,GAAG,CAAC,sBAAsB,CAAC;IAChD,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC;IAC1C,aAAa,EAAE,MAAM,CAAC,GAAG,CAAC,uBAAuB,CAAC;IAClD,gBAAgB,EAAE,MAAM,CAAC,GAAG,CAAC,0BAA0B,CAAC;IACxD,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC;IAC1C,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC;IACxC,eAAe,EAAE,MAAM,CAAC,GAAG,CAAC,yBAAyB,CAAC;IACtD,eAAe,EAAE,MAAM,CAAC,GAAG,CAAC,yBAAyB,CAAC;IACtD,gBAAgB,EAAE,MAAM,CAAC,GAAG,CAAC,0BAA0B,CAAC;IACxD,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC;IAC5C,aAAa,EAAE,MAAM,CAAC,GAAG,CAAC,uBAAuB,CAAC;IAClD,aAAa,EAAE,MAAM,CAAC,GAAG,CAAC,uBAAuB,CAAC;IAClD,eAAe,EAAE,MAAM,CAAC,GAAG,CAAC,yBAAyB,CAAC;IACtD,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC;IACpC,mBAAmB,EAAE,MAAM,CAAC,GAAG,CAAC,6BAA6B,CAAC;CACtD,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { ScrapmeConfig } from '../config/config.models.js';
2
+ import { type AttemptFailedListener, type AttemptStartedListener, type Command, type CommandContext, type ResolvedStepPolicy, type StepDefinition, type StepExecution } from './flow.models.js';
3
+ /**
4
+ * Runs one step inside a flow attempt: attempts with a timeout each, retried with backoff while the error is
5
+ * retryable. Never throws. (The whole flow is retried by pg-boss; this is the per-command retry inside it.)
6
+ */
7
+ export declare class CommandExecutor {
8
+ private readonly config;
9
+ constructor(config: ScrapmeConfig);
10
+ /** step settings > command.options > defaults (Scrapme.create({ defaults })). */
11
+ policy(step: StepDefinition, command: Command): ResolvedStepPolicy;
12
+ execute(step: StepDefinition, command: Command, input: unknown, base: Omit<CommandContext, 'attempt' | 'maxAttempts' | 'signal'>, runSignal: AbortSignal | undefined, listeners: {
13
+ started?: AttemptStartedListener;
14
+ failed: AttemptFailedListener;
15
+ }): Promise<StepExecution>;
16
+ private report;
17
+ private shouldRetry;
18
+ }