@oxygen-agent/cli 1.987.20 → 1.1010.1
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/README.md +1 -1
- package/dist/admin-primary-providers-render.d.ts +0 -2
- package/dist/admin-primary-providers-render.js +1 -1
- package/dist/browser-login.js +1 -4
- package/dist/column-decision-options.d.ts +20 -0
- package/dist/column-decision-options.js +54 -0
- package/dist/command-manifest.d.ts +3 -2
- package/dist/command-manifest.js +25 -2
- package/dist/credentials.d.ts +1 -1
- package/dist/functions-commands.js +13 -9
- package/dist/help.d.ts +8 -0
- package/dist/help.js +46 -0
- package/dist/index.js +2751 -242
- package/dist/knowledge-mirror.d.ts +2 -2
- package/dist/runtime.d.ts +0 -15
- package/dist/runtime.js +1 -1
- package/dist/search-ai-filter-notice.d.ts +17 -0
- package/dist/search-ai-filter-notice.js +38 -0
- package/dist/session.d.ts +4 -3
- package/dist/skills.d.ts +8 -7
- package/dist/skills.js +58 -20
- package/dist/transcript.d.ts +2 -1
- package/dist/util.d.ts +10 -1
- package/dist/util.js +14 -2
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +296 -140
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.d.ts +9 -0
- package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +34 -0
- package/node_modules/@oxygen/formula/dist/coerce.d.ts +10 -0
- package/node_modules/@oxygen/formula/dist/coerce.js +10 -0
- package/node_modules/@oxygen/formula/dist/formula-functions.js +65 -0
- package/node_modules/@oxygen/formula/dist/hash.d.ts +19 -0
- package/node_modules/@oxygen/formula/dist/hash.js +199 -0
- package/node_modules/@oxygen/formula/dist/value-cleaners.d.ts +6 -1
- package/node_modules/@oxygen/formula/dist/value-cleaners.js +10 -26
- package/node_modules/@oxygen/recipe-sdk/dist/index.d.ts +13 -0
- package/node_modules/@oxygen/shared/dist/array-utils.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/array-utils.js +11 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +99 -22
- package/node_modules/@oxygen/shared/dist/billing.js +195 -40
- package/node_modules/@oxygen/shared/dist/byok-connect.js +5 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +27 -0
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +311 -28
- package/node_modules/@oxygen/shared/dist/cli-http-error.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/cli-http-error.js +8 -0
- package/node_modules/@oxygen/shared/dist/cli-result.js +1 -0
- package/node_modules/@oxygen/shared/dist/column-autofill.d.ts +52 -0
- package/node_modules/@oxygen/shared/dist/column-autofill.js +62 -0
- package/node_modules/@oxygen/shared/dist/column-decision.d.ts +50 -0
- package/node_modules/@oxygen/shared/dist/column-decision.js +228 -0
- package/node_modules/@oxygen/shared/dist/column-output-fields.js +2 -6
- package/node_modules/@oxygen/shared/dist/company-enrichment-fields.d.ts +113 -0
- package/node_modules/@oxygen/shared/dist/company-enrichment-fields.js +548 -0
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +6 -6
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +6 -6
- package/node_modules/@oxygen/shared/dist/cutover-freeze.d.ts +26 -0
- package/node_modules/@oxygen/shared/dist/cutover-freeze.js +52 -0
- package/node_modules/@oxygen/shared/dist/data-suppliers.d.ts +57 -0
- package/node_modules/@oxygen/shared/dist/data-suppliers.js +59 -0
- package/node_modules/@oxygen/shared/dist/deploy-env.d.ts +74 -0
- package/node_modules/@oxygen/shared/dist/deploy-env.js +82 -0
- package/node_modules/@oxygen/shared/dist/dnc-rules.d.ts +130 -0
- package/node_modules/@oxygen/shared/dist/dnc-rules.js +221 -0
- package/node_modules/@oxygen/shared/dist/enrichment-intents.d.ts +107 -0
- package/node_modules/@oxygen/shared/dist/enrichment-intents.js +809 -0
- package/node_modules/@oxygen/shared/dist/error-message.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/error-message.js +3 -0
- package/node_modules/@oxygen/shared/dist/error-redaction.js +1 -3
- package/node_modules/@oxygen/shared/dist/external-write-policy.d.ts +33 -0
- package/node_modules/@oxygen/shared/dist/external-write-policy.js +68 -0
- package/node_modules/@oxygen/shared/dist/format-percent.d.ts +8 -0
- package/node_modules/@oxygen/shared/dist/format-percent.js +13 -0
- package/node_modules/@oxygen/shared/dist/freemail-domains.d.ts +81 -0
- package/node_modules/@oxygen/shared/dist/freemail-domains.js +157 -0
- package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/future-signup-lifecycle-projection.js +1 -1
- package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +60 -4
- package/node_modules/@oxygen/shared/dist/hosted-ai.js +125 -10
- package/node_modules/@oxygen/shared/dist/index.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/index.js +15 -0
- package/node_modules/@oxygen/shared/dist/json-path.js +1 -3
- package/node_modules/@oxygen/shared/dist/knowledge-bases.js +1 -3
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.d.ts +2 -2
- package/node_modules/@oxygen/shared/dist/knowledge-bootstrap.js +2 -2
- package/node_modules/@oxygen/shared/dist/langfuse.d.ts +44 -1
- package/node_modules/@oxygen/shared/dist/langfuse.js +416 -14
- package/node_modules/@oxygen/shared/dist/linkedin-countries.d.ts +33 -0
- package/node_modules/@oxygen/shared/dist/linkedin-countries.js +361 -0
- package/node_modules/@oxygen/shared/dist/linkedin-country-timezones.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/linkedin-country-timezones.js +276 -0
- package/node_modules/@oxygen/shared/dist/linkedin-message-deletion.d.ts +2 -0
- package/node_modules/@oxygen/shared/dist/linkedin-message-deletion.js +5 -0
- package/node_modules/@oxygen/shared/dist/linkedin-post-keywords.d.ts +44 -0
- package/node_modules/@oxygen/shared/dist/linkedin-post-keywords.js +116 -0
- package/node_modules/@oxygen/shared/dist/linkedin-sequences.d.ts +96 -0
- package/node_modules/@oxygen/shared/dist/linkedin-sequences.js +123 -0
- package/node_modules/@oxygen/shared/dist/llm-durable-capture.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/llm-durable-capture.js +89 -0
- package/node_modules/@oxygen/shared/dist/llm-prompts.d.ts +75 -0
- package/node_modules/@oxygen/shared/dist/llm-prompts.js +161 -0
- package/node_modules/@oxygen/shared/dist/log-sink-selector.d.ts +39 -0
- package/node_modules/@oxygen/shared/dist/log-sink-selector.js +56 -0
- package/node_modules/@oxygen/shared/dist/log.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/log.js +6 -1
- package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.d.ts +90 -0
- package/node_modules/@oxygen/shared/dist/mailbox-egress-ownership.js +130 -0
- package/node_modules/@oxygen/shared/dist/object-storage.d.ts +17 -0
- package/node_modules/@oxygen/shared/dist/object-storage.js +21 -0
- package/node_modules/@oxygen/shared/dist/operational-telemetry.d.ts +24 -0
- package/node_modules/@oxygen/shared/dist/operational-telemetry.js +73 -0
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.d.ts +79 -0
- package/node_modules/@oxygen/shared/dist/otlp-log-sink.js +366 -0
- package/node_modules/@oxygen/shared/dist/plan-capabilities.js +1 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +23 -22
- package/node_modules/@oxygen/shared/dist/plan-limits.js +45 -18
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +48 -41
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +36 -25
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +22 -22
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +40 -34
- package/node_modules/@oxygen/shared/dist/product-analytics-environment.js +9 -0
- package/node_modules/@oxygen/shared/dist/product-analytics-events.d.ts +36 -2
- package/node_modules/@oxygen/shared/dist/product-analytics-events.js +36 -1
- package/node_modules/@oxygen/shared/dist/rate-window.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/rate-window.js +8 -0
- package/node_modules/@oxygen/shared/dist/redaction.js +4 -1
- package/node_modules/@oxygen/shared/dist/research-output-contract.js +1 -3
- package/node_modules/@oxygen/shared/dist/scraper-lane-credential.d.ts +18 -0
- package/node_modules/@oxygen/shared/dist/scraper-lane-credential.js +23 -0
- package/node_modules/@oxygen/shared/dist/search-vocab.js +4 -5
- package/node_modules/@oxygen/shared/dist/select-options.js +6 -1
- package/node_modules/@oxygen/shared/dist/sequence-failures.js +1 -5
- package/node_modules/@oxygen/shared/dist/sequences.d.ts +23 -0
- package/node_modules/@oxygen/shared/dist/sequences.js +115 -5
- package/node_modules/@oxygen/shared/dist/signup-lead-payload.d.ts +80 -0
- package/node_modules/@oxygen/shared/dist/signup-lead-payload.js +198 -0
- package/node_modules/@oxygen/shared/dist/social-capabilities.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/social-capabilities.js +25 -16
- package/node_modules/@oxygen/shared/dist/social-post-metrics-core.d.ts +32 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-core.js +32 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-linkedin.d.ts +31 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-linkedin.js +103 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-series.d.ts +96 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-series.js +213 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-x.d.ts +13 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics-x.js +78 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics.d.ts +36 -0
- package/node_modules/@oxygen/shared/dist/social-post-metrics.js +51 -0
- package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +22 -10
- package/node_modules/@oxygen/shared/dist/spend-safety.js +15 -21
- package/node_modules/@oxygen/shared/dist/sql-rows.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/sql-rows.js +3 -0
- package/node_modules/@oxygen/shared/dist/stripe-price-catalog.d.ts +36 -0
- package/node_modules/@oxygen/shared/dist/stripe-price-catalog.js +184 -0
- package/node_modules/@oxygen/shared/dist/stripe-subscription-kind.d.ts +41 -0
- package/node_modules/@oxygen/shared/dist/stripe-subscription-kind.js +44 -0
- package/node_modules/@oxygen/shared/dist/table-limits.d.ts +3 -0
- package/node_modules/@oxygen/shared/dist/table-limits.js +3 -0
- package/node_modules/@oxygen/shared/dist/telemetry-export-observer.d.ts +94 -0
- package/node_modules/@oxygen/shared/dist/telemetry-export-observer.js +298 -0
- package/node_modules/@oxygen/shared/dist/telemetry.d.ts +11 -0
- package/node_modules/@oxygen/shared/dist/telemetry.js +28 -2
- package/node_modules/@oxygen/shared/dist/type-guards.d.ts +22 -0
- package/node_modules/@oxygen/shared/dist/type-guards.js +35 -0
- package/node_modules/@oxygen/shared/dist/ugc.d.ts +22 -11
- package/node_modules/@oxygen/shared/dist/ugc.js +10 -0
- package/node_modules/@oxygen/shared/dist/value-readers.d.ts +21 -0
- package/node_modules/@oxygen/shared/dist/value-readers.js +59 -0
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/version.generated.js +2 -0
- package/node_modules/@oxygen/shared/dist/version.js +8 -1
- package/node_modules/@oxygen/shared/dist/workspace-event-catalog.js +0 -23
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +29 -0
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +31 -0
- package/node_modules/@oxygen/shared/package.json +59 -0
- package/node_modules/@oxygen/workflows/dist/graph/expression.js +2 -5
- package/node_modules/@oxygen/workflows/dist/graph/params.js +1 -1
- package/package.json +3 -2
|
@@ -3,5 +3,14 @@ export type Field = {
|
|
|
3
3
|
kind: string;
|
|
4
4
|
description: string;
|
|
5
5
|
required: boolean;
|
|
6
|
+
/** CLI flag name when it differs from the body key (e.g. `at` for scheduled_for). */
|
|
7
|
+
flag?: string;
|
|
6
8
|
};
|
|
9
|
+
/** `2/week`, `4/month`, `10/total` or `none` (clears); a count of 1–100. */
|
|
10
|
+
export declare function parsePostingTarget(value: string): {
|
|
11
|
+
count: number;
|
|
12
|
+
period: "week" | "month" | "total";
|
|
13
|
+
} | null;
|
|
14
|
+
/** A JSON array of emails, or emails separated by commas, semicolons or whitespace. */
|
|
15
|
+
export declare function parseEmailList(value: string): string[];
|
|
7
16
|
export declare function parseField(value: unknown, field: Field): unknown;
|
|
@@ -1,4 +1,38 @@
|
|
|
1
|
+
/** `2/week`, `4/month`, `10/total` or `none` (clears); a count of 1–100. */
|
|
2
|
+
export function parsePostingTarget(value) {
|
|
3
|
+
const text = value.trim().toLowerCase();
|
|
4
|
+
if (text === "none")
|
|
5
|
+
return null;
|
|
6
|
+
const match = /^(\d{1,3})\s*\/\s*(week|month|total)$/.exec(text);
|
|
7
|
+
const count = match ? Number(match[1]) : 0;
|
|
8
|
+
if (!match || count < 1 || count > 100)
|
|
9
|
+
throw new Error("--posting-target must be <n>/month, <n>/total or <n>/week with n from 1 to 100, or none.");
|
|
10
|
+
return { count, period: match[2] };
|
|
11
|
+
}
|
|
12
|
+
/** A JSON array of emails, or emails separated by commas, semicolons or whitespace. */
|
|
13
|
+
export function parseEmailList(value) {
|
|
14
|
+
const text = value.trim();
|
|
15
|
+
let items;
|
|
16
|
+
if (text.startsWith("[")) {
|
|
17
|
+
items = JSON.parse(text);
|
|
18
|
+
if (!Array.isArray(items) || !items.every((item) => typeof item === "string"))
|
|
19
|
+
throw new Error("--emails must be a JSON array of strings or a comma-separated list.");
|
|
20
|
+
}
|
|
21
|
+
else
|
|
22
|
+
items = text.split(/[\s,;]+/);
|
|
23
|
+
const emails = items.map((item) => item.trim()).filter(Boolean);
|
|
24
|
+
if (!emails.length)
|
|
25
|
+
throw new Error("--emails needs at least one email address.");
|
|
26
|
+
return emails;
|
|
27
|
+
}
|
|
1
28
|
export function parseField(value, field) {
|
|
29
|
+
if (field.kind === "posting_target")
|
|
30
|
+
return parsePostingTarget(String(value));
|
|
31
|
+
if (field.kind === "email_list")
|
|
32
|
+
return parseEmailList(String(value));
|
|
33
|
+
// Comma-separated words; an empty value is an empty list (it clears).
|
|
34
|
+
if (field.kind === "text_list")
|
|
35
|
+
return String(value).split(",").map((item) => item.trim()).filter(Boolean);
|
|
2
36
|
if (field.kind === "array" || field.kind === "object") {
|
|
3
37
|
const parsed = JSON.parse(String(value));
|
|
4
38
|
if (field.kind === "array"
|
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
* imported from a host package) so the engine stays dependency-free and
|
|
4
4
|
* browser-safe; tenant-db re-exports it from its own `coerce.ts` so the
|
|
5
5
|
* existing internal import path keeps working.
|
|
6
|
+
*
|
|
7
|
+
* Do NOT replace this with `export { isRecord } from "@oxygen/shared"`. This
|
|
8
|
+
* file is value-re-exported by ./index.ts, so that edge drags the whole
|
|
9
|
+
* @oxygen/shared root barrel into every browser bundle that touches the formula
|
|
10
|
+
* engine — packages/workflows/src/graph/browser-safety.test.ts fails on exactly
|
|
11
|
+
* that ("packages/formula/src/coerce.ts imports @oxygen/shared"). @oxygen/shared
|
|
12
|
+
* does export a `./type-guards` leaf, but importing it would still make this
|
|
13
|
+
* browser-safe package depend on @oxygen/shared, which the same guard forbids
|
|
14
|
+
* (packages/formula declares no @oxygen/* dependency at all); the duplicate body
|
|
15
|
+
* is deliberate.
|
|
6
16
|
*/
|
|
7
17
|
/** Loose record check: any non-array object (class instances included). */
|
|
8
18
|
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
* imported from a host package) so the engine stays dependency-free and
|
|
4
4
|
* browser-safe; tenant-db re-exports it from its own `coerce.ts` so the
|
|
5
5
|
* existing internal import path keeps working.
|
|
6
|
+
*
|
|
7
|
+
* Do NOT replace this with `export { isRecord } from "@oxygen/shared"`. This
|
|
8
|
+
* file is value-re-exported by ./index.ts, so that edge drags the whole
|
|
9
|
+
* @oxygen/shared root barrel into every browser bundle that touches the formula
|
|
10
|
+
* engine — packages/workflows/src/graph/browser-safety.test.ts fails on exactly
|
|
11
|
+
* that ("packages/formula/src/coerce.ts imports @oxygen/shared"). @oxygen/shared
|
|
12
|
+
* does export a `./type-guards` leaf, but importing it would still make this
|
|
13
|
+
* browser-safe package depend on @oxygen/shared, which the same guard forbids
|
|
14
|
+
* (packages/formula declares no @oxygen/* dependency at all); the duplicate body
|
|
15
|
+
* is deliberate.
|
|
6
16
|
*/
|
|
7
17
|
/** Loose record check: any non-array object (class instances included). */
|
|
8
18
|
export function isRecord(value) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
2
2
|
import { walkJsonPath } from "@oxygen/shared/json-path";
|
|
3
|
+
import { resolveLinkedInCountry } from "@oxygen/shared/linkedin-countries";
|
|
4
|
+
import { md5Hex, sha256Hex } from "./hash.js";
|
|
3
5
|
import { normalizeDomain, normalizeEmail, normalizeLinkedinUrl, } from "./value-normalizers.js";
|
|
4
6
|
import { classifyEmailType, cleanJobTitle, normalizeCompanyName, splitDelimitedList, } from "./value-cleaners.js";
|
|
5
7
|
// Pattern LENGTH is not a safety bound and never was -- catastrophic backtracking
|
|
@@ -578,6 +580,56 @@ const SPECS = [
|
|
|
578
580
|
examples: [{ expression: "title_case(\"acme CORP\")", result: "Acme Corp" }],
|
|
579
581
|
evaluate: (args) => asText(args[0]).replace(/([A-Za-z])([A-Za-z]*)/g, (_match, first, rest) => first.toUpperCase() + rest.toLowerCase()),
|
|
580
582
|
}),
|
|
583
|
+
spec({
|
|
584
|
+
name: "sha256",
|
|
585
|
+
category: "string",
|
|
586
|
+
signature: "sha256(text)",
|
|
587
|
+
description: "Hex-encoded SHA-256 digest of the text (UTF-8). Computed in-process, so it costs no credits. Blank input returns null.",
|
|
588
|
+
minArgs: 1,
|
|
589
|
+
maxArgs: 1,
|
|
590
|
+
examples: [{ expression: "sha256(\"abc\")", result: "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" }],
|
|
591
|
+
evaluate: (args) => blankToNull(args[0], (input) => sha256Hex(asText(input))),
|
|
592
|
+
}),
|
|
593
|
+
spec({
|
|
594
|
+
name: "md5",
|
|
595
|
+
category: "string",
|
|
596
|
+
signature: "md5(text)",
|
|
597
|
+
description: "Hex-encoded MD5 digest of the text (UTF-8). Computed in-process, so it costs no credits. Blank input returns null. MD5 exists here for ad-platform audience matching, never for anything security-bearing.",
|
|
598
|
+
minArgs: 1,
|
|
599
|
+
maxArgs: 1,
|
|
600
|
+
examples: [{ expression: "md5(\"abc\")", result: "900150983cd24fb0d6963f7d28e17f72" }],
|
|
601
|
+
evaluate: (args) => blankToNull(args[0], (input) => md5Hex(asText(input))),
|
|
602
|
+
}),
|
|
603
|
+
spec({
|
|
604
|
+
name: "email_hash",
|
|
605
|
+
category: "string",
|
|
606
|
+
signature: "email_hash(email, algorithm?)",
|
|
607
|
+
description: "Normalizes an email address and returns its hex digest — the hashed identifier ad platforms take for custom audiences. Algorithm is \"sha256\" (default) or \"md5\"; blank input returns null.",
|
|
608
|
+
minArgs: 1,
|
|
609
|
+
maxArgs: 2,
|
|
610
|
+
examples: [{ expression: "email_hash(\" Ada@Example.COM \")", result: "b5fc85e55755f9e0d030a10ab4429b6b2944855f9a0d60077fe832becbc41d72" }],
|
|
611
|
+
evaluate: (args) => {
|
|
612
|
+
// The algorithm is checked before the blank check on purpose: an unknown
|
|
613
|
+
// algorithm is an authoring mistake, not row data, so it must fail on
|
|
614
|
+
// every row rather than only on the rows that carry an address.
|
|
615
|
+
const algorithm = args.length > 1 && !isBlankFormulaValue(args[1])
|
|
616
|
+
? asText(args[1]).trim().toLowerCase()
|
|
617
|
+
: "sha256";
|
|
618
|
+
if (algorithm !== "sha256" && algorithm !== "md5") {
|
|
619
|
+
throw formulaExpressionError("email_hash supports the \"sha256\" and \"md5\" algorithms only.", {
|
|
620
|
+
function: "email_hash",
|
|
621
|
+
algorithm: args[1],
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
return blankToNull(args[0], (input) => {
|
|
625
|
+
// Trim + lowercase and nothing else. Meta, Google, and LinkedIn all
|
|
626
|
+
// specify exactly that normalization, so stripping dots or plus-tags
|
|
627
|
+
// here would hash an address the platform never matches.
|
|
628
|
+
const normalized = normalizeEmail(asText(input));
|
|
629
|
+
return algorithm === "md5" ? md5Hex(normalized) : sha256Hex(normalized);
|
|
630
|
+
});
|
|
631
|
+
},
|
|
632
|
+
}),
|
|
581
633
|
spec({
|
|
582
634
|
name: "pad_start",
|
|
583
635
|
category: "string",
|
|
@@ -943,6 +995,19 @@ const SPECS = [
|
|
|
943
995
|
}
|
|
944
996
|
}),
|
|
945
997
|
}),
|
|
998
|
+
spec({
|
|
999
|
+
name: "linkedin_country",
|
|
1000
|
+
category: "url_email",
|
|
1001
|
+
signature: "linkedin_country(code_or_name)",
|
|
1002
|
+
description: "Canonical LinkedIn country name for an ISO alpha-2 code, a country name, or a known alias. Returns blank for anything unrecognized, so a select column never stores a value outside its own option list.",
|
|
1003
|
+
minArgs: 1,
|
|
1004
|
+
maxArgs: 1,
|
|
1005
|
+
examples: [
|
|
1006
|
+
{ expression: "linkedin_country(\"US\")", result: "United States" },
|
|
1007
|
+
{ expression: "linkedin_country(\"Congo - Kinshasa\")", result: "Congo (DRC)" },
|
|
1008
|
+
],
|
|
1009
|
+
evaluate: (args) => blankToNull(args[0], (input) => resolveLinkedInCountry(asText(input))),
|
|
1010
|
+
}),
|
|
946
1011
|
spec({
|
|
947
1012
|
name: "normalize_linkedin_url",
|
|
948
1013
|
category: "url_email",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SHA-256 and MD5, hand-rolled and dependency-free.
|
|
3
|
+
*
|
|
4
|
+
* WHY HAND-ROLLED: this package is bundled into the browser (formula editor
|
|
5
|
+
* previews, the visual workflow editor) and its tsconfig sets `types: []` on
|
|
6
|
+
* purpose, so `node:crypto` is not importable here; `crypto.subtle.digest` is
|
|
7
|
+
* async and formula evaluation is synchronous per row, so a Promise cannot be
|
|
8
|
+
* returned to a cell either. Both digests are therefore computed inline —
|
|
9
|
+
* the textbook algorithms (FIPS 180-4 / RFC 1321), UTF-8 in, lowercase hex out.
|
|
10
|
+
*
|
|
11
|
+
* NOT FOR SECRETS. These exist because the ad platforms (Meta, Google,
|
|
12
|
+
* LinkedIn) specify exactly SHA-256 or MD5 over a trimmed, lowercased address
|
|
13
|
+
* for customer-match audience uploads. Do not reach for them for anything that
|
|
14
|
+
* needs a password hash, a MAC, or collision resistance.
|
|
15
|
+
*/
|
|
16
|
+
/** Hex-encoded SHA-256 of the UTF-8 encoding of `input`. */
|
|
17
|
+
export declare function sha256Hex(input: string): string;
|
|
18
|
+
/** Hex-encoded MD5 of the UTF-8 encoding of `input`. */
|
|
19
|
+
export declare function md5Hex(input: string): string;
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SHA-256 and MD5, hand-rolled and dependency-free.
|
|
3
|
+
*
|
|
4
|
+
* WHY HAND-ROLLED: this package is bundled into the browser (formula editor
|
|
5
|
+
* previews, the visual workflow editor) and its tsconfig sets `types: []` on
|
|
6
|
+
* purpose, so `node:crypto` is not importable here; `crypto.subtle.digest` is
|
|
7
|
+
* async and formula evaluation is synchronous per row, so a Promise cannot be
|
|
8
|
+
* returned to a cell either. Both digests are therefore computed inline —
|
|
9
|
+
* the textbook algorithms (FIPS 180-4 / RFC 1321), UTF-8 in, lowercase hex out.
|
|
10
|
+
*
|
|
11
|
+
* NOT FOR SECRETS. These exist because the ad platforms (Meta, Google,
|
|
12
|
+
* LinkedIn) specify exactly SHA-256 or MD5 over a trimmed, lowercased address
|
|
13
|
+
* for customer-match audience uploads. Do not reach for them for anything that
|
|
14
|
+
* needs a password hash, a MAC, or collision resistance.
|
|
15
|
+
*/
|
|
16
|
+
// `noUncheckedIndexedAccess` types every element read as possibly undefined.
|
|
17
|
+
// Every index below is provably in range (fixed-size constant tables and a
|
|
18
|
+
// 64-word schedule), so the reads go through these accessors rather than
|
|
19
|
+
// scattering assertions through the rounds.
|
|
20
|
+
function wordAt(words, index) {
|
|
21
|
+
return words[index];
|
|
22
|
+
}
|
|
23
|
+
function shiftAt(shifts, index) {
|
|
24
|
+
return shifts[index];
|
|
25
|
+
}
|
|
26
|
+
function rotateRight(value, bits) {
|
|
27
|
+
return ((value >>> bits) | (value << (32 - bits))) >>> 0;
|
|
28
|
+
}
|
|
29
|
+
function rotateLeft(value, bits) {
|
|
30
|
+
return ((value << bits) | (value >>> (32 - bits))) >>> 0;
|
|
31
|
+
}
|
|
32
|
+
function hex32BigEndian(value) {
|
|
33
|
+
return (value >>> 0).toString(16).padStart(8, "0");
|
|
34
|
+
}
|
|
35
|
+
function hex32LittleEndian(value) {
|
|
36
|
+
let out = "";
|
|
37
|
+
for (let byte = 0; byte < 4; byte += 1) {
|
|
38
|
+
out += ((value >>> (byte * 8)) & 0xff).toString(16).padStart(2, "0");
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Merkle-Damgård padding shared by both digests: append 0x80, zero-fill to a
|
|
44
|
+
* 64-byte boundary, then write the message length in bits into the last eight
|
|
45
|
+
* bytes (big-endian for SHA-256, little-endian for MD5).
|
|
46
|
+
*/
|
|
47
|
+
function padMessage(bytes, littleEndianLength) {
|
|
48
|
+
const paddedLength = (((bytes.length + 8) >> 6) + 1) << 6;
|
|
49
|
+
const padded = new Uint8Array(paddedLength);
|
|
50
|
+
padded.set(bytes);
|
|
51
|
+
padded[bytes.length] = 0x80;
|
|
52
|
+
const view = new DataView(padded.buffer);
|
|
53
|
+
const bitLength = bytes.length * 8;
|
|
54
|
+
const low = bitLength >>> 0;
|
|
55
|
+
const high = Math.floor(bitLength / 0x1_0000_0000);
|
|
56
|
+
if (littleEndianLength) {
|
|
57
|
+
view.setUint32(paddedLength - 8, low, true);
|
|
58
|
+
view.setUint32(paddedLength - 4, high, true);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
view.setUint32(paddedLength - 8, high, false);
|
|
62
|
+
view.setUint32(paddedLength - 4, low, false);
|
|
63
|
+
}
|
|
64
|
+
return view;
|
|
65
|
+
}
|
|
66
|
+
function utf8Bytes(input) {
|
|
67
|
+
return new TextEncoder().encode(input);
|
|
68
|
+
}
|
|
69
|
+
const SHA256_K = new Uint32Array([
|
|
70
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
71
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
72
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
73
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
74
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
75
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
76
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
77
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
78
|
+
]);
|
|
79
|
+
/** Hex-encoded SHA-256 of the UTF-8 encoding of `input`. */
|
|
80
|
+
export function sha256Hex(input) {
|
|
81
|
+
const view = padMessage(utf8Bytes(input), false);
|
|
82
|
+
const state = new Uint32Array([
|
|
83
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
|
84
|
+
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
85
|
+
]);
|
|
86
|
+
const schedule = new Uint32Array(64);
|
|
87
|
+
for (let offset = 0; offset < view.byteLength; offset += 64) {
|
|
88
|
+
for (let i = 0; i < 16; i += 1) {
|
|
89
|
+
schedule[i] = view.getUint32(offset + i * 4, false);
|
|
90
|
+
}
|
|
91
|
+
for (let i = 16; i < 64; i += 1) {
|
|
92
|
+
const previous = wordAt(schedule, i - 15);
|
|
93
|
+
const recent = wordAt(schedule, i - 2);
|
|
94
|
+
const s0 = rotateRight(previous, 7) ^ rotateRight(previous, 18) ^ (previous >>> 3);
|
|
95
|
+
const s1 = rotateRight(recent, 17) ^ rotateRight(recent, 19) ^ (recent >>> 10);
|
|
96
|
+
schedule[i] = (wordAt(schedule, i - 16) + s0 + wordAt(schedule, i - 7) + s1) >>> 0;
|
|
97
|
+
}
|
|
98
|
+
let a = wordAt(state, 0);
|
|
99
|
+
let b = wordAt(state, 1);
|
|
100
|
+
let c = wordAt(state, 2);
|
|
101
|
+
let d = wordAt(state, 3);
|
|
102
|
+
let e = wordAt(state, 4);
|
|
103
|
+
let f = wordAt(state, 5);
|
|
104
|
+
let g = wordAt(state, 6);
|
|
105
|
+
let h = wordAt(state, 7);
|
|
106
|
+
for (let i = 0; i < 64; i += 1) {
|
|
107
|
+
const s1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
|
|
108
|
+
const choice = (e & f) ^ (~e & g);
|
|
109
|
+
const temp1 = (h + s1 + choice + wordAt(SHA256_K, i) + wordAt(schedule, i)) >>> 0;
|
|
110
|
+
const s0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
|
|
111
|
+
const majority = (a & b) ^ (a & c) ^ (b & c);
|
|
112
|
+
const temp2 = (s0 + majority) >>> 0;
|
|
113
|
+
h = g;
|
|
114
|
+
g = f;
|
|
115
|
+
f = e;
|
|
116
|
+
e = (d + temp1) >>> 0;
|
|
117
|
+
d = c;
|
|
118
|
+
c = b;
|
|
119
|
+
b = a;
|
|
120
|
+
a = (temp1 + temp2) >>> 0;
|
|
121
|
+
}
|
|
122
|
+
state[0] = (wordAt(state, 0) + a) >>> 0;
|
|
123
|
+
state[1] = (wordAt(state, 1) + b) >>> 0;
|
|
124
|
+
state[2] = (wordAt(state, 2) + c) >>> 0;
|
|
125
|
+
state[3] = (wordAt(state, 3) + d) >>> 0;
|
|
126
|
+
state[4] = (wordAt(state, 4) + e) >>> 0;
|
|
127
|
+
state[5] = (wordAt(state, 5) + f) >>> 0;
|
|
128
|
+
state[6] = (wordAt(state, 6) + g) >>> 0;
|
|
129
|
+
state[7] = (wordAt(state, 7) + h) >>> 0;
|
|
130
|
+
}
|
|
131
|
+
let digest = "";
|
|
132
|
+
for (let i = 0; i < 8; i += 1)
|
|
133
|
+
digest += hex32BigEndian(wordAt(state, i));
|
|
134
|
+
return digest;
|
|
135
|
+
}
|
|
136
|
+
const MD5_K = new Uint32Array([
|
|
137
|
+
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
|
|
138
|
+
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
|
|
139
|
+
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
|
|
140
|
+
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
|
|
141
|
+
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
|
|
142
|
+
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
|
|
143
|
+
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
|
|
144
|
+
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
|
|
145
|
+
]);
|
|
146
|
+
const MD5_SHIFTS = [
|
|
147
|
+
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
|
|
148
|
+
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
|
|
149
|
+
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
|
|
150
|
+
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
|
|
151
|
+
];
|
|
152
|
+
/** Hex-encoded MD5 of the UTF-8 encoding of `input`. */
|
|
153
|
+
export function md5Hex(input) {
|
|
154
|
+
const view = padMessage(utf8Bytes(input), true);
|
|
155
|
+
const state = new Uint32Array([0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]);
|
|
156
|
+
const block = new Uint32Array(16);
|
|
157
|
+
for (let offset = 0; offset < view.byteLength; offset += 64) {
|
|
158
|
+
for (let i = 0; i < 16; i += 1) {
|
|
159
|
+
block[i] = view.getUint32(offset + i * 4, true);
|
|
160
|
+
}
|
|
161
|
+
let a = wordAt(state, 0);
|
|
162
|
+
let b = wordAt(state, 1);
|
|
163
|
+
let c = wordAt(state, 2);
|
|
164
|
+
let d = wordAt(state, 3);
|
|
165
|
+
for (let i = 0; i < 64; i += 1) {
|
|
166
|
+
let mixed;
|
|
167
|
+
let wordIndex;
|
|
168
|
+
if (i < 16) {
|
|
169
|
+
mixed = (b & c) | (~b & d);
|
|
170
|
+
wordIndex = i;
|
|
171
|
+
}
|
|
172
|
+
else if (i < 32) {
|
|
173
|
+
mixed = (d & b) | (~d & c);
|
|
174
|
+
wordIndex = (5 * i + 1) % 16;
|
|
175
|
+
}
|
|
176
|
+
else if (i < 48) {
|
|
177
|
+
mixed = b ^ c ^ d;
|
|
178
|
+
wordIndex = (3 * i + 5) % 16;
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
mixed = c ^ (b | ~d);
|
|
182
|
+
wordIndex = (7 * i) % 16;
|
|
183
|
+
}
|
|
184
|
+
const rotated = rotateLeft((mixed + a + wordAt(MD5_K, i) + wordAt(block, wordIndex)) >>> 0, shiftAt(MD5_SHIFTS, i));
|
|
185
|
+
a = d;
|
|
186
|
+
d = c;
|
|
187
|
+
c = b;
|
|
188
|
+
b = (b + rotated) >>> 0;
|
|
189
|
+
}
|
|
190
|
+
state[0] = (wordAt(state, 0) + a) >>> 0;
|
|
191
|
+
state[1] = (wordAt(state, 1) + b) >>> 0;
|
|
192
|
+
state[2] = (wordAt(state, 2) + c) >>> 0;
|
|
193
|
+
state[3] = (wordAt(state, 3) + d) >>> 0;
|
|
194
|
+
}
|
|
195
|
+
let digest = "";
|
|
196
|
+
for (let i = 0; i < 4; i += 1)
|
|
197
|
+
digest += hex32LittleEndian(wordAt(state, i));
|
|
198
|
+
return digest;
|
|
199
|
+
}
|
|
@@ -52,7 +52,12 @@ export declare function cleanJobTitle(value: unknown): string | null;
|
|
|
52
52
|
* Blank input returns an empty array.
|
|
53
53
|
*/
|
|
54
54
|
export declare function splitDelimitedList(value: unknown, separator?: string | null): string[];
|
|
55
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Consumer mailbox providers — a person, not a company domain. Re-exported from
|
|
57
|
+
* `@oxygen/shared/freemail-domains`, which is now the ONE list; this module used to
|
|
58
|
+
* carry its own 101-entry copy and was the best-maintained of eight divergent forks.
|
|
59
|
+
* Its entries survive as the bulk of the shared broad tier.
|
|
60
|
+
*/
|
|
56
61
|
export declare const FREE_MAIL_DOMAINS: ReadonlySet<string>;
|
|
57
62
|
/** Throwaway inbox providers — never worth a send, never worth a credit. */
|
|
58
63
|
export declare const DISPOSABLE_EMAIL_DOMAINS: ReadonlySet<string>;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* model. When a deterministic list is not enough, the paid verifier columns
|
|
10
10
|
* are the upgrade — these helpers are the free first pass.
|
|
11
11
|
*/
|
|
12
|
+
import { PERSONAL_EMAIL_DOMAINS } from "@oxygen/shared/freemail-domains";
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
13
14
|
// Shared text helpers
|
|
14
15
|
// ---------------------------------------------------------------------------
|
|
@@ -279,32 +280,15 @@ export function splitDelimitedList(value, separator) {
|
|
|
279
280
|
// ---------------------------------------------------------------------------
|
|
280
281
|
// Email classification
|
|
281
282
|
// ---------------------------------------------------------------------------
|
|
282
|
-
/**
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
"protonmail.com", "proton.me", "pm.me",
|
|
292
|
-
"gmx.com", "gmx.de", "gmx.net", "gmx.at", "gmx.ch",
|
|
293
|
-
"web.de", "t-online.de", "freenet.de",
|
|
294
|
-
"mail.com", "mail.ru", "yandex.ru", "yandex.com",
|
|
295
|
-
"zoho.com", "fastmail.com", "hey.com", "tutanota.com", "tuta.io",
|
|
296
|
-
"orange.fr", "wanadoo.fr", "free.fr", "laposte.net", "sfr.fr",
|
|
297
|
-
"libero.it", "virgilio.it", "seznam.cz",
|
|
298
|
-
"wp.pl", "o2.pl", "onet.pl", "interia.pl",
|
|
299
|
-
"qq.com", "163.com", "126.com", "sina.com",
|
|
300
|
-
"naver.com", "daum.net", "hanmail.net", "rediffmail.com",
|
|
301
|
-
"bigpond.com", "optusnet.com.au", "telus.net", "shaw.ca", "sympatico.ca", "rogers.com",
|
|
302
|
-
"btinternet.com", "sky.com", "talktalk.net", "virginmedia.com", "blueyonder.co.uk",
|
|
303
|
-
"comcast.net", "verizon.net", "att.net", "sbcglobal.net", "cox.net",
|
|
304
|
-
"charter.net", "earthlink.net", "bellsouth.net",
|
|
305
|
-
"ziggo.nl", "kpnmail.nl", "home.nl", "telenet.be", "skynet.be", "bluewin.ch",
|
|
306
|
-
"uol.com.br", "bol.com.br", "terra.com.br",
|
|
307
|
-
]);
|
|
283
|
+
/**
|
|
284
|
+
* Consumer mailbox providers — a person, not a company domain. Re-exported from
|
|
285
|
+
* `@oxygen/shared/freemail-domains`, which is now the ONE list; this module used to
|
|
286
|
+
* carry its own 101-entry copy and was the best-maintained of eight divergent forks.
|
|
287
|
+
* Its entries survive as the bulk of the shared broad tier.
|
|
288
|
+
*/
|
|
289
|
+
// Aliased rather than `export … from`, because this module also USES the set locally
|
|
290
|
+
// (see classifyDomain below) and a re-export creates no local binding.
|
|
291
|
+
export const FREE_MAIL_DOMAINS = PERSONAL_EMAIL_DOMAINS;
|
|
308
292
|
/** Throwaway inbox providers — never worth a send, never worth a credit. */
|
|
309
293
|
export const DISPOSABLE_EMAIL_DOMAINS = new Set([
|
|
310
294
|
"mailinator.com", "guerrillamail.com", "guerrillamail.net",
|
|
@@ -9,6 +9,11 @@ export type RecipeToolRunOptions = {
|
|
|
9
9
|
connectionId?: string;
|
|
10
10
|
/** Pin the credential rail instead of automatically selecting a connected key. */
|
|
11
11
|
credentialMode?: "managed" | "user_api_key" | "user_oauth";
|
|
12
|
+
/**
|
|
13
|
+
* With credentialMode user_api_key on a LinkedIn rail (scraper.*) tool: run on
|
|
14
|
+
* the workspace's own Up2Data key rather than its HarvestAPI key.
|
|
15
|
+
*/
|
|
16
|
+
byokKeySource?: "up2data";
|
|
12
17
|
};
|
|
13
18
|
export type RecipeToolApi = {
|
|
14
19
|
run: <T = unknown>(toolId: string, payload?: Record<string, unknown>, options?: RecipeToolRunOptions) => Promise<T>;
|
|
@@ -113,9 +118,17 @@ export type RecipeCrmExternalRef = {
|
|
|
113
118
|
provider_event_id?: string | null;
|
|
114
119
|
confidence?: number | null;
|
|
115
120
|
};
|
|
121
|
+
/** Source evidence only; never registers or resolves a provider external identity. */
|
|
122
|
+
export type RecipeCrmSourceProvenance = {
|
|
123
|
+
provider: string;
|
|
124
|
+
object: string;
|
|
125
|
+
external_id: string;
|
|
126
|
+
provider_account: string;
|
|
127
|
+
};
|
|
116
128
|
export type RecipeCrmApi = {
|
|
117
129
|
assert: <T = unknown>(object: string, identity: RecipeCrmIdentity, values: Record<string, unknown> | undefined, options: {
|
|
118
130
|
key: string;
|
|
131
|
+
provenance?: RecipeCrmSourceProvenance;
|
|
119
132
|
}) => Promise<T>;
|
|
120
133
|
assertFromProvider: <T = unknown>(object: string, identity: RecipeCrmIdentity, values: Record<string, unknown> | undefined, external: RecipeCrmExternalRef, options: {
|
|
121
134
|
key: string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small array helpers shared across every OXYGEN package.
|
|
3
|
+
*/
|
|
4
|
+
/** Split `values` into consecutive slices of at most `size` entries. */
|
|
5
|
+
export function chunk(values, size) {
|
|
6
|
+
const chunks = [];
|
|
7
|
+
for (let index = 0; index < values.length; index += size) {
|
|
8
|
+
chunks.push(values.slice(index, index + size));
|
|
9
|
+
}
|
|
10
|
+
return chunks;
|
|
11
|
+
}
|