@cosmicdrift/kumiko-renderer-puppeteer-client 0.387.0 → 0.389.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/document-pdf.js +24 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/render-and-store.d.ts +9 -0
- package/dist/render-and-store.js +27 -7
- package/package.json +6 -6
- package/src/changes.json +12 -0
package/dist/document-pdf.js
CHANGED
|
@@ -10,6 +10,7 @@ import { collectRegionResourceKeys, compileDocumentSource, createDocumentHandleb
|
|
|
10
10
|
const LINE_BREAKS = /[\r\n]+/g;
|
|
11
11
|
const IMAGE_CONTENT_TYPE_PREFIX = "image/";
|
|
12
12
|
const MAX_RECORD_IMAGES = 24;
|
|
13
|
+
const MAX_RECORD_IMAGE_LOOKUPS = 100;
|
|
13
14
|
const MAX_RECORD_IMAGES_TOTAL_BYTES = 20 * 1024 * 1024;
|
|
14
15
|
const PREVIEW_PAGE_NUMBER = "1";
|
|
15
16
|
const EMPTY_CHROMIUM_REGION = "<span></span>";
|
|
@@ -176,8 +177,10 @@ async function loadRecordImageDataUris(ctx, variables, imageVariableNames) {
|
|
|
176
177
|
for (const fileRefId of fileRefIdsOf(variables[name])) {
|
|
177
178
|
if (seenIds.has(fileRefId))
|
|
178
179
|
continue;
|
|
180
|
+
if (seenIds.size >= MAX_RECORD_IMAGE_LOOKUPS)
|
|
181
|
+
break;
|
|
179
182
|
seenIds.add(fileRefId);
|
|
180
|
-
if (loadedCount >= MAX_RECORD_IMAGES)
|
|
183
|
+
if (loadedCount >= MAX_RECORD_IMAGES || loadedBytes >= MAX_RECORD_IMAGES_TOTAL_BYTES)
|
|
181
184
|
break;
|
|
182
185
|
const image = await readRecordImage(ctx, filesForTenant, fileRefId, Math.min(maxBytes, MAX_RECORD_IMAGES_TOTAL_BYTES - loadedBytes));
|
|
183
186
|
if (image === null)
|
|
@@ -190,10 +193,22 @@ async function loadRecordImageDataUris(ctx, variables, imageVariableNames) {
|
|
|
190
193
|
}
|
|
191
194
|
return dataUrisByVariable;
|
|
192
195
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
196
|
+
function isImagesVariable(definitions, name) {
|
|
197
|
+
return Object.hasOwn(definitions, name) && definitions[name]?.type === "images";
|
|
198
|
+
}
|
|
199
|
+
// An images variable's value is file ids, which no placeholder, title or file name pattern
|
|
200
|
+
// may print; the text context carries only whether images exist, so a condition on it works.
|
|
201
|
+
function imagesAsPresenceFlags(variables, definitions, recordImages, recordImageUris) {
|
|
202
|
+
const hasImages = (name) => {
|
|
203
|
+
if (recordImages === "placeholder")
|
|
204
|
+
return true;
|
|
205
|
+
const resolved = recordImageUris.get(name);
|
|
206
|
+
return resolved !== undefined ? resolved.length > 0 : fileRefIdsOf(variables[name]).length > 0;
|
|
207
|
+
};
|
|
208
|
+
return Object.fromEntries(Object.entries(variables).map(([name, value]) => [
|
|
209
|
+
name,
|
|
210
|
+
isImagesVariable(definitions, name) ? hasImages(name) : value,
|
|
211
|
+
]));
|
|
197
212
|
}
|
|
198
213
|
function renderRegion(hbs, region, context, resourceSrc) {
|
|
199
214
|
if (region.trim() === "")
|
|
@@ -304,17 +319,17 @@ async function assembleDocument(input, ctx, markers, recordImages = "placeholder
|
|
|
304
319
|
]);
|
|
305
320
|
const hbs = createDocumentHandlebars(format);
|
|
306
321
|
const variableDefinitions = resolveTemplateVariables(ctx.registry, slug, template.variableSchema);
|
|
322
|
+
const recordImageUris = recordImages === "resolve"
|
|
323
|
+
? await loadRecordImageDataUris(ctx, variables, collectDocumentImageVariables(template.content).filter((name) => isImagesVariable(variableDefinitions, name)))
|
|
324
|
+
: new Map();
|
|
307
325
|
const context = {
|
|
308
|
-
...
|
|
326
|
+
...imagesAsPresenceFlags(variables, variableDefinitions, recordImages, recordImageUris),
|
|
309
327
|
tenantName,
|
|
310
328
|
signature: "",
|
|
311
329
|
imprint: "",
|
|
312
330
|
page: "",
|
|
313
331
|
pages: "",
|
|
314
332
|
};
|
|
315
|
-
const recordImageUris = recordImages === "resolve"
|
|
316
|
-
? await loadRecordImageDataUris(ctx, variables, collectDocumentImageVariables(template.content))
|
|
317
|
-
: new Map();
|
|
318
333
|
const bodyKeys = collectDocumentResourceKeys(template.content);
|
|
319
334
|
const layoutKeys = collectRegionResourceKeys(layout.regions);
|
|
320
335
|
const bodyRefs = await resolveResourceFileRefs(tplApi, ctx, template, bodyKeys);
|
package/dist/index.d.ts
CHANGED
|
@@ -5,4 +5,4 @@ export { createRendererPuppeteerClientFeature } from "./feature.ts";
|
|
|
5
5
|
export { countPdfPages } from "./pdf-page-count.ts";
|
|
6
6
|
export { RendererPuppeteerClientQueries } from "./qualified-names.ts";
|
|
7
7
|
export { puppeteerRender } from "./render.ts";
|
|
8
|
-
export { type RenderedPdfUpload, type RenderPdfArgs, renderPdfBeforeTransaction, type StoredRenderedPdf, storeRenderedPdf, } from "./render-and-store.ts";
|
|
8
|
+
export { type RenderedPdfBytes, type RenderedPdfUpload, type RenderPdfArgs, renderPdfBeforeTransaction, renderPdfBytes, type StoredRenderedPdf, storeRenderedPdf, } from "./render-and-store.ts";
|
package/dist/index.js
CHANGED
|
@@ -5,4 +5,4 @@ export { createRendererPuppeteerClientFeature } from "./feature.js";
|
|
|
5
5
|
export { countPdfPages } from "./pdf-page-count.js";
|
|
6
6
|
export { RendererPuppeteerClientQueries } from "./qualified-names.js";
|
|
7
7
|
export { puppeteerRender } from "./render.js";
|
|
8
|
-
export { renderPdfBeforeTransaction, storeRenderedPdf, } from "./render-and-store.js";
|
|
8
|
+
export { renderPdfBeforeTransaction, renderPdfBytes, storeRenderedPdf, } from "./render-and-store.js";
|
|
@@ -31,5 +31,14 @@ export type StoredRenderedPdf = {
|
|
|
31
31
|
readonly layout?: RenderedTemplateRef;
|
|
32
32
|
};
|
|
33
33
|
export declare function clientErrorForRenderFailure(e: unknown, ctx: HandlerContext, slug: string): KumikoError;
|
|
34
|
+
export type RenderedPdfBytes = {
|
|
35
|
+
readonly fileName: string;
|
|
36
|
+
readonly pdfBytes: Uint8Array;
|
|
37
|
+
readonly pageCount: number;
|
|
38
|
+
readonly sizeBytes: number;
|
|
39
|
+
readonly template: RenderedTemplateRef;
|
|
40
|
+
readonly layout?: RenderedTemplateRef;
|
|
41
|
+
};
|
|
42
|
+
export declare function renderPdfBytes(ctx: HandlerContext, args: Pick<RenderPdfArgs, "slug" | "locale" | "variables">): Promise<WriteResult<RenderedPdfBytes>>;
|
|
34
43
|
export declare function renderPdfBeforeTransaction(ctx: HandlerContext, args: RenderPdfArgs): Promise<BeforeTransactionResult<RenderedPdfUpload>>;
|
|
35
44
|
export declare function storeRenderedPdf(ctx: HandlerContext, rendered: RenderedPdfUpload): Promise<WriteResult<StoredRenderedPdf>>;
|
package/dist/render-and-store.js
CHANGED
|
@@ -25,9 +25,9 @@ export function clientErrorForRenderFailure(e, ctx, slug) {
|
|
|
25
25
|
function renderFailure(e, ctx, slug) {
|
|
26
26
|
return writeFailure(clientErrorForRenderFailure(e, ctx, slug));
|
|
27
27
|
}
|
|
28
|
-
// Renders
|
|
29
|
-
//
|
|
30
|
-
export async function
|
|
28
|
+
// Renders without storing: no file, no rendered-pdf row. For callers that keep the bytes
|
|
29
|
+
// themselves (mail attachment, download); same no-transaction rule as renderPdfBeforeTransaction.
|
|
30
|
+
export async function renderPdfBytes(ctx, args) {
|
|
31
31
|
const tenantId = ctx.user.tenantId;
|
|
32
32
|
const plugin = createRendererFoundationApi(collectRendererPlugins(ctx.registry)).createRendererForTenant({ tenantId, kind: "document-pdf" });
|
|
33
33
|
let response;
|
|
@@ -53,15 +53,35 @@ export async function renderPdfBeforeTransaction(ctx, args) {
|
|
|
53
53
|
if (template === undefined) {
|
|
54
54
|
throw new RendererError(`renderer "${plugin.name}" did not report which template produced the PDF`, "other");
|
|
55
55
|
}
|
|
56
|
+
return {
|
|
57
|
+
isSuccess: true,
|
|
58
|
+
data: {
|
|
59
|
+
fileName: response.fileName,
|
|
60
|
+
pdfBytes: response.pdfBytes,
|
|
61
|
+
pageCount: response.pageCount,
|
|
62
|
+
sizeBytes: response.sizeBytes,
|
|
63
|
+
template,
|
|
64
|
+
...(layout !== undefined && { layout }),
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// Renders and uploads the PDF while no transaction is open: the print service call can take
|
|
69
|
+
// seconds and must not hold a pool connection. Use as a handler's `beforeTransaction`.
|
|
70
|
+
export async function renderPdfBeforeTransaction(ctx, args) {
|
|
71
|
+
const rendered = await renderPdfBytes(ctx, args);
|
|
72
|
+
if (!rendered.isSuccess)
|
|
73
|
+
return rendered;
|
|
74
|
+
const { fileName, pdfBytes, pageCount, sizeBytes, template, layout } = rendered.data;
|
|
75
|
+
const tenantId = ctx.user.tenantId;
|
|
56
76
|
const uploaded = await uploadFileBytesBeforeTransaction({
|
|
57
77
|
db: ctx.db,
|
|
58
78
|
files: fileContextForTenant(ctx, tenantId, tenantId),
|
|
59
79
|
registry: ctx.registry,
|
|
60
80
|
user: ctx.user,
|
|
61
81
|
}, {
|
|
62
|
-
fileName
|
|
82
|
+
fileName,
|
|
63
83
|
mimeType: PDF_MIME_TYPE,
|
|
64
|
-
data:
|
|
84
|
+
data: pdfBytes,
|
|
65
85
|
...(args.attachTo !== undefined && { attachTo: args.attachTo }),
|
|
66
86
|
});
|
|
67
87
|
if (!uploaded.isSuccess)
|
|
@@ -74,8 +94,8 @@ export async function renderPdfBeforeTransaction(ctx, args) {
|
|
|
74
94
|
locale: args.locale,
|
|
75
95
|
template,
|
|
76
96
|
...(layout !== undefined && { layout }),
|
|
77
|
-
pageCount
|
|
78
|
-
sizeBytes
|
|
97
|
+
pageCount,
|
|
98
|
+
sizeBytes,
|
|
79
99
|
},
|
|
80
100
|
...(uploaded.release && { release: uploaded.release }),
|
|
81
101
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-renderer-puppeteer-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.389.0",
|
|
4
4
|
"description": "Renderer-Foundation plugin: HTTP client for puppeteer-service (PDF + image snapshot via remote browser).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -19,13 +19,13 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
23
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
24
|
-
"@cosmicdrift/kumiko-renderer-mail-html": "0.
|
|
25
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
22
|
+
"@cosmicdrift/kumiko-bundled-features": "0.389.0",
|
|
23
|
+
"@cosmicdrift/kumiko-framework": "0.389.0",
|
|
24
|
+
"@cosmicdrift/kumiko-renderer-mail-html": "0.389.0",
|
|
25
|
+
"@cosmicdrift/kumiko-types": "0.389.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"@cosmicdrift/kumiko-testing": "0.
|
|
28
|
+
"@cosmicdrift/kumiko-testing": "0.389.0"
|
|
29
29
|
},
|
|
30
30
|
"repository": {
|
|
31
31
|
"type": "git",
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.389.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Render a PDF without storing it",
|
|
6
|
+
"detail": "renderPdfBytes is the render half of renderPdfBeforeTransaction. Use it when the bytes go straight into a mail or a download and no file or rendered-pdf row should exist."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.388.0",
|
|
10
|
+
"type": "fix",
|
|
11
|
+
"title": "Conditions on images variables work in the PDF render",
|
|
12
|
+
"detail": "A data-condition on an images variable now shows its block when the record has resolvable images, instead of never. Record image loading stops once the byte budget is used up, caps the number of looked-up ids, and ignores image blocks whose variable is not declared as images."
|
|
13
|
+
},
|
|
2
14
|
{
|
|
3
15
|
"version": "0.379.0",
|
|
4
16
|
"type": "improvement",
|