@ironfang/render 3.0.1 → 3.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.
package/README.md CHANGED
@@ -18,6 +18,16 @@ the HMAC key, while a Render secret is the hex of the 32-byte key deliveries
18
18
  are signed with, so it rejected every genuine delivery. Upgrade if you
19
19
  verify Render webhooks with it.
20
20
 
21
+ 3.0.2 types a template's `vars` as `Record<string, string>` in `image()` and
22
+ `sign()`, as the API has always required: a number, boolean or object passed
23
+ the type check and then came back as a 400. Code that passed one no longer
24
+ compiles; pass it as text (`String(n)`).
25
+
26
+ 3.1.0 adds capture bundles (`bundle()`), stored document templates
27
+ (`documentTemplates`), and the request fields for lazy content, success
28
+ rules, PDF controls, language and time zone, with each render's `diagnostics`.
29
+ Additions only; see the [changelog](CHANGELOG.md).
30
+
21
31
  ```sh
22
32
  npm install @ironfang/render
23
33
  ```
@@ -65,6 +75,28 @@ to 1,024 UTF-8 bytes and share the render timeout. Invalid input returns
65
75
  messages. A screenshot cache hit does not re-execute controls; use `no_cache`
66
76
  for fresh execution. [Full reference](https://ironfang.com/render/docs#page-controls).
67
77
 
78
+ ## Capture bundles
79
+
80
+ One navigation, the outputs you choose: a screenshot, the rendered HTML, the
81
+ page's text, Markdown and its metadata. HTML, text and metadata are read
82
+ together after the page's readiness and actions; the screenshot follows.
83
+
84
+ ```ts
85
+ const b = await client.bundle({
86
+ url: 'https://example.com/blog/post',
87
+ outputs: ['screenshot', 'markdown', 'metadata'],
88
+ });
89
+ console.log(b.manifest.metadata?.title, b.markdown);
90
+ for (const o of b.manifest.outputs) {
91
+ if (o.status !== 'included') console.warn(o.name, o.status, o.reason);
92
+ }
93
+ ```
94
+
95
+ An output over its bound is `truncated` or `omitted` in the manifest, never
96
+ dropped silently. Iframe content is not in the text outputs. A bundle counts one
97
+ `render.screenshot`. Captured HTML is the page's own: never insert it into a
98
+ page of yours.
99
+
68
100
  ## Durable jobs
69
101
 
70
102
  ```ts
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { ClipRequest, Destination, DestinationDraft, ImageRequest, Job, JobSubmission, PdfRequest, QrRequest, RequestFilter, RequestRecord, ScreenshotRequest, SignRequest, SignedUrl, SitePreviewRequest, UnmeteredUsage, Usage } from './types.js';
2
- export declare const VERSION = "3.0.1";
1
+ import type { ClipRequest, Destination, DestinationDraft, DocumentRenderRequest, DocumentRevision, DocumentTemplate, DocumentTemplateInput, DocumentValidation, ImageRequest, Job, JobSubmission, PdfRequest, QrRequest, RequestFilter, RequestRecord, BundleManifest, BundleRequest, ScreenshotRequest, SignRequest, SignedUrl, SitePreviewRequest, UnmeteredUsage, Usage } from './types.js';
2
+ export declare const VERSION = "3.1.0";
3
3
  export interface ClientOptions {
4
4
  /** Your `rw_live_...` key. Never ship this to a browser. */
5
5
  apiKey: string;
@@ -41,6 +41,24 @@ export interface RenderResult {
41
41
  /** Write the bytes to a file. Node only; throws elsewhere. */
42
42
  saveTo(path: string): Promise<void>;
43
43
  }
44
+ /**
45
+ * A capture bundle. The text outputs are the page's own content: untrusted,
46
+ * never to be inserted into a page as HTML.
47
+ */
48
+ export interface BundleResult {
49
+ manifest: BundleManifest;
50
+ /** Each output is undefined when the bundle does not include it. */
51
+ screenshot: Uint8Array | undefined;
52
+ renderedHtml: string | undefined;
53
+ text: string | undefined;
54
+ markdown: string | undefined;
55
+ /** `render.screenshot`. */
56
+ meter: string | undefined;
57
+ quantity: number;
58
+ renderMs: number;
59
+ requestId: string;
60
+ headers: Headers;
61
+ }
44
62
  /** A site preview: the poster frame and the scrolling video. */
45
63
  export interface SitePreviewResult {
46
64
  poster: Uint8Array;
@@ -73,8 +91,49 @@ export declare class IronfangRender {
73
91
  /** Render one of your stored templates with variables filled in. */
74
92
  image(templateId: string, req?: ImageRequest, opts?: RequestOptions): Promise<RenderResult>;
75
93
  clip(req: ClipRequest, opts?: RequestOptions): Promise<RenderResult>;
94
+ /**
95
+ * Capture a bundle: the outputs you select - screenshot, rendered HTML,
96
+ * text, Markdown, metadata - from one navigation of the page.
97
+ */
98
+ bundle(req: BundleRequest, opts?: RequestOptions): Promise<BundleResult>;
76
99
  sitePreview(req: SitePreviewRequest, opts?: RequestOptions): Promise<SitePreviewResult>;
77
100
  private render;
101
+ /**
102
+ * Stored PDF templates with structured JSON data: a draft, published as
103
+ * immutable numbered revisions. Render a published revision with `render`,
104
+ * or as a job of kind `document`.
105
+ */
106
+ readonly documentTemplates: {
107
+ create: (tpl: DocumentTemplateInput, opts?: RequestOptions) => Promise<DocumentTemplate>;
108
+ list: (opts?: RequestOptions & {
109
+ archived?: boolean;
110
+ }) => Promise<{
111
+ templates: DocumentTemplate[];
112
+ }>;
113
+ get: (id: string, opts?: RequestOptions) => Promise<DocumentTemplate>;
114
+ /** Replace the draft. What is published changes only on `publish`. */
115
+ update: (id: string, tpl: DocumentTemplateInput, opts?: RequestOptions) => Promise<DocumentTemplate>;
116
+ /** Archive: no more edits or unversioned renders; revisions and accepted jobs are kept. */
117
+ archive: (id: string, opts?: RequestOptions) => Promise<void>;
118
+ /** Check the draft against data (or its sample data) without printing or billing. */
119
+ validate: (id: string, data?: Record<string, unknown>, opts?: RequestOptions) => Promise<DocumentValidation>;
120
+ /** Print the draft with data (or its sample data). A PDF render; throws on any failure. */
121
+ preview: (id: string, data?: Record<string, unknown>, opts?: RequestOptions) => Promise<RenderResult>;
122
+ /** Publish the draft as a new revision (or the latest again, if unchanged). */
123
+ publish: (id: string, opts?: RequestOptions) => Promise<DocumentRevision>;
124
+ /** Publish an existing revision again: a rollback. */
125
+ publishRevision: (id: string, revision: number, opts?: RequestOptions) => Promise<DocumentRevision>;
126
+ revisions: (id: string, opts?: RequestOptions) => Promise<{
127
+ revisions: DocumentRevision[];
128
+ }>;
129
+ revision: (id: string, revision: number, opts?: RequestOptions) => Promise<DocumentRevision>;
130
+ /**
131
+ * Print a revision - the published one unless `req.revision` names
132
+ * another - with `req.data`. Data problems throw `data_invalid` before
133
+ * anything is charged; the error's `fields` names each path.
134
+ */
135
+ render: (id: string, req: DocumentRenderRequest, opts?: RequestOptions) => Promise<RenderResult>;
136
+ };
78
137
  readonly jobs: {
79
138
  /**
80
139
  * Submit a durable job. Pass `idempotencyKey` to make a retry of the same
package/dist/client.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { BILLING_CODES, IronfangBillingError, IronfangRenderError } from './errors.js';
2
- export const VERSION = '3.0.1';
2
+ export const VERSION = '3.1.0';
3
3
  export class IronfangRender {
4
4
  apiKey;
5
5
  baseUrl;
@@ -31,6 +31,36 @@ export class IronfangRender {
31
31
  clip(req, opts = {}) {
32
32
  return this.render('/v1/video', req, opts);
33
33
  }
34
+ /**
35
+ * Capture a bundle: the outputs you select - screenshot, rendered HTML,
36
+ * text, Markdown, metadata - from one navigation of the page.
37
+ */
38
+ async bundle(req, opts = {}) {
39
+ const resp = await this.send('POST', '/v1/bundle', req, opts, idempotency(opts));
40
+ const form = await resp.formData();
41
+ const manifest = form.get('manifest');
42
+ if (manifest === null) {
43
+ throw new IronfangRenderError('the response did not include a manifest', {
44
+ status: resp.status, code: 'bad_response', requestId: requestIdOf(resp),
45
+ });
46
+ }
47
+ const text = async (name) => {
48
+ const part = form.get(name);
49
+ return part instanceof Blob ? await part.text() : undefined;
50
+ };
51
+ const shot = form.get('screenshot');
52
+ return {
53
+ manifest: JSON.parse(typeof manifest === 'string' ? manifest : await manifest.text()),
54
+ screenshot: shot instanceof Blob ? new Uint8Array(await shot.arrayBuffer()) : undefined,
55
+ renderedHtml: await text('rendered_html'),
56
+ text: await text('text'),
57
+ markdown: await text('markdown'),
58
+ ...usageOf(resp),
59
+ renderMs: num(resp.headers.get('X-Ironfang-Render-Ms')),
60
+ requestId: requestIdOf(resp),
61
+ headers: resp.headers,
62
+ };
63
+ }
34
64
  async sitePreview(req, opts = {}) {
35
65
  const resp = await this.send('POST', '/v1/site-preview', req, opts, idempotency(opts));
36
66
  const form = await resp.formData();
@@ -66,6 +96,66 @@ export class IronfangRender {
66
96
  saveTo: (target) => saveBytes(target, bytes),
67
97
  };
68
98
  }
99
+ /* -------------------------- document templates ------------------------ */
100
+ /**
101
+ * Stored PDF templates with structured JSON data: a draft, published as
102
+ * immutable numbered revisions. Render a published revision with `render`,
103
+ * or as a job of kind `document`.
104
+ */
105
+ documentTemplates = {
106
+ create: async (tpl, opts = {}) => {
107
+ const resp = await this.send('POST', '/v1/document-templates', tpl, opts);
108
+ return (await resp.json());
109
+ },
110
+ list: async (opts = {}) => {
111
+ const resp = await this.send('GET', `/v1/document-templates${opts.archived ? '?archived=true' : ''}`, undefined, opts);
112
+ return (await resp.json());
113
+ },
114
+ get: async (id, opts = {}) => {
115
+ const resp = await this.send('GET', `/v1/document-templates/${encodeURIComponent(id)}`, undefined, opts);
116
+ return (await resp.json());
117
+ },
118
+ /** Replace the draft. What is published changes only on `publish`. */
119
+ update: async (id, tpl, opts = {}) => {
120
+ const resp = await this.send('PUT', `/v1/document-templates/${encodeURIComponent(id)}`, tpl, opts);
121
+ return (await resp.json());
122
+ },
123
+ /** Archive: no more edits or unversioned renders; revisions and accepted jobs are kept. */
124
+ archive: async (id, opts = {}) => {
125
+ await this.send('DELETE', `/v1/document-templates/${encodeURIComponent(id)}`, undefined, opts);
126
+ },
127
+ /** Check the draft against data (or its sample data) without printing or billing. */
128
+ validate: async (id, data, opts = {}) => {
129
+ const resp = await this.send('POST', `/v1/document-templates/${encodeURIComponent(id)}/validate`, data ? { data } : {}, opts);
130
+ return (await resp.json());
131
+ },
132
+ /** Print the draft with data (or its sample data). A PDF render; throws on any failure. */
133
+ preview: (id, data, opts = {}) => this.render(`/v1/document-templates/${encodeURIComponent(id)}/preview`, data ? { data } : {}, opts),
134
+ /** Publish the draft as a new revision (or the latest again, if unchanged). */
135
+ publish: async (id, opts = {}) => {
136
+ const resp = await this.send('POST', `/v1/document-templates/${encodeURIComponent(id)}/publish`, undefined, opts);
137
+ return (await resp.json());
138
+ },
139
+ /** Publish an existing revision again: a rollback. */
140
+ publishRevision: async (id, revision, opts = {}) => {
141
+ const resp = await this.send('POST', `/v1/document-templates/${encodeURIComponent(id)}/revisions/${revision}/publish`, undefined, opts);
142
+ return (await resp.json());
143
+ },
144
+ revisions: async (id, opts = {}) => {
145
+ const resp = await this.send('GET', `/v1/document-templates/${encodeURIComponent(id)}/revisions`, undefined, opts);
146
+ return (await resp.json());
147
+ },
148
+ revision: async (id, revision, opts = {}) => {
149
+ const resp = await this.send('GET', `/v1/document-templates/${encodeURIComponent(id)}/revisions/${revision}`, undefined, opts);
150
+ return (await resp.json());
151
+ },
152
+ /**
153
+ * Print a revision - the published one unless `req.revision` names
154
+ * another - with `req.data`. Data problems throw `data_invalid` before
155
+ * anything is charged; the error's `fields` names each path.
156
+ */
157
+ render: (id, req, opts = {}) => this.render(`/v1/document-templates/${encodeURIComponent(id)}/render`, req, opts),
158
+ };
69
159
  /* -------------------------------- jobs -------------------------------- */
70
160
  jobs = {
71
161
  /**
@@ -216,6 +306,7 @@ async function errorFrom(resp) {
216
306
  let code = `http_${resp.status}`;
217
307
  let message = `request failed with HTTP ${resp.status}`;
218
308
  let detail = {};
309
+ let fields;
219
310
  try {
220
311
  const body = (await resp.json());
221
312
  if (body?.error?.code)
@@ -224,11 +315,13 @@ async function errorFrom(resp) {
224
315
  message = body.error.message;
225
316
  if (body?.error)
226
317
  detail = body.error;
318
+ if (Array.isArray(body?.fields))
319
+ fields = body.fields;
227
320
  }
228
321
  catch {
229
322
  // Not JSON; the status is all there is.
230
323
  }
231
- const opts = { status: resp.status, code, requestId: requestIdOf(resp), retryAfter: retryAfterOf(resp) };
324
+ const opts = { status: resp.status, code, requestId: requestIdOf(resp), retryAfter: retryAfterOf(resp), fields };
232
325
  if (BILLING_CODES.includes(code)) {
233
326
  return new IronfangBillingError(message, {
234
327
  ...opts, product: text(detail.product), meter: text(detail.meter), resetAt: text(detail.reset_at),
package/dist/errors.d.ts CHANGED
@@ -12,11 +12,25 @@ export declare class IronfangRenderError extends Error {
12
12
  readonly code: string;
13
13
  readonly requestId: string | undefined;
14
14
  readonly retryAfter: number | undefined;
15
+ /**
16
+ * Per-field problems, when the request failed on particular fields: for
17
+ * `data_invalid`, each path in a document template's data and what is wrong.
18
+ */
19
+ readonly fields: {
20
+ field: string;
21
+ code: string;
22
+ message: string;
23
+ }[] | undefined;
15
24
  constructor(message: string, opts: {
16
25
  status: number;
17
26
  code: string;
18
27
  requestId?: string | undefined;
19
28
  retryAfter?: number | undefined;
29
+ fields?: {
30
+ field: string;
31
+ code: string;
32
+ message: string;
33
+ }[] | undefined;
20
34
  });
21
35
  /** The request was refused before any work was done. */
22
36
  get isClientError(): boolean;
package/dist/errors.js CHANGED
@@ -12,6 +12,11 @@ export class IronfangRenderError extends Error {
12
12
  code;
13
13
  requestId;
14
14
  retryAfter;
15
+ /**
16
+ * Per-field problems, when the request failed on particular fields: for
17
+ * `data_invalid`, each path in a document template's data and what is wrong.
18
+ */
19
+ fields;
15
20
  constructor(message, opts) {
16
21
  super(message);
17
22
  this.name = 'IronfangRenderError';
@@ -19,6 +24,7 @@ export class IronfangRenderError extends Error {
19
24
  this.code = opts.code;
20
25
  this.requestId = opts.requestId;
21
26
  this.retryAfter = opts.retryAfter;
27
+ this.fields = opts.fields;
22
28
  }
23
29
  /** The request was refused before any work was done. */
24
30
  get isClientError() {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { IronfangRender, VERSION } from './client.js';
2
- export type { ClientOptions, RequestOptions, RenderResult, SitePreviewResult, WaitOptions } from './client.js';
2
+ export type { ClientOptions, RequestOptions, BundleResult, RenderResult, SitePreviewResult, WaitOptions } from './client.js';
3
3
  export { IronfangBillingError, IronfangRenderError } from './errors.js';
4
4
  export { verifyWebhook, signWebhook } from './webhook.js';
5
5
  export type { WebhookInput } from './webhook.js';
package/dist/types.d.ts CHANGED
@@ -31,6 +31,31 @@ export interface RenderCommon {
31
31
  wait_for_selector?: string;
32
32
  timeout_ms?: number;
33
33
  device_scale_factor?: number;
34
+ /**
35
+ * The browser's language, a BCP 47 tag such as `de-DE`: Intl formatting,
36
+ * `navigator.language(s)` and `Accept-Language`. An `Accept-Language` in
37
+ * `headers` is sent exactly as given instead. Not an IP address in that country.
38
+ */
39
+ locale?: string;
40
+ /** The browser's clock, an IANA zone such as `Europe/Berlin`. Default UTC. */
41
+ timezone?: string;
42
+ }
43
+ /**
44
+ * Opt-in rules for output that should count as a failure: a render one fails
45
+ * is a 422 `success_rule_failed` naming the rule (`error.rule`), and is not
46
+ * charged. Without rules a render behaves as before.
47
+ */
48
+ export interface SuccessRules {
49
+ /** Target statuses that fail the render: `404`, `'5xx'` or `'400-499'`. Needs `url`. */
50
+ fail_on_status?: (number | string)[];
51
+ /** A CSS selector that must match an element in the ready page. Does not wait. */
52
+ require_selector?: string;
53
+ /** Text that must be displayed on the page: literal, any case, whitespace collapsed. */
54
+ require_text?: string[];
55
+ /** Text that must not be displayed on the page, matched as `require_text`. */
56
+ forbid_text?: string[];
57
+ /** Screenshots only: fail a capture that is a single flat colour. */
58
+ fail_on_blank?: boolean;
34
59
  }
35
60
  export interface ScreenshotRequest extends RenderCommon {
36
61
  url?: string;
@@ -39,6 +64,12 @@ export interface ScreenshotRequest extends RenderCommon {
39
64
  height?: number;
40
65
  full_page?: boolean;
41
66
  full_page_max_height?: number;
67
+ /**
68
+ * `scroll` walks a `full_page` capture down first, so lazy images and
69
+ * IntersectionObserver content are in it. Bounded (at most 10 s); omitted,
70
+ * the capture is taken as before. Requires `full_page`.
71
+ */
72
+ full_page_readiness?: 'scroll';
42
73
  selector?: string;
43
74
  clip?: {
44
75
  x: number;
@@ -52,6 +83,7 @@ export interface ScreenshotRequest extends RenderCommon {
52
83
  quality?: number;
53
84
  device?: 'desktop' | 'tablet' | 'mobile';
54
85
  delay_ms?: number;
86
+ success_rules?: SuccessRules;
55
87
  }
56
88
  export interface PdfRequest extends RenderCommon {
57
89
  url?: string;
@@ -68,6 +100,22 @@ export interface PdfRequest extends RenderCommon {
68
100
  header_html?: string;
69
101
  footer_html?: string;
70
102
  scale?: number;
103
+ /** A custom size with units, such as `210mm`; both or neither, never with `paper_format`. */
104
+ paper_width?: string;
105
+ paper_height?: string;
106
+ /** Let the page's own `@page { size }` decide the page size. */
107
+ prefer_css_page_size?: boolean;
108
+ /** Pages to keep, such as `1-3, 5, 8-`; the whole document is still bounded. */
109
+ page_ranges?: string;
110
+ /** The PDF's document properties. */
111
+ metadata?: {
112
+ title?: string;
113
+ author?: string;
114
+ subject?: string;
115
+ keywords?: string;
116
+ };
117
+ /** As on a screenshot, less `fail_on_blank`. */
118
+ success_rules?: Omit<SuccessRules, 'fail_on_blank'>;
71
119
  }
72
120
  export interface QrRequest {
73
121
  data: string;
@@ -85,7 +133,9 @@ export interface QrRequest {
85
133
  logo_pad?: boolean;
86
134
  }
87
135
  export interface ImageRequest {
88
- vars?: Record<string, unknown>;
136
+ /** The template's placeholder values. Text only: the API refuses a number,
137
+ * boolean or object with 400, so pass `String(n)` for a number. */
138
+ vars?: Record<string, string>;
89
139
  format?: 'png' | 'jpeg' | 'webp';
90
140
  }
91
141
  export interface ClipRequest {
@@ -102,6 +152,57 @@ export interface ClipRequest {
102
152
  end?: number;
103
153
  }[];
104
154
  }
155
+ /** The outputs a capture bundle can include. */
156
+ export type BundleOutputName = 'screenshot' | 'rendered_html' | 'text' | 'markdown' | 'metadata';
157
+ /**
158
+ * A capture bundle: every screenshot option, and the outputs wanted from the
159
+ * one navigation. `selector`, `clip`, `format`, `quality`, `omit_background`
160
+ * and `fail_on_blank` need `screenshot` among the outputs.
161
+ */
162
+ export interface BundleRequest extends ScreenshotRequest {
163
+ outputs: BundleOutputName[];
164
+ }
165
+ /** One output's line in a bundle's manifest. */
166
+ export interface BundleOutput {
167
+ name: BundleOutputName;
168
+ /** `truncated` is the start of it, to its limit; `omitted` is none of it. */
169
+ status: 'included' | 'truncated' | 'omitted';
170
+ reason?: 'too_large' | 'source_too_large' | 'unparseable';
171
+ part?: string;
172
+ filename?: string;
173
+ content_type?: string;
174
+ bytes?: number;
175
+ sha256?: string;
176
+ source_bytes?: number;
177
+ limit_bytes?: number;
178
+ }
179
+ /** A bundle's manifest: what it holds and how it was captured. */
180
+ export interface BundleManifest {
181
+ version: 1;
182
+ /** The main document's final response; null for `html` input. */
183
+ target: {
184
+ status: number;
185
+ url: string;
186
+ redirects: number;
187
+ } | null;
188
+ capture: {
189
+ started_at: string;
190
+ page_read_at: string;
191
+ screenshot_at?: string;
192
+ finished_at: string;
193
+ };
194
+ outputs: BundleOutput[];
195
+ metadata?: {
196
+ title: string;
197
+ description?: string;
198
+ lang?: string;
199
+ canonical_url?: string;
200
+ robots?: string;
201
+ open_graph?: Record<string, string>;
202
+ };
203
+ /** Frames whose content is not in the HTML, text or Markdown. */
204
+ child_frames: number;
205
+ }
105
206
  export interface SitePreviewRequest extends RenderCommon {
106
207
  url: string;
107
208
  width?: number;
@@ -111,7 +212,7 @@ export interface SitePreviewRequest extends RenderCommon {
111
212
  device?: 'desktop' | 'tablet' | 'mobile';
112
213
  delay_ms?: number;
113
214
  }
114
- export type JobKind = 'screenshot' | 'pdf' | 'qr' | 'image' | 'clip' | 'site_preview';
215
+ export type JobKind = 'screenshot' | 'pdf' | 'qr' | 'image' | 'clip' | 'site_preview' | 'document' | 'bundle';
115
216
  export type JobStatus = 'queued' | 'running' | 'succeeded' | 'failed' | 'cancelled';
116
217
  export interface JobDelivery {
117
218
  webhook_destination?: string;
@@ -135,6 +236,43 @@ export interface JobSubmission {
135
236
  external_id?: string;
136
237
  delivery?: JobDelivery;
137
238
  }
239
+ /**
240
+ * What a render learned about the page: on a job, its webhook and a request
241
+ * history row. A synchronous render gives the same as `X-Ironfang-*` headers.
242
+ */
243
+ export interface RenderDiagnostics {
244
+ settle?: 'stable' | 'ceiling' | 'fixed' | 'skipped';
245
+ settle_ms?: number;
246
+ /** The page's own final HTTP status after redirects; absent for raw HTML. */
247
+ target_status?: number;
248
+ /** The final address, without credentials, query or fragment. */
249
+ target_url?: string;
250
+ target_redirects?: number;
251
+ navigate_ms?: number;
252
+ capture_ms?: number;
253
+ viewport?: {
254
+ width: number;
255
+ height: number;
256
+ scale: number;
257
+ mobile: boolean;
258
+ };
259
+ /** What a `full_page_readiness` pass did. */
260
+ readiness?: {
261
+ profile: 'scroll';
262
+ steps: number;
263
+ ms: number;
264
+ images_loading?: number;
265
+ images_broken?: number;
266
+ };
267
+ /**
268
+ * Output complete as a render but not the whole page: `height_capped`,
269
+ * `scroll_budget_exhausted`, `images_incomplete`, `images_broken`; and
270
+ * `bot_challenge` when the page is recognisably a challenge interstitial.
271
+ */
272
+ warnings?: string[];
273
+ /** The `success_rules` field that failed the render. */
274
+ failed_rule?: 'fail_on_status' | 'require_selector' | 'require_text' | 'forbid_text' | 'fail_on_blank';
275
+ }
138
276
  export interface Job {
139
277
  id: string;
140
278
  kind: JobKind;
@@ -154,6 +292,8 @@ export interface Job {
154
292
  code: string;
155
293
  message: string;
156
294
  };
295
+ /** Once an attempt has loaded the page, failed attempts included. */
296
+ diagnostics?: RenderDiagnostics;
157
297
  result?: {
158
298
  available: boolean;
159
299
  content_type: string;
@@ -168,10 +308,18 @@ export interface SignRequest {
168
308
  kind: 'screenshot' | 'image';
169
309
  url?: string;
170
310
  template?: string;
171
- vars?: Record<string, unknown>;
311
+ /** Placeholder values for an `image` URL: text only, as for `image()`. */
312
+ vars?: Record<string, string>;
172
313
  width?: number;
173
314
  height?: number;
174
315
  full_page?: boolean;
316
+ /** Screenshot only, with `full_page`: as on `ScreenshotRequest`. */
317
+ full_page_readiness?: 'scroll';
318
+ /** Screenshot only: as on `ScreenshotRequest`, signed into the URL. */
319
+ success_rules?: SuccessRules;
320
+ /** Screenshot only: as on `ScreenshotRequest`, signed into the URL. */
321
+ locale?: string;
322
+ timezone?: string;
175
323
  ttl_hours?: number;
176
324
  }
177
325
  export interface SignedUrl {
@@ -253,6 +401,8 @@ export interface RequestRecord {
253
401
  option_fields: string[];
254
402
  client?: string;
255
403
  created_at: string;
404
+ /** For a render that loaded a page; a cache hit has none. */
405
+ diagnostics?: RenderDiagnostics;
256
406
  }
257
407
  export interface RequestFilter {
258
408
  since?: string;
@@ -296,3 +446,79 @@ export interface Destination {
296
446
  /** Present on a webhook destination's creation response only. */
297
447
  signing_secret?: string;
298
448
  }
449
+ /** How a document template prints, kept with each revision. As on `PdfRequest`. */
450
+ export interface DocumentOptions {
451
+ landscape?: boolean;
452
+ paper_format?: 'a3' | 'a4' | 'a5' | 'letter' | 'legal' | 'tabloid';
453
+ margin?: {
454
+ top?: number;
455
+ right?: number;
456
+ bottom?: number;
457
+ left?: number;
458
+ };
459
+ print_background?: boolean;
460
+ header_html?: string;
461
+ footer_html?: string;
462
+ scale?: number;
463
+ /** A custom size with units, such as `210mm`; both or neither, never with `paper_format`. */
464
+ paper_width?: string;
465
+ paper_height?: string;
466
+ /** Let the page's own `@page { size }` decide the page size. */
467
+ prefer_css_page_size?: boolean;
468
+ /** Pages to keep, such as `1-3, 5, 8-`; the whole document is still bounded. */
469
+ page_ranges?: string;
470
+ /** The PDF's document properties. */
471
+ metadata?: {
472
+ title?: string;
473
+ author?: string;
474
+ subject?: string;
475
+ keywords?: string;
476
+ };
477
+ }
478
+ /**
479
+ * A stored PDF template's draft: html/template source over JSON data, a
480
+ * strict JSON Schema subset for the data, print options and sample data.
481
+ */
482
+ export interface DocumentTemplateInput {
483
+ name: string;
484
+ source: string;
485
+ schema?: Record<string, unknown>;
486
+ options?: DocumentOptions;
487
+ sample_data?: Record<string, unknown>;
488
+ }
489
+ export interface DocumentTemplate extends DocumentTemplateInput {
490
+ id: string;
491
+ /** The revision unversioned renders use; null until first published. */
492
+ published_revision: number | null;
493
+ archived_at?: string;
494
+ created_at: string;
495
+ updated_at: string;
496
+ }
497
+ /** An immutable published version of a document template. */
498
+ export interface DocumentRevision {
499
+ id: string;
500
+ template_id: string;
501
+ number: number;
502
+ /** Absent from lists. */
503
+ source?: string;
504
+ schema?: Record<string, unknown>;
505
+ options: DocumentOptions;
506
+ sha256: string;
507
+ created_at: string;
508
+ }
509
+ export interface DocumentRenderRequest {
510
+ data?: Record<string, unknown>;
511
+ /** A revision number; absent or 0 for the published one. */
512
+ revision?: number;
513
+ }
514
+ /** A field the data got wrong, for `data_invalid` (the error's `fields`). */
515
+ export interface DocumentFieldError {
516
+ field: string;
517
+ code: 'required' | 'invalid';
518
+ message: string;
519
+ }
520
+ export interface DocumentValidation {
521
+ valid: boolean;
522
+ fields?: DocumentFieldError[];
523
+ error?: string;
524
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ironfang/render",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
4
4
  "description": "Ironfang Render API client: screenshots, PDFs, QR codes, template images, clips and site previews from a URL or HTML.",
5
5
  "license": "MIT",
6
6
  "type": "module",