deepline 0.1.85 → 0.1.89
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.
- package/dist/cli/index.js +419 -70
- package/dist/cli/index.mjs +438 -83
- package/dist/index.d.mts +442 -60
- package/dist/index.d.ts +442 -60
- package/dist/index.js +161 -4
- package/dist/index.mjs +161 -4
- package/dist/repo/apps/play-runner-workers/src/dedup-do.ts +1 -0
- package/dist/repo/apps/play-runner-workers/src/entry.ts +276 -192
- package/dist/repo/sdk/src/client.ts +155 -1
- package/dist/repo/sdk/src/http.ts +11 -0
- package/dist/repo/sdk/src/index.ts +24 -1
- package/dist/repo/sdk/src/play.ts +198 -15
- package/dist/repo/sdk/src/release.ts +2 -2
- package/dist/repo/sdk/src/types.ts +61 -0
- package/dist/repo/sdk/src/worker-play-entry.ts +6 -3
- package/dist/repo/shared_libs/play-runtime/cell-staleness.ts +23 -0
- package/dist/repo/shared_libs/play-runtime/coordinator-headers.ts +10 -0
- package/dist/repo/shared_libs/play-runtime/scheduler-backend.ts +10 -1
- package/dist/repo/shared_libs/play-runtime/tool-result.ts +202 -12
- package/dist/repo/shared_libs/plays/bundling/index.ts +23 -16
- package/dist/repo/shared_libs/plays/dataset.ts +2 -0
- package/dist/repo/shared_libs/plays/row-identity.ts +14 -8
- package/package.json +1 -1
|
@@ -24,6 +24,16 @@ export const COORDINATOR_RUN_SCOPE_HEADER = 'x-deepline-run-scope';
|
|
|
24
24
|
export const COORDINATOR_URL_OVERRIDE_HEADER = 'x-deepline-coordinator-url';
|
|
25
25
|
export const WORKER_CALLBACK_URL_OVERRIDE_HEADER =
|
|
26
26
|
'x-deepline-worker-callback-url';
|
|
27
|
+
/**
|
|
28
|
+
* CLI→app marker (NOT a coordinator header — it lives here only because both
|
|
29
|
+
* the SDK HTTP client and the run route already import this module). Set by
|
|
30
|
+
* automated test harnesses (e.g. `tests/v2-plays`) so their intentionally
|
|
31
|
+
* failing plays — depth-guard probes, error-path scenarios — do not page the
|
|
32
|
+
* SDK CLI error channel as if a real customer run had failed. The run route
|
|
33
|
+
* honors it ONLY in non-prod (see run/route.ts); a forged header from a real
|
|
34
|
+
* prod customer can never suppress their own failure alerts.
|
|
35
|
+
*/
|
|
36
|
+
export const SYNTHETIC_RUN_HEADER = 'x-deepline-synthetic-run';
|
|
27
37
|
|
|
28
38
|
let warnedAboutMissingInternalToken = false;
|
|
29
39
|
|
|
@@ -136,7 +136,16 @@ export type PlaySchedulerSubmitInput = {
|
|
|
136
136
|
};
|
|
137
137
|
|
|
138
138
|
export type PlaySchedulerProgressEvent =
|
|
139
|
-
| {
|
|
139
|
+
| {
|
|
140
|
+
type: 'status';
|
|
141
|
+
status: string;
|
|
142
|
+
logs?: string[];
|
|
143
|
+
ts: number;
|
|
144
|
+
activeNodeId?: string | null;
|
|
145
|
+
activeArtifactTableNamespace?: string | null;
|
|
146
|
+
updatedAt?: number | null;
|
|
147
|
+
liveNodeProgress?: unknown;
|
|
148
|
+
}
|
|
140
149
|
| { type: 'log'; line: string; ts: number }
|
|
141
150
|
| { type: 'row'; update: PlayRowUpdate; ts: number }
|
|
142
151
|
| { type: 'execution_event'; event: PlayExecutionEvent; ts: number }
|
|
@@ -17,7 +17,10 @@ export type {
|
|
|
17
17
|
EmailStatusVerdict,
|
|
18
18
|
} from './email-status';
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
buildEmailStatus,
|
|
22
|
+
type EmailStatusExtractorConfig,
|
|
23
|
+
} from './email-status';
|
|
21
24
|
import {
|
|
22
25
|
JOB_CHANGE_STATUS_VALUES,
|
|
23
26
|
type JobChangeGetterValue,
|
|
@@ -56,12 +59,12 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
56
59
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
|
|
62
|
+
type V2ToolExecuteOutput = {
|
|
60
63
|
raw: unknown;
|
|
61
64
|
meta?: Record<string, unknown>;
|
|
62
65
|
};
|
|
63
66
|
|
|
64
|
-
|
|
67
|
+
function parseV2ToolExecuteOutput(
|
|
65
68
|
data: Record<string, unknown>,
|
|
66
69
|
): V2ToolExecuteOutput | null {
|
|
67
70
|
const toolResponse = data.toolResponse;
|
|
@@ -77,9 +80,10 @@ export function parseV2ToolExecuteOutput(
|
|
|
77
80
|
return null;
|
|
78
81
|
}
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
function adaptV2ExecuteResponseToToolResult(data: Record<string, unknown>): {
|
|
84
|
+
output?: V2ToolExecuteOutput;
|
|
85
|
+
result: unknown;
|
|
86
|
+
} {
|
|
83
87
|
const output = parseV2ToolExecuteOutput(data);
|
|
84
88
|
if (!output) {
|
|
85
89
|
return { result: data.result ?? data };
|
|
@@ -93,7 +97,195 @@ export function adaptV2ExecuteResponseToToolResult(
|
|
|
93
97
|
};
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Parsed view of a raw `/api/v2/integrations/:toolId/execute` JSON body.
|
|
102
|
+
*
|
|
103
|
+
* This is the single runtime-side seam from a V2 execute response to the
|
|
104
|
+
* inputs of {@link createToolExecuteResult}. Both runner substrates (the
|
|
105
|
+
* in-process cjs runtime in `shared_libs/play-runtime/context.ts` and the
|
|
106
|
+
* Workers runtime in `apps/play-runner-workers`) must go through
|
|
107
|
+
* {@link parseToolExecuteResponse}; the intermediate envelope shapes
|
|
108
|
+
* (`toolResponse` adaptation, `_metadata.tool` parsing, status derivation)
|
|
109
|
+
* are private to this module.
|
|
110
|
+
*
|
|
111
|
+
* Boundary note: provider-specific redaction (wiza/apify/bettercontact billing
|
|
112
|
+
* fields) happens server-side in
|
|
113
|
+
* `src/lib/integrations/execute-result-normalization.ts` BEFORE the response
|
|
114
|
+
* leaves the API. This module only ever sees the already-redacted public
|
|
115
|
+
* response — do not move redaction here, it would leak provider internals
|
|
116
|
+
* into the shared runtime bundles.
|
|
117
|
+
*/
|
|
118
|
+
export type ParsedToolExecuteResponse = {
|
|
119
|
+
status: string;
|
|
120
|
+
jobId?: string;
|
|
121
|
+
meta?: Record<string, unknown>;
|
|
122
|
+
toolResponse?: {
|
|
123
|
+
raw?: unknown;
|
|
124
|
+
meta?: Record<string, unknown>;
|
|
125
|
+
};
|
|
126
|
+
/** Legacy `{ data, meta }` envelope consumed by createToolExecuteResult. */
|
|
127
|
+
result: unknown;
|
|
128
|
+
/** Tool extractor metadata parsed from `_metadata.tool`, if present. */
|
|
129
|
+
metadata: ToolResultMetadataInput | null;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export function parseToolExecuteResponse(
|
|
133
|
+
toolId: string,
|
|
134
|
+
body: Record<string, unknown>,
|
|
135
|
+
): ParsedToolExecuteResponse {
|
|
136
|
+
const { result } = adaptV2ExecuteResponseToToolResult(body);
|
|
137
|
+
const status =
|
|
138
|
+
typeof body.status === 'string'
|
|
139
|
+
? body.status
|
|
140
|
+
: result == null
|
|
141
|
+
? 'no_result'
|
|
142
|
+
: 'completed';
|
|
143
|
+
return {
|
|
144
|
+
status,
|
|
145
|
+
jobId: typeof body.job_id === 'string' ? body.job_id : undefined,
|
|
146
|
+
meta: isRecord(body.meta) ? body.meta : undefined,
|
|
147
|
+
toolResponse: isRecord(body.toolResponse)
|
|
148
|
+
? (body.toolResponse as ParsedToolExecuteResponse['toolResponse'])
|
|
149
|
+
: undefined,
|
|
150
|
+
result,
|
|
151
|
+
metadata: parseExecuteToolMetadata(toolId, body),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function parseExecuteToolMetadata(
|
|
156
|
+
toolId: string,
|
|
157
|
+
data: Record<string, unknown>,
|
|
158
|
+
): ToolResultMetadataInput | null {
|
|
159
|
+
const metadata = data._metadata;
|
|
160
|
+
if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
const tool = (metadata as Record<string, unknown>).tool;
|
|
164
|
+
if (!tool || typeof tool !== 'object' || Array.isArray(tool)) return null;
|
|
165
|
+
const record = tool as Record<string, unknown>;
|
|
166
|
+
const metadataToolId =
|
|
167
|
+
typeof record.toolId === 'string' && record.toolId.trim()
|
|
168
|
+
? record.toolId
|
|
169
|
+
: toolId;
|
|
170
|
+
const readGetters = (value: unknown): Record<string, readonly string[]> => {
|
|
171
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
172
|
+
return Object.fromEntries(
|
|
173
|
+
Object.entries(value as Record<string, unknown>).flatMap(
|
|
174
|
+
([key, paths]) => {
|
|
175
|
+
if (!Array.isArray(paths)) return [];
|
|
176
|
+
const normalized = paths.filter(
|
|
177
|
+
(path): path is string =>
|
|
178
|
+
typeof path === 'string' && path.trim().length > 0,
|
|
179
|
+
);
|
|
180
|
+
return normalized.length > 0 ? [[key, normalized]] : [];
|
|
181
|
+
},
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
const readExtractors = (
|
|
186
|
+
value: unknown,
|
|
187
|
+
): ToolResultMetadataInput['extractors'] => {
|
|
188
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
189
|
+
return Object.fromEntries(
|
|
190
|
+
Object.entries(value as Record<string, unknown>).flatMap(
|
|
191
|
+
([key, entry]) => {
|
|
192
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
193
|
+
return [];
|
|
194
|
+
const recordEntry = entry as Record<string, unknown>;
|
|
195
|
+
if (!Array.isArray(recordEntry.paths)) return [];
|
|
196
|
+
const paths = recordEntry.paths.filter(
|
|
197
|
+
(path): path is string =>
|
|
198
|
+
typeof path === 'string' && path.trim().length > 0,
|
|
199
|
+
);
|
|
200
|
+
if (paths.length === 0) return [];
|
|
201
|
+
const overrides = Array.isArray(recordEntry.overrides)
|
|
202
|
+
? recordEntry.overrides.flatMap((override) => {
|
|
203
|
+
if (
|
|
204
|
+
!override ||
|
|
205
|
+
typeof override !== 'object' ||
|
|
206
|
+
Array.isArray(override)
|
|
207
|
+
) {
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
const overrideRecord = override as Record<string, unknown>;
|
|
211
|
+
const overridePaths = Array.isArray(overrideRecord.paths)
|
|
212
|
+
? overrideRecord.paths.filter(
|
|
213
|
+
(path): path is string =>
|
|
214
|
+
typeof path === 'string' && path.trim().length > 0,
|
|
215
|
+
)
|
|
216
|
+
: [];
|
|
217
|
+
if (overridePaths.length === 0) return [];
|
|
218
|
+
const readOverridePrimitive = (
|
|
219
|
+
candidate: unknown,
|
|
220
|
+
): string | number | boolean | null | undefined => {
|
|
221
|
+
if (candidate === null) return null;
|
|
222
|
+
if (typeof candidate === 'string') return candidate;
|
|
223
|
+
if (typeof candidate === 'number') return candidate;
|
|
224
|
+
if (typeof candidate === 'boolean') return candidate;
|
|
225
|
+
return undefined;
|
|
226
|
+
};
|
|
227
|
+
const value = readOverridePrimitive(overrideRecord.value);
|
|
228
|
+
if (value === undefined) {
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
const equals: string | number | boolean | null =
|
|
232
|
+
readOverridePrimitive(overrideRecord.equals) ?? true;
|
|
233
|
+
return [{ paths: overridePaths, equals, value }];
|
|
234
|
+
})
|
|
235
|
+
: [];
|
|
236
|
+
const emailStatus =
|
|
237
|
+
recordEntry.emailStatus &&
|
|
238
|
+
typeof recordEntry.emailStatus === 'object' &&
|
|
239
|
+
!Array.isArray(recordEntry.emailStatus)
|
|
240
|
+
? (recordEntry.emailStatus as EmailStatusExtractorConfig)
|
|
241
|
+
: undefined;
|
|
242
|
+
return [
|
|
243
|
+
[
|
|
244
|
+
key,
|
|
245
|
+
{
|
|
246
|
+
paths,
|
|
247
|
+
...(Array.isArray(recordEntry.transforms)
|
|
248
|
+
? {
|
|
249
|
+
transforms: recordEntry.transforms.filter(
|
|
250
|
+
(transform): transform is string =>
|
|
251
|
+
typeof transform === 'string' &&
|
|
252
|
+
transform.trim().length > 0,
|
|
253
|
+
),
|
|
254
|
+
}
|
|
255
|
+
: {}),
|
|
256
|
+
...(Array.isArray(recordEntry.enum)
|
|
257
|
+
? {
|
|
258
|
+
enum: recordEntry.enum.filter(
|
|
259
|
+
(value): value is string =>
|
|
260
|
+
typeof value === 'string' && value.trim().length > 0,
|
|
261
|
+
),
|
|
262
|
+
}
|
|
263
|
+
: {}),
|
|
264
|
+
...(overrides.length > 0 ? { overrides } : {}),
|
|
265
|
+
...(emailStatus ? { emailStatus } : {}),
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
];
|
|
269
|
+
},
|
|
270
|
+
),
|
|
271
|
+
);
|
|
272
|
+
};
|
|
273
|
+
const listExtractorPaths = Array.isArray(record.listExtractorPaths)
|
|
274
|
+
? record.listExtractorPaths.filter(
|
|
275
|
+
(path): path is string =>
|
|
276
|
+
typeof path === 'string' && path.trim().length > 0,
|
|
277
|
+
)
|
|
278
|
+
: [];
|
|
279
|
+
return {
|
|
280
|
+
toolId: metadataToolId,
|
|
281
|
+
extractors: readExtractors(record.extractors),
|
|
282
|
+
targetGetters: readGetters(record.targetGetters),
|
|
283
|
+
listExtractorPaths,
|
|
284
|
+
listIdentityGetters: readGetters(record.listIdentityGetters),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function toV2RawToolOutputPath(path: string): string {
|
|
97
289
|
const normalized = String(path || '')
|
|
98
290
|
.trim()
|
|
99
291
|
.replace(/^\./, '');
|
|
@@ -445,9 +637,7 @@ function normalizeJobChangeStatus(value: unknown): unknown {
|
|
|
445
637
|
if (['false', 'no', 'same', 'no_change'].includes(normalized))
|
|
446
638
|
return 'no_change';
|
|
447
639
|
if (['left', 'left_company'].includes(normalized)) return 'left_company';
|
|
448
|
-
if (
|
|
449
|
-
(JOB_CHANGE_STATUS_VALUES as readonly string[]).includes(normalized)
|
|
450
|
-
) {
|
|
640
|
+
if ((JOB_CHANGE_STATUS_VALUES as readonly string[]).includes(normalized)) {
|
|
451
641
|
return normalized;
|
|
452
642
|
}
|
|
453
643
|
return 'unknown';
|
|
@@ -481,12 +671,12 @@ function normalizeJobChange(value: unknown): JobChangeGetterValue {
|
|
|
481
671
|
return {
|
|
482
672
|
status,
|
|
483
673
|
date: moved
|
|
484
|
-
? normalizeString(
|
|
674
|
+
? (normalizeString(
|
|
485
675
|
output.date ??
|
|
486
676
|
output.job_change_date ??
|
|
487
677
|
output.change_date ??
|
|
488
678
|
output.changed_at,
|
|
489
|
-
) ?? firstExperienceDate(person.experiences)
|
|
679
|
+
) ?? firstExperienceDate(person.experiences))
|
|
490
680
|
: null,
|
|
491
681
|
new_company: moved
|
|
492
682
|
? normalizeString(
|
|
@@ -26,10 +26,7 @@ import type {
|
|
|
26
26
|
} from '../artifact-types';
|
|
27
27
|
import { buildPlayContractCompatibility } from '../contracts';
|
|
28
28
|
import { validatePlaySourceFilesHaveNoInlineSecrets } from '../secret-guardrails';
|
|
29
|
-
import {
|
|
30
|
-
MAX_ESM_WORKERS_BUNDLE_BYTES,
|
|
31
|
-
MAX_PLAY_BUNDLE_BYTES,
|
|
32
|
-
} from './limits';
|
|
29
|
+
import { MAX_ESM_WORKERS_BUNDLE_BYTES, MAX_PLAY_BUNDLE_BYTES } from './limits';
|
|
33
30
|
|
|
34
31
|
const PLAY_BUNDLE_CACHE_VERSION = 24;
|
|
35
32
|
const PLAY_ARTIFACT_CACHE_DIR = join(
|
|
@@ -443,7 +440,13 @@ function findPackageJsonPathFrom(
|
|
|
443
440
|
startDir: string,
|
|
444
441
|
packageName: string,
|
|
445
442
|
): string | null {
|
|
446
|
-
|
|
443
|
+
if (!isAbsolute(startDir)) {
|
|
444
|
+
throw new Error(
|
|
445
|
+
`Package resolution requires an absolute start directory, got ${startDir}`,
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
let current = startDir;
|
|
447
450
|
while (true) {
|
|
448
451
|
const packageJsonPath = join(
|
|
449
452
|
current,
|
|
@@ -469,17 +472,15 @@ function findPackageJsonPath(
|
|
|
469
472
|
adapter: PlayBundlingAdapter,
|
|
470
473
|
): string | null {
|
|
471
474
|
const startDirs = [
|
|
472
|
-
dirname(fromFile),
|
|
473
|
-
adapter.projectRoot,
|
|
474
|
-
dirname(adapter.sdkPackageJson),
|
|
475
|
-
process.cwd(),
|
|
475
|
+
resolve(dirname(fromFile)),
|
|
476
|
+
resolve(adapter.projectRoot),
|
|
477
|
+
resolve(dirname(adapter.sdkPackageJson)),
|
|
476
478
|
];
|
|
477
479
|
const seen = new Set<string>();
|
|
478
480
|
for (const startDir of startDirs) {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const packageJsonPath = findPackageJsonPathFrom(normalized, packageName);
|
|
481
|
+
if (seen.has(startDir)) continue;
|
|
482
|
+
seen.add(startDir);
|
|
483
|
+
const packageJsonPath = findPackageJsonPathFrom(startDir, packageName);
|
|
483
484
|
if (packageJsonPath) return packageJsonPath;
|
|
484
485
|
}
|
|
485
486
|
|
|
@@ -1139,7 +1140,10 @@ async function writeArtifactCache(
|
|
|
1139
1140
|
);
|
|
1140
1141
|
}
|
|
1141
1142
|
|
|
1142
|
-
function normalizeSourceMapForRuntime(
|
|
1143
|
+
function normalizeSourceMapForRuntime(
|
|
1144
|
+
sourceMapText: string,
|
|
1145
|
+
projectRoot: string,
|
|
1146
|
+
): string {
|
|
1143
1147
|
const parsed = JSON.parse(sourceMapText) as {
|
|
1144
1148
|
sourceRoot?: string;
|
|
1145
1149
|
sources?: string[];
|
|
@@ -1155,7 +1159,7 @@ function normalizeSourceMapForRuntime(sourceMapText: string): string {
|
|
|
1155
1159
|
return sourcePath;
|
|
1156
1160
|
}
|
|
1157
1161
|
|
|
1158
|
-
return
|
|
1162
|
+
return join(projectRoot, sourcePath);
|
|
1159
1163
|
});
|
|
1160
1164
|
parsed.sourceRoot = undefined;
|
|
1161
1165
|
|
|
@@ -1524,7 +1528,10 @@ export async function bundlePlayFile(
|
|
|
1524
1528
|
}
|
|
1525
1529
|
const { bundledCode, sourceMapText, outputExtension } = buildOutcome;
|
|
1526
1530
|
|
|
1527
|
-
const normalizedSourceMap = normalizeSourceMapForRuntime(
|
|
1531
|
+
const normalizedSourceMap = normalizeSourceMapForRuntime(
|
|
1532
|
+
sourceMapText,
|
|
1533
|
+
resolve(adapter.projectRoot),
|
|
1534
|
+
);
|
|
1528
1535
|
const virtualBaseName =
|
|
1529
1536
|
exportName === 'default'
|
|
1530
1537
|
? basename(absolutePath).replace(/\.[^.]+$/, '')
|
|
@@ -74,6 +74,8 @@ export type PlayDatasetTransformOptions = {
|
|
|
74
74
|
* small and bounded. `PlayDataset` intentionally does not expose `.rows`,
|
|
75
75
|
* `.toArray()`, or other array aliases; those hide the runtime cost of loading
|
|
76
76
|
* persisted rows into memory.
|
|
77
|
+
*
|
|
78
|
+
* @sdkReference runtime 190
|
|
77
79
|
*/
|
|
78
80
|
export interface PlayDataset<T> extends AsyncIterable<T> {
|
|
79
81
|
readonly [PLAY_DATASET_BRAND]: true;
|
|
@@ -169,19 +169,25 @@ export function normalizePlayName(value: string): string {
|
|
|
169
169
|
return validateIdentifierPart(value, 'Play name', PLAY_NAME_MAX_LENGTH);
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Normalize a play name into the leading segment of a physical sheet table name.
|
|
174
|
+
*
|
|
175
|
+
* A qualified reference like "prebuilt/name-and-domain-to-email-waterfall" folds
|
|
176
|
+
* its namespace separator into a plain identifier
|
|
177
|
+
* ("prebuilt_name_and_domain_to_email_waterfall") so the physical table reads
|
|
178
|
+
* cleanly. `validatePlaySheetTableName` enforces the 63-char Postgres identifier
|
|
179
|
+
* limit on the play + namespace combination and fails loudly when a name is
|
|
180
|
+
* genuinely too long — no opaque content digest is mixed into the table name.
|
|
181
|
+
*/
|
|
172
182
|
export function normalizePlayNameForSheet(value: string): string {
|
|
173
183
|
if (!value.includes('/')) {
|
|
174
184
|
return normalizePlayName(value);
|
|
175
185
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
186
|
+
return validateIdentifierPart(
|
|
187
|
+
value.replace(/\//g, '_'),
|
|
188
|
+
'Play name',
|
|
189
|
+
PLAY_NAME_MAX_LENGTH,
|
|
179
190
|
);
|
|
180
|
-
const prefixLength = Math.max(1, PLAY_NAME_MAX_LENGTH - digest.length - 1);
|
|
181
|
-
const prefix =
|
|
182
|
-
normalizedReference.slice(0, prefixLength).replace(/_+$/g, '') ||
|
|
183
|
-
'qualified_play';
|
|
184
|
-
return `${prefix}_${digest}`;
|
|
185
191
|
}
|
|
186
192
|
|
|
187
193
|
export function normalizeTableNamespace(value: string): string {
|