deepline 0.2.48 → 0.2.50

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.
@@ -818,10 +818,14 @@ export type MonitorsNamespace = {
818
818
  list: (options?: MonitorsListOptions) => Promise<MonitorsListResult>;
819
819
  /** Fetch one deployed monitor by public key (without dependents). */
820
820
  get: (key: string) => Promise<MonitorDetail>;
821
- /** Send an explicit payload through the deployed monitor's normal webhook path. */
821
+ /**
822
+ * Test a deployed monitor. `validationOnly` safely verifies the callback
823
+ * envelope; omitted options preserve the historic full-ingestion behavior.
824
+ */
822
825
  test: (
823
826
  key: string,
824
827
  payload: Record<string, unknown>,
828
+ options?: { validationOnly?: boolean },
825
829
  ) => Promise<MonitorTestResult>;
826
830
  validate: (key: string) => Promise<MonitorValidateResult>;
827
831
  /** List the published plays depending on one monitor's output streams. */
@@ -1634,7 +1638,8 @@ export class DeeplineClient {
1634
1638
  deploy: (definition, options) => this.deployMonitor(definition, options),
1635
1639
  list: (options) => this.listMonitors(options),
1636
1640
  get: (key) => this.getMonitor(key),
1637
- test: (key, payload) => this.testMonitorWebhook(key, payload),
1641
+ test: (key, payload, options) =>
1642
+ this.testMonitorWebhook(key, payload, options),
1638
1643
  validate: (key) => this.validateMonitor(key),
1639
1644
  dependents: (key) => this.getMonitorDependents(key),
1640
1645
  update: (key, patch) => this.updateMonitor(key, patch),
@@ -4547,10 +4552,17 @@ export class DeeplineClient {
4547
4552
  async testMonitorWebhook(
4548
4553
  key: string,
4549
4554
  payload: Record<string, unknown>,
4555
+ options?: { validationOnly?: boolean },
4550
4556
  ): Promise<MonitorTestResult> {
4551
4557
  return this.http.request<MonitorTestResult>(
4552
4558
  `/api/v2/monitors/deployed/${encodeURIComponent(key)}/test`,
4553
- { method: 'POST', body: { payload } },
4559
+ {
4560
+ method: 'POST',
4561
+ body: {
4562
+ payload,
4563
+ ...(options?.validationOnly ? { mode: 'validation_only' } : {}),
4564
+ },
4565
+ },
4554
4566
  );
4555
4567
  }
4556
4568
 
@@ -176,7 +176,8 @@ export type PlayFetchResponse = PlayAuthoringFetchResponse;
176
176
  *
177
177
  * A play can be triggered three ways, declared as the third argument to
178
178
  * {@link definePlay}:
179
- * - `webhook` — an inbound HTTP call (with optional HMAC signature verification);
179
+ * - `webhook` — an inbound HTTP call (with optional legacy HMAC or Standard
180
+ * Webhooks signature verification);
180
181
  * - `cron` — a schedule; or
181
182
  * - `sqlListeners` — a **monitor**: the play runs whenever a monitor writes a new
182
183
  * row to its output stream. This is how you build a play "on top of" a monitor
@@ -198,6 +199,19 @@ export type PlayFetchResponse = PlayAuthoringFetchResponse;
198
199
  * });
199
200
  * ```
200
201
  *
202
+ * @example Svix / Standard Webhooks verification with Deepline Secrets
203
+ * ```typescript
204
+ * definePlay('visitor-webhook', handler, {
205
+ * webhook: {
206
+ * auth: {
207
+ * type: 'standard-webhooks',
208
+ * headerFamily: 'svix',
209
+ * signingSecrets: ['VECTOR_WEBHOOK_SECRET'],
210
+ * },
211
+ * },
212
+ * });
213
+ * ```
214
+ *
201
215
  * @example Cron schedule
202
216
  * ```typescript
203
217
  * definePlay('nightly-sync', handler, {
@@ -160,7 +160,7 @@ export const SDK_RELEASE = {
160
160
  // 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
161
161
  // exposed storage-dependent synchronous access. This deliberate minor
162
162
  // release keeps lazy paging semantics independent of row residency.
163
- version: '0.2.48',
163
+ version: '0.2.50',
164
164
  contracts: {
165
165
  api: {
166
166
  name: 'sdk-http-api',
@@ -56,6 +56,7 @@ export const PLAY_AUTHORING_CONTRACT_ISSUE_CODES = [
56
56
  'play_authoring_billing_limit_invalid',
57
57
  'play_authoring_billing_limit_unresolved',
58
58
  'play_authoring_webhook_hmac_invalid',
59
+ 'play_authoring_standard_webhooks_invalid',
59
60
  'play_authoring_secret_invalid',
60
61
  'play_authoring_cron_timezone_invalid',
61
62
  'play_authoring_tool_request_invalid',
@@ -83,6 +84,16 @@ export type PlayAuthoringContractIssue = {
83
84
  };
84
85
 
85
86
  export type PlaySqlListenerOperation = 'INSERT' | 'UPDATE' | 'DELETE';
87
+ export type PlayStandardWebhookHeaderFamily = 'standard' | 'svix';
88
+ export type PlayStandardWebhookAuth = {
89
+ type: 'standard-webhooks';
90
+ /** `webhook-*` for Standard Webhooks, `svix-*` for Svix senders. */
91
+ headerFamily: PlayStandardWebhookHeaderFamily;
92
+ /** Deepline Secret names used for ordinary operation and rotation overlap. */
93
+ signingSecrets: string[];
94
+ /** Replay-protection window. Omitted means the Standard Webhooks 5-minute default. */
95
+ toleranceSeconds?: number;
96
+ };
86
97
  export const PLAY_SQL_LISTENER_WHERE_OPERATORS = [
87
98
  'eq',
88
99
  'neq',
@@ -259,6 +270,7 @@ export type PlayAuthoringAstBindings = {
259
270
  header?: string;
260
271
  secretEnv: string;
261
272
  };
273
+ auth?: PlayStandardWebhookAuth;
262
274
  };
263
275
  cron?: { schedule: string; timezone?: string };
264
276
  sqlListeners?: PlayAuthoringAstSqlListenerDeclaration[];
@@ -287,6 +299,7 @@ export type PlayAuthoringBindings = {
287
299
  header?: string;
288
300
  secretEnv: string;
289
301
  };
302
+ auth?: PlayStandardWebhookAuth;
290
303
  };
291
304
  cron?: {
292
305
  schedule: string;
@@ -973,6 +986,81 @@ export const PLAY_AUTHORING_FIELD_REGISTRY = {
973
986
  errorMessage:
974
987
  'bindings.webhook.hmac.header must be a non-empty static string.',
975
988
  },
989
+ 'bindings.webhook.auth.type': {
990
+ schema: Type.Literal('standard-webhooks'),
991
+ fixtures: {
992
+ valid: 'standard-webhooks',
993
+ invalid: 'svix',
994
+ absent: undefined,
995
+ unresolved: { expression: 'type' },
996
+ edition1: undefined,
997
+ },
998
+ referenceType: "'standard-webhooks'",
999
+ // auth itself is optional; once present, the AST adapter requires this
1000
+ // field together with headerFamily and signingSecrets.
1001
+ required: false,
1002
+ resolution: 'static-required',
1003
+ issueCode: 'play_authoring_standard_webhooks_invalid',
1004
+ description: 'Uses the Standard Webhooks v1 symmetric signing scheme.',
1005
+ errorMessage:
1006
+ 'bindings.webhook.auth.type must be the static literal "standard-webhooks".',
1007
+ },
1008
+ 'bindings.webhook.auth.headerFamily': {
1009
+ schema: Type.Union([Type.Literal('standard'), Type.Literal('svix')]),
1010
+ fixtures: {
1011
+ valid: 'svix',
1012
+ invalid: 'webhook',
1013
+ absent: undefined,
1014
+ unresolved: { expression: 'headerFamily' },
1015
+ edition1: undefined,
1016
+ },
1017
+ referenceType: "'standard' | 'svix'",
1018
+ // auth itself is optional; once present, the AST adapter requires this
1019
+ // field together with type and signingSecrets.
1020
+ required: false,
1021
+ resolution: 'static-required',
1022
+ issueCode: 'play_authoring_standard_webhooks_invalid',
1023
+ description: 'Header namespace expected from the webhook provider.',
1024
+ errorMessage:
1025
+ 'bindings.webhook.auth.headerFamily must be the static literal "standard" or "svix".',
1026
+ },
1027
+ 'bindings.webhook.auth.signingSecrets[]': {
1028
+ schema: SecretEnvironmentNameSchema,
1029
+ fixtures: {
1030
+ valid: 'VECTOR_WEBHOOK_SECRET',
1031
+ invalid: 'vector_webhook_secret',
1032
+ absent: undefined,
1033
+ unresolved: { expression: 'secret' },
1034
+ edition1: undefined,
1035
+ },
1036
+ referenceType: 'string',
1037
+ // auth itself is optional; once present, the AST adapter requires this
1038
+ // field together with type and headerFamily.
1039
+ required: false,
1040
+ resolution: 'static-required',
1041
+ issueCode: 'play_authoring_standard_webhooks_invalid',
1042
+ description: 'Deepline Secret name used to verify Standard Webhooks.',
1043
+ errorMessage:
1044
+ 'bindings.webhook.auth.signingSecrets entries must be uppercase Deepline Secret names beginning with a letter.',
1045
+ },
1046
+ 'bindings.webhook.auth.toleranceSeconds': {
1047
+ schema: Type.Integer({ minimum: 1, maximum: 3600 }),
1048
+ fixtures: {
1049
+ valid: 300,
1050
+ invalid: 0,
1051
+ absent: undefined,
1052
+ unresolved: { expression: 'toleranceSeconds' },
1053
+ edition1: undefined,
1054
+ },
1055
+ referenceType: 'number',
1056
+ required: false,
1057
+ resolution: 'static-required',
1058
+ issueCode: 'play_authoring_standard_webhooks_invalid',
1059
+ description:
1060
+ 'Accepted delivery timestamp skew in seconds, from 1 through 3600.',
1061
+ errorMessage:
1062
+ 'bindings.webhook.auth.toleranceSeconds must be a static whole number from 1 through 3600.',
1063
+ },
976
1064
  'bindings.cron.schedule': {
977
1065
  schema: Type.String({ minLength: 1 }),
978
1066
  fixtures: {
@@ -2157,7 +2245,7 @@ export const PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
2157
2245
  ` inline?: ${cloudReferenceType('inline')};`,
2158
2246
  ` billing?: { maxCreditsPerRun?: ${cloudReferenceType('billing.maxCreditsPerRun')} };`,
2159
2247
  ` runtime?: { timeout?: ${cloudReferenceType('runtime.timeout')}; size?: ${cloudReferenceType('runtime.size')} };`,
2160
- ` webhook?: { hmac?: { algorithm?: ${cloudReferenceType('bindings.webhook.hmac.algorithm')}; header?: ${cloudReferenceType('bindings.webhook.hmac.header')}; secretEnv: ${cloudReferenceType('bindings.webhook.hmac.secretEnv')} } };`,
2248
+ ` webhook?: { hmac?: { algorithm?: ${cloudReferenceType('bindings.webhook.hmac.algorithm')}; header?: ${cloudReferenceType('bindings.webhook.hmac.header')}; secretEnv: ${cloudReferenceType('bindings.webhook.hmac.secretEnv')} }; auth?: { type: ${cloudReferenceType('bindings.webhook.auth.type')}; headerFamily: ${cloudReferenceType('bindings.webhook.auth.headerFamily')}; signingSecrets: readonly ${cloudReferenceType('bindings.webhook.auth.signingSecrets[]')}[]; toleranceSeconds?: ${cloudReferenceType('bindings.webhook.auth.toleranceSeconds')} } };`,
2161
2249
  ` cron?: { schedule: ${cloudReferenceType('bindings.cron.schedule')}; timezone?: ${cloudReferenceType('bindings.cron.timezone')} };`,
2162
2250
  ' sqlListeners?: readonly SqlListenerDeclaration[];',
2163
2251
  ` secrets?: readonly ${cloudReferenceType('bindings.secrets[]')}[];`,
@@ -10,6 +10,7 @@ import {
10
10
  join,
11
11
  relative,
12
12
  resolve,
13
+ sep,
13
14
  } from 'node:path';
14
15
  import { builtinModules } from 'node:module';
15
16
  import { Parser } from 'acorn';
@@ -94,6 +95,8 @@ export type PlayLocalFileDiscoveryResult = {
94
95
 
95
96
  export type PlayBundlingAdapter = {
96
97
  projectRoot: string;
98
+ /** Optional root used only to make source-graph identity independent of temporary absolute paths. */
99
+ sourceIdentityRoot?: string;
97
100
  nodeModulesDir: string;
98
101
  cacheDir?: string;
99
102
  sdkSourceRoot: string;
@@ -179,6 +182,24 @@ function sha256(value: string): string {
179
182
  return createHash('sha256').update(value).digest('hex');
180
183
  }
181
184
 
185
+ function sourceIdentityPath(
186
+ filePath: string,
187
+ adapter: PlayBundlingAdapter,
188
+ ): string {
189
+ if (!adapter.sourceIdentityRoot) return filePath;
190
+ const identityRoot = resolve(adapter.sourceIdentityRoot);
191
+ const logicalPath = relative(identityRoot, resolve(filePath));
192
+ if (
193
+ !logicalPath ||
194
+ logicalPath === '..' ||
195
+ logicalPath.startsWith(`..${sep}`) ||
196
+ isAbsolute(logicalPath)
197
+ ) {
198
+ return filePath;
199
+ }
200
+ return logicalPath.split(/[\\/]+/).join('/');
201
+ }
202
+
182
203
  function formatEsbuildMessage(message: Message): string {
183
204
  const location = message.location
184
205
  ? `${message.location.file}:${message.location.line}:${message.location.column}`
@@ -1598,9 +1619,12 @@ async function analyzeSourceGraph(
1598
1619
  const sourceHash = sha256(sourceCode);
1599
1620
  const graphHash = sha256(
1600
1621
  JSON.stringify({
1601
- entryFile: absoluteEntryFile,
1622
+ entryFile: sourceIdentityPath(absoluteEntryFile, adapter),
1602
1623
  localFiles: [...localFiles.entries()]
1603
- .map(([filePath, contents]) => ({ filePath, hash: sha256(contents) }))
1624
+ .map(([filePath, contents]) => ({
1625
+ filePath: sourceIdentityPath(filePath, adapter),
1626
+ hash: sha256(contents),
1627
+ }))
1604
1628
  .sort((left, right) => left.filePath.localeCompare(right.filePath)),
1605
1629
  nodeBuiltins: [...nodeBuiltins].sort(),
1606
1630
  packages: [...packages.entries()]
@@ -1608,7 +1632,7 @@ async function analyzeSourceGraph(
1608
1632
  .sort((left, right) => left.name.localeCompare(right.name)),
1609
1633
  importedPlayDependencies: [...importedPlayDependencies.values()]
1610
1634
  .map((dependency) => ({
1611
- filePath: dependency.filePath,
1635
+ filePath: sourceIdentityPath(dependency.filePath, adapter),
1612
1636
  playName: dependency.playName,
1613
1637
  }))
1614
1638
  .sort((left, right) => left.filePath.localeCompare(right.filePath)),
@@ -1660,7 +1684,7 @@ function artifactCachePath(
1660
1684
  adapter: PlayBundlingAdapter,
1661
1685
  ): string {
1662
1686
  return join(
1663
- adapter.cacheDir ?? PLAY_ARTIFACT_CACHE_DIR,
1687
+ /* turbopackIgnore: true */ adapter.cacheDir ?? PLAY_ARTIFACT_CACHE_DIR,
1664
1688
  `${graphHash}.${artifactKind}.json`,
1665
1689
  );
1666
1690
  }
@@ -1672,7 +1696,11 @@ async function readArtifactCache(
1672
1696
  ): Promise<PlayBundleArtifact | null> {
1673
1697
  try {
1674
1698
  const serialized = await readFile(
1675
- artifactCachePath(graphHash, artifactKind, adapter),
1699
+ /* turbopackIgnore: true */ artifactCachePath(
1700
+ graphHash,
1701
+ artifactKind,
1702
+ adapter,
1703
+ ),
1676
1704
  'utf-8',
1677
1705
  );
1678
1706
  return JSON.parse(serialized) as PlayBundleArtifact;
@@ -1686,9 +1714,9 @@ async function writeArtifactCache(
1686
1714
  adapter: PlayBundlingAdapter,
1687
1715
  ): Promise<void> {
1688
1716
  const cacheDir = adapter.cacheDir ?? PLAY_ARTIFACT_CACHE_DIR;
1689
- await mkdir(cacheDir, { recursive: true });
1717
+ await mkdir(/* turbopackIgnore: true */ cacheDir, { recursive: true });
1690
1718
  await writeFile(
1691
- artifactCachePath(
1719
+ /* turbopackIgnore: true */ artifactCachePath(
1692
1720
  artifact.graphHash,
1693
1721
  artifact.artifactKind ?? PLAY_ARTIFACT_KINDS.cjsNode20,
1694
1722
  adapter,
@@ -0,0 +1,21 @@
1
+ export const ENRICH_COMPAT_DEFAULT_PLAY_NAME = 'deepline-enrich-v1-compat';
2
+ export const ENRICH_COMPAT_DEFAULT_MAP_NAME = 'deepline_enrich_rows';
3
+
4
+ export type EnrichCompatibilityOptions = {
5
+ playName?: string;
6
+ mapName?: string;
7
+ };
8
+
9
+ export type EnrichCompatibilityPlan = {
10
+ playName: string;
11
+ mapName: string;
12
+ };
13
+
14
+ export function buildEnrichCompatibilityPlan(
15
+ options: EnrichCompatibilityOptions = {},
16
+ ): EnrichCompatibilityPlan {
17
+ return {
18
+ playName: options.playName?.trim() || ENRICH_COMPAT_DEFAULT_PLAY_NAME,
19
+ mapName: options.mapName?.trim() || ENRICH_COMPAT_DEFAULT_MAP_NAME,
20
+ };
21
+ }