@campfire-interactive/master-data-client 5.22.0 → 5.24.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/dist/programs.js DELETED
@@ -1,193 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ProgramResource = void 0;
4
- class ProgramResource {
5
- http;
6
- constructor(http) {
7
- this.http = http;
8
- }
9
- /**
10
- * Drain-pagination variant of {@link list}. Internally cursors through every
11
- * page until the server says `hasMore=false` and returns the full
12
- * `Program[]` — callers don't manage the cursor.
13
- *
14
- * Use this for picker / dropdown loads where the consumer needs the complete
15
- * set within a scope (typically OEM-scoped via `customerCompanyId`). For
16
- * per-keystroke search where the user types into a typeahead, prefer
17
- * {@link list} with `q` + explicit pagination — `listAll` trades cold-start
18
- * latency for a stable, complete result and is wrong for high-frequency
19
- * search.
20
- *
21
- * Accepts the same filters as `list()` but no `cursor` (managed internally).
22
- * `limit` is interpreted as page size if supplied; default 200 minimizes
23
- * round-trips against master-data's 200/page server cap.
24
- *
25
- * Safety cap: 5000 programs across all drained pages. If the server returns
26
- * `hasMore=true` indefinitely (or returns the same cursor twice), this
27
- * throws rather than spin a browser tab. Past 5000 the right answer is
28
- * server-side typeahead, not a fatter drain.
29
- */
30
- async listAll(options) {
31
- const PAGE_SIZE = options?.limit ?? 200;
32
- const HARD_CAP = 5000;
33
- const out = [];
34
- let cursor;
35
- // eslint-disable-next-line no-constant-condition
36
- while (true) {
37
- const page = await this.list({ ...options, limit: PAGE_SIZE, cursor });
38
- out.push(...page.data);
39
- if (out.length > HARD_CAP) {
40
- throw new Error(`programs.listAll: drained ${out.length} programs past the ${HARD_CAP} safety cap. ` +
41
- 'Check server pagination or switch to a typeahead via list() + q.');
42
- }
43
- if (!page.meta.hasMore || !page.meta.nextCursor)
44
- break;
45
- cursor = page.meta.nextCursor;
46
- }
47
- return out;
48
- }
49
- async list(options) {
50
- const query = {};
51
- if (options) {
52
- if (options.limit !== undefined)
53
- query.limit = String(options.limit);
54
- if (options.cursor)
55
- query.cursor = options.cursor;
56
- if (options.sort)
57
- query.sort = options.sort;
58
- if (options.q)
59
- query.q = options.q;
60
- if (options.status)
61
- query.status = options.status;
62
- if (options.quality)
63
- query.quality = options.quality;
64
- // Forward whichever alias the caller passed; prefer canonical when both
65
- // are set. Server treats them as equivalent (rejects only on conflict).
66
- const customerCompanyId = options.customerCompanyId ?? options.oemCompanyId;
67
- if (customerCompanyId)
68
- query.customerCompanyId = customerCompanyId;
69
- if (options.ids?.length)
70
- query.ids = options.ids.join(',');
71
- if (options.include)
72
- query.include = options.include.join(',');
73
- if (options.geographicScopeId)
74
- query.geographicScopeId = options.geographicScopeId;
75
- if (options.productScopeId)
76
- query.productScopeId = options.productScopeId;
77
- }
78
- return this.http.request({
79
- method: 'GET',
80
- path: '/v1/programs',
81
- query,
82
- });
83
- }
84
- async get(id, options) {
85
- const query = {};
86
- if (options?.include) {
87
- query.include = options.include.join(',');
88
- }
89
- return this.http.request({
90
- method: 'GET',
91
- path: `/v1/programs/${encodeURIComponent(id)}`,
92
- query,
93
- });
94
- }
95
- async create(input, idempotencyKey) {
96
- return this.http.request({
97
- method: 'POST',
98
- path: '/v1/programs',
99
- body: input,
100
- idempotencyKey,
101
- });
102
- }
103
- async update(id, input, idempotencyKey) {
104
- return this.http.request({
105
- method: 'PUT',
106
- path: `/v1/programs/${encodeURIComponent(id)}`,
107
- body: input,
108
- idempotencyKey,
109
- });
110
- }
111
- async archive(id) {
112
- await this.http.request({
113
- method: 'DELETE',
114
- path: `/v1/programs/${encodeURIComponent(id)}`,
115
- });
116
- }
117
- /** Archive many programs in one call (soft; reversible via {@link batchRestore}). */
118
- async batchArchive(ids) {
119
- return this.http.request({
120
- method: 'POST',
121
- path: '/v1/programs/batch-archive',
122
- body: { ids },
123
- });
124
- }
125
- /** Restore (un-archive) many programs in one call. */
126
- async batchRestore(ids) {
127
- return this.http.request({
128
- method: 'POST',
129
- path: '/v1/programs/batch-restore',
130
- body: { ids },
131
- });
132
- }
133
- async getAudit(id) {
134
- return this.http.request({
135
- method: 'GET',
136
- path: `/v1/programs/${encodeURIComponent(id)}/audit`,
137
- });
138
- }
139
- /**
140
- * Find programs similar to the given name within a specific customer,
141
- * optionally unioned with an exact-code match. Two-tier:
142
- * - If `code` is supplied and exact-matches (tenantId, customerCompanyId,
143
- * code), that hit is returned first at score=1.0 with matchType='exact_code'.
144
- * - pg_trgm similarity matches on name (scope-filtered by customer) follow,
145
- * ordered by score, with matchType='trgm_name'.
146
- * Used by OMSF bulk-import to surface "did you mean..." suggestions during
147
- * Programs-sheet reconciliation; auto-resolve hooks key off matchType.
148
- */
149
- async matchByName(input) {
150
- const customerCompanyId = input.customerCompanyId ?? input.oemCompanyId;
151
- if (!customerCompanyId) {
152
- throw new Error('matchByName requires customerCompanyId (or deprecated oemCompanyId)');
153
- }
154
- const query = {
155
- name: input.name,
156
- customerCompanyId,
157
- };
158
- if (input.code !== undefined)
159
- query.code = input.code;
160
- if (input.threshold !== undefined)
161
- query.threshold = String(input.threshold);
162
- if (input.limit !== undefined)
163
- query.limit = String(input.limit);
164
- return this.http.request({
165
- method: 'GET',
166
- path: '/v1/programs/match',
167
- query,
168
- });
169
- }
170
- /**
171
- * Batch variant for fan-out workloads (Programs-sheet ingestion). Returns
172
- * parallel arrays — `data[i]` is the matches for `queries[i]`, same length
173
- * and input order. Failed queries surface as empty arrays so one bad input
174
- * doesn't blackhole the rest. Up to 500 queries per request.
175
- *
176
- * Different shape from Company `matchBatch` (which keys by name): Programs
177
- * sheets occasionally share names across customers, so a stable input-order
178
- * index is the only safe rejoin key.
179
- *
180
- * Each query accepts either `customerCompanyId` (canonical) or
181
- * `oemCompanyId` (deprecated alias). Server forwards whichever is set and
182
- * rejects on conflict.
183
- */
184
- async matchBatch(input) {
185
- return this.http.request({
186
- method: 'POST',
187
- path: '/v1/programs/match-batch',
188
- body: input,
189
- });
190
- }
191
- }
192
- exports.ProgramResource = ProgramResource;
193
- //# sourceMappingURL=programs.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"programs.js","sourceRoot":"","sources":["../src/programs.ts"],"names":[],"mappings":"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"}
@@ -1,103 +0,0 @@
1
- import { HttpTransport } from './http';
2
- import type { ScopeDimension, SingleResponse } from './types';
3
- /**
4
- * The scope gate: who may see which records, once a tenant turns gating on.
5
- *
6
- * ONE implementation, shared. master-data can express the gate as an RLS policy
7
- * because it owns the tables; OMSF and pm cannot — they have no RLS extension,
8
- * so their gate is a clause in a query builder. A clause copied into a dozen
9
- * call sites is a clause that gets missed in one of them, and a missed scope
10
- * clause is a data leak rather than a cosmetic bug. So it lives here, with the
11
- * fetching and caching it needs, and callers spread the result.
12
- *
13
- * TWO RULES SHAPE EVERYTHING, and both fail OPEN by deliberate decision
14
- * (nextgen ADR 2026-09-14, decisions 3 and 4):
15
- *
16
- * - A user with NO assignment is unrestricted. On the day gating ships nobody
17
- * has an assignment, and failing closed would lock out every user in the
18
- * tenant at once — with the omission that caused it looking exactly like
19
- * the feature working.
20
- * - A record with NO scope is visible to everyone. Almost nothing is scoped
21
- * at first, so failing closed would hide the entire dataset until someone
22
- * hand-scoped it.
23
- *
24
- * The consequence, which is easy to misread as the gate being broken: gating
25
- * restricts nothing until BOTH a user is assigned AND records are scoped.
26
- */
27
- export interface UserScopeSets {
28
- userId: string;
29
- geographicScopeIds: string[];
30
- productScopeIds: string[];
31
- }
32
- /** A Prisma-shaped `where` fragment. Deliberately a plain object: this package
33
- * has no Prisma dependency and does not want one, and every consumer speaks
34
- * this shape. Spread it into an existing `where`. */
35
- export type ScopeGatePredicate = Record<string, unknown>;
36
- export interface BuildScopeGateOptions {
37
- /** Which dimensions the tenant has gating switched ON for. A dimension that
38
- * is off contributes no clause at all. */
39
- gatedDimensions: ScopeDimension[];
40
- /** Column names, so a consumer whose table spells them differently can say
41
- * so. Defaults match master-data and OMSF. */
42
- columns?: {
43
- geographic?: string;
44
- product?: string;
45
- };
46
- }
47
- /**
48
- * Build the `where` fragment restricting a list to what this user may see.
49
- *
50
- * Returns an EMPTY object when the gate should not restrict — spreading `{}`
51
- * into a `where` is a no-op, so a caller never has to branch on "is gating on".
52
- *
53
- * Per dimension, when gating is on for it AND the user has assignments:
54
- *
55
- * { OR: [ { <col>: null }, { <col>: { in: [...] } } ] }
56
- *
57
- * The `null` branch is rule 2 — an unscoped record is visible to everyone. Drop
58
- * it and the gate hides almost the whole dataset on day one.
59
- *
60
- * Both dimensions gated produces an AND of the two, so a user must match on
61
- * each. That is intersection, not union: someone assigned to Europe and to
62
- * Doors sees European Doors work, which is what "my slice" means to the person
63
- * asking for this feature.
64
- */
65
- export declare function buildScopeGate(scopes: UserScopeSets | null, options: BuildScopeGateOptions): ScopeGatePredicate;
66
- /** Clear everything. Exposed for tests and for a process that wants a clean
67
- * slate; ordinary revocation is handled by the TTL. */
68
- export declare function clearScopeCache(): void;
69
- export declare class UserScopeResource {
70
- private readonly http;
71
- private readonly ttlMs;
72
- private readonly cache;
73
- constructor(http: HttpTransport, ttlMs?: number);
74
- /** The caller's own sets, uncached. */
75
- me(): Promise<SingleResponse<UserScopeSets>>;
76
- /**
77
- * The caller's own sets, cached for the TTL.
78
- *
79
- * Returns `null` rather than throwing when master-data cannot be reached.
80
- * That is deliberate and it is the fail-open rule again: a gate that throws
81
- * on a network blip takes every list in the app down, which is a worse
82
- * outcome than briefly not restricting a feature that restricts nothing for
83
- * most tenants anyway. Callers pass the null straight to buildScopeGate.
84
- */
85
- mineCached(cacheKey: string): Promise<UserScopeSets | null>;
86
- /** Drop a cached entry, so an admin who just changed an assignment can see it
87
- * take effect without waiting out the TTL. */
88
- invalidate(cacheKey: string): void;
89
- /** Admin surface: every user in the tenant WITH an assignment. Users with
90
- * none are absent — master-data does not know the tenant's roster, so the
91
- * caller joins this against identity's user list. */
92
- list(): Promise<{
93
- data: UserScopeSets[];
94
- }>;
95
- get(userId: string): Promise<SingleResponse<UserScopeSets>>;
96
- /** Replace a user's sets. Omit a dimension to leave it alone; send `[]` to
97
- * clear it, which returns the user to unrestricted. */
98
- set(userId: string, input: {
99
- geographicScopeIds?: string[];
100
- productScopeIds?: string[];
101
- }, idempotencyKey?: string): Promise<SingleResponse<UserScopeSets>>;
102
- }
103
- //# sourceMappingURL=scopeGate.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"scopeGate.d.ts","sourceRoot":"","sources":["../src/scopeGate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED;;sDAEsD;AACtD,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEzD,MAAM,WAAW,qBAAqB;IACpC;+CAC2C;IAC3C,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC;mDAC+C;IAC/C,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,aAAa,GAAG,IAAI,EAC5B,OAAO,EAAE,qBAAqB,GAC7B,kBAAkB,CAyBpB;AAiCD;wDACwD;AACxD,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED,qBAAa,iBAAiB;IAI1B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAJxB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;gBAGjB,IAAI,EAAE,aAAa,EACnB,KAAK,GAAE,MAAe;IAGzC,uCAAuC;IACjC,EAAE,IAAI,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IAOlD;;;;;;;;OAQG;IACG,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAejE;mDAC+C;IAC/C,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAIlC;;0DAEsD;IAChD,IAAI,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,aAAa,EAAE,CAAA;KAAE,CAAC;IAO1C,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IAOjE;4DACwD;IAClD,GAAG,CACP,MAAM,EAAE,MAAM,EACd,KAAK,EAAE;QAAE,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,EACpE,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;CAQ1C"}
package/dist/scopeGate.js DELETED
@@ -1,155 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.UserScopeResource = void 0;
4
- exports.buildScopeGate = buildScopeGate;
5
- exports.clearScopeCache = clearScopeCache;
6
- /**
7
- * Build the `where` fragment restricting a list to what this user may see.
8
- *
9
- * Returns an EMPTY object when the gate should not restrict — spreading `{}`
10
- * into a `where` is a no-op, so a caller never has to branch on "is gating on".
11
- *
12
- * Per dimension, when gating is on for it AND the user has assignments:
13
- *
14
- * { OR: [ { <col>: null }, { <col>: { in: [...] } } ] }
15
- *
16
- * The `null` branch is rule 2 — an unscoped record is visible to everyone. Drop
17
- * it and the gate hides almost the whole dataset on day one.
18
- *
19
- * Both dimensions gated produces an AND of the two, so a user must match on
20
- * each. That is intersection, not union: someone assigned to Europe and to
21
- * Doors sees European Doors work, which is what "my slice" means to the person
22
- * asking for this feature.
23
- */
24
- function buildScopeGate(scopes, options) {
25
- // No scope information at all — treat as unrestricted rather than guessing.
26
- // A resolver that failed is the likely cause, and failing a read closed on a
27
- // network blip would take the app down for everyone.
28
- if (!scopes)
29
- return {};
30
- const geoColumn = options.columns?.geographic ?? 'geographicScopeId';
31
- const productColumn = options.columns?.product ?? 'productScopeId';
32
- const clauses = [];
33
- if (options.gatedDimensions.includes('geographic') && scopes.geographicScopeIds.length > 0) {
34
- clauses.push({
35
- OR: [{ [geoColumn]: null }, { [geoColumn]: { in: [...scopes.geographicScopeIds] } }],
36
- });
37
- }
38
- if (options.gatedDimensions.includes('product') && scopes.productScopeIds.length > 0) {
39
- clauses.push({
40
- OR: [{ [productColumn]: null }, { [productColumn]: { in: [...scopes.productScopeIds] } }],
41
- });
42
- }
43
- if (clauses.length === 0)
44
- return {};
45
- if (clauses.length === 1)
46
- return clauses[0];
47
- return { AND: clauses };
48
- }
49
- /**
50
- * The caller's own scope sets, cached briefly.
51
- *
52
- * The cache exists because the gate needs this on EVERY request in OMSF and pm,
53
- * and a cross-service hop in front of every list query is the one place latency
54
- * is most visible. master-data itself needs none of this — its tables are local.
55
- *
56
- * **The TTL is a revocation window.** Removing someone's scope does not take
57
- * effect until their entry expires. 60 seconds is the agreed value: short
58
- * enough that "I removed her access and she can still see it" never becomes a
59
- * support call, long enough that a warm Lambda makes roughly one call per user
60
- * per container lifetime.
61
- *
62
- * Keyed by tenant AND user: a container serves many callers, and the whole
63
- * point of the gate is that they get different answers.
64
- */
65
- /**
66
- * MODULE-LEVEL, not per-instance, and that is not an optimization — it is the
67
- * only thing that makes the cache work at all.
68
- *
69
- * OMSF constructs a fresh `MasterDataClient` on every call, because the auth
70
- * token comes from the request context. A cache living on the resource instance
71
- * would therefore be born and discarded inside a single request and never once
72
- * be read, while looking entirely correct. Hoisting it to the module makes the
73
- * lifetime what the design assumes: the container's.
74
- *
75
- * Safe to share because every key carries the tenant AND the user — two callers
76
- * can never collide, which is the whole point of the gate.
77
- */
78
- const scopeCache = new Map();
79
- /** Clear everything. Exposed for tests and for a process that wants a clean
80
- * slate; ordinary revocation is handled by the TTL. */
81
- function clearScopeCache() {
82
- scopeCache.clear();
83
- }
84
- class UserScopeResource {
85
- http;
86
- ttlMs;
87
- cache = scopeCache;
88
- constructor(http, ttlMs = 60_000) {
89
- this.http = http;
90
- this.ttlMs = ttlMs;
91
- }
92
- /** The caller's own sets, uncached. */
93
- async me() {
94
- return this.http.request({
95
- method: 'GET',
96
- path: '/v1/user-scopes/me',
97
- });
98
- }
99
- /**
100
- * The caller's own sets, cached for the TTL.
101
- *
102
- * Returns `null` rather than throwing when master-data cannot be reached.
103
- * That is deliberate and it is the fail-open rule again: a gate that throws
104
- * on a network blip takes every list in the app down, which is a worse
105
- * outcome than briefly not restricting a feature that restricts nothing for
106
- * most tenants anyway. Callers pass the null straight to buildScopeGate.
107
- */
108
- async mineCached(cacheKey) {
109
- const hit = this.cache.get(cacheKey);
110
- if (hit && Date.now() - hit.at < this.ttlMs)
111
- return hit.value;
112
- try {
113
- const response = await this.me();
114
- this.cache.set(cacheKey, { at: Date.now(), value: response.data });
115
- return response.data;
116
- }
117
- catch {
118
- // Serve a stale entry if we have one — it is closer to the truth than
119
- // nothing, and it keeps the revocation window bounded to the outage.
120
- return hit ? hit.value : null;
121
- }
122
- }
123
- /** Drop a cached entry, so an admin who just changed an assignment can see it
124
- * take effect without waiting out the TTL. */
125
- invalidate(cacheKey) {
126
- this.cache.delete(cacheKey);
127
- }
128
- /** Admin surface: every user in the tenant WITH an assignment. Users with
129
- * none are absent — master-data does not know the tenant's roster, so the
130
- * caller joins this against identity's user list. */
131
- async list() {
132
- return this.http.request({
133
- method: 'GET',
134
- path: '/v1/user-scopes',
135
- });
136
- }
137
- async get(userId) {
138
- return this.http.request({
139
- method: 'GET',
140
- path: `/v1/user-scopes/${encodeURIComponent(userId)}`,
141
- });
142
- }
143
- /** Replace a user's sets. Omit a dimension to leave it alone; send `[]` to
144
- * clear it, which returns the user to unrestricted. */
145
- async set(userId, input, idempotencyKey) {
146
- return this.http.request({
147
- method: 'PUT',
148
- path: `/v1/user-scopes/${encodeURIComponent(userId)}`,
149
- body: input,
150
- idempotencyKey,
151
- });
152
- }
153
- }
154
- exports.UserScopeResource = UserScopeResource;
155
- //# sourceMappingURL=scopeGate.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"scopeGate.js","sourceRoot":"","sources":["../src/scopeGate.ts"],"names":[],"mappings":"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"}
@@ -1,80 +0,0 @@
1
- import { HttpTransport } from './http';
2
- import type { StandardAttribute, CreateStandardAttributeInput, CreateStandardAttributeDefaultInput, UpdateStandardAttributeInput, StandardAttributeValue, SetStandardAttributeValueInput, SetStandardAttributeValueResult, StandardAttributeHistoryEntry, StandardAttributeDefault, UpdateStandardAttributeDefaultInput, SingleResponse, ListResponse } from './types';
3
- export declare class StandardAttributeResource {
4
- private readonly http;
5
- constructor(http: HttpTransport);
6
- /** Add an attribute to THIS TENANT. Filled by a person — the enrichment
7
- * pass only knows the platform catalog. */
8
- /** @deprecated Since 5.13.0 the server answers 410 Gone: standard attributes are default definitions, authored in `client.defaultAttributes` and switched per tenant with `attributeDefinitions.setEnabled`. */
9
- create(input: CreateStandardAttributeInput, idempotencyKey?: string): Promise<SingleResponse<StandardAttribute>>;
10
- /** ARCHIVE one. It stops being offered and every value already recorded
11
- * against it stays readable — so this never conflicts with what is stored,
12
- * and it is reversible. */
13
- /** @deprecated Since 5.13.0 the server answers 410 Gone: standard attributes are default definitions, authored in `client.defaultAttributes` and switched per tenant with `attributeDefinitions.setEnabled`. */
14
- archive(id: string): Promise<SingleResponse<StandardAttribute>>;
15
- /** This tenant's attribute catalog, in display order. */
16
- list(options?: {
17
- enabledOnly?: boolean;
18
- }): Promise<ListResponse<StandardAttribute>>;
19
- /** Rename, reorder, enable or disable one attribute. `key` is immutable. */
20
- update(id: string, input: UpdateStandardAttributeInput, idempotencyKey?: string): Promise<SingleResponse<StandardAttribute>>;
21
- /**
22
- * Quote ids whose attribute VALUES match free text.
23
- *
24
- * Prefer `quotes.list({ q })` — it matches these too, and it filters the page,
25
- * so the count and the cursor agree with what you get. Reach for this only
26
- * when you are narrowing an already-fetched list in a browser and need to know
27
- * which rows matched on an attribute rather than on a visible column.
28
- *
29
- * Enabled, active attributes only: one switched off or archived is not offered
30
- * on a quote, so a row returned because it matched one would be returned for a
31
- * reason the user cannot see anywhere on screen.
32
- */
33
- searchQuoteIds(q: string): Promise<SingleResponse<{
34
- ids: string[];
35
- }>>;
36
- /** Every stored value on one quote, in catalog order. */
37
- listValues(quoteId: string): Promise<ListResponse<StandardAttributeValue>>;
38
- /**
39
- * Write values on a quote.
40
- *
41
- * Send the whole set in one call rather than looping: the server writes them
42
- * in a single transaction, and `onlyIfBlank` on each input is what makes the
43
- * enrichment pass fill blanks without ever overwriting a rep's correction.
44
- */
45
- setValues(quoteId: string, values: SetStandardAttributeValueInput[], idempotencyKey?: string): Promise<ListResponse<SetStandardAttributeValueResult>>;
46
- /** Every stored value on one opportunity, in catalog order. */
47
- listOpportunityValues(opportunityId: string): Promise<ListResponse<StandardAttributeValue>>;
48
- /** Write values on an opportunity. Send the whole set in one call. */
49
- setOpportunityValues(opportunityId: string, values: SetStandardAttributeValueInput[], idempotencyKey?: string): Promise<ListResponse<SetStandardAttributeValueResult>>;
50
- /** One attribute's timeline on an opportunity, or the whole one's when no key. */
51
- listOpportunityHistory(opportunityId: string, key?: string): Promise<ListResponse<StandardAttributeHistoryEntry>>;
52
- /** One attribute's timeline on a quote, or the whole quote's when no key. */
53
- listHistory(quoteId: string, key?: string): Promise<ListResponse<StandardAttributeHistoryEntry>>;
54
- /** Add an attribute to the PLATFORM catalog. Seeds NEW tenants only —
55
- * tenants already provisioned keep the copy they have. */
56
- /** @deprecated Since 5.13.0 the server answers 410 Gone: standard attributes are default definitions, authored in `client.defaultAttributes` and switched per tenant with `attributeDefinitions.setEnabled`. */
57
- createDefault(input: CreateStandardAttributeDefaultInput, idempotencyKey?: string): Promise<SingleResponse<StandardAttributeDefault>>;
58
- /** Stop seeding one into new tenants. Existing tenant copies and every value
59
- * ever recorded against the key are untouched. */
60
- /** @deprecated Since 5.13.0 the server answers 410 Gone: standard attributes are default definitions, authored in `client.defaultAttributes` and switched per tenant with `attributeDefinitions.setEnabled`. */
61
- removeDefault(id: string): Promise<SingleResponse<{
62
- id: string;
63
- }>>;
64
- /**
65
- * The PLATFORM-wide defaults, in display order — what a new tenant's copy is
66
- * seeded from. Readable by any tenant; only the platform tenant can write.
67
- */
68
- listDefaults(): Promise<ListResponse<StandardAttributeDefault>>;
69
- /**
70
- * Rename or reorder one platform default.
71
- *
72
- * Changes what the NEXT tenant is seeded with. It does not reach tenants
73
- * already provisioned — their copy is theirs to name.
74
- *
75
- * `key` is immutable — it is what every stored value references.
76
- */
77
- /** @deprecated Since 5.13.0 the server answers 410 Gone: standard attributes are default definitions, authored in `client.defaultAttributes` and switched per tenant with `attributeDefinitions.setEnabled`. */
78
- updateDefault(id: string, input: UpdateStandardAttributeDefaultInput, idempotencyKey?: string): Promise<SingleResponse<StandardAttributeDefault>>;
79
- }
80
- //# sourceMappingURL=standardAttributes.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"standardAttributes.d.ts","sourceRoot":"","sources":["../src/standardAttributes.ts"],"names":[],"mappings":"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"}