@instruments/materialgraph 1.4.0 → 2.1.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.
Files changed (44) hide show
  1. package/catalog-client.d.ts +1 -6
  2. package/catalog-client.js +0 -15
  3. package/catalog-types.d.ts +0 -43
  4. package/client-response-schema.js +0 -1
  5. package/client.d.ts +1 -11
  6. package/client.js +1 -12
  7. package/colors-client.d.ts +1 -1
  8. package/colors-client.js +6 -2
  9. package/find-sampled-together-types.d.ts +0 -2
  10. package/get-variant-types.d.ts +1 -1
  11. package/http-transport.d.ts +0 -16
  12. package/http-transport.js +0 -73
  13. package/identifiers.d.ts +1 -6
  14. package/identifiers.js +1 -1
  15. package/index.d.ts +5 -5
  16. package/index.js +1 -1
  17. package/nearest-paints-types.d.ts +2 -0
  18. package/operation-responses.d.ts +2 -7
  19. package/operations.generated.d.ts +1 -1
  20. package/operations.generated.js +0 -30
  21. package/package.json +2 -6
  22. package/resolve-color-types.d.ts +0 -6
  23. package/response-schemas.generated.d.ts +1 -1
  24. package/response-schemas.generated.js +36 -43
  25. package/response-schemas.js +1 -2
  26. package/scheme-bundle-types.d.ts +20 -0
  27. package/scheme-bundle-types.js +10 -0
  28. package/search-catalog-subset-types.d.ts +1 -1
  29. package/source-material-types.d.ts +59 -0
  30. package/source-material.d.ts +8 -0
  31. package/source-material.js +92 -0
  32. package/webmcp.generated.js +4 -4
  33. package/run-types.d.ts +0 -161
  34. package/run-types.js +0 -6
  35. package/runs.d.ts +0 -36
  36. package/runs.js +0 -198
  37. package/samplize-catalog-types.d.ts +0 -98
  38. package/samplize-colorvec.d.ts +0 -70
  39. package/samplize-colorvec.js +0 -222
  40. package/sse.d.ts +0 -36
  41. package/sse.js +0 -167
  42. package/workflows-client.d.ts +0 -54
  43. package/workflows-client.js +0 -41
  44. /package/{samplize-catalog-types.js → source-material-types.js} +0 -0
package/runs.js DELETED
@@ -1,198 +0,0 @@
1
- /**
2
- * The workflow Run + source-material calls — the agentic half of the SDK. These
3
- * functions take the shared {@link HttpTransport} and speak the hosted Run
4
- * lifecycle: upload source material, start and read a workflow's Runs, and
5
- * stream (or drain) their events. `mg.workflows` and `uploadSourceMaterial`
6
- * are thin wrappers over exactly these calls.
7
- */
8
- import { MaterialGraphError } from "./errors.js";
9
- import { findSdkOperation, operationPath } from "./operations.js";
10
- import { TERMINAL_RUN_EVENT_TYPES } from "./run-types.js";
11
- import { readSseFrames } from "./sse.js";
12
- const hasSourceIdentity = (source) => typeof source === "object" &&
13
- source !== null &&
14
- "sourceId" in source &&
15
- typeof source.sourceId === "string" &&
16
- "role" in source &&
17
- typeof source.role === "string" &&
18
- "filename" in source &&
19
- typeof source.filename === "string" &&
20
- "mediaType" in source &&
21
- typeof source.mediaType === "string";
22
- const hasSourceMeasurements = (source) => typeof source === "object" &&
23
- source !== null &&
24
- "sha256" in source &&
25
- typeof source.sha256 === "string" &&
26
- "bytes" in source &&
27
- typeof source.bytes === "number" &&
28
- "pages" in source &&
29
- (source.pages === null || typeof source.pages === "number");
30
- const isSourceReceipt = (value) => typeof value === "object" &&
31
- value !== null &&
32
- "batchId" in value &&
33
- typeof value.batchId === "string" &&
34
- "sources" in value &&
35
- Array.isArray(value.sources) &&
36
- value.sources.every((source) => typeof source === "object" &&
37
- source !== null &&
38
- hasSourceIdentity(source) &&
39
- hasSourceMeasurements(source));
40
- const isRunEvent = (value) => typeof value === "object" &&
41
- value !== null &&
42
- "sequence" in value &&
43
- typeof value.sequence === "number" &&
44
- "type" in value &&
45
- typeof value.type === "string";
46
- const runOperation = (operationId) => {
47
- const match = findSdkOperation(operationId);
48
- if (!match) {
49
- throw MaterialGraphError.unexpected(`Unknown SDK operation: ${operationId}`);
50
- }
51
- return { ...match, operationId };
52
- };
53
- /** Build a `Blob` from raw bytes, normalizing to an ArrayBuffer-backed view. */
54
- const bytesToBlob = (input) => {
55
- const type = input.contentType !== undefined && input.contentType !== ""
56
- ? { type: input.contentType }
57
- : {};
58
- if (input.data instanceof ArrayBuffer) {
59
- return new Blob([input.data], type);
60
- }
61
- // A Uint8Array may be SharedArrayBuffer-backed; copy into a fresh view so the
62
- // Blob part is unambiguously ArrayBuffer-backed (no cast needed).
63
- const copy = new Uint8Array(input.data.byteLength);
64
- copy.set(input.data);
65
- return new Blob([copy], type);
66
- };
67
- /** Append one file to the multipart body, from either input form. */
68
- const appendFile = (form, field, input) => {
69
- if (input instanceof Blob) {
70
- if (input instanceof File) {
71
- form.append(field, input, input.name);
72
- }
73
- else {
74
- form.append(field, input);
75
- }
76
- return;
77
- }
78
- form.append(field, bytesToBlob(input), input.filename);
79
- };
80
- /**
81
- * Upload one atomic source-material batch (`POST /api/v1/source-material`).
82
- * Each entry of `{ files }` becomes one part of the repeatable multipart
83
- * `files` field.
84
- */
85
- export const uploadSourceMaterial = async (transport, input, signal) => {
86
- const form = new FormData();
87
- for (const file of input.files) {
88
- appendFile(form, "files", file);
89
- }
90
- const url = transport.buildUrl(operationPath(runOperation("upload_source_material")));
91
- // No Content-Type header: fetch sets multipart/form-data with the boundary.
92
- const { parsed } = await transport.send({
93
- body: form,
94
- headers: transport.authHeaders(),
95
- method: "POST",
96
- signal,
97
- url,
98
- });
99
- if (!isSourceReceipt(parsed)) {
100
- throw MaterialGraphError.unexpected("MaterialGraph source-material upload returned an invalid receipt");
101
- }
102
- return parsed;
103
- };
104
- /** Start an asynchronous Run of one workflow (`POST /api/v1/workflows/{workflowId}/runs`). */
105
- export const startWorkflowRun = async (transport, workflowId, body, signal) => await transport.requestJson(runOperation("create_public_run"), {
106
- body,
107
- pathParams: { workflowId },
108
- signal,
109
- });
110
- /** Get a Run's current state (`GET /api/v1/workflows/{workflowId}/runs/{runId}`). */
111
- export const getWorkflowRun = async (transport, workflowId, runId, signal) => await transport.requestJson(runOperation("get_public_run"), {
112
- pathParams: { runId, workflowId },
113
- signal,
114
- });
115
- /** Decode one SSE `data:` frame into a {@link RunEvent}, or `null` when unusable. */
116
- const decodeRunEvent = (data) => {
117
- if (data.length === 0) {
118
- return null;
119
- }
120
- let parsed;
121
- try {
122
- parsed = JSON.parse(data);
123
- }
124
- catch (cause) {
125
- throw MaterialGraphError.unexpected(`MaterialGraph run-event stream sent an unparseable data frame: ${String(cause)}`);
126
- }
127
- return isRunEvent(parsed) ? parsed : null;
128
- };
129
- /**
130
- * Stream ordered Run events as an async iterator (`GET
131
- * /api/v1/workflows/{workflowId}/runs/{runId}/events`, SSE). Iterates until the stream ends or
132
- * `signal` aborts. One connection is bounded server-side (~25s or a terminal
133
- * event); pass `after` to resume from the last observed sequence.
134
- * @yields {RunEvent} Each usable Run event from the stream.
135
- */
136
- export const streamRunEvents = async function* streamRunEvents(transport, workflowId, runId, options = {}) {
137
- const body = await transport.openStream(runOperation("list_public_run_events"), {
138
- pathParams: { runId, workflowId },
139
- ...(options.after === undefined
140
- ? {}
141
- : { query: { after: options.after } }),
142
- ...(options.signal ? { signal: options.signal } : {}),
143
- accept: "text/event-stream",
144
- subject: "run-event stream",
145
- });
146
- for await (const frame of readSseFrames(body, options.signal)) {
147
- const event = decodeRunEvent(frame.data);
148
- if (event) {
149
- yield event;
150
- }
151
- }
152
- };
153
- /**
154
- * Collect Run events until a terminal event, the deadline, or the stream ends —
155
- * the bounded events-log pattern the console uses. A single pass over
156
- * {@link streamRunEvents}; the deadline is enforced by aborting the stream.
157
- *
158
- * The deadline abort is this drain's OWN, expected termination — not a failure.
159
- * Where it lands decides how {@link streamRunEvents} surfaces it: mid-read it
160
- * ends the iterator cleanly, but while the underlying `fetch` is still pending —
161
- * the common case for a poll whose upstream has flushed no bytes yet (no new
162
- * events after the cursor, so no immediate SSE frame) — it rejects with an
163
- * `aborted` {@link MaterialGraphError}. Both mean the same thing here, so the
164
- * self-inflicted abort is swallowed and the collected events returned. Any other
165
- * failure (a real 4xx/5xx or transport error) still propagates, keeping the
166
- * fail-fast surface for genuine upstream problems.
167
- */
168
- export const drainRunEvents = async (transport, workflowId, runId, options) => {
169
- const collected = [];
170
- const controller = new AbortController();
171
- const timer = setTimeout(() => {
172
- controller.abort();
173
- }, options.deadlineMs);
174
- try {
175
- for await (const event of streamRunEvents(transport, workflowId, runId, {
176
- after: options.after,
177
- signal: controller.signal,
178
- })) {
179
- collected.push(event);
180
- if (TERMINAL_RUN_EVENT_TYPES.has(event.type)) {
181
- break;
182
- }
183
- }
184
- }
185
- catch (error) {
186
- const causedByDeadline = controller.signal.aborted &&
187
- error instanceof MaterialGraphError &&
188
- error.kind === "aborted";
189
- if (!causedByDeadline) {
190
- throw error;
191
- }
192
- }
193
- finally {
194
- clearTimeout(timer);
195
- controller.abort();
196
- }
197
- return collected;
198
- };
@@ -1,98 +0,0 @@
1
- export type SamplizeCatalogOptions = {
2
- limit?: number;
3
- /** Continue with unchanged filters and sourceVersion; omit when filters change. */
4
- after?: string;
5
- /** Exact canonical identities, at most 100. Unknown/ineligible IDs are omitted; no substitution. Combined filters intersect. */
6
- variantIds?: string[];
7
- /** Pin every subsequent page to the version returned by the first page. */
8
- sourceVersion?: string;
9
- };
10
- export type SamplizeCatalogProduct = {
11
- variantId: string;
12
- productId: string;
13
- name: string;
14
- code: string;
15
- brand: {
16
- slug: string;
17
- name: string;
18
- };
19
- hex: string;
20
- hexSource: {
21
- publisher: string;
22
- url: string | null;
23
- source: "resolved_variant";
24
- valueSource: string | null;
25
- };
26
- lrv: number | null;
27
- lrvSource: SamplizeLrvObservation | null;
28
- lrvObservations: SamplizeLrvObservation[];
29
- /** The primary sample variant count, never an aggregate of product orders. */
30
- demand: SamplizeDemand | null;
31
- collectionMemberships: SamplizeCollectionMembership[];
32
- colorAnalysis: Record<string, unknown>;
33
- oklab: {
34
- L: number;
35
- a: number;
36
- b: number;
37
- };
38
- sourceProductId: string | null;
39
- url: string | null;
40
- sizeVariants: {
41
- sourceVariantId: string;
42
- sku: string | null;
43
- title: string | null;
44
- price: number | null;
45
- inventoryAvailable: boolean;
46
- demand: SamplizeDemand | null;
47
- }[];
48
- publishedHexes: SamplizeHexObservation[];
49
- hexObservations: SamplizeHexObservation[];
50
- };
51
- export type SamplizeCatalogPage = {
52
- schemaVersion: 2;
53
- searchCatalogSubset: "samplizeCatalog";
54
- sourceVersion: string;
55
- source: Record<string, unknown>;
56
- /** Number of eligible records matching the supplied filters. */
57
- total: number;
58
- unavailableFields: string[];
59
- items: SamplizeCatalogProduct[];
60
- nextCursor: string | null;
61
- /** True on the final page; completeness requires collecting total unique IDs. */
62
- complete: boolean;
63
- };
64
- export type SamplizeHexObservation = {
65
- hex: string;
66
- publisher: string;
67
- source: string;
68
- sourceUrl: string | null;
69
- valueSource: string;
70
- observedAt: string | null;
71
- differsFromSelected: boolean;
72
- };
73
- export type SamplizePublicationSource = {
74
- publisher: "Samplize";
75
- source: "algolia_shopify_products";
76
- sourceUrl: string;
77
- observedAt: string | null;
78
- };
79
- export type SamplizeLrvObservation = {
80
- field: string;
81
- value: number;
82
- } & SamplizePublicationSource;
83
- export type SamplizeDemand = {
84
- field: "recently_ordered_count";
85
- sourceVariantId: string;
86
- defaultAttributeLevel: "shopify_variant";
87
- recentlyOrderedCount: number;
88
- /** The publisher does not supply the time window. */
89
- window: null;
90
- } & SamplizePublicationSource;
91
- export type SamplizeCollectionMembership = {
92
- sourceCollectionId: string;
93
- handle: string;
94
- url: string;
95
- publisher: "Samplize";
96
- source: "algolia_shopify_products";
97
- observedAt: string | null;
98
- };
@@ -1,70 +0,0 @@
1
- export declare const SAMPLIZE_COLORVEC_RANKING_VERSION = "solid-colorvec-cosine-v2";
2
- export declare const SAMPLIZE_COLORVEC_EMBEDDING_VERSION = "oklch-anchor-quantiles-v12";
3
- export type SamplizeColorVecPaint = {
4
- variantId: string;
5
- hex: string;
6
- brand: string;
7
- oklab: {
8
- L: number;
9
- a: number;
10
- b: number;
11
- };
12
- family: string;
13
- lightness: string;
14
- intensity: string;
15
- vector: number[] | string;
16
- };
17
- export type SamplizeColorVecCache = {
18
- sourceVersion: string;
19
- dictionaryVersion: string;
20
- embeddingVersion: string;
21
- rankingVersion: string;
22
- generatedAt: string;
23
- paints: SamplizeColorVecPaint[];
24
- };
25
- export type SamplizeColorVecInput = {
26
- hex?: string;
27
- reference?: string;
28
- palette?: {
29
- hex: string;
30
- proportion: number;
31
- }[];
32
- direction?: "similar" | "lighter" | "darker" | "muted";
33
- brand?: string[];
34
- family?: string[];
35
- lightness?: string[];
36
- intensity?: string[];
37
- lightnessMin?: number;
38
- lightnessMax?: number;
39
- temperatureMin?: number;
40
- temperatureMax?: number;
41
- limit?: number;
42
- };
43
- export declare const samplizeSolidColorVector: (hex: string) => number[];
44
- /** Canonical constraints are shared by full-cache similarity and constrained text retrieval. */
45
- export declare const selectSamplizeEligiblePaints: <T extends Omit<SamplizeColorVecPaint, "vector">>(paints: T[], input: SamplizeColorVecInput) => {
46
- counts: Record<"brand" | "family" | "intensity" | "lightness", Record<string, number>>;
47
- eligible: T[];
48
- reference: T | undefined;
49
- };
50
- /** Official portable MG ranking over its complete versioned sampling-channel projection. */
51
- export declare const createSamplizeColorVecIndex: (cache: SamplizeColorVecCache) => {
52
- search(input: SamplizeColorVecInput): {
53
- countBasis: "complete-catalog";
54
- counts: Record<"brand" | "family" | "intensity" | "lightness", Record<string, number>>;
55
- dictionaryVersion: string;
56
- eligibleCount: number;
57
- embeddingVersion: string;
58
- groups: {
59
- hex: string;
60
- ids: string[];
61
- proportion: number;
62
- quota: number;
63
- shortfall: number;
64
- }[];
65
- ids: string[];
66
- limited: boolean;
67
- rankingVersion: string;
68
- sourceVersion: string;
69
- };
70
- };
@@ -1,222 +0,0 @@
1
- import { EMBEDDING_VERSION, EMBEDDING_VECTOR_SIZE, embeddingCosineSimilarity, paletteToEmbedding, } from "@materialinstruments/colorscope/embedding";
2
- export const SAMPLIZE_COLORVEC_RANKING_VERSION = "solid-colorvec-cosine-v2";
3
- export const SAMPLIZE_COLORVEC_EMBEDDING_VERSION = EMBEDDING_VERSION;
4
- const decodeVector = (encoded) => {
5
- const bytes = Uint8Array.from(atob(encoded), (c) => {
6
- const codePoint = c.codePointAt(0);
7
- if (codePoint === undefined) {
8
- throw new Error("Missing encoded vector byte.");
9
- }
10
- return codePoint;
11
- });
12
- if (bytes.length !== EMBEDDING_VECTOR_SIZE * 4) {
13
- throw new Error("Invalid color vector size.");
14
- }
15
- const view = new DataView(bytes.buffer);
16
- return Array.from({ length: EMBEDDING_VECTOR_SIZE }, (_, i) => view.getFloat32(i * 4, true));
17
- };
18
- const facets = ["brand", "family", "lightness", "intensity"];
19
- const identityOrder = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
20
- export const samplizeSolidColorVector = (hex) => {
21
- if (!/^#[0-9a-f]{6}$/iu.test(hex)) {
22
- throw new Error("A six-digit published color is required.");
23
- }
24
- return paletteToEmbedding([{ hex, proportion: 1 }], { validateOklab: true });
25
- };
26
- const findReferencePaint = (paints, input) => {
27
- const reference = input.reference !== undefined && input.reference !== ""
28
- ? paints.find((p) => p.variantId === input.reference)
29
- : undefined;
30
- if (input.reference !== undefined && input.reference !== "" && !reference) {
31
- throw new Error("The reference is absent from this catalog version.");
32
- }
33
- return reference;
34
- };
35
- /** Canonical constraints are shared by full-cache similarity and constrained text retrieval. */
36
- export const selectSamplizeEligiblePaints = (paints, input) => {
37
- const reference = findReferencePaint(paints, input);
38
- const low = input.lightnessMin ?? 0;
39
- const high = input.lightnessMax ?? 1;
40
- const cool = input.temperatureMin ?? -0.4;
41
- const warm = input.temperatureMax ?? 0.4;
42
- if (![low, high, cool, warm].every(Number.isFinite) ||
43
- low > high ||
44
- cool > warm) {
45
- throw new Error("Appearance ranges must be finite and ordered.");
46
- }
47
- const candidates = paints.filter((p) => {
48
- if (!reference) {
49
- return true;
50
- }
51
- if (p.variantId === reference.variantId) {
52
- return false;
53
- }
54
- if (input.direction === "lighter") {
55
- return p.oklab.L > reference.oklab.L;
56
- }
57
- if (input.direction === "darker") {
58
- return p.oklab.L < reference.oklab.L;
59
- }
60
- if (input.direction === "muted") {
61
- return (Math.hypot(p.oklab.a, p.oklab.b) <
62
- Math.hypot(reference.oklab.a, reference.oklab.b));
63
- }
64
- return true;
65
- });
66
- const matches = (p, omit) => p.oklab.L >= low &&
67
- p.oklab.L <= high &&
68
- p.oklab.b >= cool &&
69
- p.oklab.b <= warm &&
70
- facets.every((f) => f === omit ||
71
- input[f] === undefined ||
72
- input[f].length === 0 ||
73
- input[f].includes(p[f]));
74
- const counts = {
75
- brand: {},
76
- family: {},
77
- intensity: {},
78
- lightness: {},
79
- };
80
- for (const p of candidates) {
81
- for (const f of facets) {
82
- if (matches(p, f)) {
83
- counts[f][p[f]] = (counts[f][p[f]] ?? 0) + 1;
84
- }
85
- }
86
- }
87
- const eligible = candidates.filter((p) => matches(p));
88
- return { counts, eligible, reference };
89
- };
90
- const searchPalette = (input, reference) => {
91
- const palette = (input.palette !== undefined && input.palette.length > 0
92
- ? input.palette
93
- : [{ hex: input.hex ?? reference?.hex ?? "", proportion: 1 }])
94
- .map((p) => ({ ...p, hex: p.hex.toLowerCase() }))
95
- .toSorted((a, b) => b.proportion - a.proportion || identityOrder(a.hex, b.hex));
96
- if (palette.length > 12 ||
97
- palette.some((p) => !Number.isFinite(p.proportion) || p.proportion <= 0 || p.proportion > 1)) {
98
- throw new Error("Supply up to twelve positive image color shares.");
99
- }
100
- return palette;
101
- };
102
- const searchSamplizePaints = (cache, paints, input, vectorFor) => {
103
- const limit = input.limit ?? 48;
104
- if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
105
- throw new Error("Choose between 1 and 100 suggestions.");
106
- }
107
- const { reference, eligible, counts } = selectSamplizeEligiblePaints(paints, input);
108
- const palette = searchPalette(input, reference);
109
- const total = palette.reduce((sum, p) => sum + p.proportion, 0);
110
- const buckets = palette.map((p, position) => {
111
- const bucketPaints = [];
112
- return {
113
- ...p,
114
- paints: bucketPaints,
115
- position,
116
- quota: Math.floor((limit * p.proportion) / total),
117
- remainder: ((limit * p.proportion) / total) % 1,
118
- vector: vectorFor(p.hex),
119
- };
120
- });
121
- let remaining = limit - buckets.reduce((sum, p) => sum + p.quota, 0);
122
- for (const p of [...buckets].toSorted((a, b) => b.remainder - a.remainder || a.position - b.position)) {
123
- if (remaining-- > 0) {
124
- p.quota++;
125
- }
126
- }
127
- for (const paint of eligible) {
128
- let best = buckets[0];
129
- if (best === undefined) {
130
- throw new Error("Missing first palette bucket.");
131
- }
132
- let score = embeddingCosineSimilarity(paint.vector, best.vector);
133
- for (let i = 1; i < buckets.length; i++) {
134
- const candidate = buckets[i];
135
- if (candidate === undefined) {
136
- throw new Error("Missing palette bucket.");
137
- }
138
- const next = embeddingCosineSimilarity(paint.vector, candidate.vector);
139
- if (next > score) {
140
- best = candidate;
141
- score = next;
142
- }
143
- }
144
- best.paints.push({ id: paint.variantId, score });
145
- }
146
- const groups = buckets
147
- .toSorted((a, b) => b.proportion - a.proportion || a.position - b.position)
148
- .map((p) => {
149
- const ids = p.paints
150
- .toSorted((a, b) => b.score - a.score || identityOrder(a.id, b.id))
151
- .slice(0, p.quota)
152
- .map((paint) => paint.id);
153
- return {
154
- hex: p.hex,
155
- ids,
156
- proportion: p.proportion,
157
- quota: p.quota,
158
- shortfall: p.quota - ids.length,
159
- };
160
- });
161
- const ids = groups.flatMap((p) => p.ids);
162
- return {
163
- countBasis: "complete-catalog",
164
- counts,
165
- dictionaryVersion: cache.dictionaryVersion,
166
- eligibleCount: eligible.length,
167
- embeddingVersion: cache.embeddingVersion,
168
- groups,
169
- ids,
170
- limited: eligible.length > ids.length,
171
- rankingVersion: cache.rankingVersion,
172
- sourceVersion: cache.sourceVersion,
173
- };
174
- };
175
- /** Official portable MG ranking over its complete versioned sampling-channel projection. */
176
- export const createSamplizeColorVecIndex = (cache) => {
177
- if (cache.embeddingVersion !== EMBEDDING_VERSION ||
178
- cache.rankingVersion !== SAMPLIZE_COLORVEC_RANKING_VERSION) {
179
- throw new Error("The color cache uses an incompatible embedding or ranking version. Refresh it before searching.");
180
- }
181
- if (!cache.sourceVersion ||
182
- !cache.dictionaryVersion ||
183
- !Number.isFinite(Date.parse(cache.generatedAt))) {
184
- throw new Error("The color cache is missing its provenance.");
185
- }
186
- const paints = cache.paints.map((p) => ({
187
- ...p,
188
- vector: typeof p.vector === "string" ? decodeVector(p.vector) : p.vector,
189
- }));
190
- const identities = new Set();
191
- for (const paint of paints) {
192
- if (identities.has(paint.variantId) ||
193
- paint.vector.length !== EMBEDDING_VECTOR_SIZE ||
194
- !paint.vector.every(Number.isFinite) ||
195
- ![paint.oklab.L, paint.oklab.a, paint.oklab.b].every(Number.isFinite)) {
196
- throw new Error("The color cache contains invalid or duplicate paint records.");
197
- }
198
- identities.add(paint.variantId);
199
- }
200
- const vectors = new Map();
201
- const vectorFor = (hex) => {
202
- const key = hex.toLowerCase();
203
- let value = vectors.get(key);
204
- if (!value) {
205
- value = samplizeSolidColorVector(hex);
206
- if (vectors.size >= 512) {
207
- const oldest = vectors.keys().next().value;
208
- if (oldest === undefined) {
209
- throw new Error("Missing oldest cached color vector.");
210
- }
211
- vectors.delete(oldest);
212
- }
213
- vectors.set(key, value);
214
- }
215
- return value;
216
- };
217
- return {
218
- search(input) {
219
- return searchSamplizePaints(cache, paints, input, vectorFor);
220
- },
221
- };
222
- };
package/sse.d.ts DELETED
@@ -1,36 +0,0 @@
1
- /**
2
- * A minimal, dependency-free Server-Sent Events reader — the primitive behind
3
- * {@link WorkflowsClient.streamRunEvents}. Hand-rolled (no `eventsource`,
4
- * no `undici`) so the SDK stays zero-runtime-dep and works unchanged inside
5
- * Node route handlers, where `fetch` yields a byte `ReadableStream`.
6
- *
7
- * The parser follows the WHATWG event-stream tokenizer closely enough for the
8
- * `/api/v1/workflows/{workflowId}/runs/{runId}/events` contract: `\n`, `\r\n`, and `\r` line endings,
9
- * multi-line `data:` accumulation, comment (`:`-prefixed) lines, and a single
10
- * optional leading space after the field colon. Frames split across network
11
- * chunks are held in an internal buffer until their terminating blank line.
12
- */
13
- /** One dispatched SSE frame. `data` is the joined data lines (no trailing newline). */
14
- export type SseFrame = {
15
- id?: string;
16
- event?: string;
17
- data: string;
18
- };
19
- /**
20
- * A push-based SSE line assembler. Feed it decoded text with {@link push}; it
21
- * returns any frames completed by that text. Call {@link flush} once the stream
22
- * ends to dispatch a final frame that had data but no terminating blank line.
23
- */
24
- export declare class SseDecoder {
25
- #private;
26
- push(text: string): SseFrame[];
27
- /** Dispatch a trailing frame that had fields but no closing blank line. */
28
- flush(): SseFrame[];
29
- }
30
- /**
31
- * Iterate the SSE frames of a byte stream. Stops cleanly when the stream ends
32
- * or `signal` aborts (mid-read abort rejections are swallowed once the signal
33
- * is set). Frames with no `data` field (comments, bare `id:`) are not yielded.
34
- * @yields {SseFrame} Each completed frame containing fields.
35
- */
36
- export declare const readSseFrames: (body: ReadableStream<Uint8Array>, signal?: AbortSignal) => AsyncGenerator<SseFrame, void, unknown>;