@memberjunction/integration-engine 5.41.0 → 5.43.0
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 +51 -0
- package/dist/BaseIntegrationConnector.d.ts +109 -0
- package/dist/BaseIntegrationConnector.d.ts.map +1 -1
- package/dist/BaseIntegrationConnector.js +308 -93
- package/dist/BaseIntegrationConnector.js.map +1 -1
- package/dist/BaseRESTIntegrationConnector.d.ts +36 -0
- package/dist/BaseRESTIntegrationConnector.d.ts.map +1 -1
- package/dist/BaseRESTIntegrationConnector.js +228 -28
- package/dist/BaseRESTIntegrationConnector.js.map +1 -1
- package/dist/ContentHash.d.ts +22 -0
- package/dist/ContentHash.d.ts.map +1 -1
- package/dist/ContentHash.js +35 -0
- package/dist/ContentHash.js.map +1 -1
- package/dist/CustomColumnPromotion.d.ts +108 -0
- package/dist/CustomColumnPromotion.d.ts.map +1 -0
- package/dist/CustomColumnPromotion.js +202 -0
- package/dist/CustomColumnPromotion.js.map +1 -0
- package/dist/CustomOverflow.d.ts +64 -0
- package/dist/CustomOverflow.d.ts.map +1 -0
- package/dist/CustomOverflow.js +74 -0
- package/dist/CustomOverflow.js.map +1 -0
- package/dist/FieldMappingEngine.d.ts +16 -90
- package/dist/FieldMappingEngine.d.ts.map +1 -1
- package/dist/FieldMappingEngine.js +42 -257
- package/dist/FieldMappingEngine.js.map +1 -1
- package/dist/IntegrationConnectorCreationPipeline.d.ts +33 -0
- package/dist/IntegrationConnectorCreationPipeline.d.ts.map +1 -1
- package/dist/IntegrationConnectorCreationPipeline.js +157 -0
- package/dist/IntegrationConnectorCreationPipeline.js.map +1 -1
- package/dist/IntegrationEngine.d.ts +120 -6
- package/dist/IntegrationEngine.d.ts.map +1 -1
- package/dist/IntegrationEngine.js +890 -168
- package/dist/IntegrationEngine.js.map +1 -1
- package/dist/IntegrationSchemaSync.d.ts +84 -13
- package/dist/IntegrationSchemaSync.d.ts.map +1 -1
- package/dist/IntegrationSchemaSync.js +198 -36
- package/dist/IntegrationSchemaSync.js.map +1 -1
- package/dist/KeySerialization.d.ts +21 -0
- package/dist/KeySerialization.d.ts.map +1 -0
- package/dist/KeySerialization.js +29 -0
- package/dist/KeySerialization.js.map +1 -0
- package/dist/MatchEngine.d.ts.map +1 -1
- package/dist/MatchEngine.js +12 -4
- package/dist/MatchEngine.js.map +1 -1
- package/dist/RecordFlatten.d.ts +49 -0
- package/dist/RecordFlatten.d.ts.map +1 -0
- package/dist/RecordFlatten.js +50 -0
- package/dist/RecordFlatten.js.map +1 -0
- package/dist/StreamingDiscovery.d.ts +145 -0
- package/dist/StreamingDiscovery.d.ts.map +1 -0
- package/dist/StreamingDiscovery.js +306 -0
- package/dist/StreamingDiscovery.js.map +1 -0
- package/dist/SyncLogger.d.ts +1 -1
- package/dist/SyncLogger.d.ts.map +1 -1
- package/dist/SyncLogger.js +19 -1
- package/dist/SyncLogger.js.map +1 -1
- package/dist/auth-helpers/BasicAuthHeaderBuilder.d.ts +36 -0
- package/dist/auth-helpers/BasicAuthHeaderBuilder.d.ts.map +1 -0
- package/dist/auth-helpers/BasicAuthHeaderBuilder.js +38 -0
- package/dist/auth-helpers/BasicAuthHeaderBuilder.js.map +1 -0
- package/dist/auth-helpers/OAuth1aSigner.d.ts +56 -0
- package/dist/auth-helpers/OAuth1aSigner.d.ts.map +1 -0
- package/dist/auth-helpers/OAuth1aSigner.js +91 -0
- package/dist/auth-helpers/OAuth1aSigner.js.map +1 -0
- package/dist/auth-helpers/OAuth2TokenManager.d.ts +103 -0
- package/dist/auth-helpers/OAuth2TokenManager.d.ts.map +1 -0
- package/dist/auth-helpers/OAuth2TokenManager.js +143 -0
- package/dist/auth-helpers/OAuth2TokenManager.js.map +1 -0
- package/dist/auth-helpers/index.d.ts +11 -0
- package/dist/auth-helpers/index.d.ts.map +1 -0
- package/dist/auth-helpers/index.js +8 -0
- package/dist/auth-helpers/index.js.map +1 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +73 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time-bounded streaming discovery (the final discovery abstraction).
|
|
3
|
+
*
|
|
4
|
+
* Stage-2 of `DiscoverFields` for sources with no describe endpoint: stream the source's actual
|
|
5
|
+
* records (read-only — no save, no ack) and, in ONE pass, accumulate two things:
|
|
6
|
+
* 1. the full COLUMN corpus (every key seen) + a bounded sample of values per key for typing, and
|
|
7
|
+
* 2. per-column UNIQUENESS / NULL statistics — the evidence a data-informed (not nominal) PK
|
|
8
|
+
* decision is made from.
|
|
9
|
+
*
|
|
10
|
+
* It stops when the stream is exhausted OR a TIME BUDGET is hit — whichever comes first — and
|
|
11
|
+
* returns whatever it gathered. More rows → stronger claims; the budget guarantees discovery never
|
|
12
|
+
* runs away. Memory stays bounded regardless of row count: only the key-set + capped per-key
|
|
13
|
+
* sample-values + a capped distinct-set per key are retained (never the rows themselves).
|
|
14
|
+
*
|
|
15
|
+
* Pure + clock-injectable, so it's deterministically testable. Reuses {@link inferColumnTypeFromSamples}.
|
|
16
|
+
*/
|
|
17
|
+
import { inferColumnTypeFromSamples } from './CustomColumnPromotion.js';
|
|
18
|
+
/** Positive integer from an env var, falling back when unset/invalid. */
|
|
19
|
+
function envInt(name, fallback) {
|
|
20
|
+
const v = parseInt(process.env[name] ?? '', 10);
|
|
21
|
+
return Number.isFinite(v) && v > 0 ? v : fallback;
|
|
22
|
+
}
|
|
23
|
+
// The discovery budget is operator-tunable via env — any sampling bound works (time- or record-based).
|
|
24
|
+
// Unset → the sensible defaults below. The time budget is the primary "how long a discovery pass runs"
|
|
25
|
+
// knob; the record/distinct caps bound memory.
|
|
26
|
+
const DEFAULT_TIME_BUDGET_MS = envInt('MJ_INTEGRATION_DISCOVERY_TIME_BUDGET_MS', 30_000);
|
|
27
|
+
const DEFAULT_SAMPLE_VALUE_CAP = envInt('MJ_INTEGRATION_DISCOVERY_SAMPLE_VALUE_CAP', 10);
|
|
28
|
+
const DEFAULT_MAX_DISTINCT_TRACKED = envInt('MJ_INTEGRATION_DISCOVERY_MAX_DISTINCT', 100_000);
|
|
29
|
+
const DEFAULT_COMPOSITE_SAMPLE_ROW_CAP = envInt('MJ_INTEGRATION_DISCOVERY_COMPOSITE_ROW_CAP', 2_000);
|
|
30
|
+
/**
|
|
31
|
+
* Streams records (sync or async iterable), accumulating the column corpus + uniqueness/null stats
|
|
32
|
+
* until exhaustion or the time budget. Read-only; the caller supplies whatever read-only fetch
|
|
33
|
+
* yields the records (no save, no ack happens here).
|
|
34
|
+
*/
|
|
35
|
+
export async function discoverFromStream(records, opts = {}) {
|
|
36
|
+
const timeBudgetMs = opts.TimeBudgetMs ?? DEFAULT_TIME_BUDGET_MS;
|
|
37
|
+
const sampleCap = opts.SampleValueCap ?? DEFAULT_SAMPLE_VALUE_CAP;
|
|
38
|
+
const distinctCap = opts.MaxDistinctTracked ?? DEFAULT_MAX_DISTINCT_TRACKED;
|
|
39
|
+
const now = opts.Now ?? (() => Date.now());
|
|
40
|
+
const start = now();
|
|
41
|
+
const compositeCap = opts.CompositeSampleRowCap ?? DEFAULT_COMPOSITE_SAMPLE_ROW_CAP;
|
|
42
|
+
const acc = new Map();
|
|
43
|
+
const rowSamples = [];
|
|
44
|
+
let totalRows = 0;
|
|
45
|
+
let stoppedReason = 'exhausted';
|
|
46
|
+
for await (const record of toAsyncIterable(records)) {
|
|
47
|
+
totalRows++;
|
|
48
|
+
accumulateRecord(record, acc, sampleCap, distinctCap);
|
|
49
|
+
// Retain a bounded per-cell value-hash sample for composite-key discovery — never raw values,
|
|
50
|
+
// never more than the cap. This is the per-row evidence the greedy composite pick needs.
|
|
51
|
+
if (rowSamples.length < compositeCap) {
|
|
52
|
+
const rowHash = {};
|
|
53
|
+
for (const [k, v] of Object.entries(record)) {
|
|
54
|
+
if (v !== null && v !== undefined)
|
|
55
|
+
rowHash[k] = stableValueKey(v);
|
|
56
|
+
}
|
|
57
|
+
rowSamples.push(rowHash);
|
|
58
|
+
}
|
|
59
|
+
if (now() - start > timeBudgetMs) {
|
|
60
|
+
stoppedReason = 'time-budget';
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const columns = [...acc.entries()].map(([key, e]) => ({
|
|
65
|
+
Key: key,
|
|
66
|
+
Occurrences: e.occurrences,
|
|
67
|
+
TotalRows: totalRows,
|
|
68
|
+
DistinctNonNull: e.distinct.size,
|
|
69
|
+
DistinctCapped: e.capped,
|
|
70
|
+
SampleValues: e.samples,
|
|
71
|
+
Inferred: inferColumnTypeFromSamples(e.samples),
|
|
72
|
+
}));
|
|
73
|
+
return { Columns: columns, RowsScanned: totalRows, StoppedReason: stoppedReason, RowSamples: rowSamples };
|
|
74
|
+
}
|
|
75
|
+
/** Folds one record into the accumulator — O(columns), bounded memory. */
|
|
76
|
+
function accumulateRecord(record, acc, sampleCap, distinctCap) {
|
|
77
|
+
for (const [key, value] of Object.entries(record)) {
|
|
78
|
+
let e = acc.get(key);
|
|
79
|
+
if (!e) {
|
|
80
|
+
e = { occurrences: 0, samples: [], distinct: new Set(), capped: false };
|
|
81
|
+
acc.set(key, e);
|
|
82
|
+
}
|
|
83
|
+
if (value === null || value === undefined)
|
|
84
|
+
continue;
|
|
85
|
+
e.occurrences++;
|
|
86
|
+
if (e.samples.length < sampleCap)
|
|
87
|
+
e.samples.push(value);
|
|
88
|
+
if (!e.capped) {
|
|
89
|
+
if (e.distinct.size < distinctCap)
|
|
90
|
+
e.distinct.add(stableValueKey(value));
|
|
91
|
+
else
|
|
92
|
+
e.capped = true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Recognizes a primary-key naming CONVENTION (`id`, `uuid`, `guid`, `pk`, or any `*_id` / `*_key` /
|
|
98
|
+
* `*_code` form). Used ONLY by the soft best-available fallback — a convention name is the identity
|
|
99
|
+
* SIGNAL that lets a not-provably-unique column still serve as a soft (dedup-only) key, rather than
|
|
100
|
+
* leaving the object PK-less. Never used to override a provably-unique strict candidate.
|
|
101
|
+
*/
|
|
102
|
+
function isConventionPkName(name) {
|
|
103
|
+
return /^(id|uuid|guid|pk)$|(^|_)(id|uuid|guid|key|code)$/i.test(name);
|
|
104
|
+
}
|
|
105
|
+
/** Minimum distinct/non-null ratio for a convention-named column to qualify as a SOFT key. */
|
|
106
|
+
const SOFT_NEAR_UNIQUE_RATIO = 0.9;
|
|
107
|
+
/**
|
|
108
|
+
* Statistics-first PK pick from streamed evidence. A column is a candidate ONLY if it is, over a
|
|
109
|
+
* statistically meaningful number of rows, **non-null on every row** AND **all-distinct** (and its
|
|
110
|
+
* uniqueness was provable — the distinct-cap wasn't hit). One candidate → that's the PK (p<0.05:
|
|
111
|
+
* all-distinct over a large N is not chance). Several candidates → naming breaks the tie if it can;
|
|
112
|
+
* otherwise it's flagged ambiguous for the LLM tiebreaker. Zero candidates → no PK (no fabrication).
|
|
113
|
+
*/
|
|
114
|
+
export function pickPrimaryKeyFromStats(columns, opts = {}) {
|
|
115
|
+
const minRows = opts.MinRowsForSignificance ?? 50;
|
|
116
|
+
const candidates = columns.filter(c => c.TotalRows >= minRows &&
|
|
117
|
+
c.Occurrences === c.TotalRows && // non-null on EVERY row
|
|
118
|
+
!c.DistinctCapped && // uniqueness was provable from the scan
|
|
119
|
+
c.DistinctNonNull === c.Occurrences);
|
|
120
|
+
const names = candidates.map(c => c.Key);
|
|
121
|
+
if (candidates.length === 0) {
|
|
122
|
+
// SOFT best-available fallback (no hard significance gate). A column that is not PROVABLY unique
|
|
123
|
+
// over a significant sample would otherwise yield NO PK — which stalls CodeGen SQL generation and
|
|
124
|
+
// leaves the table unsyncable. A soft key is identity-for-dedup only (it can NEVER reject a row),
|
|
125
|
+
// so we may pick the best-available CONVENTION-named column that still carries an identity signal:
|
|
126
|
+
// non-null on every scanned row AND high-cardinality (near-unique, or distinct-capped = cardinality
|
|
127
|
+
// beyond the tracking cap). Only convention-named columns qualify — an unnamed/low-signal column is
|
|
128
|
+
// still left PK-less (no fabrication). This is the "all keys are soft, best-available" policy.
|
|
129
|
+
// #A4: a time-budget-truncated scan only saw a prefix, so demand FULL prefix-uniqueness (1.0) for a
|
|
130
|
+
// soft key rather than merely near-unique (0.9) — a column that's near-unique over a partial scan
|
|
131
|
+
// could be null/duplicated in the unscanned tail and isn't trustworthy even as a soft dedup key.
|
|
132
|
+
const scanComplete = opts.ScanComplete !== false;
|
|
133
|
+
const softRatio = scanComplete ? SOFT_NEAR_UNIQUE_RATIO : 1.0;
|
|
134
|
+
const soft = columns.filter(c => isConventionPkName(c.Key) &&
|
|
135
|
+
c.TotalRows > 0 &&
|
|
136
|
+
c.Occurrences === c.TotalRows && // non-null on every row
|
|
137
|
+
(c.DistinctCapped || c.DistinctNonNull / c.Occurrences >= softRatio));
|
|
138
|
+
if (soft.length > 0) {
|
|
139
|
+
const rank = opts.NameRank ?? (() => 0);
|
|
140
|
+
const card = (c) => (c.DistinctCapped ? Number.MAX_SAFE_INTEGER : c.DistinctNonNull);
|
|
141
|
+
const best = [...soft].sort((a, b) => card(b) - card(a) || rank(b.Key) - rank(a.Key) || a.Key.localeCompare(b.Key))[0];
|
|
142
|
+
const caveat = scanComplete ? '' : ' [partial scan — time-budget-truncated; re-verify on full data]';
|
|
143
|
+
return { Field: best.Key, UniqueCandidates: [best.Key], AmbiguousForLLM: false, Reason: `Soft best-available key "${best.Key}" — convention-named with an identity signal (not provably unique; soft, dedup-only).${caveat}` };
|
|
144
|
+
}
|
|
145
|
+
return { Field: null, UniqueCandidates: [], AmbiguousForLLM: false, Reason: 'No column is provably unique + non-null over a significant sample, and none is a plausibly-identifying convention-named column.' };
|
|
146
|
+
}
|
|
147
|
+
if (candidates.length === 1) {
|
|
148
|
+
return { Field: names[0], UniqueCandidates: names, AmbiguousForLLM: false, Reason: `"${names[0]}" is unique + non-null across ${candidates[0].TotalRows} rows (statistically the key).` };
|
|
149
|
+
}
|
|
150
|
+
// Multiple unique columns — naming tiebreaker, else hand to the LLM judge with the stats in view.
|
|
151
|
+
const rank = opts.NameRank ?? (() => 0);
|
|
152
|
+
const ranked = [...candidates].sort((a, b) => rank(b.Key) - rank(a.Key));
|
|
153
|
+
if (rank(ranked[0].Key) > rank(ranked[1].Key)) {
|
|
154
|
+
return { Field: ranked[0].Key, UniqueCandidates: names, AmbiguousForLLM: false, Reason: `${candidates.length} unique columns; naming chose "${ranked[0].Key}".` };
|
|
155
|
+
}
|
|
156
|
+
return { Field: null, UniqueCandidates: names, AmbiguousForLLM: true, Reason: `${candidates.length} equally-named unique columns — ambiguous; defer to the evidence-fed LLM tiebreaker.` };
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* COMPOSITE PK pick from streamed evidence — for objects whose identity is a multi-column combination
|
|
160
|
+
* (no single column is unique). Statistics-first + provable-only, same philosophy as the single-column
|
|
161
|
+
* pick: it emits a column set ONLY when that set is provably unique + fully-non-null over a significant
|
|
162
|
+
* sample; otherwise null (→ the caller uses the content-hash identity floor). No naming guesses.
|
|
163
|
+
*
|
|
164
|
+
* Cost is GREEDY, not combinatorial — O(R·C), never the 2^C subset blow-up:
|
|
165
|
+
* 1. Candidate columns = those non-null on EVERY row (a null component can't be part of a stable key),
|
|
166
|
+
* sorted by cardinality desc (so uniqueness is reached in the fewest columns).
|
|
167
|
+
* 2. Greedily add the next-highest-cardinality candidate and test combined-tuple uniqueness over the
|
|
168
|
+
* cached per-row cell-hashes. First column set whose tuples are all-distinct over the sample wins.
|
|
169
|
+
* Greedy finds *a* provable unique combination (sufficient for identity/dedup), not necessarily the
|
|
170
|
+
* globally-minimal one — minimality is a nice-to-have the identity path doesn't need. A significance
|
|
171
|
+
* gate (MinRowsForSignificance) keeps a small-sample "uniqueness" from being a fluke.
|
|
172
|
+
*/
|
|
173
|
+
export function pickCompositeKeyFromStats(columns, rowSamples, opts = {}) {
|
|
174
|
+
const minRows = opts.MinRowsForSignificance ?? 50;
|
|
175
|
+
const n = rowSamples.length;
|
|
176
|
+
if (n < minRows) {
|
|
177
|
+
return { Fields: null, Reason: `Composite key not attempted — only ${n} sampled rows (< ${minRows} for significance).` };
|
|
178
|
+
}
|
|
179
|
+
// A key column must be non-null on EVERY scanned row; order by cardinality desc for the greedy.
|
|
180
|
+
const candidates = columns
|
|
181
|
+
.filter(c => c.TotalRows > 0 && c.Occurrences === c.TotalRows)
|
|
182
|
+
.sort((a, b) => b.DistinctNonNull - a.DistinctNonNull)
|
|
183
|
+
.map(c => c.Key);
|
|
184
|
+
if (candidates.length < 2) {
|
|
185
|
+
return { Fields: null, Reason: `Fewer than 2 fully-non-null columns — no composite key possible (a single key would already have been found).` };
|
|
186
|
+
}
|
|
187
|
+
const NULL_MARK = String.fromCharCode(0), SEP = String.fromCharCode(1);
|
|
188
|
+
const keyCols = [];
|
|
189
|
+
for (const cand of candidates) {
|
|
190
|
+
keyCols.push(cand);
|
|
191
|
+
const seen = new Set();
|
|
192
|
+
let unique = true;
|
|
193
|
+
for (const row of rowSamples) {
|
|
194
|
+
const tuple = keyCols.map(k => row[k] ?? NULL_MARK).join(SEP);
|
|
195
|
+
if (seen.has(tuple)) {
|
|
196
|
+
unique = false;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
seen.add(tuple);
|
|
200
|
+
}
|
|
201
|
+
// A single column going unique here is already covered by pickPrimaryKeyFromStats; require ≥2.
|
|
202
|
+
if (unique && keyCols.length >= 2) {
|
|
203
|
+
return { Fields: [...keyCols], Reason: `Composite key {${keyCols.join(', ')}} is unique + non-null across ${n} rows (greedy by cardinality, provable over the sample).` };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return { Fields: null, Reason: `No combination of the ${candidates.length} non-null columns is unique over ${n} rows — genuinely keyless; use the content-hash identity floor.` };
|
|
207
|
+
}
|
|
208
|
+
/** All size-k combinations of `items` (k small; callers cap the candidate list). */
|
|
209
|
+
function* combinations(items, k) {
|
|
210
|
+
if (k <= 0) {
|
|
211
|
+
yield [];
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
for (let i = 0; i <= items.length - k; i++) {
|
|
215
|
+
for (const rest of combinations(items.slice(i + 1), k - 1))
|
|
216
|
+
yield [items[i], ...rest];
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Key-ness of a column SUBSET over the cached row-hash sample, via the Chao1 nonparametric
|
|
221
|
+
* cardinality estimator. A subset is a PROVABLE key iff its estimated value-domain provably EXCEEDS
|
|
222
|
+
* the sample (D̂ > n) — i.e. the domain has NOT saturated: we keep seeing brand-new tuples, the
|
|
223
|
+
* signature of an identifier, not a category. Robust to skew (it reads the singleton/doubleton
|
|
224
|
+
* structure of the repeats, not a raw distinct ratio) and CPU-cheap (one pass over the row-hashes).
|
|
225
|
+
*/
|
|
226
|
+
function subsetKeyness(rowSamples, cols, minRows) {
|
|
227
|
+
const n = rowSamples.length;
|
|
228
|
+
const NULL_MARK = String.fromCharCode(0), SEP = String.fromCharCode(1);
|
|
229
|
+
const counts = new Map();
|
|
230
|
+
for (const row of rowSamples) {
|
|
231
|
+
const tuple = cols.map(c => row[c] ?? NULL_MARK).join(SEP);
|
|
232
|
+
counts.set(tuple, (counts.get(tuple) ?? 0) + 1);
|
|
233
|
+
}
|
|
234
|
+
const d = counts.size;
|
|
235
|
+
let f1 = 0, f2 = 0;
|
|
236
|
+
for (const v of counts.values()) {
|
|
237
|
+
if (v === 1)
|
|
238
|
+
f1++;
|
|
239
|
+
else if (v === 2)
|
|
240
|
+
f2++;
|
|
241
|
+
}
|
|
242
|
+
// Chao1 (bias-corrected when no doubletons): D̂ blows up when values are mostly singletons
|
|
243
|
+
// (domain ≫ sample → unsaturated → key); collapses toward `d` when repeats dominate (saturated).
|
|
244
|
+
const Dhat = f2 > 0 ? d + (f1 * f1) / (2 * f2) : d + (f1 * (f1 - 1)) / 2;
|
|
245
|
+
return { Dhat, isKey: n >= minRows && Dhat > n };
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Provable-only key pick — single OR composite, in ONE pass. Picks the BEST contender at each subset
|
|
249
|
+
* size (1, 2, 3 …), then takes the SMALLEST size whose best contender is a provable key (minimality:
|
|
250
|
+
* a 1-col key beats a 2-col beats a 3-col). "Best" + "is it a key" are the same Chao1 saturation test
|
|
251
|
+
* ({@link subsetKeyness}) applied to the subset's combined tuple — no arbitrary distinct-ratio. If NO
|
|
252
|
+
* size yields a provable key, returns null (the object is honestly keyless — never a fabricated key).
|
|
253
|
+
*/
|
|
254
|
+
export function pickKeyFromStats(columns, rowSamples, opts = {}) {
|
|
255
|
+
const minRows = opts.MinRowsForSignificance ?? 50;
|
|
256
|
+
const maxK = opts.MaxKeyColumns ?? 3;
|
|
257
|
+
const maxCandidates = opts.MaxCandidates ?? 15;
|
|
258
|
+
if (rowSamples.length < minRows) {
|
|
259
|
+
return { Fields: null, Reason: `Insufficient sample — ${rowSamples.length} rows (< ${minRows} for significance).` };
|
|
260
|
+
}
|
|
261
|
+
// A key column must be non-null on EVERY row; rank by cardinality, cap the candidate set so the
|
|
262
|
+
// per-size subset enumeration stays cheap (C(maxCandidates, k) for k ≤ maxK).
|
|
263
|
+
const candidates = columns
|
|
264
|
+
.filter(c => c.TotalRows > 0 && c.Occurrences === c.TotalRows)
|
|
265
|
+
.sort((a, b) => b.DistinctNonNull - a.DistinctNonNull)
|
|
266
|
+
.slice(0, maxCandidates)
|
|
267
|
+
.map(c => c.Key);
|
|
268
|
+
if (candidates.length === 0) {
|
|
269
|
+
return { Fields: null, Reason: 'No fully-non-null column to anchor a key.' };
|
|
270
|
+
}
|
|
271
|
+
for (let k = 1; k <= Math.min(maxK, candidates.length); k++) {
|
|
272
|
+
let best = null;
|
|
273
|
+
for (const subset of combinations(candidates, k)) {
|
|
274
|
+
const v = subsetKeyness(rowSamples, subset, minRows);
|
|
275
|
+
if (!best || v.Dhat > best.Dhat)
|
|
276
|
+
best = { subset, Dhat: v.Dhat, isKey: v.isKey };
|
|
277
|
+
}
|
|
278
|
+
if (best && best.isKey) {
|
|
279
|
+
return { Fields: best.subset, Reason: `Provable ${k}-column key {${best.subset.join(', ')}} — Chao1 domain D̂≈${Math.round(best.Dhat)} ≫ n=${rowSamples.length} (unsaturated).` };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return { Fields: null, Reason: `No provable key at sizes 1..${Math.min(maxK, candidates.length)} over ${rowSamples.length} rows — every best contender's domain saturated (Chao1 D̂ ≤ n).` };
|
|
283
|
+
}
|
|
284
|
+
/** Stable string key for a value so distinct-counting is correct across primitives + objects. */
|
|
285
|
+
function stableValueKey(value) {
|
|
286
|
+
if (typeof value === 'object') {
|
|
287
|
+
try {
|
|
288
|
+
return 'o:' + JSON.stringify(value);
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
return 'o:[unserializable]';
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return typeof value + ':' + String(value);
|
|
295
|
+
}
|
|
296
|
+
/** Normalizes a sync or async iterable to an async iterable so the loop handles both. */
|
|
297
|
+
async function* toAsyncIterable(src) {
|
|
298
|
+
if (Symbol.asyncIterator in Object(src)) {
|
|
299
|
+
yield* src;
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
for (const item of src)
|
|
303
|
+
yield item;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
//# sourceMappingURL=StreamingDiscovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StreamingDiscovery.js","sourceRoot":"","sources":["../src/StreamingDiscovery.ts"],"names":[],"mappings":"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"}
|
package/dist/SyncLogger.d.ts
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
* tail -f /tmp/mjapi.log | grep '"event":"sync\.push\.' # only outbound writes
|
|
36
36
|
* tail -f /tmp/mjapi.log | grep '"event":"sync\..*\.error"' # only errors
|
|
37
37
|
*/
|
|
38
|
-
export type SyncLogEvent = 'sync.run.start' | 'sync.config.loaded' | 'sync.connector.built' | 'sync.connector.test' | 'sync.entity-map.start' | 'sync.resume.keyset' | 'sync.partition.reconcile' | 'sync.fetch.batch.start' | 'sync.fetch.batch.complete' | 'sync.record.decision' | 'sync.record.saved' | 'sync.record.error' | 'sync.record.archived' | 'sync.record.conflict' | 'sync.push.candidates' | 'sync.push.record' | 'sync.push.response' | 'sync.entity-map.complete' | 'sync.run.complete' | 'sync.run.fail' | 'sync.run.cancelled' | 'sync.warning';
|
|
38
|
+
export type SyncLogEvent = 'sync.run.start' | 'sync.config.loaded' | 'sync.connector.built' | 'sync.connector.test' | 'sync.entity-map.start' | 'sync.resume.keyset' | 'sync.partition.reconcile' | 'sync.fetch.batch.start' | 'sync.fetch.batch.complete' | 'sync.fetch.retry' | 'sync.record.decision' | 'sync.record.saved' | 'sync.record.error' | 'sync.record.retry' | 'sync.record.archived' | 'sync.record.conflict' | 'sync.push.candidates' | 'sync.push.record' | 'sync.push.response' | 'sync.entity-map.complete' | 'sync.run.complete' | 'sync.run.fail' | 'sync.run.cancelled' | 'sync.schema_update' | 'sync.warning';
|
|
39
39
|
import type { IntegrationProgressEmitter } from '@memberjunction/integration-progress-artifacts';
|
|
40
40
|
export interface SyncLoggerContext {
|
|
41
41
|
/** CompanyIntegration ID — the per-run anchor for filtering. */
|
package/dist/SyncLogger.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SyncLogger.d.ts","sourceRoot":"","sources":["../src/SyncLogger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,MAAM,MAAM,YAAY,GAClB,gBAAgB,GAChB,oBAAoB,GACpB,sBAAsB,GACtB,qBAAqB,GACrB,uBAAuB,GACvB,oBAAoB,GACpB,0BAA0B,GAC1B,wBAAwB,GACxB,2BAA2B,GAC3B,sBAAsB,GACtB,mBAAmB,GACnB,mBAAmB,GACnB,sBAAsB,GACtB,sBAAsB,GACtB,sBAAsB,GACtB,kBAAkB,GAClB,oBAAoB,GACpB,0BAA0B,GAC1B,mBAAmB,GACnB,eAAe,GACf,oBAAoB,GACpB,cAAc,CAAC;AAErB,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gDAAgD,CAAC;AAEjG,MAAM,WAAW,iBAAiB;IAC9B,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACvC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,YAAY,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,iDAAiD;IACjD,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;
|
|
1
|
+
{"version":3,"file":"SyncLogger.d.ts","sourceRoot":"","sources":["../src/SyncLogger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,MAAM,MAAM,YAAY,GAClB,gBAAgB,GAChB,oBAAoB,GACpB,sBAAsB,GACtB,qBAAqB,GACrB,uBAAuB,GACvB,oBAAoB,GACpB,0BAA0B,GAC1B,wBAAwB,GACxB,2BAA2B,GAC3B,kBAAkB,GAClB,sBAAsB,GACtB,mBAAmB,GACnB,mBAAmB,GACnB,mBAAmB,GACnB,sBAAsB,GACtB,sBAAsB,GACtB,sBAAsB,GACtB,kBAAkB,GAClB,oBAAoB,GACpB,0BAA0B,GAC1B,mBAAmB,GACnB,eAAe,GACf,oBAAoB,GACpB,oBAAoB,GACpB,cAAc,CAAC;AAErB,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gDAAgD,CAAC;AAEjG,MAAM,WAAW,iBAAiB;IAC9B,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACvC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,YAAY,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,iDAAiD;IACjD,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAkBD;;;;GAIG;AACH,qBAAa,UAAU;IACnB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoB;IACxC,OAAO,CAAC,OAAO,CAAC,CAA6B;gBAEjC,GAAG,EAAE,iBAAiB;IAIlC,6DAA6D;IACtD,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIvC,6EAA6E;IACtE,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAInE;;;;;;;OAOG;IACI,aAAa,CAAC,OAAO,EAAE,0BAA0B,GAAG,IAAI;IAI/D;;;;;OAKG;IACI,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAI/E;;;;;;;OAOG;IACI,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAI3F,IAAI,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,IAAI;IAuB1E;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAyDxB,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,GAAG;IAIX,qFAAqF;IACrF,OAAO,CAAC,QAAQ;CAGnB"}
|
package/dist/SyncLogger.js
CHANGED
|
@@ -35,6 +35,21 @@
|
|
|
35
35
|
* tail -f /tmp/mjapi.log | grep '"event":"sync\.push\.' # only outbound writes
|
|
36
36
|
* tail -f /tmp/mjapi.log | grep '"event":"sync\..*\.error"' # only errors
|
|
37
37
|
*/
|
|
38
|
+
/**
|
|
39
|
+
* High-volume per-record events fire ONCE PER ROW — on a multi-million-row sync they would flood
|
|
40
|
+
* stdout (and any tee'd log file) without bound, a real disk-exhaustion path. They are gated behind
|
|
41
|
+
* `MJ_INTEGRATION_VERBOSE_RECORD_LOGS` (default OFF): only the console spam is suppressed — the
|
|
42
|
+
* structured per-record detail still reaches the durable progress artifact via `forwardToEmitter`
|
|
43
|
+
* where applicable. Errors, warnings, and run/batch-level events ALWAYS log regardless of the flag.
|
|
44
|
+
*/
|
|
45
|
+
const VERBOSE_RECORD_EVENTS = new Set([
|
|
46
|
+
'sync.record.decision',
|
|
47
|
+
'sync.record.saved',
|
|
48
|
+
'sync.record.archived',
|
|
49
|
+
'sync.push.record',
|
|
50
|
+
'sync.push.response',
|
|
51
|
+
]);
|
|
52
|
+
const VERBOSE_RECORD_LOGS_ENABLED = process.env.MJ_INTEGRATION_VERBOSE_RECORD_LOGS === 'true';
|
|
38
53
|
/**
|
|
39
54
|
* Light wrapper that prepends an ISO timestamp + the per-run context to every
|
|
40
55
|
* line. Writes to console.log (or console.error for fail events) so the line
|
|
@@ -99,7 +114,10 @@ export class SyncLogger {
|
|
|
99
114
|
else if (event === 'sync.warning') {
|
|
100
115
|
console.warn(line);
|
|
101
116
|
}
|
|
102
|
-
else {
|
|
117
|
+
else if (!VERBOSE_RECORD_EVENTS.has(event) || VERBOSE_RECORD_LOGS_ENABLED) {
|
|
118
|
+
// Per-record events (one per row) are suppressed from the console unless explicitly
|
|
119
|
+
// enabled — otherwise a large sync floods stdout / the tee'd log without bound. The
|
|
120
|
+
// structured per-record detail still reaches the durable artifact via forwardToEmitter.
|
|
103
121
|
console.log(line);
|
|
104
122
|
}
|
|
105
123
|
this.forwardToEmitter(event, data);
|
package/dist/SyncLogger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SyncLogger.js","sourceRoot":"","sources":["../src/SyncLogger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;
|
|
1
|
+
{"version":3,"file":"SyncLogger.js","sourceRoot":"","sources":["../src/SyncLogger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BasicAuthHeaderBuilder — shared HTTP Basic Authentication header helper for REST
|
|
3
|
+
* integration connectors. Centralizes the RFC 7617 `Authorization: Basic <base64>`
|
|
4
|
+
* construction so concrete connectors never inline base64 / crypto logic (the
|
|
5
|
+
* connector-code-conventions "no inline crypto / no inline base64" rule).
|
|
6
|
+
*
|
|
7
|
+
* RFC 7617 is `Basic base64(userid ":" password)` — the userid MUST NOT contain a
|
|
8
|
+
* colon (the password may). For Neon CRM the userid is the org id and the password is
|
|
9
|
+
* the API key; for other vendors it is username:password or apiKey:"" etc. This helper
|
|
10
|
+
* does ONLY the encoding — it never logs, stores, or transports the credential bytes.
|
|
11
|
+
*
|
|
12
|
+
* The base64 encoding is `Buffer.from(...).toString('base64')` — the Node standard
|
|
13
|
+
* library primitive, kept behind one tested helper rather than copy-pasted into every
|
|
14
|
+
* connector. No round-trip, no secret retention.
|
|
15
|
+
*/
|
|
16
|
+
/** Inputs for a single HTTP Basic Authorization header value. */
|
|
17
|
+
export interface BasicAuthRequest {
|
|
18
|
+
/** The userid component (left of the colon). For Neon CRM, the org id. MUST NOT contain ':'. */
|
|
19
|
+
Username: string;
|
|
20
|
+
/** The password component (right of the colon). For Neon CRM, the API key. May contain ':'. */
|
|
21
|
+
Password: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Builds the value for an HTTP Basic `Authorization` header: `Basic <base64(user:pass)>`.
|
|
25
|
+
* Throws on a colon in the username (ambiguous per RFC 7617) or on an empty username.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildBasicAuthHeaderValue(req: BasicAuthRequest): string;
|
|
28
|
+
/**
|
|
29
|
+
* APIKeyHeaderBuilder — convenience over {@link buildBasicAuthHeaderValue} returning a
|
|
30
|
+
* ready-to-spread header object. Useful when a connector wants the full `Authorization`
|
|
31
|
+
* header pair rather than just the value.
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildBasicAuthHeader(req: BasicAuthRequest): {
|
|
34
|
+
Authorization: string;
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=BasicAuthHeaderBuilder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BasicAuthHeaderBuilder.d.ts","sourceRoot":"","sources":["../../src/auth-helpers/BasicAuthHeaderBuilder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,iEAAiE;AACjE,MAAM,WAAW,gBAAgB;IAC7B,gGAAgG;IAChG,QAAQ,EAAE,MAAM,CAAC;IACjB,+FAA+F;IAC/F,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,gBAAgB,GAAG,MAAM,CASvE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,gBAAgB,GAAG;IAAE,aAAa,EAAE,MAAM,CAAA;CAAE,CAErF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BasicAuthHeaderBuilder — shared HTTP Basic Authentication header helper for REST
|
|
3
|
+
* integration connectors. Centralizes the RFC 7617 `Authorization: Basic <base64>`
|
|
4
|
+
* construction so concrete connectors never inline base64 / crypto logic (the
|
|
5
|
+
* connector-code-conventions "no inline crypto / no inline base64" rule).
|
|
6
|
+
*
|
|
7
|
+
* RFC 7617 is `Basic base64(userid ":" password)` — the userid MUST NOT contain a
|
|
8
|
+
* colon (the password may). For Neon CRM the userid is the org id and the password is
|
|
9
|
+
* the API key; for other vendors it is username:password or apiKey:"" etc. This helper
|
|
10
|
+
* does ONLY the encoding — it never logs, stores, or transports the credential bytes.
|
|
11
|
+
*
|
|
12
|
+
* The base64 encoding is `Buffer.from(...).toString('base64')` — the Node standard
|
|
13
|
+
* library primitive, kept behind one tested helper rather than copy-pasted into every
|
|
14
|
+
* connector. No round-trip, no secret retention.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Builds the value for an HTTP Basic `Authorization` header: `Basic <base64(user:pass)>`.
|
|
18
|
+
* Throws on a colon in the username (ambiguous per RFC 7617) or on an empty username.
|
|
19
|
+
*/
|
|
20
|
+
export function buildBasicAuthHeaderValue(req) {
|
|
21
|
+
if (req.Username == null || req.Username.length === 0) {
|
|
22
|
+
throw new Error('BasicAuthHeaderBuilder: Username (userid) is required and cannot be empty.');
|
|
23
|
+
}
|
|
24
|
+
if (req.Username.includes(':')) {
|
|
25
|
+
throw new Error('BasicAuthHeaderBuilder: Username (userid) MUST NOT contain a colon per RFC 7617.');
|
|
26
|
+
}
|
|
27
|
+
const encoded = Buffer.from(`${req.Username}:${req.Password ?? ''}`, 'utf8').toString('base64');
|
|
28
|
+
return `Basic ${encoded}`;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* APIKeyHeaderBuilder — convenience over {@link buildBasicAuthHeaderValue} returning a
|
|
32
|
+
* ready-to-spread header object. Useful when a connector wants the full `Authorization`
|
|
33
|
+
* header pair rather than just the value.
|
|
34
|
+
*/
|
|
35
|
+
export function buildBasicAuthHeader(req) {
|
|
36
|
+
return { Authorization: buildBasicAuthHeaderValue(req) };
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=BasicAuthHeaderBuilder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BasicAuthHeaderBuilder.js","sourceRoot":"","sources":["../../src/auth-helpers/BasicAuthHeaderBuilder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAUH;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAAqB;IAC3D,IAAI,GAAG,CAAC,QAAQ,IAAI,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,4EAA4E,CAAC,CAAC;IAClG,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,kFAAkF,CAAC,CAAC;IACxG,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAChG,OAAO,SAAS,OAAO,EAAE,CAAC;AAC9B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAqB;IACtD,OAAO,EAAE,aAAa,EAAE,yBAAyB,CAAC,GAAG,CAAC,EAAE,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** HMAC variants OAuth 1.0a permits. NetSuite TBA mandates HMAC-SHA256. */
|
|
2
|
+
export type OAuth1aSignatureMethod = 'HMAC-SHA256' | 'HMAC-SHA1';
|
|
3
|
+
/** Inputs needed to sign one request with OAuth 1.0a TBA. */
|
|
4
|
+
export interface OAuth1aSignRequest {
|
|
5
|
+
/** OAuth consumer key (a.k.a. client id / integration key). */
|
|
6
|
+
ConsumerKey: string;
|
|
7
|
+
/** OAuth consumer secret. */
|
|
8
|
+
ConsumerSecret: string;
|
|
9
|
+
/** OAuth token id (the TBA access token id). */
|
|
10
|
+
TokenId: string;
|
|
11
|
+
/** OAuth token secret. */
|
|
12
|
+
TokenSecret: string;
|
|
13
|
+
/** HTTP method of the request being signed (uppercased internally). */
|
|
14
|
+
Method: string;
|
|
15
|
+
/** Full request URL, including any query string (query params are folded into the base string). */
|
|
16
|
+
Url: string;
|
|
17
|
+
/**
|
|
18
|
+
* OAuth `realm` parameter. NetSuite requires the account id (uppercased, `-` → `_`,
|
|
19
|
+
* e.g. `1234567` or `1234567_SB1`). Optional — omitted from the header when absent.
|
|
20
|
+
*/
|
|
21
|
+
Realm?: string;
|
|
22
|
+
/** Signature method. Defaults to `HMAC-SHA256` (NetSuite TBA's requirement). */
|
|
23
|
+
SignatureMethod?: OAuth1aSignatureMethod;
|
|
24
|
+
/**
|
|
25
|
+
* Deterministic nonce + timestamp override — for unit testing only. Production callers
|
|
26
|
+
* leave this undefined so a fresh random nonce + current timestamp are generated.
|
|
27
|
+
*/
|
|
28
|
+
NonceOverride?: string;
|
|
29
|
+
TimestampOverride?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* RFC 3986 percent-encoding (stricter than `encodeURIComponent`, which leaves
|
|
33
|
+
* `! ' ( ) *` unescaped). OAuth 1.0a requires these to be escaped too.
|
|
34
|
+
*/
|
|
35
|
+
export declare function percentEncodeRFC3986(value: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Stateless OAuth 1.0a signer. One static method builds the `Authorization` header
|
|
38
|
+
* value for a request; connectors call it per request.
|
|
39
|
+
*/
|
|
40
|
+
export declare class OAuth1aSigner {
|
|
41
|
+
/**
|
|
42
|
+
* Builds the full `Authorization: OAuth ...` header value for a single request,
|
|
43
|
+
* computing the HMAC signature over the canonical signature base string.
|
|
44
|
+
*
|
|
45
|
+
* Spec steps implemented (RFC 5849 §3.4):
|
|
46
|
+
* 1. Collect oauth_* params + the request's query params.
|
|
47
|
+
* 2. Percent-encode, sort, and `&`-join them (the normalized parameter string).
|
|
48
|
+
* 3. Build the base string `METHOD&encode(baseURL)&encode(params)`.
|
|
49
|
+
* 4. Sign with key `encode(consumerSecret)&encode(tokenSecret)`.
|
|
50
|
+
* 5. Emit `OAuth realm="...", oauth_consumer_key="...", ..., oauth_signature="..."`.
|
|
51
|
+
*/
|
|
52
|
+
static BuildAuthorizationHeader(req: OAuth1aSignRequest): string;
|
|
53
|
+
/** Splits a URL into its base (scheme://host/path, no query) + a decoded query-param map. */
|
|
54
|
+
private static splitUrl;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=OAuth1aSigner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"OAuth1aSigner.d.ts","sourceRoot":"","sources":["../../src/auth-helpers/OAuth1aSigner.ts"],"names":[],"mappings":"AAkBA,2EAA2E;AAC3E,MAAM,MAAM,sBAAsB,GAAG,aAAa,GAAG,WAAW,CAAC;AAEjE,6DAA6D;AAC7D,MAAM,WAAW,kBAAkB;IAC/B,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,6BAA6B;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,MAAM,EAAE,MAAM,CAAC;IACf,mGAAmG;IACnG,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,eAAe,CAAC,EAAE,sBAAsB,CAAC;IACzC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAO1D;AAED;;;GAGG;AACH,qBAAa,aAAa;IACtB;;;;;;;;;;OAUG;WACW,wBAAwB,CAAC,GAAG,EAAE,kBAAkB,GAAG,MAAM;IAyCvE,6FAA6F;IAC7F,OAAO,CAAC,MAAM,CAAC,QAAQ;CAQ1B"}
|