deepline 0.1.76 → 0.1.78

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.
@@ -663,6 +663,9 @@ export interface PlayListItem {
663
663
  isDraftDirty?: boolean;
664
664
  inputSchema?: Record<string, unknown> | null;
665
665
  outputSchema?: Record<string, unknown> | null;
666
+ staticPipeline?: unknown;
667
+ currentRevision?: PlayRevisionSummary | null;
668
+ liveRevision?: PlayRevisionSummary | null;
666
669
  aliases?: string[];
667
670
  }
668
671
 
@@ -677,6 +680,7 @@ export interface PlayDescription {
677
680
  aliases: string[];
678
681
  inputSchema?: Record<string, unknown> | null;
679
682
  outputSchema?: Record<string, unknown> | null;
683
+ staticPipeline?: Record<string, unknown> | null;
680
684
  csvInput?: Record<string, unknown> | null;
681
685
  rowOutputSchema?: Record<string, unknown> | null;
682
686
  runCommand: string;
@@ -0,0 +1,301 @@
1
+ export type EmailStatusVerdict =
2
+ | 'send'
3
+ | 'send_with_caution'
4
+ | 'verify_next'
5
+ | 'hold'
6
+ | 'drop';
7
+
8
+ export type EmailStatusValue =
9
+ | 'valid'
10
+ | 'invalid'
11
+ | 'catch_all'
12
+ | 'valid_catch_all'
13
+ | 'unknown'
14
+ | 'do_not_mail'
15
+ | 'spamtrap'
16
+ | 'abuse'
17
+ | 'disposable';
18
+
19
+ export type EmailDeliverability = 'high' | 'medium' | 'low' | 'unknown';
20
+
21
+ export type EmailMxClass =
22
+ | 'consumer_mailbox'
23
+ | 'workspace_mailbox'
24
+ | 'security_gateway'
25
+ | 'on_prem'
26
+ | 'unknown';
27
+
28
+ export type EmailStatus = {
29
+ verdict: EmailStatusVerdict;
30
+ status: EmailStatusValue;
31
+ verified: boolean;
32
+ confidence: number | null;
33
+ reasons: string[];
34
+ signals: {
35
+ catch_all: boolean | null;
36
+ deliverability: EmailDeliverability;
37
+ mx_class: EmailMxClass;
38
+ mx_provider: string | null;
39
+ mx_record: string | null;
40
+ fraud_score: number | null;
41
+ disposable: boolean | null;
42
+ role_based: boolean | null;
43
+ free_email: boolean | null;
44
+ abuse: boolean | null;
45
+ spamtrap: boolean | null;
46
+ suspect: boolean | null;
47
+ valid: boolean | null;
48
+ };
49
+ provider: {
50
+ name: string;
51
+ raw_status: string | boolean | number | null;
52
+ raw_score: number | null;
53
+ };
54
+ };
55
+
56
+ export type EmailStatusMapEntry = {
57
+ status: EmailStatusValue;
58
+ verdict?: EmailStatusVerdict;
59
+ verified?: boolean;
60
+ reason?: string;
61
+ };
62
+
63
+ export type EmailStatusRule = EmailStatusMapEntry & {
64
+ when: Record<string, string | number | boolean | null>;
65
+ };
66
+
67
+ export type EmailStatusExtractorConfig = {
68
+ provider: string;
69
+ rawStatus?: string[];
70
+ rawScore?: string[];
71
+ valid?: string[];
72
+ deliverability?: string[];
73
+ catchAll?: string[];
74
+ mxProvider?: string[];
75
+ mxRecord?: string[];
76
+ fraudScore?: string[];
77
+ disposable?: string[];
78
+ roleBased?: string[];
79
+ freeEmail?: string[];
80
+ abuse?: string[];
81
+ spamtrap?: string[];
82
+ suspect?: string[];
83
+ statusMap?: Record<string, EmailStatusMapEntry>;
84
+ rules?: EmailStatusRule[];
85
+ };
86
+
87
+ export type EmailStatusBuildInput = {
88
+ config: EmailStatusExtractorConfig;
89
+ values: Record<string, unknown>;
90
+ };
91
+
92
+ const DEFAULT_STATUS_MAP: Record<string, EmailStatusMapEntry> = {
93
+ verified: { status: 'valid', verdict: 'send', verified: true },
94
+ valid: { status: 'valid', verdict: 'send', verified: true },
95
+ deliverable: { status: 'valid', verdict: 'send', verified: true },
96
+ true: { status: 'valid', verdict: 'send', verified: true },
97
+ invalid: { status: 'invalid', verdict: 'drop', verified: false },
98
+ undeliverable: { status: 'invalid', verdict: 'drop', verified: false },
99
+ false: { status: 'invalid', verdict: 'drop', verified: false },
100
+ 'catch-all': {
101
+ status: 'catch_all',
102
+ verdict: 'verify_next',
103
+ verified: false,
104
+ },
105
+ catch_all: {
106
+ status: 'catch_all',
107
+ verdict: 'verify_next',
108
+ verified: false,
109
+ },
110
+ valid_catch_all: {
111
+ status: 'valid_catch_all',
112
+ verdict: 'send_with_caution',
113
+ verified: true,
114
+ },
115
+ accept_all: {
116
+ status: 'catch_all',
117
+ verdict: 'verify_next',
118
+ verified: false,
119
+ },
120
+ unknown: { status: 'unknown', verdict: 'hold', verified: false },
121
+ unavailable: { status: 'unknown', verdict: 'hold', verified: false },
122
+ do_not_mail: { status: 'do_not_mail', verdict: 'drop', verified: false },
123
+ spamtrap: { status: 'spamtrap', verdict: 'drop', verified: false },
124
+ abuse: { status: 'abuse', verdict: 'drop', verified: false },
125
+ disposable: { status: 'disposable', verdict: 'drop', verified: false },
126
+ };
127
+
128
+ function normalizeKey(value: unknown): string | null {
129
+ if (value == null) return null;
130
+ if (typeof value === 'boolean') return String(value);
131
+ const normalized = String(value).trim().toLowerCase().replace(/\s+/g, '_');
132
+ return normalized || null;
133
+ }
134
+
135
+ function boolish(value: unknown): boolean | null {
136
+ if (typeof value === 'boolean') return value;
137
+ if (typeof value === 'number') return value === 1 ? true : value === 0 ? false : null;
138
+ if (typeof value !== 'string') return null;
139
+ const normalized = value.trim().toLowerCase();
140
+ if (['true', 'yes', 'y', '1'].includes(normalized)) return true;
141
+ if (['false', 'no', 'n', '0'].includes(normalized)) return false;
142
+ return null;
143
+ }
144
+
145
+ function numberish(value: unknown): number | null {
146
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
147
+ if (typeof value !== 'string' || value.trim() === '') return null;
148
+ const parsed = Number(value);
149
+ return Number.isFinite(parsed) ? parsed : null;
150
+ }
151
+
152
+ function stringish(value: unknown): string | null {
153
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
154
+ }
155
+
156
+ function deliverability(value: unknown): EmailDeliverability {
157
+ const normalized = normalizeKey(value);
158
+ return normalized === 'high' || normalized === 'medium' || normalized === 'low'
159
+ ? normalized
160
+ : 'unknown';
161
+ }
162
+
163
+ function mxClass(mxProvider: unknown, mxRecord: unknown): EmailMxClass {
164
+ const haystack = `${stringish(mxProvider) ?? ''} ${stringish(mxRecord) ?? ''}`.toLowerCase();
165
+ if (!haystack.trim()) return 'unknown';
166
+ if (
167
+ /proofpoint|pphosted|mimecast|barracuda|ess\.barracudanetworks|ironport|cisco|iphmx|messagelabs|symantec/.test(
168
+ haystack,
169
+ )
170
+ ) {
171
+ return 'security_gateway';
172
+ }
173
+ if (/aspmx\.l\.google|google|g-suite|google workspace/.test(haystack)) {
174
+ return 'workspace_mailbox';
175
+ }
176
+ if (/protection\.outlook|office365|microsoft|outlook|exchange online/.test(haystack)) {
177
+ return 'workspace_mailbox';
178
+ }
179
+ if (/gmail|yahoo|icloud|aol|hotmail/.test(haystack)) return 'consumer_mailbox';
180
+ if (/postfix|exim|sendmail|zimbra|plesk|cpanel|mail\./.test(haystack)) return 'on_prem';
181
+ return 'unknown';
182
+ }
183
+
184
+ function entryForStatus(
185
+ key: string | null,
186
+ map: Record<string, EmailStatusMapEntry> | undefined,
187
+ ): EmailStatusMapEntry | null {
188
+ if (!key) return null;
189
+ return map?.[key] ?? DEFAULT_STATUS_MAP[key] ?? null;
190
+ }
191
+
192
+ function read(values: Record<string, unknown>, name: string): unknown {
193
+ return values[name];
194
+ }
195
+
196
+ function matchesRule(
197
+ rule: EmailStatusRule,
198
+ values: Record<string, unknown>,
199
+ ): boolean {
200
+ return Object.entries(rule.when).every(([key, expected]) => {
201
+ const actual = read(values, key);
202
+ if (key.endsWith('Lt')) {
203
+ const source = numberish(read(values, key.slice(0, -2)));
204
+ return typeof expected === 'number' && source != null && source < expected;
205
+ }
206
+ if (typeof expected === 'boolean') return boolish(actual) === expected;
207
+ if (typeof expected === 'number') return numberish(actual) === expected;
208
+ return normalizeKey(actual) === normalizeKey(expected);
209
+ });
210
+ }
211
+
212
+ export function buildEmailStatus({
213
+ config,
214
+ values,
215
+ }: EmailStatusBuildInput): EmailStatus {
216
+ const rawStatus = read(values, 'rawStatus');
217
+ const rawScore = numberish(read(values, 'rawScore'));
218
+ const valid = boolish(read(values, 'valid'));
219
+ const catchAll = boolish(read(values, 'catchAll'));
220
+ const disposable = boolish(read(values, 'disposable'));
221
+ const abuse = boolish(read(values, 'abuse'));
222
+ const spamtrap = boolish(read(values, 'spamtrap'));
223
+ const suspect = boolish(read(values, 'suspect'));
224
+ const rawKey = normalizeKey(rawStatus);
225
+ const mapped =
226
+ config.rules?.find((rule) => matchesRule(rule, values)) ??
227
+ entryForStatus(rawKey, config.statusMap) ??
228
+ entryForStatus(valid == null ? null : String(valid), config.statusMap);
229
+
230
+ const status =
231
+ mapped?.status ??
232
+ (disposable
233
+ ? 'disposable'
234
+ : abuse
235
+ ? 'abuse'
236
+ : spamtrap
237
+ ? 'spamtrap'
238
+ : catchAll
239
+ ? 'catch_all'
240
+ : valid === true
241
+ ? 'valid'
242
+ : valid === false
243
+ ? 'invalid'
244
+ : 'unknown');
245
+ const defaultVerdict: EmailStatusVerdict =
246
+ status === 'valid'
247
+ ? 'send'
248
+ : status === 'valid_catch_all'
249
+ ? 'send_with_caution'
250
+ : status === 'catch_all'
251
+ ? 'verify_next'
252
+ : status === 'unknown'
253
+ ? 'hold'
254
+ : 'drop';
255
+ const verdict = mapped?.verdict ?? defaultVerdict;
256
+ const verified =
257
+ mapped?.verified ??
258
+ (status === 'valid' || status === 'valid_catch_all' || verdict === 'send');
259
+ const reasons = [
260
+ mapped?.reason,
261
+ catchAll ? 'catch_all_domain' : null,
262
+ mxClass(read(values, 'mxProvider'), read(values, 'mxRecord')) ===
263
+ 'security_gateway'
264
+ ? 'security_gateway_mx'
265
+ : null,
266
+ suspect ? 'provider_marked_suspect' : null,
267
+ ].filter((reason): reason is string => typeof reason === 'string');
268
+
269
+ return {
270
+ verdict,
271
+ status,
272
+ verified,
273
+ confidence: rawScore,
274
+ reasons,
275
+ signals: {
276
+ catch_all: catchAll,
277
+ deliverability: deliverability(read(values, 'deliverability')),
278
+ mx_class: mxClass(read(values, 'mxProvider'), read(values, 'mxRecord')),
279
+ mx_provider: stringish(read(values, 'mxProvider')),
280
+ mx_record: stringish(read(values, 'mxRecord')),
281
+ fraud_score: numberish(read(values, 'fraudScore')),
282
+ disposable,
283
+ role_based: boolish(read(values, 'roleBased')),
284
+ free_email: boolish(read(values, 'freeEmail')),
285
+ abuse,
286
+ spamtrap,
287
+ suspect,
288
+ valid,
289
+ },
290
+ provider: {
291
+ name: config.provider,
292
+ raw_status:
293
+ typeof rawStatus === 'string' ||
294
+ typeof rawStatus === 'boolean' ||
295
+ typeof rawStatus === 'number'
296
+ ? rawStatus
297
+ : null,
298
+ raw_score: rawScore,
299
+ },
300
+ };
301
+ }
@@ -9,7 +9,15 @@ export type {
9
9
  ToolResultTargetMetadata,
10
10
  ToolResultTargetAccessor,
11
11
  } from './tool-result-types';
12
-
12
+ export type {
13
+ EmailDeliverability,
14
+ EmailMxClass,
15
+ EmailStatus,
16
+ EmailStatusValue,
17
+ EmailStatusVerdict,
18
+ } from './email-status';
19
+
20
+ import { buildEmailStatus } from './email-status';
13
21
  import type {
14
22
  SerializedToolExecuteResult,
15
23
  ToolExecuteResult,
@@ -302,6 +310,50 @@ function findFirstTargetByPath(
302
310
  return null;
303
311
  }
304
312
 
313
+ function firstValueForPaths(
314
+ result: unknown,
315
+ paths: readonly string[] | undefined,
316
+ ): ToolResultTargetMetadata | null {
317
+ return findFirstTargetByPath(result, paths);
318
+ }
319
+
320
+ function buildEmailStatusTarget(
321
+ result: unknown,
322
+ descriptor: ToolResultExtractorDescriptor,
323
+ ): ToolResultTargetMetadata | null {
324
+ const config = descriptor.emailStatus;
325
+ if (!config) return null;
326
+ const values: Record<string, unknown> = {};
327
+ const pathSets: Record<string, readonly string[] | undefined> = {
328
+ rawStatus: config.rawStatus,
329
+ rawScore: config.rawScore,
330
+ valid: config.valid,
331
+ deliverability: config.deliverability,
332
+ catchAll: config.catchAll,
333
+ mxProvider: config.mxProvider,
334
+ mxRecord: config.mxRecord,
335
+ fraudScore: config.fraudScore,
336
+ disposable: config.disposable,
337
+ roleBased: config.roleBased,
338
+ freeEmail: config.freeEmail,
339
+ abuse: config.abuse,
340
+ spamtrap: config.spamtrap,
341
+ suspect: config.suspect,
342
+ };
343
+ let firstPath: string | null = null;
344
+ for (const [name, paths] of Object.entries(pathSets)) {
345
+ const match = firstValueForPaths(result, paths);
346
+ if (!match) continue;
347
+ values[name] = match.value;
348
+ firstPath ??= match.path;
349
+ }
350
+ if (!firstPath) return null;
351
+ return {
352
+ path: firstPath,
353
+ value: buildEmailStatus({ config, values }),
354
+ };
355
+ }
356
+
305
357
  function findFirstTargetByKey(
306
358
  result: unknown,
307
359
  target: string,
@@ -507,6 +559,11 @@ function buildTargets(
507
559
  ): Record<string, ToolResultTargetMetadata> {
508
560
  const targets: Record<string, ToolResultTargetMetadata> = {};
509
561
  for (const [target, descriptor] of Object.entries(extractors ?? {})) {
562
+ const emailStatusTarget = buildEmailStatusTarget(result, descriptor);
563
+ if (emailStatusTarget) {
564
+ targets[target] = emailStatusTarget;
565
+ continue;
566
+ }
510
567
  const fromExtractor = findFirstTargetByPath(result, descriptor.paths);
511
568
  if (!fromExtractor) continue;
512
569
  const transformed = coerceToEnum(
@@ -71,7 +71,9 @@ export type PlayDatasetTransformOptions = {
71
71
  * Deepline keeps row progress, retries, memory use, and table output under
72
72
  * runtime control. Use `count()` and `peek()` for bounded inspection. Use
73
73
  * `materialize(limit)` or async iteration only when the dataset is intentionally
74
- * small and bounded.
74
+ * small and bounded. `PlayDataset` intentionally does not expose `.rows`,
75
+ * `.toArray()`, or other array aliases; those hide the runtime cost of loading
76
+ * persisted rows into memory.
75
77
  */
76
78
  export interface PlayDataset<T> extends AsyncIterable<T> {
77
79
  readonly [PLAY_DATASET_BRAND]: true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.76",
3
+ "version": "0.1.78",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {