s3db.js 13.6.0 → 14.0.2

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 (193) hide show
  1. package/README.md +139 -43
  2. package/dist/s3db.cjs +72425 -38970
  3. package/dist/s3db.cjs.map +1 -1
  4. package/dist/s3db.es.js +72177 -38764
  5. package/dist/s3db.es.js.map +1 -1
  6. package/mcp/lib/base-handler.js +157 -0
  7. package/mcp/lib/handlers/connection-handler.js +280 -0
  8. package/mcp/lib/handlers/query-handler.js +533 -0
  9. package/mcp/lib/handlers/resource-handler.js +428 -0
  10. package/mcp/lib/tool-registry.js +336 -0
  11. package/mcp/lib/tools/connection-tools.js +161 -0
  12. package/mcp/lib/tools/query-tools.js +267 -0
  13. package/mcp/lib/tools/resource-tools.js +404 -0
  14. package/package.json +94 -49
  15. package/src/clients/memory-client.class.js +346 -191
  16. package/src/clients/memory-storage.class.js +300 -84
  17. package/src/clients/s3-client.class.js +7 -6
  18. package/src/concerns/geo-encoding.js +19 -2
  19. package/src/concerns/ip.js +59 -9
  20. package/src/concerns/money.js +8 -1
  21. package/src/concerns/password-hashing.js +49 -8
  22. package/src/concerns/plugin-storage.js +186 -18
  23. package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
  24. package/src/database.class.js +139 -29
  25. package/src/errors.js +332 -42
  26. package/src/plugins/api/auth/oidc-auth.js +66 -17
  27. package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
  28. package/src/plugins/api/auth/strategies/factory.class.js +63 -0
  29. package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
  30. package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
  31. package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
  32. package/src/plugins/api/concerns/failban-manager.js +106 -57
  33. package/src/plugins/api/concerns/opengraph-helper.js +116 -0
  34. package/src/plugins/api/concerns/route-context.js +601 -0
  35. package/src/plugins/api/concerns/state-machine.js +288 -0
  36. package/src/plugins/api/index.js +180 -41
  37. package/src/plugins/api/routes/auth-routes.js +198 -30
  38. package/src/plugins/api/routes/resource-routes.js +19 -4
  39. package/src/plugins/api/server/health-manager.class.js +163 -0
  40. package/src/plugins/api/server/middleware-chain.class.js +310 -0
  41. package/src/plugins/api/server/router.class.js +472 -0
  42. package/src/plugins/api/server.js +280 -1303
  43. package/src/plugins/api/utils/custom-routes.js +17 -5
  44. package/src/plugins/api/utils/guards.js +76 -17
  45. package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
  46. package/src/plugins/api/utils/openapi-generator.js +7 -6
  47. package/src/plugins/api/utils/template-engine.js +77 -3
  48. package/src/plugins/audit.plugin.js +30 -8
  49. package/src/plugins/backup.plugin.js +110 -14
  50. package/src/plugins/cache/cache.class.js +22 -5
  51. package/src/plugins/cache/filesystem-cache.class.js +116 -19
  52. package/src/plugins/cache/memory-cache.class.js +211 -57
  53. package/src/plugins/cache/multi-tier-cache.class.js +371 -0
  54. package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
  55. package/src/plugins/cache/redis-cache.class.js +552 -0
  56. package/src/plugins/cache/s3-cache.class.js +17 -8
  57. package/src/plugins/cache.plugin.js +176 -61
  58. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
  59. package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
  60. package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
  61. package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
  62. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
  63. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
  64. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
  65. package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
  66. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
  67. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
  68. package/src/plugins/cloud-inventory/index.js +29 -8
  69. package/src/plugins/cloud-inventory/registry.js +64 -42
  70. package/src/plugins/cloud-inventory.plugin.js +240 -138
  71. package/src/plugins/concerns/plugin-dependencies.js +54 -0
  72. package/src/plugins/concerns/resource-names.js +100 -0
  73. package/src/plugins/consumers/index.js +10 -2
  74. package/src/plugins/consumers/sqs-consumer.js +12 -2
  75. package/src/plugins/cookie-farm-suite.plugin.js +278 -0
  76. package/src/plugins/cookie-farm.errors.js +73 -0
  77. package/src/plugins/cookie-farm.plugin.js +869 -0
  78. package/src/plugins/costs.plugin.js +7 -1
  79. package/src/plugins/eventual-consistency/analytics.js +94 -19
  80. package/src/plugins/eventual-consistency/config.js +15 -7
  81. package/src/plugins/eventual-consistency/consolidation.js +29 -11
  82. package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
  83. package/src/plugins/eventual-consistency/helpers.js +39 -14
  84. package/src/plugins/eventual-consistency/install.js +21 -2
  85. package/src/plugins/eventual-consistency/utils.js +32 -10
  86. package/src/plugins/fulltext.plugin.js +38 -11
  87. package/src/plugins/geo.plugin.js +61 -9
  88. package/src/plugins/identity/concerns/config.js +61 -0
  89. package/src/plugins/identity/concerns/mfa-manager.js +15 -2
  90. package/src/plugins/identity/concerns/rate-limit.js +124 -0
  91. package/src/plugins/identity/concerns/resource-schemas.js +9 -1
  92. package/src/plugins/identity/concerns/token-generator.js +29 -4
  93. package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
  94. package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
  95. package/src/plugins/identity/drivers/index.js +18 -0
  96. package/src/plugins/identity/drivers/password-driver.js +122 -0
  97. package/src/plugins/identity/email-service.js +17 -2
  98. package/src/plugins/identity/index.js +413 -69
  99. package/src/plugins/identity/oauth2-server.js +413 -30
  100. package/src/plugins/identity/oidc-discovery.js +16 -8
  101. package/src/plugins/identity/rsa-keys.js +115 -35
  102. package/src/plugins/identity/server.js +166 -45
  103. package/src/plugins/identity/session-manager.js +53 -7
  104. package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
  105. package/src/plugins/identity/ui/routes.js +363 -255
  106. package/src/plugins/importer/index.js +153 -20
  107. package/src/plugins/index.js +9 -2
  108. package/src/plugins/kubernetes-inventory/index.js +6 -0
  109. package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
  110. package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
  111. package/src/plugins/kubernetes-inventory.plugin.js +980 -0
  112. package/src/plugins/metrics.plugin.js +64 -16
  113. package/src/plugins/ml/base-model.class.js +25 -15
  114. package/src/plugins/ml/regression-model.class.js +1 -1
  115. package/src/plugins/ml.errors.js +57 -25
  116. package/src/plugins/ml.plugin.js +28 -4
  117. package/src/plugins/namespace.js +210 -0
  118. package/src/plugins/plugin.class.js +180 -8
  119. package/src/plugins/puppeteer/console-monitor.js +729 -0
  120. package/src/plugins/puppeteer/cookie-manager.js +492 -0
  121. package/src/plugins/puppeteer/network-monitor.js +816 -0
  122. package/src/plugins/puppeteer/performance-manager.js +746 -0
  123. package/src/plugins/puppeteer/proxy-manager.js +478 -0
  124. package/src/plugins/puppeteer/stealth-manager.js +556 -0
  125. package/src/plugins/puppeteer.errors.js +81 -0
  126. package/src/plugins/puppeteer.plugin.js +1327 -0
  127. package/src/plugins/queue-consumer.plugin.js +69 -14
  128. package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
  129. package/src/plugins/recon/concerns/command-runner.js +148 -0
  130. package/src/plugins/recon/concerns/diff-detector.js +372 -0
  131. package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
  132. package/src/plugins/recon/concerns/process-manager.js +338 -0
  133. package/src/plugins/recon/concerns/report-generator.js +478 -0
  134. package/src/plugins/recon/concerns/security-analyzer.js +571 -0
  135. package/src/plugins/recon/concerns/target-normalizer.js +68 -0
  136. package/src/plugins/recon/config/defaults.js +321 -0
  137. package/src/plugins/recon/config/resources.js +370 -0
  138. package/src/plugins/recon/index.js +778 -0
  139. package/src/plugins/recon/managers/dependency-manager.js +174 -0
  140. package/src/plugins/recon/managers/scheduler-manager.js +179 -0
  141. package/src/plugins/recon/managers/storage-manager.js +745 -0
  142. package/src/plugins/recon/managers/target-manager.js +274 -0
  143. package/src/plugins/recon/stages/asn-stage.js +314 -0
  144. package/src/plugins/recon/stages/certificate-stage.js +84 -0
  145. package/src/plugins/recon/stages/dns-stage.js +107 -0
  146. package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
  147. package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
  148. package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
  149. package/src/plugins/recon/stages/http-stage.js +89 -0
  150. package/src/plugins/recon/stages/latency-stage.js +148 -0
  151. package/src/plugins/recon/stages/massdns-stage.js +302 -0
  152. package/src/plugins/recon/stages/osint-stage.js +1373 -0
  153. package/src/plugins/recon/stages/ports-stage.js +169 -0
  154. package/src/plugins/recon/stages/screenshot-stage.js +94 -0
  155. package/src/plugins/recon/stages/secrets-stage.js +514 -0
  156. package/src/plugins/recon/stages/subdomains-stage.js +295 -0
  157. package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
  158. package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
  159. package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
  160. package/src/plugins/recon/stages/whois-stage.js +349 -0
  161. package/src/plugins/recon.plugin.js +75 -0
  162. package/src/plugins/recon.plugin.js.backup +2635 -0
  163. package/src/plugins/relation.errors.js +87 -14
  164. package/src/plugins/replicator.plugin.js +514 -137
  165. package/src/plugins/replicators/base-replicator.class.js +89 -1
  166. package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
  167. package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
  168. package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
  169. package/src/plugins/replicators/mysql-replicator.class.js +52 -17
  170. package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
  171. package/src/plugins/replicators/postgres-replicator.class.js +62 -27
  172. package/src/plugins/replicators/s3db-replicator.class.js +25 -18
  173. package/src/plugins/replicators/schema-sync.helper.js +3 -3
  174. package/src/plugins/replicators/sqs-replicator.class.js +8 -2
  175. package/src/plugins/replicators/turso-replicator.class.js +23 -3
  176. package/src/plugins/replicators/webhook-replicator.class.js +42 -4
  177. package/src/plugins/s3-queue.plugin.js +464 -65
  178. package/src/plugins/scheduler.plugin.js +20 -6
  179. package/src/plugins/state-machine.plugin.js +40 -9
  180. package/src/plugins/tfstate/README.md +126 -126
  181. package/src/plugins/tfstate/base-driver.js +28 -4
  182. package/src/plugins/tfstate/errors.js +65 -10
  183. package/src/plugins/tfstate/filesystem-driver.js +52 -8
  184. package/src/plugins/tfstate/index.js +163 -90
  185. package/src/plugins/tfstate/s3-driver.js +64 -6
  186. package/src/plugins/ttl.plugin.js +72 -17
  187. package/src/plugins/vector/distances.js +18 -12
  188. package/src/plugins/vector/kmeans.js +26 -4
  189. package/src/resource.class.js +115 -19
  190. package/src/testing/factory.class.js +20 -3
  191. package/src/testing/seeder.class.js +7 -1
  192. package/src/clients/memory-client.md +0 -917
  193. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
@@ -0,0 +1,100 @@
1
+ import { PluginError } from '../../errors.js';
2
+
3
+ const PREFIX = 'plg_';
4
+
5
+ function normalizeNamespace(namespace) {
6
+ if (!namespace) return null;
7
+ const text = String(namespace).trim().toLowerCase();
8
+ if (!text) return null;
9
+ const normalized = text
10
+ .replace(/[^a-z0-9]+/g, '-') // Use hyphens instead of underscores
11
+ .replace(/^-+/, '')
12
+ .replace(/-+$/, '');
13
+ return normalized || null;
14
+ }
15
+
16
+ function applyNamespace(name, namespace) {
17
+ const ensured = ensurePlgPrefix(name);
18
+ if (!namespace) {
19
+ return ensured;
20
+ }
21
+
22
+ const withoutPrefix = ensured.slice(PREFIX.length);
23
+ if (withoutPrefix.startsWith(`${namespace}_`)) {
24
+ return ensured;
25
+ }
26
+
27
+ return `${PREFIX}${namespace}_${withoutPrefix}`;
28
+ }
29
+
30
+ function sanitizeName(name) {
31
+ if (!name || typeof name !== 'string') {
32
+ throw new PluginError('[resource-names] Resource name must be a non-empty string', {
33
+ pluginName: 'SharedConcerns',
34
+ operation: 'resourceNames:sanitize',
35
+ statusCode: 400,
36
+ retriable: false,
37
+ suggestion: 'Pass a non-empty string when deriving resource names.'
38
+ });
39
+ }
40
+ return name.trim();
41
+ }
42
+
43
+ export function ensurePlgPrefix(name) {
44
+ const sanitized = sanitizeName(name);
45
+ if (sanitized.startsWith(PREFIX)) {
46
+ return sanitized;
47
+ }
48
+ return `${PREFIX}${sanitized.replace(/^\_+/, '')}`;
49
+ }
50
+
51
+ export function resolveResourceName(pluginKey, { defaultName, override, suffix } = {}, options = {}) {
52
+ const namespace = normalizeNamespace(options.namespace);
53
+ const applyOverrideNamespace = options.applyNamespaceToOverrides === true;
54
+
55
+ if (!defaultName && !override && !suffix) {
56
+ throw new PluginError(`[resource-names] Missing name parameters for plugin "${pluginKey}"`, {
57
+ pluginName: 'SharedConcerns',
58
+ operation: 'resourceNames:resolve',
59
+ statusCode: 400,
60
+ retriable: false,
61
+ suggestion: 'Provide at least one of defaultName, override, or suffix when resolving resource names.'
62
+ });
63
+ }
64
+
65
+ if (override) {
66
+ const ensured = ensurePlgPrefix(override);
67
+ return applyOverrideNamespace ? applyNamespace(ensured, namespace) : ensured;
68
+ }
69
+
70
+ if (defaultName) {
71
+ const ensured = defaultName.startsWith(PREFIX) ? defaultName : ensurePlgPrefix(defaultName);
72
+ return applyNamespace(ensured, namespace);
73
+ }
74
+
75
+ if (!suffix) {
76
+ throw new PluginError(`[resource-names] Cannot derive resource name for plugin "${pluginKey}" without suffix`, {
77
+ pluginName: 'SharedConcerns',
78
+ operation: 'resourceNames:resolve',
79
+ statusCode: 400,
80
+ retriable: false,
81
+ suggestion: 'Provide a suffix or defaultName when computing derived resource names.'
82
+ });
83
+ }
84
+
85
+ const ensured = ensurePlgPrefix(`${pluginKey}_${suffix}`);
86
+ return applyNamespace(ensured, namespace);
87
+ }
88
+
89
+ export function resolveResourceNames(pluginKey, descriptors = {}, options = {}) {
90
+ const result = {};
91
+ for (const [key, descriptor] of Object.entries(descriptors)) {
92
+ if (typeof descriptor === 'string') {
93
+ result[key] = resolveResourceName(pluginKey, { defaultName: descriptor }, options);
94
+ continue;
95
+ }
96
+
97
+ result[key] = resolveResourceName(pluginKey, descriptor, options);
98
+ }
99
+ return result;
100
+ }
@@ -1,5 +1,6 @@
1
1
  import { SqsConsumer } from './sqs-consumer.js';
2
2
  import { RabbitMqConsumer } from './rabbitmq-consumer.js';
3
+ import { PluginError } from '../../errors.js';
3
4
 
4
5
  export { SqsConsumer, RabbitMqConsumer };
5
6
 
@@ -18,7 +19,14 @@ export const CONSUMER_DRIVERS = {
18
19
  export function createConsumer(driver, config) {
19
20
  const ConsumerClass = CONSUMER_DRIVERS[driver];
20
21
  if (!ConsumerClass) {
21
- throw new Error(`Unknown consumer driver: ${driver}. Available: ${Object.keys(CONSUMER_DRIVERS).join(', ')}`);
22
+ throw new PluginError(`Unknown consumer driver: ${driver}`, {
23
+ pluginName: 'ConsumersPlugin',
24
+ operation: 'createConsumer',
25
+ statusCode: 400,
26
+ retriable: false,
27
+ suggestion: `Use one of the available drivers: ${Object.keys(CONSUMER_DRIVERS).join(', ')}`,
28
+ driver
29
+ });
22
30
  }
23
31
  return new ConsumerClass(config);
24
- }
32
+ }
@@ -1,5 +1,6 @@
1
1
  import tryFn from "../../concerns/try-fn.js";
2
2
  import requirePluginDependency from "../concerns/plugin-dependencies.js";
3
+ import { PluginError } from '../../errors.js';
3
4
  // Remove static SDK import
4
5
  // import { SQSClient, ReceiveMessageCommand, DeleteMessageCommand } from '@aws-sdk/client-sqs';
5
6
 
@@ -31,7 +32,16 @@ export class SqsConsumer {
31
32
 
32
33
  // Carregar SDK dinamicamente
33
34
  const [ok, err, sdk] = await tryFn(() => import('@aws-sdk/client-sqs'));
34
- if (!ok) throw new Error('SqsConsumer: @aws-sdk/client-sqs is not installed. Please install it to use the SQS consumer.');
35
+ if (!ok) {
36
+ throw new PluginError('SqsConsumer requires @aws-sdk/client-sqs', {
37
+ pluginName: 'ConsumersPlugin',
38
+ operation: 'SqsConsumer.start',
39
+ statusCode: 500,
40
+ retriable: false,
41
+ suggestion: 'Install @aws-sdk/client-sqs as a dependency to enable SQS consumption.',
42
+ original: err
43
+ });
44
+ }
35
45
  const { SQSClient, ReceiveMessageCommand, DeleteMessageCommand } = sdk;
36
46
  this._SQSClient = SQSClient;
37
47
  this._ReceiveMessageCommand = ReceiveMessageCommand;
@@ -103,4 +113,4 @@ export class SqsConsumer {
103
113
  }
104
114
  return { $body: body, $attributes: attributes, $raw: msg };
105
115
  }
106
- }
116
+ }
@@ -0,0 +1,278 @@
1
+ import tryFn from "../concerns/try-fn.js";
2
+ import { Plugin } from "./plugin.class.js";
3
+ import { PuppeteerPlugin } from "./puppeteer.plugin.js";
4
+ import { CookieFarmPlugin } from "./cookie-farm.plugin.js";
5
+ import { S3QueuePlugin } from "./s3-queue.plugin.js";
6
+ import { TTLPlugin } from "./ttl.plugin.js";
7
+ import { PluginError } from "../errors.js";
8
+
9
+ function sanitizeNamespace(value) {
10
+ return (value || 'persona')
11
+ .toString()
12
+ .trim()
13
+ .toLowerCase()
14
+ .replace(/[^a-z0-9]+/g, '-');
15
+ }
16
+
17
+ function defaultJobsResource(namespace) {
18
+ return `${namespace.replace(/[^a-z0-9]+/g, '_')}_persona_jobs`;
19
+ }
20
+
21
+ /**
22
+ * CookieFarmSuitePlugin
23
+ *
24
+ * Bundles CookieFarm + Puppeteer + S3Queue (+ optional TTL) with shared
25
+ * namespace handling for persona farming workloads.
26
+ */
27
+ export class CookieFarmSuitePlugin extends Plugin {
28
+ constructor(options = {}) {
29
+ const namespace = options.namespace || 'persona';
30
+ super({ ...options, namespace });
31
+
32
+ this.namespace = this.namespace || sanitizeNamespace(namespace);
33
+
34
+ const jobsResource =
35
+ options.jobsResource ||
36
+ (options.resources && options.resources.jobs) ||
37
+ defaultJobsResource(this.namespace);
38
+
39
+ this.config = {
40
+ namespace: this.namespace,
41
+ jobsResource,
42
+ queue: {
43
+ resource: jobsResource,
44
+ deadLetterResource: options.queue?.deadLetterResource || null,
45
+ visibilityTimeout: options.queue?.visibilityTimeout || 30000,
46
+ pollInterval: options.queue?.pollInterval || 1000,
47
+ maxAttempts: options.queue?.maxAttempts || 3,
48
+ concurrency: options.queue?.concurrency || 1,
49
+ autoStart: options.queue?.autoStart === true,
50
+ ...options.queue
51
+ },
52
+ puppeteer: {
53
+ pool: { enabled: false },
54
+ ...options.puppeteer
55
+ },
56
+ cookieFarm: {
57
+ ...options.cookieFarm
58
+ },
59
+ ttl: options.ttl || null,
60
+ processor: typeof options.processor === 'function' ? options.processor : null
61
+ };
62
+
63
+ this.pluginFactories = {
64
+ puppeteer: options.pluginFactories?.puppeteer ||
65
+ ((pluginOptions) => new PuppeteerPlugin(pluginOptions)),
66
+ cookieFarm: options.pluginFactories?.cookieFarm ||
67
+ ((pluginOptions) => new CookieFarmPlugin(pluginOptions)),
68
+ queue: options.pluginFactories?.queue ||
69
+ ((queueOptions) => new S3QueuePlugin(queueOptions)),
70
+ ttl: options.pluginFactories?.ttl ||
71
+ ((ttlOptions) => new TTLPlugin(ttlOptions))
72
+ };
73
+
74
+ this.dependencies = [];
75
+ this.jobsResource = null;
76
+ this.puppeteerPlugin = null;
77
+ this.cookieFarmPlugin = null;
78
+ this.queuePlugin = null;
79
+ this.ttlPlugin = null;
80
+ this.processor = this.config.processor;
81
+
82
+ this.queueHandler = this.queueHandler.bind(this);
83
+ }
84
+
85
+ _dependencyName(alias) {
86
+ return `${this.namespace}-${alias}`.toLowerCase();
87
+ }
88
+
89
+ async _installDependency(alias, plugin) {
90
+ const name = this._dependencyName(alias);
91
+ const instance = await this.database.usePlugin(plugin, name);
92
+ this.dependencies.push({ name, instance });
93
+ return instance;
94
+ }
95
+
96
+ async _ensureJobsResource() {
97
+ if (this.database.resources?.[this.config.jobsResource]) {
98
+ this.jobsResource = this.database.resources[this.config.jobsResource];
99
+ return;
100
+ }
101
+
102
+ const [created, err, resource] = await tryFn(() => this.database.createResource({
103
+ name: this.config.jobsResource,
104
+ attributes: {
105
+ id: 'string|required',
106
+ jobType: 'string|required',
107
+ payload: 'json|optional',
108
+ priority: 'number|default:0',
109
+ requestedBy: 'string|optional',
110
+ metadata: 'json|optional',
111
+ createdAt: 'string|required'
112
+ },
113
+ behavior: 'body-overflow',
114
+ timestamps: true,
115
+ asyncPartitions: true,
116
+ partitions: {
117
+ byJobType: { fields: { jobType: 'string' } },
118
+ byPriority: { fields: { priority: 'number' } },
119
+ byDate: { fields: { createdAt: 'string|maxlength:10' } }
120
+ }
121
+ }));
122
+
123
+ if (!created) {
124
+ if (resource) {
125
+ this.jobsResource = resource;
126
+ return;
127
+ }
128
+ throw err;
129
+ }
130
+
131
+ this.jobsResource = this.database.resources[this.config.jobsResource];
132
+ }
133
+
134
+ async onInstall() {
135
+ await this._ensureJobsResource();
136
+
137
+ this.puppeteerPlugin = await this._installDependency('puppeteer',
138
+ this.pluginFactories.puppeteer({
139
+ namespace: this.namespace,
140
+ ...this.config.puppeteer
141
+ })
142
+ );
143
+
144
+ this.cookieFarmPlugin = await this._installDependency('cookie-farm',
145
+ this.pluginFactories.cookieFarm({
146
+ namespace: this.namespace,
147
+ ...this.config.cookieFarm
148
+ })
149
+ );
150
+
151
+ const queueOptions = {
152
+ namespace: this.namespace,
153
+ resource: this.config.queue.resource,
154
+ deadLetterResource: this.config.queue.deadLetterResource,
155
+ visibilityTimeout: this.config.queue.visibilityTimeout,
156
+ pollInterval: this.config.queue.pollInterval,
157
+ maxAttempts: this.config.queue.maxAttempts,
158
+ concurrency: this.config.queue.concurrency,
159
+ autoStart: this.config.queue.autoStart && typeof this.processor === 'function',
160
+ onMessage: this.queueHandler,
161
+ verbose: this.config.queue.verbose
162
+ };
163
+
164
+ this.queuePlugin = await this._installDependency('queue', this.pluginFactories.queue(queueOptions));
165
+
166
+ if (this.config.ttl) {
167
+ const ttlConfig = {
168
+ namespace: this.namespace,
169
+ ...this.config.ttl
170
+ };
171
+
172
+ ttlConfig.resources = ttlConfig.resources || {};
173
+
174
+ if (!ttlConfig.resources[this.queuePlugin.queueResourceName]) {
175
+ ttlConfig.resources[this.queuePlugin.queueResourceName] = {
176
+ ttl: ttlConfig.queue?.ttl || 7200,
177
+ onExpire: ttlConfig.queue?.onExpire || 'hard-delete',
178
+ field: ttlConfig.queue?.field || null
179
+ };
180
+ }
181
+
182
+ delete ttlConfig.queue;
183
+
184
+ this.ttlPlugin = await this._installDependency('ttl', this.pluginFactories.ttl(ttlConfig));
185
+ }
186
+
187
+ this.emit('cookieFarmSuite.installed', {
188
+ namespace: this.namespace,
189
+ jobsResource: this.config.jobsResource
190
+ });
191
+ }
192
+
193
+ async onStart() {
194
+ if (this.config.queue.autoStart && typeof this.processor === 'function') {
195
+ await this.startProcessing();
196
+ }
197
+ }
198
+
199
+ async onStop() {
200
+ await this.stopProcessing();
201
+ }
202
+
203
+ async onUninstall(options = {}) {
204
+ await this.onStop();
205
+
206
+ for (const dep of [...this.dependencies].reverse()) {
207
+ await this.database.uninstallPlugin(dep.name, { purgeData: options.purgeData === true });
208
+ }
209
+ this.dependencies = [];
210
+ }
211
+
212
+ /**
213
+ * Register a job processor.
214
+ */
215
+ async setProcessor(handler, { autoStart = true, concurrency } = {}) {
216
+ this.processor = handler;
217
+
218
+ if (autoStart && typeof handler === 'function') {
219
+ await this.startProcessing({ concurrency });
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Enqueue a persona job.
225
+ */
226
+ async enqueueJob(data, options = {}) {
227
+ if (!this.jobsResource?.enqueue) {
228
+ throw new PluginError('[CookieFarmSuitePlugin] Queue helpers not initialized yet', {
229
+ pluginName: 'CookieFarmSuitePlugin',
230
+ operation: 'enqueueJob',
231
+ statusCode: 500,
232
+ retriable: false,
233
+ suggestion: 'Call plugin.initialize() before enqueuing jobs so queue helpers are registered.'
234
+ });
235
+ }
236
+ return await this.jobsResource.enqueue({
237
+ createdAt: new Date().toISOString().slice(0, 10),
238
+ ...data
239
+ }, options);
240
+ }
241
+
242
+ async startProcessing(options = {}) {
243
+ if (!this.jobsResource?.startProcessing) return;
244
+ const concurrency = options.concurrency || this.config.queue.concurrency;
245
+ await this.jobsResource.startProcessing(this.queueHandler, { concurrency });
246
+ }
247
+
248
+ async stopProcessing() {
249
+ if (this.jobsResource?.stopProcessing) {
250
+ await this.jobsResource.stopProcessing();
251
+ }
252
+ }
253
+
254
+ async queueHandler(record, context) {
255
+ if (typeof this.processor !== 'function') {
256
+ throw new PluginError('[CookieFarmSuitePlugin] No processor registered. Call setProcessor(fn) first.', {
257
+ pluginName: 'CookieFarmSuitePlugin',
258
+ operation: 'queueHandler',
259
+ statusCode: 500,
260
+ retriable: false,
261
+ suggestion: 'Register a processor via plugin.setProcessor(jobHandler) before starting the queue.'
262
+ });
263
+ }
264
+
265
+ const helpers = {
266
+ puppeteer: this.puppeteerPlugin,
267
+ cookieFarm: this.cookieFarmPlugin,
268
+ queue: this.queuePlugin,
269
+ enqueue: this.enqueueJob.bind(this),
270
+ resource: this.jobsResource,
271
+ plugin: this
272
+ };
273
+
274
+ return await this.processor(record, context, helpers);
275
+ }
276
+ }
277
+
278
+ export default CookieFarmSuitePlugin;
@@ -0,0 +1,73 @@
1
+ import { PluginError } from '../errors.js';
2
+
3
+ /**
4
+ * Custom errors for CookieFarmPlugin with actionable guidance.
5
+ */
6
+
7
+ export class CookieFarmError extends PluginError {
8
+ constructor(message, details = {}) {
9
+ const merged = {
10
+ pluginName: details.pluginName || 'CookieFarmPlugin',
11
+ operation: details.operation || 'unknown',
12
+ statusCode: details.statusCode ?? 500,
13
+ retriable: details.retriable ?? false,
14
+ suggestion: details.suggestion ?? 'Check CookieFarmPlugin configuration and persona storage before retrying.',
15
+ ...details
16
+ };
17
+ super(message, merged);
18
+ }
19
+ }
20
+
21
+ export class PersonaNotFoundError extends CookieFarmError {
22
+ constructor(personaId, details = {}) {
23
+ super(`Persona not found: ${personaId}`, {
24
+ code: 'PERSONA_NOT_FOUND',
25
+ personaId,
26
+ statusCode: 404,
27
+ retriable: false,
28
+ suggestion: 'Ensure the persona exists or create it before running CookieFarm operations.',
29
+ docs: details.docs || 'https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/cookie-farm.md',
30
+ ...details
31
+ });
32
+ this.name = 'PersonaNotFoundError';
33
+ }
34
+ }
35
+
36
+ export class WarmupError extends CookieFarmError {
37
+ constructor(message, details = {}) {
38
+ super(message, {
39
+ code: 'WARMUP_ERROR',
40
+ statusCode: details.statusCode ?? 500,
41
+ retriable: details.retriable ?? true,
42
+ suggestion: details.suggestion ?? 'Inspect warmup logs and retry; ensure upstream services are reachable.',
43
+ ...details
44
+ });
45
+ this.name = 'WarmupError';
46
+ }
47
+ }
48
+
49
+ export class GenerationError extends CookieFarmError {
50
+ constructor(message, details = {}) {
51
+ super(message, {
52
+ code: 'GENERATION_ERROR',
53
+ statusCode: details.statusCode ?? 500,
54
+ retriable: details.retriable ?? false,
55
+ suggestion: details.suggestion ?? 'Review persona generation inputs and plugin configuration before retrying.',
56
+ ...details
57
+ });
58
+ this.name = 'GenerationError';
59
+ }
60
+ }
61
+
62
+ export class QualityCalculationError extends CookieFarmError {
63
+ constructor(message, details = {}) {
64
+ super(message, {
65
+ code: 'QUALITY_CALCULATION_ERROR',
66
+ statusCode: details.statusCode ?? 500,
67
+ retriable: details.retriable ?? false,
68
+ suggestion: details.suggestion ?? 'Verify quality metrics configuration and input scores.',
69
+ ...details
70
+ });
71
+ this.name = 'QualityCalculationError';
72
+ }
73
+ }