@cat-factory/integrations 0.148.0 → 0.150.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/modules/documents/DocumentImportService.d.ts +48 -1
- package/dist/modules/documents/DocumentImportService.d.ts.map +1 -1
- package/dist/modules/documents/DocumentImportService.js +154 -1
- package/dist/modules/documents/DocumentImportService.js.map +1 -1
- package/dist/modules/documents/FigmaProvider.d.ts +29 -1
- package/dist/modules/documents/FigmaProvider.d.ts.map +1 -1
- package/dist/modules/documents/FigmaProvider.js +143 -3
- package/dist/modules/documents/FigmaProvider.js.map +1 -1
- package/dist/modules/documents/figma.logic.d.ts +43 -1
- package/dist/modules/documents/figma.logic.d.ts.map +1 -1
- package/dist/modules/documents/figma.logic.js +70 -0
- package/dist/modules/documents/figma.logic.js.map +1 -1
- package/dist/modules/documents/http.d.ts +12 -3
- package/dist/modules/documents/http.d.ts.map +1 -1
- package/dist/modules/documents/http.js +17 -4
- package/dist/modules/documents/http.js.map +1 -1
- package/dist/modules/kubernetes/KubernetesRunnerTransport.d.ts +13 -0
- package/dist/modules/kubernetes/KubernetesRunnerTransport.d.ts.map +1 -1
- package/dist/modules/kubernetes/KubernetesRunnerTransport.js +56 -3
- package/dist/modules/kubernetes/KubernetesRunnerTransport.js.map +1 -1
- package/dist/modules/kubernetes/kubernetes.logic.d.ts +17 -0
- package/dist/modules/kubernetes/kubernetes.logic.d.ts.map +1 -1
- package/dist/modules/kubernetes/kubernetes.logic.js +74 -0
- package/dist/modules/kubernetes/kubernetes.logic.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { Clock, IdGenerator } from '@cat-factory/kernel';
|
|
1
|
+
import type { Clock, IdGenerator, Logger } from '@cat-factory/kernel';
|
|
2
2
|
import type { DocumentSourceRegistry } from '@cat-factory/kernel';
|
|
3
3
|
import type { DocumentRecord, DocumentRepository } from '@cat-factory/kernel';
|
|
4
4
|
import type { GroupCacheHandle, LinkedDocumentRefreshOutcome } from '@cat-factory/kernel';
|
|
5
|
+
import type { ResolveBinaryArtifactStore } from '@cat-factory/kernel';
|
|
5
6
|
import type { SourceDocument, DocumentSearchResult, DocumentSourceKind } from '@cat-factory/kernel';
|
|
6
7
|
import { type ResolvedDocumentRef } from '@cat-factory/contracts';
|
|
7
8
|
import type { WorkspaceRepository } from '@cat-factory/kernel';
|
|
@@ -21,6 +22,17 @@ export interface DocumentImportServiceDependencies {
|
|
|
21
22
|
* is the fresh one and an invalidation would race the store it precedes.
|
|
22
23
|
*/
|
|
23
24
|
versionCache?: GroupCacheHandle<LinkedDocumentRefreshOutcome>;
|
|
25
|
+
/**
|
|
26
|
+
* Where a design source's rendered frames are retained, resolved per workspace because the blob
|
|
27
|
+
* backend is an ACCOUNT choice rather than a boot-time one.
|
|
28
|
+
*
|
|
29
|
+
* Optional, and its absence is a first-class answer rather than a missing wire: a deployment that
|
|
30
|
+
* has configured no image storage imports the design textually and the row says
|
|
31
|
+
* `storage_unavailable`, which is a different fact from a source that offered no image.
|
|
32
|
+
*/
|
|
33
|
+
resolveBinaryArtifactStore?: ResolveBinaryArtifactStore;
|
|
34
|
+
/** Where the best-effort render pass reports what it could not retain. */
|
|
35
|
+
logger?: Logger;
|
|
24
36
|
}
|
|
25
37
|
/** A document body supplied directly by a caller rather than fetched from a connected source. */
|
|
26
38
|
export interface UploadedDocument {
|
|
@@ -47,6 +59,7 @@ export declare function assertUploadReadable(input: UploadedDocument): string;
|
|
|
47
59
|
export declare function toSourceDocument(record: DocumentRecord): SourceDocument;
|
|
48
60
|
export declare class DocumentImportService {
|
|
49
61
|
private readonly deps;
|
|
62
|
+
private readonly log;
|
|
50
63
|
constructor(deps: DocumentImportServiceDependencies);
|
|
51
64
|
private requireProvider;
|
|
52
65
|
/**
|
|
@@ -113,6 +126,40 @@ export declare class DocumentImportService {
|
|
|
113
126
|
reimport(workspaceId: string, source: DocumentSourceKind, externalId: string, opts?: {
|
|
114
127
|
fallbackVersion?: string;
|
|
115
128
|
}): Promise<DocumentRecord>;
|
|
129
|
+
/**
|
|
130
|
+
* Re-retain the document's rendered images, returning what became of them.
|
|
131
|
+
*
|
|
132
|
+
* The order is prune-then-fetch, deliberately: a document's pictures are never a mix of two
|
|
133
|
+
* revisions, and the cost is a window where a failed download leaves it with none, which the
|
|
134
|
+
* returned status states. The reverse ordering is the trap, because it fails INVISIBLY — the
|
|
135
|
+
* body of the new revision lands beside last month's frames, and a row saying `failed` beside a
|
|
136
|
+
* full set of pictures reads to everyone as a transient blip rather than as a document
|
|
137
|
+
* illustrating something that no longer exists.
|
|
138
|
+
*
|
|
139
|
+
* Everything here is BEST-EFFORT — an image is an enrichment, and an import that already holds
|
|
140
|
+
* the body must not fail because a CDN did.
|
|
141
|
+
*
|
|
142
|
+
* The status is derived from what was RETAINED rather than from what was downloaded, so a store
|
|
143
|
+
* that rejects half the bytes reads as `partial` exactly like a source that rendered half the
|
|
144
|
+
* frames. What a caller does about either is the same, and claiming `stored` over an artifact
|
|
145
|
+
* that never landed is the one answer that would send them looking in the wrong place.
|
|
146
|
+
*/
|
|
147
|
+
private refreshRenders;
|
|
148
|
+
/**
|
|
149
|
+
* The workspace's artifact store, or WHY there isn't one.
|
|
150
|
+
*
|
|
151
|
+
* Three outcomes rather than a nullable store, because the two empty ones are opposite facts
|
|
152
|
+
* that the same `null` would have stated identically. `none` is a settled deployment answer
|
|
153
|
+
* ("this account retains no images"), which the row records as `storage_unavailable` and a
|
|
154
|
+
* person fixes by configuring storage. `unreadable` is an OUTAGE in the account-settings read,
|
|
155
|
+
* and telling an operator their storage is unconfigured when it is configured sends them to
|
|
156
|
+
* change a setting that is already right; worse, the status is then carried forward untouched
|
|
157
|
+
* by every token-only re-import, so the misattribution outlives the outage that caused it.
|
|
158
|
+
*
|
|
159
|
+
* The failure is still swallowed rather than propagated: the caller is a best-effort enrichment
|
|
160
|
+
* on a path that already holds the document body.
|
|
161
|
+
*/
|
|
162
|
+
private resolveArtifactStore;
|
|
116
163
|
/**
|
|
117
164
|
* Persist a document body the caller supplied directly, as an `upload`-origin projection.
|
|
118
165
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocumentImportService.d.ts","sourceRoot":"","sources":["../../../src/modules/documents/DocumentImportService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"DocumentImportService.d.ts","sourceRoot":"","sources":["../../../src/modules/documents/DocumentImportService.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { contentHash, redactSecrets, ValidationError } from '@cat-factory/kernel';
|
|
1
|
+
import { contentHash, describeError, noopLogger, redactSecrets, ValidationError, } from '@cat-factory/kernel';
|
|
2
2
|
import { orderSourcesByClaimConfidence, } from '@cat-factory/contracts';
|
|
3
3
|
import { requireWorkspace } from '@cat-factory/kernel';
|
|
4
4
|
import { buildExcerpt, probeCacheKey, sameAgentVisibleProjection } from './documents.logic.js';
|
|
@@ -36,12 +36,15 @@ export function toSourceDocument(record) {
|
|
|
36
36
|
role: record.role,
|
|
37
37
|
docKind: record.docKind,
|
|
38
38
|
syncedAt: record.syncedAt,
|
|
39
|
+
renderStatus: record.renderStatus,
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
42
|
export class DocumentImportService {
|
|
42
43
|
deps;
|
|
44
|
+
log;
|
|
43
45
|
constructor(deps) {
|
|
44
46
|
this.deps = deps;
|
|
47
|
+
this.log = deps.logger ?? noopLogger;
|
|
45
48
|
}
|
|
46
49
|
requireProvider(source) {
|
|
47
50
|
const provider = this.deps.registry.get(source);
|
|
@@ -166,6 +169,17 @@ export class DocumentImportService {
|
|
|
166
169
|
sameAgentVisibleProjection(existing, { ...content, contentHash: hash });
|
|
167
170
|
if (existing && unchangedForReaders && existing.sourceVersion === version)
|
|
168
171
|
return existing;
|
|
172
|
+
// Renders belong to the BODY, so they are refreshed on exactly the writes that change one. A
|
|
173
|
+
// token-only write (below) carries the previous status forward untouched: a design file's
|
|
174
|
+
// version moves on any edit anywhere in it, and re-rasterising six frames because someone
|
|
175
|
+
// renamed a layer on another page is the cost this branch exists to avoid.
|
|
176
|
+
const renderStatus = unchangedForReaders
|
|
177
|
+
? (existing?.renderStatus ?? null)
|
|
178
|
+
: // The credentials this import already opened, rather than a second `requireConnection`:
|
|
179
|
+
// the render pass runs against the very revision `fetchDocument` just read, so re-opening
|
|
180
|
+
// the sealed bag buys nothing and adds a decrypt plus a window in which the connection can
|
|
181
|
+
// vanish between the two reads.
|
|
182
|
+
await this.refreshRenders(workspaceId, provider, source, connection.credentials, content);
|
|
169
183
|
// The write that remains, when `unchangedForReaders` still holds, exists ONLY to record the
|
|
170
184
|
// moved token: a page whose version bumped without changing a byte (a Figma file version moves
|
|
171
185
|
// on any edit in the file, including frames this document does not cover) would otherwise keep
|
|
@@ -188,6 +202,7 @@ export class DocumentImportService {
|
|
|
188
202
|
// any existing tag across a re-import (the repo's upsert also leaves these columns alone).
|
|
189
203
|
role: existing?.role ?? null,
|
|
190
204
|
docKind: existing?.docKind ?? null,
|
|
205
|
+
renderStatus,
|
|
191
206
|
// `syncedAt` is "when the body was last written", which is what every reader renders it as,
|
|
192
207
|
// so a token-only write must NOT move it. Moving it would put a fresh timestamp on bytes
|
|
193
208
|
// nobody changed, and the person reading it would conclude their edit had landed.
|
|
@@ -197,6 +212,141 @@ export class DocumentImportService {
|
|
|
197
212
|
await this.deps.documentRepository.upsert(record);
|
|
198
213
|
return record;
|
|
199
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Re-retain the document's rendered images, returning what became of them.
|
|
217
|
+
*
|
|
218
|
+
* The order is prune-then-fetch, deliberately: a document's pictures are never a mix of two
|
|
219
|
+
* revisions, and the cost is a window where a failed download leaves it with none, which the
|
|
220
|
+
* returned status states. The reverse ordering is the trap, because it fails INVISIBLY — the
|
|
221
|
+
* body of the new revision lands beside last month's frames, and a row saying `failed` beside a
|
|
222
|
+
* full set of pictures reads to everyone as a transient blip rather than as a document
|
|
223
|
+
* illustrating something that no longer exists.
|
|
224
|
+
*
|
|
225
|
+
* Everything here is BEST-EFFORT — an image is an enrichment, and an import that already holds
|
|
226
|
+
* the body must not fail because a CDN did.
|
|
227
|
+
*
|
|
228
|
+
* The status is derived from what was RETAINED rather than from what was downloaded, so a store
|
|
229
|
+
* that rejects half the bytes reads as `partial` exactly like a source that rendered half the
|
|
230
|
+
* frames. What a caller does about either is the same, and claiming `stored` over an artifact
|
|
231
|
+
* that never landed is the one answer that would send them looking in the wrong place.
|
|
232
|
+
*/
|
|
233
|
+
async refreshRenders(workspaceId, provider, source, credentials, content) {
|
|
234
|
+
// A source with nothing to rasterise: not a failure and not an empty set, but a question that
|
|
235
|
+
// does not apply. NULL is the only value that says so.
|
|
236
|
+
if (!provider.fetchRenders)
|
|
237
|
+
return null;
|
|
238
|
+
const externalId = content.externalId;
|
|
239
|
+
const storage = await this.resolveArtifactStore(workspaceId);
|
|
240
|
+
// Asked BEFORE the download, so an unconfigured deployment spends no bandwidth on bytes it
|
|
241
|
+
// could not keep. An account that retains no images and a settings read that FAILED are
|
|
242
|
+
// deliberately different answers: the first is a deployment fact a person fixes by configuring
|
|
243
|
+
// storage, the second is an outage this import should be retried past.
|
|
244
|
+
if (storage.kind === 'unreadable')
|
|
245
|
+
return 'failed';
|
|
246
|
+
if (storage.kind === 'none')
|
|
247
|
+
return 'storage_unavailable';
|
|
248
|
+
const store = storage.store;
|
|
249
|
+
const document = { source, externalId };
|
|
250
|
+
try {
|
|
251
|
+
// The FIRST thing this does, so every later failure — a rate-limited source, a CDN outage,
|
|
252
|
+
// a store that will not take the bytes — can only ever leave the document with no pictures,
|
|
253
|
+
// never with the previous revision's beside the new body. A prune that itself throws lands
|
|
254
|
+
// in the same `catch` and records `failed`, which is honest for the one case where the old
|
|
255
|
+
// set may genuinely survive: nobody can then claim the set is current.
|
|
256
|
+
await store.pruneByDocument(workspaceId, document);
|
|
257
|
+
const result = await provider.fetchRenders(credentials, externalId, workspaceId, content.renderPlan);
|
|
258
|
+
let stored = 0;
|
|
259
|
+
// Frames the provider's own cap excluded were never going to be retained, so they count
|
|
260
|
+
// against completeness exactly like a failed download: six pictures of a twenty-frame design
|
|
261
|
+
// is a partly illustrated document, not a fully illustrated six-frame one.
|
|
262
|
+
let dropped = result.failed + result.capped;
|
|
263
|
+
for (const render of result.renders) {
|
|
264
|
+
try {
|
|
265
|
+
await store.store({
|
|
266
|
+
meta: {
|
|
267
|
+
workspaceId,
|
|
268
|
+
// No run and no block: an import happens before either exists, and the document's
|
|
269
|
+
// own identity is what a later reader joins on.
|
|
270
|
+
executionId: null,
|
|
271
|
+
blockId: null,
|
|
272
|
+
kind: 'reference',
|
|
273
|
+
view: render.view,
|
|
274
|
+
contentType: render.contentType,
|
|
275
|
+
document,
|
|
276
|
+
},
|
|
277
|
+
blob: render.bytes,
|
|
278
|
+
});
|
|
279
|
+
stored += 1;
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
// A bespoke catch rather than `runBestEffort`: the failure is a VALUE here (it decides
|
|
283
|
+
// between `stored` and `partial`), and the loop must go on to the next frame.
|
|
284
|
+
dropped += 1;
|
|
285
|
+
this.log.warn('document render could not be retained', {
|
|
286
|
+
workspaceId,
|
|
287
|
+
source,
|
|
288
|
+
externalId,
|
|
289
|
+
view: render.view,
|
|
290
|
+
...describeError(error),
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (result.causes.length || result.capped > 0) {
|
|
295
|
+
this.log.warn('document renders were not fully retrieved', {
|
|
296
|
+
workspaceId,
|
|
297
|
+
source,
|
|
298
|
+
externalId,
|
|
299
|
+
retrieved: result.renders.length,
|
|
300
|
+
failed: result.failed,
|
|
301
|
+
// Named apart from `failed` on the line too: a retry fixes one and never the other.
|
|
302
|
+
capped: result.capped,
|
|
303
|
+
causes: result.causes.join(', '),
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
if (stored > 0)
|
|
307
|
+
return dropped > 0 ? 'partial' : 'stored';
|
|
308
|
+
return dropped > 0 ? 'failed' : 'none';
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
this.log.warn('document render pass failed', {
|
|
312
|
+
workspaceId,
|
|
313
|
+
source,
|
|
314
|
+
externalId,
|
|
315
|
+
...describeError(error),
|
|
316
|
+
});
|
|
317
|
+
return 'failed';
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* The workspace's artifact store, or WHY there isn't one.
|
|
322
|
+
*
|
|
323
|
+
* Three outcomes rather than a nullable store, because the two empty ones are opposite facts
|
|
324
|
+
* that the same `null` would have stated identically. `none` is a settled deployment answer
|
|
325
|
+
* ("this account retains no images"), which the row records as `storage_unavailable` and a
|
|
326
|
+
* person fixes by configuring storage. `unreadable` is an OUTAGE in the account-settings read,
|
|
327
|
+
* and telling an operator their storage is unconfigured when it is configured sends them to
|
|
328
|
+
* change a setting that is already right; worse, the status is then carried forward untouched
|
|
329
|
+
* by every token-only re-import, so the misattribution outlives the outage that caused it.
|
|
330
|
+
*
|
|
331
|
+
* The failure is still swallowed rather than propagated: the caller is a best-effort enrichment
|
|
332
|
+
* on a path that already holds the document body.
|
|
333
|
+
*/
|
|
334
|
+
async resolveArtifactStore(workspaceId) {
|
|
335
|
+
const resolve = this.deps.resolveBinaryArtifactStore;
|
|
336
|
+
if (!resolve)
|
|
337
|
+
return { kind: 'none' };
|
|
338
|
+
try {
|
|
339
|
+
const store = await resolve(workspaceId);
|
|
340
|
+
return store ? { kind: 'store', store } : { kind: 'none' };
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
this.log.warn('could not resolve artifact storage for document renders', {
|
|
344
|
+
workspaceId,
|
|
345
|
+
...describeError(error),
|
|
346
|
+
});
|
|
347
|
+
return { kind: 'unreadable' };
|
|
348
|
+
}
|
|
349
|
+
}
|
|
200
350
|
/**
|
|
201
351
|
* Persist a document body the caller supplied directly, as an `upload`-origin projection.
|
|
202
352
|
*
|
|
@@ -236,6 +386,9 @@ export class DocumentImportService {
|
|
|
236
386
|
linkedBlockId: null,
|
|
237
387
|
role: null,
|
|
238
388
|
docKind: null,
|
|
389
|
+
// Markdown handed to the platform directly: there is no source to rasterise, which is the
|
|
390
|
+
// same "does not apply" the null states one field over.
|
|
391
|
+
renderStatus: null,
|
|
239
392
|
syncedAt: this.deps.clock.now(),
|
|
240
393
|
deletedAt: null,
|
|
241
394
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocumentImportService.js","sourceRoot":"","sources":["../../../src/modules/documents/DocumentImportService.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DocumentImportService.js","sourceRoot":"","sources":["../../../src/modules/documents/DocumentImportService.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type DocumentContent, type DocumentCredentials, type DocumentSourceProvider, type NormalizedConnection } from '@cat-factory/kernel';
|
|
1
|
+
import { type DocumentContent, type DocumentCredentials, type DocumentRenderPlan, type DocumentRenderResult, type DocumentSourceProvider, type NormalizedConnection } from '@cat-factory/kernel';
|
|
2
2
|
/** Carries the HTTP status so callers can surface a meaningful error. */
|
|
3
3
|
export declare class FigmaApiError extends Error {
|
|
4
4
|
readonly status: number;
|
|
@@ -35,6 +35,34 @@ export declare class FigmaProvider implements DocumentSourceProvider {
|
|
|
35
35
|
*/
|
|
36
36
|
droppedScope(input: string, externalId: string): string | null;
|
|
37
37
|
fetchDocument(credentials: DocumentCredentials, externalId: string, _workspaceId: string | null): Promise<DocumentContent>;
|
|
38
|
+
/**
|
|
39
|
+
* Rasterise the referenced frames and download the PNGs.
|
|
40
|
+
*
|
|
41
|
+
* Three hops, and the split matters: a STRUCTURAL read learns which frames to render and what
|
|
42
|
+
* they are called (the `view` a captured screenshot pairs with), the `/v1/images` endpoint turns
|
|
43
|
+
* those ids into short-lived signed URLs, and the URLs are downloaded from the asset host with no
|
|
44
|
+
* credential attached. A frame whose own download fails is counted rather than thrown, so one
|
|
45
|
+
* oversize artboard costs its own picture and not the other five.
|
|
46
|
+
*
|
|
47
|
+
* The first hop is SKIPPED when the caller hands over the plan its own `fetchDocument` produced,
|
|
48
|
+
* which is the normal path: an import fetches the body and the renders back to back off one
|
|
49
|
+
* revision, and the structural read is the same request twice against a rate-limited API.
|
|
50
|
+
*/
|
|
51
|
+
fetchRenders(credentials: DocumentCredentials, externalId: string, _workspaceId: string | null, plan?: DocumentRenderPlan): Promise<DocumentRenderResult>;
|
|
52
|
+
/**
|
|
53
|
+
* The frames to rasterise, with their names, for a caller that supplied no plan.
|
|
54
|
+
*
|
|
55
|
+
* A node link renders the frame it names; a whole-file link renders the first
|
|
56
|
+
* {@link figmaLogic.MAX_RENDERS} top-level frames in document order — the same frames, in the
|
|
57
|
+
* same order, the text import covers, so the pictures and the prose describe the same screens.
|
|
58
|
+
* This is the FALLBACK: it repeats the structural read `fetchDocument` performs, which is why
|
|
59
|
+
* the import hands its own plan over instead.
|
|
60
|
+
*/
|
|
61
|
+
private resolveRenderTargets;
|
|
62
|
+
/** `/v1/images` in ONE call for every target: id → signed URL, absent when Figma rendered none. */
|
|
63
|
+
private fetchRenderUrls;
|
|
64
|
+
/** Download one signed render. No credential is sent: the URL already carries its own. */
|
|
65
|
+
private downloadRender;
|
|
38
66
|
/**
|
|
39
67
|
* The cheap version probe: read the file's metadata at `depth=1` (no node tree)
|
|
40
68
|
* for its `version` / `lastModified`, skipping the deep node fetch + variables +
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FigmaProvider.d.ts","sourceRoot":"","sources":["../../../src/modules/documents/FigmaProvider.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"FigmaProvider.d.ts","sourceRoot":"","sources":["../../../src/modules/documents/FigmaProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAA;AA2D5B,yEAAyE;AACzE,qBAAa,aAAc,SAAQ,KAAK;IAEpC,QAAQ,CAAC,MAAM,EAAE,MAAM;IADzB,YACW,MAAM,EAAE,MAAM,EACvB,OAAO,EAAE,MAAM,EAIhB;CACF;AA2HD,qBAAa,aAAc,YAAW,sBAAsB;IAC1D,QAAQ,CAAC,IAAI,EAAG,OAAO,CAAS;IAChC,QAAQ,CAAC,UAAU;;;;;;;;;;;;;;;;;MAAmB;IACtC,QAAQ,CAAC,KAAK,wDAAc;IAE5B,mBAAmB,CAAC,KAAK,EAAE,mBAAmB,GAAG,oBAAoB,CAMpE;IAED,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAErC;IAED,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEvC;IAED;;;;OAIG;IACH,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE7D;IAEK,aAAa,CACjB,WAAW,EAAE,mBAAmB,EAChC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,GAAG,IAAI,GAC1B,OAAO,CAAC,eAAe,CAAC,CA2C1B;IAED;;;;;;;;;;;;OAYG;IACG,YAAY,CAChB,WAAW,EAAE,mBAAmB,EAChC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,IAAI,CAAC,EAAE,kBAAkB,GACxB,OAAO,CAAC,oBAAoB,CAAC,CAkD/B;IAED;;;;;;;;OAQG;YACW,oBAAoB;IAsBlC,mGAAmG;YACrF,eAAe;IAkB7B,0FAA0F;YAC5E,cAAc;IAW5B;;;;OAIG;IACG,YAAY,CAChB,WAAW,EAAE,mBAAmB,EAChC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,GAAG,IAAI,GAC1B,OAAO,CAAC,MAAM,CAAC,CAUjB;IAED,gGAAgG;YAClF,UAAU;IASxB;;;;;OAKG;YACW,gBAAgB;IA4B9B;;;;;;;;OAQG;YACW,eAAe;IAoE7B;;;;OAIG;YACW,kBAAkB;IAuChC;;;;;OAKG;YACW,cAAc;IAoB5B,gGAAgG;YAClF,eAAe;IAqB7B;;;;;OAKG;IACH,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,KAAK;YAQC,GAAG;CAqBlB"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describeError, documentOAuthAccessToken, ValidationError, } from '@cat-factory/kernel';
|
|
2
2
|
import { renderDesignContext } from './design.logic.js';
|
|
3
|
-
import { FIGMA_API_HOST, FIGMA_DESCRIPTOR, FIGMA_OAUTH } from './figma.logic.js';
|
|
3
|
+
import { FIGMA_API_HOST, FIGMA_DESCRIPTOR, FIGMA_OAUTH, FIGMA_RENDER_HOSTS } from './figma.logic.js';
|
|
4
4
|
import * as figmaLogic from './figma.logic.js';
|
|
5
|
-
import { DocumentHttpError, createHostPinnedFetch, readCappedText } from './http.js';
|
|
5
|
+
import { DocumentHttpError, createHostPinnedFetch, readCappedBytes, readCappedText, } from './http.js';
|
|
6
6
|
// FigmaProvider: the document-source provider for Figma. It authenticates with
|
|
7
7
|
// whichever credential the workspace connected — a personal access token (the
|
|
8
8
|
// `X-Figma-Token` header) or an OAuth grant the platform holds (a `Bearer` token) —
|
|
@@ -37,6 +37,18 @@ const FRAME_DEPTH = figmaLogic.MAX_TREE_DEPTH + 3;
|
|
|
37
37
|
const FRAME_CHUNK = 4;
|
|
38
38
|
/** Hard cap on the bytes read off any response body, to protect the isolate. */
|
|
39
39
|
const MAX_RESPONSE_BYTES = 5_000_000;
|
|
40
|
+
/**
|
|
41
|
+
* Hard cap on one rendered PNG. A frame is rasterised at `scale=1`, so a phone screen lands well
|
|
42
|
+
* under this and a poster-sized artboard is dropped with a stated cause rather than pulled into
|
|
43
|
+
* the isolate.
|
|
44
|
+
*/
|
|
45
|
+
const MAX_RENDER_BYTES = 3_000_000;
|
|
46
|
+
/**
|
|
47
|
+
* Renders downloaded concurrently. The images come from a signed-URL CDN rather than the rate-
|
|
48
|
+
* limited API, so a small fan-out is safe; bounded anyway because each holds its bytes in memory
|
|
49
|
+
* until the caller has stored them.
|
|
50
|
+
*/
|
|
51
|
+
const RENDER_DOWNLOAD_CONCURRENCY = 3;
|
|
40
52
|
/** Carries the HTTP status so callers can surface a meaningful error. */
|
|
41
53
|
export class FigmaApiError extends Error {
|
|
42
54
|
status;
|
|
@@ -52,6 +64,15 @@ export class FigmaApiError extends Error {
|
|
|
52
64
|
* capped-read are the shared documents `http` helpers; only the host/label differ.
|
|
53
65
|
*/
|
|
54
66
|
const safeFetch = createHostPinnedFetch({ host: FIGMA_API_HOST, label: 'Figma' });
|
|
67
|
+
/**
|
|
68
|
+
* `fetch` for a rendered image, pinned to Figma's signed-asset hosts. A SEPARATE client from
|
|
69
|
+
* {@link safeFetch} because the two carry different things: this one sends NO credential (the URL
|
|
70
|
+
* is already signed) and reaches a host the API token must never be offered to.
|
|
71
|
+
*/
|
|
72
|
+
const safeRenderFetch = createHostPinnedFetch({
|
|
73
|
+
host: FIGMA_RENDER_HOSTS,
|
|
74
|
+
label: 'Figma render',
|
|
75
|
+
});
|
|
55
76
|
function emptyMaps() {
|
|
56
77
|
return { components: {}, componentSets: {}, styles: {} };
|
|
57
78
|
}
|
|
@@ -120,7 +141,7 @@ export class FigmaProvider {
|
|
|
120
141
|
if (!fileKey) {
|
|
121
142
|
throw new FigmaApiError(400, `Figma ref is missing a file key: ${externalId}`);
|
|
122
143
|
}
|
|
123
|
-
const { roots, maps, fileName, version, notes } = await this.fetchNodes(credentials, fileKey, nodeId);
|
|
144
|
+
const { roots, maps, fileName, version, notes, renderPlan } = await this.fetchNodes(credentials, fileKey, nodeId);
|
|
124
145
|
// Design tokens are Enterprise-gated; on 403/404 the published styles carried by the
|
|
125
146
|
// node tree are the fallback and the render says which one it used. A rendered preview
|
|
126
147
|
// rides along as a reference (no download): best-effort, the short-lived URL may expire
|
|
@@ -146,8 +167,120 @@ export class FigmaProvider {
|
|
|
146
167
|
url: context.url,
|
|
147
168
|
body: renderDesignContext(context),
|
|
148
169
|
version,
|
|
170
|
+
// The frames this same read already named. `fetchRenders` would otherwise re-issue the very
|
|
171
|
+
// request that produced them, against the rate-limited API, to relearn ids and names it had
|
|
172
|
+
// in hand a moment ago.
|
|
173
|
+
renderPlan,
|
|
149
174
|
};
|
|
150
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Rasterise the referenced frames and download the PNGs.
|
|
178
|
+
*
|
|
179
|
+
* Three hops, and the split matters: a STRUCTURAL read learns which frames to render and what
|
|
180
|
+
* they are called (the `view` a captured screenshot pairs with), the `/v1/images` endpoint turns
|
|
181
|
+
* those ids into short-lived signed URLs, and the URLs are downloaded from the asset host with no
|
|
182
|
+
* credential attached. A frame whose own download fails is counted rather than thrown, so one
|
|
183
|
+
* oversize artboard costs its own picture and not the other five.
|
|
184
|
+
*
|
|
185
|
+
* The first hop is SKIPPED when the caller hands over the plan its own `fetchDocument` produced,
|
|
186
|
+
* which is the normal path: an import fetches the body and the renders back to back off one
|
|
187
|
+
* revision, and the structural read is the same request twice against a rate-limited API.
|
|
188
|
+
*/
|
|
189
|
+
async fetchRenders(credentials, externalId, _workspaceId, plan) {
|
|
190
|
+
const { fileKey, nodeId } = figmaLogic.splitFigmaExternalId(externalId);
|
|
191
|
+
if (!fileKey) {
|
|
192
|
+
throw new FigmaApiError(400, `Figma ref is missing a file key: ${externalId}`);
|
|
193
|
+
}
|
|
194
|
+
const resolved = plan ?? (await this.resolveRenderTargets(credentials, fileKey, nodeId));
|
|
195
|
+
const targets = resolved.targets;
|
|
196
|
+
// The cap rides every return: frames excluded before the first request are just as absent
|
|
197
|
+
// from the stored set as ones whose download failed, and only this count says so.
|
|
198
|
+
if (!targets.length)
|
|
199
|
+
return { renders: [], failed: 0, capped: resolved.capped, causes: [] };
|
|
200
|
+
const urls = await this.fetchRenderUrls(credentials, fileKey, targets.map((t) => t.id));
|
|
201
|
+
const causes = new Set();
|
|
202
|
+
const renders = [];
|
|
203
|
+
let failed = 0;
|
|
204
|
+
// A target the images endpoint answered with no URL never had a picture to download, so it is
|
|
205
|
+
// counted with the same cause vocabulary rather than silently dropped: "Figma rendered nothing
|
|
206
|
+
// for this frame" and "the download failed" ask for different things of the person reading it.
|
|
207
|
+
const pending = targets.filter((target) => {
|
|
208
|
+
const url = urls.get(target.id);
|
|
209
|
+
if (url)
|
|
210
|
+
return true;
|
|
211
|
+
failed += 1;
|
|
212
|
+
causes.add('no render returned');
|
|
213
|
+
return false;
|
|
214
|
+
});
|
|
215
|
+
for (let i = 0; i < pending.length; i += RENDER_DOWNLOAD_CONCURRENCY) {
|
|
216
|
+
const chunk = pending.slice(i, i + RENDER_DOWNLOAD_CONCURRENCY);
|
|
217
|
+
const settled = await Promise.all(chunk.map(async (target) => {
|
|
218
|
+
try {
|
|
219
|
+
return await this.downloadRender(urls.get(target.id), target.view);
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
// silent-catch-ok: REPORTED to the caller as `failed` + this cause, which is the honest
|
|
223
|
+
// form of it. A single frame's picture may not fail the import that carries the text.
|
|
224
|
+
return describeFetchCause(err);
|
|
225
|
+
}
|
|
226
|
+
}));
|
|
227
|
+
for (const result of settled) {
|
|
228
|
+
if (typeof result === 'string') {
|
|
229
|
+
failed += 1;
|
|
230
|
+
causes.add(result);
|
|
231
|
+
}
|
|
232
|
+
else
|
|
233
|
+
renders.push(result);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return { renders, failed, capped: resolved.capped, causes: [...causes].sort() };
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* The frames to rasterise, with their names, for a caller that supplied no plan.
|
|
240
|
+
*
|
|
241
|
+
* A node link renders the frame it names; a whole-file link renders the first
|
|
242
|
+
* {@link figmaLogic.MAX_RENDERS} top-level frames in document order — the same frames, in the
|
|
243
|
+
* same order, the text import covers, so the pictures and the prose describe the same screens.
|
|
244
|
+
* This is the FALLBACK: it repeats the structural read `fetchDocument` performs, which is why
|
|
245
|
+
* the import hands its own plan over instead.
|
|
246
|
+
*/
|
|
247
|
+
async resolveRenderTargets(credentials, fileKey, nodeId) {
|
|
248
|
+
if (nodeId) {
|
|
249
|
+
// `depth=1` is the node ALONE: this read wants its name, not its subtree.
|
|
250
|
+
const res = await this.get(credentials, `/files/${encodeURIComponent(fileKey)}/nodes` +
|
|
251
|
+
`?ids=${encodeURIComponent(nodeId)}&depth=1`);
|
|
252
|
+
const node = res.nodes?.[nodeId]?.document;
|
|
253
|
+
return node ? figmaLogic.figmaRenderTargets([node]) : { targets: [], capped: 0 };
|
|
254
|
+
}
|
|
255
|
+
const res = await this.get(credentials, `/files/${encodeURIComponent(fileKey)}?depth=${FILE_DEPTH}`);
|
|
256
|
+
return figmaLogic.figmaRenderTargets(figmaLogic.figmaTopLevelFrames(res.document));
|
|
257
|
+
}
|
|
258
|
+
/** `/v1/images` in ONE call for every target: id → signed URL, absent when Figma rendered none. */
|
|
259
|
+
async fetchRenderUrls(credentials, fileKey, ids) {
|
|
260
|
+
const res = await this.get(credentials, `/images/${encodeURIComponent(fileKey)}` +
|
|
261
|
+
`?ids=${encodeURIComponent(ids.join(','))}&format=png&scale=1`);
|
|
262
|
+
if (res.err)
|
|
263
|
+
throw new FigmaApiError(502, `Figma images endpoint reported: ${res.err}`);
|
|
264
|
+
const out = new Map();
|
|
265
|
+
for (const [id, url] of Object.entries(res.images ?? {})) {
|
|
266
|
+
if (typeof url === 'string' && url)
|
|
267
|
+
out.set(id, url);
|
|
268
|
+
}
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
271
|
+
/** Download one signed render. No credential is sent: the URL already carries its own. */
|
|
272
|
+
async downloadRender(url, view) {
|
|
273
|
+
const res = await safeRenderFetch(url, {
|
|
274
|
+
method: 'GET',
|
|
275
|
+
headers: { accept: 'image/png', 'user-agent': USER_AGENT },
|
|
276
|
+
});
|
|
277
|
+
if (!res.ok)
|
|
278
|
+
throw new FigmaApiError(res.status, `Figma render GET → ${res.status}`);
|
|
279
|
+
const bytes = await readCappedBytes(res, MAX_RENDER_BYTES, 'Figma render');
|
|
280
|
+
if (!bytes.byteLength)
|
|
281
|
+
throw new FigmaApiError(502, 'Figma returned an empty render');
|
|
282
|
+
return { view, contentType: 'image/png', bytes };
|
|
283
|
+
}
|
|
151
284
|
/**
|
|
152
285
|
* The cheap version probe: read the file's metadata at `depth=1` (no node tree)
|
|
153
286
|
* for its `version` / `lastModified`, skipping the deep node fetch + variables +
|
|
@@ -188,6 +321,9 @@ export class FigmaProvider {
|
|
|
188
321
|
fileName: res.name ?? fileKey,
|
|
189
322
|
version: fileVersion(res),
|
|
190
323
|
notes: [],
|
|
324
|
+
// A node link renders the frame it names, and nothing is capped: one frame was asked for and
|
|
325
|
+
// one is covered.
|
|
326
|
+
renderPlan: figmaLogic.figmaRenderTargets([entry.document]),
|
|
191
327
|
};
|
|
192
328
|
}
|
|
193
329
|
/**
|
|
@@ -215,6 +351,10 @@ export class FigmaProvider {
|
|
|
215
351
|
fileName: res.name ?? fileKey,
|
|
216
352
|
version: fileVersion(res),
|
|
217
353
|
notes: [],
|
|
354
|
+
// Resolved against the WHOLE outline rather than the text pass's own `selected` slice: the
|
|
355
|
+
// two caps are different numbers, and `capped` has to count frames this file has that no
|
|
356
|
+
// picture will cover, not frames the text budget happened to keep.
|
|
357
|
+
renderPlan: figmaLogic.figmaRenderTargets(outline),
|
|
218
358
|
};
|
|
219
359
|
if (!outline.length)
|
|
220
360
|
return base;
|