deepline 0.2.71 → 0.2.72

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.
@@ -4008,9 +4008,15 @@ export class DeeplineClient {
4008
4008
  * console.log(`Total runs: ${detail.play.runCount}`);
4009
4009
  * ```
4010
4010
  */
4011
- async getPlay(name: string): Promise<PlayDetail> {
4011
+ async getPlay(
4012
+ name: string,
4013
+ options?: { source?: 'working' | 'live' | `version:${number}` },
4014
+ ): Promise<PlayDetail> {
4012
4015
  const encodedName = encodeURIComponent(name);
4013
- return this.http.get<PlayDetail>(`/api/v2/plays/${encodedName}`);
4016
+ const query = options?.source
4017
+ ? `?include=source&revision=${encodeURIComponent(options.source)}`
4018
+ : '';
4019
+ return this.http.get<PlayDetail>(`/api/v2/plays/${encodedName}${query}`);
4014
4020
  }
4015
4021
 
4016
4022
  /**
@@ -183,7 +183,7 @@ export const SDK_RELEASE = {
183
183
  // 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
184
184
  // exposed storage-dependent synchronous access. This deliberate minor
185
185
  // release keeps lazy paging semantics independent of row residency.
186
- version: '0.2.71',
186
+ version: '0.2.72',
187
187
  contracts: {
188
188
  api: {
189
189
  name: 'sdk-http-api',
@@ -1200,6 +1200,16 @@ export interface PlayDetail {
1200
1200
  customerDbUrl: string;
1201
1201
  /** Change cursor for incremental sync. */
1202
1202
  deltaCursor: number;
1203
+ /**
1204
+ * Present only when requested through `getPlay(..., { source: ... })`.
1205
+ * `files` is the complete authored source tree, keyed by logical path.
1206
+ */
1207
+ source?: {
1208
+ selector: 'working' | 'live' | `version:${number}`;
1209
+ revision: { id: string; version: number };
1210
+ entryFile: string;
1211
+ files: Record<string, string>;
1212
+ };
1203
1213
  }
1204
1214
 
1205
1215
  export interface ClearPlayHistoryRequest {
@@ -61,16 +61,60 @@ const liveToolResultListDatasets = new Map<
61
61
  PlayDataset<Record<string, unknown>>
62
62
  >();
63
63
 
64
- const TARGET_FALLBACK_KEYS: Record<string, readonly RegExp[]> = {
65
- email: [/^email$/i, /^address$/i, /email/i],
66
- phone: [/^phone$/i, /mobile/i, /phone/i, /telephone/i],
67
- linkedin: [/^linkedin_url$/i, /^linkedin$/i, /linkedin/i],
68
- company_linkedin_url: [/company.*linkedin/i, /linkedin.*company/i],
69
- company_domain: [/^company_domain$/i, /company.*domain/i],
70
- company_name: [/^company_name$/i, /company.*name/i],
71
- domain: [/^domain$/i, /company_domain/i, /domain/i],
72
- status: [/^email_status$/i, /^status$/i],
73
- email_status: [/^email_status$/i, /^status$/i],
64
+ /**
65
+ * Keys that describe a field rather than carry it.
66
+ *
67
+ * These sit next to the real value under a similar name, so a substring match
68
+ * on the target name reaches them as soon as the real field is absent.
69
+ * `phone_status: "unfound"` is not a phone number and `email_type: "work"` is
70
+ * not an email address.
71
+ */
72
+ const DESCRIPTOR_SUFFIX =
73
+ /_(status|type|score|count|id|verified|valid|confidence|quality|source)$/i;
74
+
75
+ /** A company-scoped key is never the person-scoped answer. */
76
+ const COMPANY_SCOPED = /company/i;
77
+
78
+ type TargetFallbackRule = {
79
+ /** Key names that may carry this target's value. */
80
+ match: readonly RegExp[];
81
+ /** Key names that must never be treated as this target's value. */
82
+ reject?: readonly RegExp[];
83
+ };
84
+
85
+ const TARGET_FALLBACK_KEYS: Record<string, TargetFallbackRule> = {
86
+ email: {
87
+ match: [/^email$/i, /^address$/i, /email/i],
88
+ reject: [DESCRIPTOR_SUFFIX],
89
+ },
90
+ phone: {
91
+ match: [/^phone$/i, /mobile/i, /phone/i, /telephone/i],
92
+ reject: [DESCRIPTOR_SUFFIX],
93
+ },
94
+ linkedin: {
95
+ match: [/^linkedin_url$/i, /^linkedin$/i, /linkedin/i],
96
+ reject: [DESCRIPTOR_SUFFIX, COMPANY_SCOPED],
97
+ },
98
+ company_linkedin_url: {
99
+ match: [/company.*linkedin/i, /linkedin.*company/i],
100
+ reject: [DESCRIPTOR_SUFFIX],
101
+ },
102
+ company_domain: {
103
+ match: [/^company_domain$/i, /company.*domain/i],
104
+ reject: [DESCRIPTOR_SUFFIX],
105
+ },
106
+ company_name: {
107
+ match: [/^company_name$/i, /company.*name/i],
108
+ reject: [DESCRIPTOR_SUFFIX],
109
+ },
110
+ domain: {
111
+ match: [/^domain$/i, /company_domain/i, /domain/i],
112
+ reject: [DESCRIPTOR_SUFFIX],
113
+ },
114
+ // Status targets legitimately want the `_status` keys the suffix rule
115
+ // rejects elsewhere, so they carry no rejections.
116
+ status: { match: [/^email_status$/i, /^status$/i] },
117
+ email_status: { match: [/^email_status$/i, /^status$/i] },
74
118
  };
75
119
 
76
120
  function isRecord(value: unknown): value is Record<string, unknown> {
@@ -586,12 +630,13 @@ function findFirstTargetByKey(
586
630
  }
587
631
  if (!isRecord(result)) return null;
588
632
 
589
- const patterns = TARGET_FALLBACK_KEYS[target] ?? [
590
- new RegExp(`^${target}$`, 'i'),
591
- ];
633
+ const rule: TargetFallbackRule = TARGET_FALLBACK_KEYS[target] ?? {
634
+ match: [new RegExp(`^${target}$`, 'i')],
635
+ };
592
636
  for (const [key, value] of Object.entries(result)) {
637
+ if (rule.reject?.some((pattern) => pattern.test(key))) continue;
593
638
  if (
594
- patterns.some((pattern) => pattern.test(key)) &&
639
+ rule.match.some((pattern) => pattern.test(key)) &&
595
640
  isMeaningfulValue(value)
596
641
  ) {
597
642
  return { value, path: pathToString([...path, key]) };
@@ -896,6 +941,14 @@ function buildTargets(
896
941
  targets[target] = fromMetadata;
897
942
  continue;
898
943
  }
944
+ // Declared paths are routinely incomplete against the shape a provider
945
+ // actually returns (zerobounce declares `result.data.email` and answers
946
+ // with `address`), so the key scan stays as the rescue. It is bounded by
947
+ // each target's `reject` rules: the scan runs precisely when the provider
948
+ // returned no value, which is when a same-named neighbour is most likely
949
+ // to be a different field. Without those rules a real Wiza miss resolved
950
+ // `phone` to `phone_status: "unfound"` and `linkedin` to
951
+ // `company_linkedin`, reporting a company page as the person's profile.
899
952
  const fallback = findFirstTargetByKey(result, target);
900
953
  if (fallback) {
901
954
  targets[target] = fallback;
@@ -137,6 +137,18 @@ export type PlayBundlingAdapter = {
137
137
  warnAboutNonDevelopmentBundling?(filePath: string): void;
138
138
  };
139
139
 
140
+ type PathRelationshipApi = {
141
+ relative(from: string, to: string): string;
142
+ isAbsolute(path: string): boolean;
143
+ sep: string;
144
+ };
145
+
146
+ const defaultPathRelationshipApi: PathRelationshipApi = {
147
+ relative,
148
+ isAbsolute,
149
+ sep,
150
+ };
151
+
140
152
  function assertValidExportName(exportName: string): void {
141
153
  if (exportName === 'default') return;
142
154
  if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(exportName)) {
@@ -207,10 +219,10 @@ function sha256(value: string): string {
207
219
 
208
220
  function sourceIdentityPath(
209
221
  filePath: string,
210
- adapter: PlayBundlingAdapter,
222
+ sourceIdentityRoot: string | undefined,
211
223
  ): string {
212
- if (!adapter.sourceIdentityRoot) return filePath;
213
- const identityRoot = resolve(adapter.sourceIdentityRoot);
224
+ if (!sourceIdentityRoot) return filePath;
225
+ const identityRoot = resolve(sourceIdentityRoot);
214
226
  const logicalPath = relative(identityRoot, resolve(filePath));
215
227
  if (
216
228
  !logicalPath ||
@@ -254,8 +266,18 @@ function createPlayWorkspace(entryFile: string): PlayWorkspace {
254
266
  };
255
267
  }
256
268
 
257
- function isPathInsideDirectory(filePath: string, directory: string): boolean {
258
- return filePath === directory || filePath.startsWith(`${directory}/`);
269
+ export function isPathInsideDirectory(
270
+ filePath: string,
271
+ directory: string,
272
+ pathApi: PathRelationshipApi = defaultPathRelationshipApi,
273
+ ): boolean {
274
+ const relationship = pathApi.relative(directory, filePath);
275
+ return (
276
+ relationship === '' ||
277
+ (relationship !== '..' &&
278
+ !relationship.startsWith(`..${pathApi.sep}`) &&
279
+ !pathApi.isAbsolute(relationship))
280
+ );
259
281
  }
260
282
 
261
283
  function assertWithinPlayWorkspace(input: {
@@ -1851,6 +1873,7 @@ async function analyzeSourceGraph(
1851
1873
  ): Promise<SourceGraphAnalysis> {
1852
1874
  const absoluteEntryFile = await normalizeLocalPath(entryFile);
1853
1875
  const workspace = createPlayWorkspace(absoluteEntryFile);
1876
+ const sourceIdentityRoot = adapter.sourceIdentityRoot;
1854
1877
  const localFiles = new Map<string, string>();
1855
1878
  const nodeBuiltins = new Set<string>();
1856
1879
  const packages = new Map<string, string | null>();
@@ -1961,10 +1984,10 @@ async function analyzeSourceGraph(
1961
1984
  const sourceHash = sha256(sourceCode);
1962
1985
  const graphHash = sha256(
1963
1986
  JSON.stringify({
1964
- entryFile: sourceIdentityPath(absoluteEntryFile, adapter),
1987
+ entryFile: sourceIdentityPath(absoluteEntryFile, sourceIdentityRoot),
1965
1988
  localFiles: [...localFiles.entries()]
1966
1989
  .map(([filePath, contents]) => ({
1967
- filePath: sourceIdentityPath(filePath, adapter),
1990
+ filePath: sourceIdentityPath(filePath, sourceIdentityRoot),
1968
1991
  hash: sha256(contents),
1969
1992
  }))
1970
1993
  .sort((left, right) => left.filePath.localeCompare(right.filePath)),
@@ -1974,7 +1997,7 @@ async function analyzeSourceGraph(
1974
1997
  .sort((left, right) => left.name.localeCompare(right.name)),
1975
1998
  importedPlayDependencies: [...importedPlayDependencies.values()]
1976
1999
  .map((dependency) => ({
1977
- filePath: sourceIdentityPath(dependency.filePath, adapter),
2000
+ filePath: sourceIdentityPath(dependency.filePath, sourceIdentityRoot),
1978
2001
  playName: dependency.playName,
1979
2002
  }))
1980
2003
  .sort((left, right) => left.filePath.localeCompare(right.filePath)),
@@ -2199,6 +2222,11 @@ export async function bundlePlayFile(
2199
2222
  const exportName = options.exportName?.trim() || 'default';
2200
2223
  assertValidExportName(exportName);
2201
2224
  const absolutePath = await normalizeLocalPath(filePath);
2225
+ // Keep the existing absolute-path wire format unless a caller explicitly
2226
+ // opts into a logical identity root. Production's deployed checker still
2227
+ // uses absolute paths to reconcile bundled imports; source export normalizes
2228
+ // those paths into a portable tree at the API boundary.
2229
+ const sourceIdentityRoot = adapter.sourceIdentityRoot;
2202
2230
  adapter.warnAboutNonDevelopmentBundling?.(absolutePath);
2203
2231
 
2204
2232
  try {
@@ -2210,6 +2238,19 @@ export async function bundlePlayFile(
2210
2238
  analysis.graphHash = sha256(
2211
2239
  `${analysis.graphHash}\nentry-export:${exportName}\nauthoring-contract-edition:${PLAY_AUTHORING_CONTRACT_EDITION}`,
2212
2240
  );
2241
+ const sourceFiles = Object.fromEntries(
2242
+ Object.entries(analysis.sourceFiles).map(([sourcePath, sourceCode]) => [
2243
+ sourceIdentityPath(sourcePath, sourceIdentityRoot),
2244
+ sourceCode,
2245
+ ]),
2246
+ );
2247
+ const importPolicy: PlayImportPolicy = {
2248
+ ...analysis.importPolicy,
2249
+ localFiles: analysis.importPolicy.localFiles.map((sourcePath) =>
2250
+ sourceIdentityPath(sourcePath, sourceIdentityRoot),
2251
+ ),
2252
+ };
2253
+ const entryFile = sourceIdentityPath(absolutePath, sourceIdentityRoot);
2213
2254
  try {
2214
2255
  validatePlaySourceFilesHaveNoInlineSecrets(analysis.sourceFiles);
2215
2256
  } catch (error) {
@@ -2267,9 +2308,9 @@ export async function bundlePlayFile(
2267
2308
  success: true,
2268
2309
  artifact: {
2269
2310
  ...cachedArtifact,
2270
- entryFile: absolutePath,
2311
+ entryFile,
2271
2312
  sourceHash: analysis.sourceHash,
2272
- importPolicy: analysis.importPolicy,
2313
+ importPolicy,
2273
2314
  compatibility: buildPlayContractCompatibility({
2274
2315
  toolErrorSchemaVersion:
2275
2316
  analysis.toolErrorSchemaVersion ?? undefined,
@@ -2277,7 +2318,7 @@ export async function bundlePlayFile(
2277
2318
  cacheHit: true,
2278
2319
  },
2279
2320
  sourceCode: analysis.sourceCode,
2280
- sourceFiles: analysis.sourceFiles,
2321
+ sourceFiles,
2281
2322
  filePath: absolutePath,
2282
2323
  playName: analysis.playName,
2283
2324
  playDescription: analysis.playDescription,
@@ -2330,7 +2371,7 @@ export async function bundlePlayFile(
2330
2371
  const artifact: PlayBundleArtifact = {
2331
2372
  codeFormat: 'cjs_module',
2332
2373
  artifactKind: target,
2333
- entryFile: absolutePath,
2374
+ entryFile,
2334
2375
  virtualFilename,
2335
2376
  sourceHash: analysis.sourceHash,
2336
2377
  graphHash: analysis.graphHash,
@@ -2338,7 +2379,7 @@ export async function bundlePlayFile(
2338
2379
  sourceMapHash: sha256(normalizedSourceMap),
2339
2380
  bundledCode: executableCode,
2340
2381
  sourceMap: normalizedSourceMap,
2341
- importPolicy: analysis.importPolicy,
2382
+ importPolicy,
2342
2383
  compatibility: buildPlayContractCompatibility({
2343
2384
  toolErrorSchemaVersion: analysis.toolErrorSchemaVersion ?? undefined,
2344
2385
  }),
@@ -2352,7 +2393,7 @@ export async function bundlePlayFile(
2352
2393
  success: true,
2353
2394
  artifact,
2354
2395
  sourceCode: analysis.sourceCode,
2355
- sourceFiles: analysis.sourceFiles,
2396
+ sourceFiles,
2356
2397
  filePath: absolutePath,
2357
2398
  playName: analysis.playName,
2358
2399
  playDescription: analysis.playDescription,