scrapely 1.2.3 → 1.2.5

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 (86) hide show
  1. package/README.md +119 -1
  2. package/dist/actor.d.ts +703 -0
  3. package/dist/actor.js +1441 -0
  4. package/dist/browser-configuration.d.ts +58 -0
  5. package/dist/browser-configuration.js +128 -0
  6. package/dist/browser-locations.d.ts +5 -0
  7. package/dist/browser-locations.js +5469 -0
  8. package/dist/charging.d.ts +153 -0
  9. package/dist/charging.js +782 -0
  10. package/dist/charging_client.d.ts +18 -0
  11. package/dist/charging_client.js +49 -0
  12. package/dist/configuration.d.ts +121 -0
  13. package/dist/configuration.js +128 -0
  14. package/dist/crypto/secrets.d.ts +44 -0
  15. package/dist/crypto/secrets.js +164 -0
  16. package/dist/events/events-client.d.ts +59 -0
  17. package/dist/events/events-client.js +120 -0
  18. package/dist/events/scrapely-event-manager.d.ts +13 -0
  19. package/dist/events/scrapely-event-manager.js +61 -0
  20. package/dist/index.d.ts +29 -0
  21. package/dist/index.js +95 -0
  22. package/dist/log-env.d.ts +1 -0
  23. package/dist/log-env.js +12 -0
  24. package/dist/manifest.d.ts +30 -0
  25. package/dist/manifest.js +47 -0
  26. package/dist/patch-browser-pool.d.ts +66 -0
  27. package/dist/patch-browser-pool.js +827 -0
  28. package/dist/proxy-configuration.d.ts +118 -0
  29. package/dist/proxy-configuration.js +88 -0
  30. package/dist/request-list.d.ts +57 -0
  31. package/dist/request-list.js +79 -0
  32. package/dist/runner/docker-manager.d.ts +88 -0
  33. package/dist/runner/docker-manager.js +283 -0
  34. package/dist/runner/index.d.ts +2 -0
  35. package/dist/runner/index.js +7 -0
  36. package/dist/runner/timeout-manager.d.ts +43 -0
  37. package/dist/runner/timeout-manager.js +90 -0
  38. package/dist/scrapely-proxy-configuration.d.ts +99 -0
  39. package/dist/scrapely-proxy-configuration.js +559 -0
  40. package/dist/storage/api-storage-client.d.ts +199 -0
  41. package/dist/storage/api-storage-client.js +910 -0
  42. package/dist/storage/billing-tracker.d.ts +163 -0
  43. package/dist/storage/billing-tracker.js +260 -0
  44. package/dist/storage/dataset-client.d.ts +43 -0
  45. package/dist/storage/dataset-client.js +232 -0
  46. package/dist/storage/dataset-collection-client.d.ts +8 -0
  47. package/dist/storage/dataset-collection-client.js +75 -0
  48. package/dist/storage/dataset-push-script.d.ts +18 -0
  49. package/dist/storage/dataset-push-script.js +38 -0
  50. package/dist/storage/dragonfly-client.d.ts +94 -0
  51. package/dist/storage/dragonfly-client.js +167 -0
  52. package/dist/storage/index.d.ts +11 -0
  53. package/dist/storage/index.js +25 -0
  54. package/dist/storage/key-value-store-client.d.ts +61 -0
  55. package/dist/storage/key-value-store-client.js +306 -0
  56. package/dist/storage/kv-store-collection-client.d.ts +8 -0
  57. package/dist/storage/kv-store-collection-client.js +76 -0
  58. package/dist/storage/kvs-iteration.d.ts +17 -0
  59. package/dist/storage/kvs-iteration.js +126 -0
  60. package/dist/storage/log-manager.d.ts +114 -0
  61. package/dist/storage/log-manager.js +244 -0
  62. package/dist/storage/patch-storage.d.ts +11 -0
  63. package/dist/storage/patch-storage.js +177 -0
  64. package/dist/storage/request-queue-client.d.ts +93 -0
  65. package/dist/storage/request-queue-client.js +522 -0
  66. package/dist/storage/rq-collection-client.d.ts +8 -0
  67. package/dist/storage/rq-collection-client.js +89 -0
  68. package/dist/storage/rq-list-requests.d.ts +59 -0
  69. package/dist/storage/rq-list-requests.js +114 -0
  70. package/dist/storage/rq-lock-script.d.ts +56 -0
  71. package/dist/storage/rq-lock-script.js +127 -0
  72. package/dist/storage/run-metadata.d.ts +98 -0
  73. package/dist/storage/run-metadata.js +256 -0
  74. package/dist/storage/s3-client.d.ts +103 -0
  75. package/dist/storage/s3-client.js +302 -0
  76. package/dist/storage/ttl-manager.d.ts +70 -0
  77. package/dist/storage/ttl-manager.js +115 -0
  78. package/dist/storage/utils.d.ts +3 -0
  79. package/dist/storage/utils.js +19 -0
  80. package/dist/storage-identifiers.d.ts +20 -0
  81. package/dist/storage-identifiers.js +6 -0
  82. package/dist/utils.d.ts +54 -0
  83. package/dist/utils.js +63 -0
  84. package/dist/validator.d.ts +24 -0
  85. package/dist/validator.js +158 -0
  86. package/package.json +90 -9
@@ -0,0 +1,103 @@
1
+ import type { UserPlan } from './ttl-manager.js';
2
+ /**
3
+ * S3 storage configuration for Hetzner Object Storage
4
+ */
5
+ export interface S3Config {
6
+ endpoint: string;
7
+ accessKey: string;
8
+ secretKey: string;
9
+ bucket: string;
10
+ region?: string;
11
+ }
12
+ /**
13
+ * Metadata stored with each S3 log object
14
+ */
15
+ export interface LogMetadata {
16
+ userId: string;
17
+ runId: string;
18
+ createdAt: string;
19
+ expiresAt: string;
20
+ userPlan: UserPlan;
21
+ contentType: string;
22
+ size?: number;
23
+ }
24
+ /**
25
+ * S3 client for log storage with Hetzner Object Storage support.
26
+ *
27
+ * Storage model:
28
+ * - Path: logs/{userId}/{runId}.log
29
+ * - Metadata includes TTL info for cleanup job
30
+ * - Supports streaming for large logs
31
+ */
32
+ export declare class S3LogClient {
33
+ private client;
34
+ private bucket;
35
+ constructor(config: S3Config);
36
+ /**
37
+ * Get the S3 key for a run's log
38
+ */
39
+ private getLogKey;
40
+ /**
41
+ * Calculate expiration timestamp based on user plan
42
+ */
43
+ private calculateExpiresAt;
44
+ /**
45
+ * Upload log content to S3 with metadata for TTL tracking.
46
+ * Called when a run completes.
47
+ */
48
+ uploadLog(userId: string, runId: string, content: string | Buffer, userPlan: UserPlan): Promise<{
49
+ key: string;
50
+ metadata: LogMetadata;
51
+ }>;
52
+ /**
53
+ * Get log content from S3.
54
+ * Returns null if not found.
55
+ */
56
+ getLog(userId: string, runId: string): Promise<string | null>;
57
+ /**
58
+ * Get log as a stream for large logs.
59
+ */
60
+ getLogStream(userId: string, runId: string): Promise<NodeJS.ReadableStream | null>;
61
+ /**
62
+ * Get metadata for a log object without fetching content.
63
+ */
64
+ getLogMetadata(userId: string, runId: string): Promise<LogMetadata | null>;
65
+ /**
66
+ * Delete a log from S3.
67
+ */
68
+ deleteLog(userId: string, runId: string): Promise<boolean>;
69
+ /**
70
+ * List logs that have expired (for cleanup job).
71
+ * Returns up to 1000 expired logs at a time.
72
+ */
73
+ listExpiredLogs(): Promise<Array<{
74
+ key: string;
75
+ userId: string;
76
+ runId: string;
77
+ expiresAt: string;
78
+ }>>;
79
+ /**
80
+ * Delete multiple expired logs in batch.
81
+ * Returns count of deleted objects.
82
+ */
83
+ deleteExpiredLogs(): Promise<{
84
+ deleted: number;
85
+ errors: number;
86
+ }>;
87
+ /**
88
+ * Check if S3 is properly configured and accessible.
89
+ */
90
+ healthCheck(): Promise<boolean>;
91
+ /**
92
+ * Create S3Config from environment variables.
93
+ */
94
+ static fromEnv(): S3Config;
95
+ }
96
+ /**
97
+ * Get or create the S3 log client singleton.
98
+ */
99
+ export declare function getS3LogClient(): S3LogClient | null;
100
+ /**
101
+ * Initialize S3 client with explicit config.
102
+ */
103
+ export declare function initS3LogClient(config: S3Config): S3LogClient;
@@ -0,0 +1,302 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.S3LogClient = void 0;
4
+ exports.getS3LogClient = getS3LogClient;
5
+ exports.initS3LogClient = initS3LogClient;
6
+ const client_s3_1 = require("@aws-sdk/client-s3");
7
+ /**
8
+ * TTL for logs based on user plan (in seconds)
9
+ */
10
+ const LOG_TTL = {
11
+ free: 7 * 24 * 60 * 60, // 7 days
12
+ paid: 31 * 24 * 60 * 60, // 31 days
13
+ };
14
+ /**
15
+ * S3 client for log storage with Hetzner Object Storage support.
16
+ *
17
+ * Storage model:
18
+ * - Path: logs/{userId}/{runId}.log
19
+ * - Metadata includes TTL info for cleanup job
20
+ * - Supports streaming for large logs
21
+ */
22
+ class S3LogClient {
23
+ client;
24
+ bucket;
25
+ constructor(config) {
26
+ this.bucket = config.bucket;
27
+ this.client = new client_s3_1.S3Client({
28
+ endpoint: config.endpoint,
29
+ region: config.region || 'us-east-1',
30
+ credentials: {
31
+ accessKeyId: config.accessKey,
32
+ secretAccessKey: config.secretKey,
33
+ },
34
+ forcePathStyle: true, // Required for Hetzner S3-compatible storage
35
+ });
36
+ }
37
+ /**
38
+ * Get the S3 key for a run's log
39
+ */
40
+ getLogKey(userId, runId) {
41
+ return `logs/${userId}/${runId}.log`;
42
+ }
43
+ /**
44
+ * Calculate expiration timestamp based on user plan
45
+ */
46
+ calculateExpiresAt(userPlan) {
47
+ const ttlSeconds = LOG_TTL[userPlan];
48
+ const expiresAt = new Date(Date.now() + ttlSeconds * 1000);
49
+ return expiresAt.toISOString();
50
+ }
51
+ /**
52
+ * Upload log content to S3 with metadata for TTL tracking.
53
+ * Called when a run completes.
54
+ */
55
+ async uploadLog(userId, runId, content, userPlan) {
56
+ const now = new Date();
57
+ const key = this.getLogKey(userId, runId);
58
+ const expiresAt = this.calculateExpiresAt(userPlan);
59
+ const buffer = typeof content === 'string' ? Buffer.from(content, 'utf-8') : content;
60
+ const metadata = {
61
+ userId,
62
+ runId,
63
+ createdAt: now.toISOString(),
64
+ expiresAt,
65
+ userPlan,
66
+ contentType: 'text/plain; charset=utf-8',
67
+ size: buffer.length,
68
+ };
69
+ await this.client.send(new client_s3_1.PutObjectCommand({
70
+ Bucket: this.bucket,
71
+ Key: key,
72
+ Body: buffer,
73
+ ContentType: 'text/plain; charset=utf-8',
74
+ Metadata: {
75
+ 'user-id': userId,
76
+ 'run-id': runId,
77
+ 'created-at': metadata.createdAt,
78
+ 'expires-at': expiresAt,
79
+ 'user-plan': userPlan,
80
+ },
81
+ }));
82
+ return { key, metadata };
83
+ }
84
+ /**
85
+ * Get log content from S3.
86
+ * Returns null if not found.
87
+ */
88
+ async getLog(userId, runId) {
89
+ const key = this.getLogKey(userId, runId);
90
+ try {
91
+ const response = await this.client.send(new client_s3_1.GetObjectCommand({
92
+ Bucket: this.bucket,
93
+ Key: key,
94
+ }));
95
+ if (!response.Body)
96
+ return null;
97
+ const content = await response.Body.transformToString('utf-8');
98
+ return content;
99
+ }
100
+ catch (error) {
101
+ if (error.name === 'NoSuchKey' || error.$metadata?.httpStatusCode === 404) {
102
+ return null;
103
+ }
104
+ throw error;
105
+ }
106
+ }
107
+ /**
108
+ * Get log as a stream for large logs.
109
+ */
110
+ async getLogStream(userId, runId) {
111
+ const key = this.getLogKey(userId, runId);
112
+ try {
113
+ const response = await this.client.send(new client_s3_1.GetObjectCommand({
114
+ Bucket: this.bucket,
115
+ Key: key,
116
+ }));
117
+ if (!response.Body)
118
+ return null;
119
+ // Convert WebStream to NodeJS ReadableStream if needed
120
+ return response.Body;
121
+ }
122
+ catch (error) {
123
+ if (error.name === 'NoSuchKey' || error.$metadata?.httpStatusCode === 404) {
124
+ return null;
125
+ }
126
+ throw error;
127
+ }
128
+ }
129
+ /**
130
+ * Get metadata for a log object without fetching content.
131
+ */
132
+ async getLogMetadata(userId, runId) {
133
+ const key = this.getLogKey(userId, runId);
134
+ try {
135
+ const response = await this.client.send(new client_s3_1.HeadObjectCommand({
136
+ Bucket: this.bucket,
137
+ Key: key,
138
+ }));
139
+ const metadata = response.Metadata || {};
140
+ return {
141
+ userId: metadata['user-id'] || userId,
142
+ runId: metadata['run-id'] || runId,
143
+ createdAt: metadata['created-at'] || new Date().toISOString(),
144
+ expiresAt: metadata['expires-at'] || '',
145
+ userPlan: metadata['user-plan'] || 'free',
146
+ contentType: 'text/plain; charset=utf-8',
147
+ size: response.ContentLength,
148
+ };
149
+ }
150
+ catch (error) {
151
+ if (error.$metadata?.httpStatusCode === 404) {
152
+ return null;
153
+ }
154
+ throw error;
155
+ }
156
+ }
157
+ /**
158
+ * Delete a log from S3.
159
+ */
160
+ async deleteLog(userId, runId) {
161
+ const key = this.getLogKey(userId, runId);
162
+ try {
163
+ await this.client.send(new client_s3_1.DeleteObjectCommand({
164
+ Bucket: this.bucket,
165
+ Key: key,
166
+ }));
167
+ return true;
168
+ }
169
+ catch (error) {
170
+ if (error.$metadata?.httpStatusCode === 404) {
171
+ return false;
172
+ }
173
+ throw error;
174
+ }
175
+ }
176
+ /**
177
+ * List logs that have expired (for cleanup job).
178
+ * Returns up to 1000 expired logs at a time.
179
+ */
180
+ async listExpiredLogs() {
181
+ const now = new Date();
182
+ const expiredLogs = [];
183
+ let continuationToken;
184
+ do {
185
+ const response = await this.client.send(new client_s3_1.ListObjectsV2Command({
186
+ Bucket: this.bucket,
187
+ Prefix: 'logs/',
188
+ ContinuationToken: continuationToken,
189
+ }));
190
+ if (!response.Contents)
191
+ break;
192
+ for (const object of response.Contents) {
193
+ if (!object.Key)
194
+ continue;
195
+ // Parse metadata to check expiration
196
+ try {
197
+ const headResponse = await this.client.send(new client_s3_1.HeadObjectCommand({
198
+ Bucket: this.bucket,
199
+ Key: object.Key,
200
+ }));
201
+ const expiresAtStr = headResponse.Metadata?.['expires-at'];
202
+ if (!expiresAtStr)
203
+ continue;
204
+ const expiresAt = new Date(expiresAtStr);
205
+ if (expiresAt <= now) {
206
+ // Parse userId and runId from key: logs/{userId}/{runId}.log
207
+ const parts = object.Key.split('/');
208
+ if (parts.length >= 3) {
209
+ const userId = parts[1];
210
+ const runId = parts[2].replace('.log', '');
211
+ expiredLogs.push({
212
+ key: object.Key,
213
+ userId,
214
+ runId,
215
+ expiresAt: expiresAtStr,
216
+ });
217
+ }
218
+ }
219
+ }
220
+ catch {
221
+ // Skip objects we can't read metadata for
222
+ continue;
223
+ }
224
+ }
225
+ continuationToken = response.NextContinuationToken;
226
+ } while (continuationToken);
227
+ return expiredLogs;
228
+ }
229
+ /**
230
+ * Delete multiple expired logs in batch.
231
+ * Returns count of deleted objects.
232
+ */
233
+ async deleteExpiredLogs() {
234
+ const expiredLogs = await this.listExpiredLogs();
235
+ let deleted = 0;
236
+ let errors = 0;
237
+ for (const log of expiredLogs) {
238
+ try {
239
+ await this.deleteLog(log.userId, log.runId);
240
+ deleted++;
241
+ }
242
+ catch {
243
+ errors++;
244
+ }
245
+ }
246
+ return { deleted, errors };
247
+ }
248
+ /**
249
+ * Check if S3 is properly configured and accessible.
250
+ */
251
+ async healthCheck() {
252
+ try {
253
+ await this.client.send(new client_s3_1.HeadObjectCommand({
254
+ Bucket: this.bucket,
255
+ Key: '__health_check__',
256
+ }));
257
+ return true; // Object exists or bucket is accessible
258
+ }
259
+ catch (error) {
260
+ // NoSuchKey means bucket exists but object doesn't - still healthy
261
+ if (error.name === 'NoSuchKey' || error.$metadata?.httpStatusCode === 404) {
262
+ return true;
263
+ }
264
+ return false;
265
+ }
266
+ }
267
+ /**
268
+ * Create S3Config from environment variables.
269
+ */
270
+ static fromEnv() {
271
+ return {
272
+ endpoint: process.env.S3_ENDPOINT || 'https://nbg1.your-objectstorage.com',
273
+ accessKey: process.env.S3_ACCESS_KEY || '',
274
+ secretKey: process.env.S3_SECRET_KEY || '',
275
+ bucket: process.env.S3_BUCKET || 'scrapely-logs',
276
+ region: process.env.S3_REGION || 'us-east-1',
277
+ };
278
+ }
279
+ }
280
+ exports.S3LogClient = S3LogClient;
281
+ // Singleton instance
282
+ let s3LogClient = null;
283
+ /**
284
+ * Get or create the S3 log client singleton.
285
+ */
286
+ function getS3LogClient() {
287
+ const config = S3LogClient.fromEnv();
288
+ if (!config.accessKey || !config.secretKey) {
289
+ return null;
290
+ }
291
+ if (!s3LogClient) {
292
+ s3LogClient = new S3LogClient(config);
293
+ }
294
+ return s3LogClient;
295
+ }
296
+ /**
297
+ * Initialize S3 client with explicit config.
298
+ */
299
+ function initS3LogClient(config) {
300
+ s3LogClient = new S3LogClient(config);
301
+ return s3LogClient;
302
+ }
@@ -0,0 +1,70 @@
1
+ import type Redis from 'ioredis';
2
+ /**
3
+ * TTL configuration in seconds
4
+ */
5
+ export interface TtlConfig {
6
+ /** TTL for unnamed datasets (free users) - default 7 days */
7
+ datasetFree: number;
8
+ /** TTL for unnamed datasets (paid users) - default 31 days */
9
+ datasetPaid: number;
10
+ /** TTL for unnamed KV stores (free users) - default 7 days */
11
+ kvFree: number;
12
+ /** TTL for unnamed KV stores (paid users) - default 31 days */
13
+ kvPaid: number;
14
+ /** Fallback TTL for request queues (safety net for orphaned RQs) - default 24 hours */
15
+ requestQueueFallback: number;
16
+ /** TTL for logs in Redis (free users) - default 12 hours */
17
+ logsFree: number;
18
+ /** TTL for logs in Redis (paid users) - default 7 days */
19
+ logsPaid: number;
20
+ /** Maximum log size in bytes - default 5MB */
21
+ logMaxSize: number;
22
+ }
23
+ /**
24
+ * Default TTL configuration based on Apify's model
25
+ */
26
+ export declare const DEFAULT_TTL_CONFIG: TtlConfig;
27
+ export type StorageType = 'dataset' | 'kv' | 'rq' | 'logs';
28
+ export type UserPlan = 'free' | 'paid';
29
+ /**
30
+ * Manages TTL (Time-To-Live) for Dragonfly storage keys.
31
+ *
32
+ * Retention policy:
33
+ * - Named storage: Indefinite (billed), no TTL set
34
+ * - Unnamed datasets/KV stores: 7 days (free) or 31 days (paid)
35
+ * - Request queues: Explicitly deleted after run, 24h fallback TTL for orphaned RQs
36
+ * - Logs: Short TTL in Redis, persisted to S3
37
+ */
38
+ export declare class TtlManager {
39
+ private config;
40
+ constructor(config?: Partial<TtlConfig>);
41
+ /**
42
+ * Get the TTL for a storage type based on user plan and whether it's named.
43
+ * Returns null for named storage (no TTL = indefinite).
44
+ */
45
+ getTtl(storageType: 'dataset' | 'kv', plan: UserPlan, isNamed: boolean): number | null;
46
+ getTtl(storageType: 'rq', plan: UserPlan): number;
47
+ getTtl(storageType: 'logs', plan: UserPlan): number;
48
+ /**
49
+ * Set expiry on multiple keys atomically using a pipeline.
50
+ * Uses 'NX' flag to only set TTL if it doesn't already exist.
51
+ */
52
+ setExpiry(redis: Redis, keys: string[], ttl: number): Promise<void>;
53
+ /**
54
+ * Set expiry on multiple keys, overwriting any existing TTL.
55
+ * Use this when you want to guarantee the TTL is set to a specific value.
56
+ */
57
+ setExpiryForce(redis: Redis, keys: string[], ttl: number): Promise<void>;
58
+ /**
59
+ * Get the maximum log size in bytes
60
+ */
61
+ getLogMaxSize(): number;
62
+ /**
63
+ * Get the full TTL config
64
+ */
65
+ getConfig(): Readonly<TtlConfig>;
66
+ /**
67
+ * Create TtlConfig from environment variables
68
+ */
69
+ static fromEnv(): TtlConfig;
70
+ }
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TtlManager = exports.DEFAULT_TTL_CONFIG = void 0;
4
+ /**
5
+ * Default TTL configuration based on Apify's model
6
+ */
7
+ exports.DEFAULT_TTL_CONFIG = {
8
+ datasetFree: 7 * 24 * 60 * 60, // 7 days
9
+ datasetPaid: 31 * 24 * 60 * 60, // 31 days
10
+ kvFree: 7 * 24 * 60 * 60, // 7 days
11
+ kvPaid: 31 * 24 * 60 * 60, // 31 days
12
+ requestQueueFallback: 24 * 60 * 60, // 24 hours
13
+ logsFree: 12 * 60 * 60, // 12 hours
14
+ logsPaid: 7 * 24 * 60 * 60, // 7 days
15
+ logMaxSize: 5 * 1024 * 1024, // 5MB
16
+ };
17
+ /**
18
+ * Manages TTL (Time-To-Live) for Dragonfly storage keys.
19
+ *
20
+ * Retention policy:
21
+ * - Named storage: Indefinite (billed), no TTL set
22
+ * - Unnamed datasets/KV stores: 7 days (free) or 31 days (paid)
23
+ * - Request queues: Explicitly deleted after run, 24h fallback TTL for orphaned RQs
24
+ * - Logs: Short TTL in Redis, persisted to S3
25
+ */
26
+ class TtlManager {
27
+ config;
28
+ constructor(config) {
29
+ this.config = { ...exports.DEFAULT_TTL_CONFIG, ...config };
30
+ }
31
+ getTtl(storageType, plan, isNamed) {
32
+ // Named storage gets no TTL (indefinite retention, billed)
33
+ if ((storageType === 'dataset' || storageType === 'kv') && isNamed) {
34
+ return null;
35
+ }
36
+ switch (storageType) {
37
+ case 'dataset':
38
+ return plan === 'paid' ? this.config.datasetPaid : this.config.datasetFree;
39
+ case 'kv':
40
+ return plan === 'paid' ? this.config.kvPaid : this.config.kvFree;
41
+ case 'rq':
42
+ return this.config.requestQueueFallback;
43
+ case 'logs':
44
+ return plan === 'paid' ? this.config.logsPaid : this.config.logsFree;
45
+ default:
46
+ return null;
47
+ }
48
+ }
49
+ /**
50
+ * Set expiry on multiple keys atomically using a pipeline.
51
+ * Uses 'NX' flag to only set TTL if it doesn't already exist.
52
+ */
53
+ async setExpiry(redis, keys, ttl) {
54
+ if (keys.length === 0 || ttl <= 0)
55
+ return;
56
+ const pipeline = redis.pipeline();
57
+ for (const key of keys) {
58
+ // 'NX' = only set expiry if key has no expiry
59
+ // This prevents accidentally shortening TTL on access
60
+ pipeline.expire(key, ttl, 'NX');
61
+ }
62
+ await pipeline.exec();
63
+ }
64
+ /**
65
+ * Set expiry on multiple keys, overwriting any existing TTL.
66
+ * Use this when you want to guarantee the TTL is set to a specific value.
67
+ */
68
+ async setExpiryForce(redis, keys, ttl) {
69
+ if (keys.length === 0 || ttl <= 0)
70
+ return;
71
+ const pipeline = redis.pipeline();
72
+ for (const key of keys) {
73
+ pipeline.expire(key, ttl);
74
+ }
75
+ await pipeline.exec();
76
+ }
77
+ /**
78
+ * Get the maximum log size in bytes
79
+ */
80
+ getLogMaxSize() {
81
+ return this.config.logMaxSize;
82
+ }
83
+ /**
84
+ * Get the full TTL config
85
+ */
86
+ getConfig() {
87
+ return { ...this.config };
88
+ }
89
+ /**
90
+ * Create TtlConfig from environment variables
91
+ */
92
+ static fromEnv() {
93
+ const parseEnv = (key, defaultValue) => {
94
+ const val = process.env[key];
95
+ if (val) {
96
+ const parsed = parseInt(val, 10);
97
+ if (!isNaN(parsed) && parsed > 0)
98
+ return parsed;
99
+ }
100
+ return defaultValue;
101
+ };
102
+ return {
103
+ datasetFree: parseEnv('SCRAPELY_TTL_DATASET_FREE', exports.DEFAULT_TTL_CONFIG.datasetFree),
104
+ datasetPaid: parseEnv('SCRAPELY_TTL_DATASET_PAID', exports.DEFAULT_TTL_CONFIG.datasetPaid),
105
+ // Bug fix: these two env vars were silently ignored before
106
+ kvFree: parseEnv('SCRAPELY_TTL_KV_FREE', exports.DEFAULT_TTL_CONFIG.kvFree),
107
+ kvPaid: parseEnv('SCRAPELY_TTL_KV_PAID', exports.DEFAULT_TTL_CONFIG.kvPaid),
108
+ requestQueueFallback: parseEnv('SCRAPELY_TTL_RQ_FALLBACK', exports.DEFAULT_TTL_CONFIG.requestQueueFallback),
109
+ logsFree: parseEnv('SCRAPELY_TTL_LOGS_FREE', exports.DEFAULT_TTL_CONFIG.logsFree),
110
+ logsPaid: parseEnv('SCRAPELY_TTL_LOGS_PAID', exports.DEFAULT_TTL_CONFIG.logsPaid),
111
+ logMaxSize: parseEnv('SCRAPELY_LOG_MAX_SIZE', exports.DEFAULT_TTL_CONFIG.logMaxSize),
112
+ };
113
+ }
114
+ }
115
+ exports.TtlManager = TtlManager;
@@ -0,0 +1,3 @@
1
+ export declare function uniqueKeyToRequestId(uniqueKey: string): string;
2
+ export declare function generateId(): string;
3
+ export declare function nowISO(): Date;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uniqueKeyToRequestId = uniqueKeyToRequestId;
4
+ exports.generateId = generateId;
5
+ exports.nowISO = nowISO;
6
+ const node_crypto_1 = require("node:crypto");
7
+ function uniqueKeyToRequestId(uniqueKey) {
8
+ return (0, node_crypto_1.createHash)('sha256')
9
+ .update(uniqueKey)
10
+ .digest('base64')
11
+ .replace(/(\+|\/|=)/g, '')
12
+ .slice(0, 15);
13
+ }
14
+ function generateId() {
15
+ return (0, node_crypto_1.randomUUID)();
16
+ }
17
+ function nowISO() {
18
+ return new Date();
19
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Storage identifier types (Apify-compatible names, so TypeScript actors importing
3
+ * them from 'apify' still compile after the apify → scrapely rewrite).
4
+ */
5
+ /** Identifies a storage by its alias from the Actor's schema storages. */
6
+ export interface StorageAlias {
7
+ alias: string;
8
+ }
9
+ /** Identifies a storage by its platform ID. */
10
+ export interface StorageId {
11
+ id: string;
12
+ }
13
+ /** Identifies a storage by its name. */
14
+ export interface StorageName {
15
+ name: string;
16
+ }
17
+ /** A plain string (ID or name), `{ alias }`, `{ id }` or `{ name }`. */
18
+ export type StorageIdentifier = string | StorageAlias | StorageId | StorageName;
19
+ /** A plain string (ID or name), `{ id }` or `{ name }` (key-value stores and request queues). */
20
+ export type StorageIdentifierWithoutAlias = string | StorageId | StorageName;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ /**
3
+ * Storage identifier types (Apify-compatible names, so TypeScript actors importing
4
+ * them from 'apify' still compile after the apify → scrapely rewrite).
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Apify v2-compatible utility functions for scrapely.
3
+ * These adapt the Apify v2 API to Crawlee v3 internally.
4
+ */
5
+ import type { CheerioAPI } from 'cheerio';
6
+ import type { RequestQueue } from 'crawlee';
7
+ /**
8
+ * Options for enqueueLinks (Apify v2 API compatible).
9
+ */
10
+ export interface EnqueueLinksOptions {
11
+ /** Cheerio instance ($) - REQUIRED in Apify v2 */
12
+ $: CheerioAPI;
13
+ /** RequestQueue instance - REQUIRED in Apify v2 */
14
+ requestQueue: RequestQueue;
15
+ /** CSS selector to find links. Default: 'a[href]' */
16
+ selector?: string;
17
+ /** Base URL for resolving relative URLs */
18
+ baseUrl?: string;
19
+ /** Optional userData to merge into the queued requests */
20
+ userData?: Record<string, any>;
21
+ /** Optional transformation function for each URL */
22
+ transformRequestFunction?: (request: {
23
+ url: string;
24
+ userData?: Record<string, any>;
25
+ }) => {
26
+ url: string;
27
+ userData?: Record<string, any>;
28
+ } | null;
29
+ }
30
+ /**
31
+ * Enqueues links found on the page using a CSS selector.
32
+ *
33
+ * This is an Apify v2-compatible implementation that adapts to Crawlee v3 internally.
34
+ * It extracts URLs from the page using cheerio and adds them to the request queue.
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * // Inside a CheerioCrawler requestHandler:
39
+ * await Apify.utils.enqueueLinks({
40
+ * $,
41
+ * requestQueue,
42
+ * selector: 'a[href*="/app/"]',
43
+ * baseUrl: new URL(request.url).origin,
44
+ * });
45
+ * ```
46
+ */
47
+ export declare function enqueueLinks(options: EnqueueLinksOptions): Promise<void>;
48
+ /**
49
+ * Utility namespace providing Apify v2-compatible helper functions.
50
+ * These adapt to Crawlee v3 APIs internally.
51
+ */
52
+ export declare const utils: {
53
+ enqueueLinks: typeof enqueueLinks;
54
+ };