@giveitsmaller/sdk 0.16.0 → 0.17.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 +21 -0
- package/dist/_audit.js +12 -2
- package/dist/client.d.ts +8 -1
- package/dist/client.js +12 -1
- package/dist/ergonomic/image_output_routes.d.ts +89 -0
- package/dist/ergonomic/image_output_routes.js +155 -0
- package/dist/ergonomic/option_types.d.ts +147 -0
- package/dist/ergonomic/option_types.js +57 -0
- package/dist/ergonomic/option_validation.d.ts +58 -0
- package/dist/ergonomic/option_validation.js +143 -0
- package/dist/ergonomic/preset_resolver.d.ts +1 -1
- package/dist/ergonomic/preset_resolver.js +35 -33
- package/dist/ergonomic/presets/document_pdf_compress.d.ts +3 -5
- package/dist/ergonomic/presets/document_pdf_compress.js +10 -12
- package/dist/ergonomic/presets/image_compress.d.ts +4 -11
- package/dist/ergonomic/presets/image_compress.js +12 -25
- package/dist/ergonomic/presets/index.d.ts +21 -1
- package/dist/ergonomic/presets/index.js +4 -4
- package/dist/errors.d.ts +25 -0
- package/dist/errors.js +34 -0
- package/dist/file-first.d.ts +78 -58
- package/dist/file-first.js +220 -66
- package/dist/generated/sdk_spec/enums.d.ts +8 -46
- package/dist/generated/sdk_spec/enums.js +12 -33
- package/dist/generated/sdk_spec/presets.js +9 -20
- package/dist/generated/sdk_spec/version.d.ts +3 -3
- package/dist/generated/sdk_spec/version.js +3 -3
- package/dist/gisl.d.ts +8 -1
- package/dist/gisl.js +12 -0
- package/dist/index.core.d.ts +5 -4
- package/dist/index.core.js +3 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -69,6 +69,27 @@ Docs are published in the [giveitsmaller-sdks](https://github.com/AntonioCS/give
|
|
|
69
69
|
- **Troubleshooting** — [`docs/typescript/troubleshooting.md`](https://github.com/AntonioCS/giveitsmaller-sdks/blob/main/docs/typescript/troubleshooting.md)
|
|
70
70
|
- **Examples** — compress, thumbnail, convert, merge, archive — [`docs/typescript/examples/`](https://github.com/AntonioCS/giveitsmaller-sdks/tree/main/docs/typescript/examples)
|
|
71
71
|
|
|
72
|
+
## Contributing: the committed `dist/`
|
|
73
|
+
|
|
74
|
+
Unlike most packages, `packages/typescript/dist/` is **committed**, not gitignored.
|
|
75
|
+
This is deliberate: consumers that install the SDK via the `file:` protocol (the
|
|
76
|
+
e2e canary, the frontend, the API repo, local dev) get whatever is on disk — npm
|
|
77
|
+
does **not** run `prepare`/`prepack` for `file:` deps, so a `dist/` that lagged
|
|
78
|
+
behind `src/` would silently ship stale exports.
|
|
79
|
+
|
|
80
|
+
When you change anything under `src/`, rebuild and commit `dist/`:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm run build # tsc → dist/
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
CI enforces this with a freshness guard that rebuilds `dist/` and fails the PR on
|
|
87
|
+
any `git diff` against the committed tree (mirroring the `git diff --exit-code
|
|
88
|
+
generated/` drift rule). `typescript` is pinned to an exact version so the rebuild
|
|
89
|
+
is reproducible. New hand-written SDK packages (PHP/Python) that grow a build step
|
|
90
|
+
should follow the same commit-`dist`-and-guard convention to avoid reintroducing
|
|
91
|
+
the `file:` gap.
|
|
92
|
+
|
|
72
93
|
## License
|
|
73
94
|
|
|
74
95
|
MIT — see the [LICENSE](https://github.com/AntonioCS/giveitsmaller-sdks/blob/main/LICENSE) file.
|
package/dist/_audit.js
CHANGED
|
@@ -68,6 +68,17 @@ export function _runAudit() {
|
|
|
68
68
|
accept();
|
|
69
69
|
// T2 / xVDTIm8C — operation-builder surface.
|
|
70
70
|
accept();
|
|
71
|
+
// 8yqUXLCS — pin the credits/limits accessor SIGNATURES on ErgonomicClient.
|
|
72
|
+
// accept<ErgonomicClient>() proves the type compiles; these prove the three
|
|
73
|
+
// methods EXIST and their signatures/return types match (indexed access errors
|
|
74
|
+
// if a method is missing; the typed LHS errors if the signature drifts). The
|
|
75
|
+
// RHS is a type-only cast (`null as unknown as …`) — no runtime property read.
|
|
76
|
+
const _creditsSig = null;
|
|
77
|
+
const _creditsUsageSig = null;
|
|
78
|
+
const _limitsSig = null;
|
|
79
|
+
void _creditsSig;
|
|
80
|
+
void _creditsUsageSig;
|
|
81
|
+
void _limitsSig;
|
|
71
82
|
accept();
|
|
72
83
|
accept();
|
|
73
84
|
accept();
|
|
@@ -113,8 +124,6 @@ export function _runAudit() {
|
|
|
113
124
|
accept();
|
|
114
125
|
accept();
|
|
115
126
|
accept();
|
|
116
|
-
accept();
|
|
117
|
-
accept();
|
|
118
127
|
// T4b / 27rE1fZn — preset resolver public types.
|
|
119
128
|
accept();
|
|
120
129
|
accept();
|
|
@@ -136,6 +145,7 @@ export function _runAudit() {
|
|
|
136
145
|
accept();
|
|
137
146
|
accept();
|
|
138
147
|
accept();
|
|
148
|
+
accept();
|
|
139
149
|
// FF5a / Ao8RPVxD — file-first Handle reattach surface.
|
|
140
150
|
accept();
|
|
141
151
|
accept();
|
package/dist/client.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AudioWatermarkDecodeRequest, AudioWatermarkDecodeResponse, ExternalImportCreatedResponse, ExternalImportRequest, LoginUserRequest, LoginUser200ResponseData, ContactRequest, CreditsBalanceResponse, CreditsUsageResponse, UploadResponse, UploadProbeResponse, WorkflowCancelResponse, WorkflowCreateResponse, WorkflowResumeResponse, WorkflowStatusResponse, WorkflowListResponse, WorkflowSummary, WorkflowDownloadResponse, MetadataResponse, RetryResponse } from '@giveitsmaller/contracts/openapi';
|
|
1
|
+
import type { AudioWatermarkDecodeRequest, AudioWatermarkDecodeResponse, ExternalImportCreatedResponse, ExternalImportRequest, LoginUserRequest, LoginUser200ResponseData, ContactRequest, AccountLimits, CreditsBalanceResponse, CreditsUsageResponse, UploadResponse, UploadProbeResponse, WorkflowCancelResponse, WorkflowCreateResponse, WorkflowResumeResponse, WorkflowStatusResponse, WorkflowListResponse, WorkflowSummary, WorkflowDownloadResponse, MetadataResponse, RetryResponse } from '@giveitsmaller/contracts/openapi';
|
|
2
2
|
import type { CreditsUsageOptions, ListWorkflowsOptions, GetSchemaOptions, GetSchemaResult, GislClientConfig, GislSseEvent, PreflightClipsResult, ProbeWaitOptions, ProbeWaitResult, ReadCapabilityOptions, UploadOptions, WaitOptions, WorkflowCreatePayload, _Sdk3HandCodedKeepaliveResult, _Sdk3HandCodedMultipartStatusResult, _Sdk3HandCodedPresignPartsResult } from './types.js';
|
|
3
3
|
export declare const MULTIPART_CONCURRENCY_DEFAULT: 4;
|
|
4
4
|
export declare const DEFAULT_MULTIPART_FIRST_CHUNK_SIZE: number;
|
|
@@ -245,6 +245,13 @@ export declare class GislClient {
|
|
|
245
245
|
* off the error envelope.
|
|
246
246
|
*/
|
|
247
247
|
getCreditsBalance(): Promise<CreditsBalanceResponse>;
|
|
248
|
+
/**
|
|
249
|
+
* Fetch the caller's effective account limits (the tier-resolved caps:
|
|
250
|
+
* upload/merge size + total caps, surfaced override-aware). `GET
|
|
251
|
+
* /api/v2/account/limits`. The success envelope's `data` is unwrapped to the
|
|
252
|
+
* {@link AccountLimits} model (mirrors {@link getCreditsBalance}).
|
|
253
|
+
*/
|
|
254
|
+
getAccountLimits(): Promise<AccountLimits>;
|
|
248
255
|
/**
|
|
249
256
|
* Authenticate with email/password. On success the server issues a
|
|
250
257
|
* session cookie via `Set-Cookie`; subsequent requests authenticate
|
package/dist/client.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// blobByteSource, which never touches these). Kept as a STATIC import (not a
|
|
5
5
|
// dynamic one) so `vi.mock('node:fs/promises')` still intercepts it in tests.
|
|
6
6
|
import { open, stat, basename } from './node-fs.js';
|
|
7
|
-
import { AudioWatermarkDecodeRequestToJSON, AudioWatermarkDecodeResponseFromJSON, ExternalImportCreatedResponseFromJSON, ExternalImportRequestToJSON, LoginUser200ResponseDataFromJSON, CreditsBalanceResponseFromJSON, CreditsUsageResponseFromJSON, UploadResponseFromJSON, UploadProbeResponseFromJSON, MultipartInitiateResponseFromJSON, MultipartInitiateRequestMetadataHintToJSON, MultipartCompleteResponseFromJSON, MultipartCompleteRequestToJSON, WorkflowCancelResponseFromJSON, WorkflowCreateResponseFromJSON, WorkflowResumeResponseFromJSON, WorkflowStatusResponseFromJSON, WorkflowListResponseFromJSON, WorkflowDownloadResponseFromJSON, MetadataResponseFromJSON, OperationsSchemaResponseFromJSON, RetryResponseFromJSON, WorkflowStatus, AuthErrorResponseFromJSON, AuthErrorType, AuthRejectionEnvelopeFromJSON, AuthRejectionEnvelopeErrorTypeEnum, BalanceExhaustedResponseFromJSON, BalanceExhaustedResponseRequiredActionEnum, FeatureNotAvailableResponseFromJSON, FeatureTierRestrictedResponseFromJSON, TierRestrictionKind, TierRestrictionResponseFromJSON, UserTier, WorkflowExpiredResponseFromJSON, ProbePendingResponseFromJSON, UploadSizeExceedsTierResponseFromJSON, UploadDurationExceedsTierResponseFromJSON, UploadConstraintsAppliedProcessingClassPreAssignmentEnum, UploadThresholdsSingleShotMaxBytesEnum, UploadThresholdsMultipartChunkSizeEnum, UploadThresholdsMultipartConcurrencyDefaultEnum, } from '@giveitsmaller/contracts/openapi';
|
|
7
|
+
import { AudioWatermarkDecodeRequestToJSON, AudioWatermarkDecodeResponseFromJSON, ExternalImportCreatedResponseFromJSON, ExternalImportRequestToJSON, LoginUser200ResponseDataFromJSON, AccountLimitsFromJSON, CreditsBalanceResponseFromJSON, CreditsUsageResponseFromJSON, UploadResponseFromJSON, UploadProbeResponseFromJSON, MultipartInitiateResponseFromJSON, MultipartInitiateRequestMetadataHintToJSON, MultipartCompleteResponseFromJSON, MultipartCompleteRequestToJSON, WorkflowCancelResponseFromJSON, WorkflowCreateResponseFromJSON, WorkflowResumeResponseFromJSON, WorkflowStatusResponseFromJSON, WorkflowListResponseFromJSON, WorkflowDownloadResponseFromJSON, MetadataResponseFromJSON, OperationsSchemaResponseFromJSON, RetryResponseFromJSON, WorkflowStatus, AuthErrorResponseFromJSON, AuthErrorType, AuthRejectionEnvelopeFromJSON, AuthRejectionEnvelopeErrorTypeEnum, BalanceExhaustedResponseFromJSON, BalanceExhaustedResponseRequiredActionEnum, FeatureNotAvailableResponseFromJSON, FeatureTierRestrictedResponseFromJSON, TierRestrictionKind, TierRestrictionResponseFromJSON, UserTier, WorkflowExpiredResponseFromJSON, ProbePendingResponseFromJSON, UploadSizeExceedsTierResponseFromJSON, UploadDurationExceedsTierResponseFromJSON, UploadConstraintsAppliedProcessingClassPreAssignmentEnum, UploadThresholdsSingleShotMaxBytesEnum, UploadThresholdsMultipartChunkSizeEnum, UploadThresholdsMultipartConcurrencyDefaultEnum, } from '@giveitsmaller/contracts/openapi';
|
|
8
8
|
import { GislAbortError, GislApiError, GislAuthError, GislAuthRejectionError, GislBalanceExhaustedError, GislError, GislFeatureNotAvailableError, GislFeatureTierRestrictedError, GislMultipartPartCountError, GislMultipartPartError, GislMultipartSessionNotFoundError, GislMultipartSessionOwnershipError, GislMultipartSessionAuthRequiredError, GislTierRestrictedError, GislTimeoutError, GislProbePendingError, GislUploadCapExceededError, GislValidationError, GislWorkflowExpiredError, } from './errors.js';
|
|
9
9
|
import { parseSseStream } from './sse.js';
|
|
10
10
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
@@ -1934,6 +1934,17 @@ export class GislClient {
|
|
|
1934
1934
|
deserialize: CreditsBalanceResponseFromJSON,
|
|
1935
1935
|
});
|
|
1936
1936
|
}
|
|
1937
|
+
/**
|
|
1938
|
+
* Fetch the caller's effective account limits (the tier-resolved caps:
|
|
1939
|
+
* upload/merge size + total caps, surfaced override-aware). `GET
|
|
1940
|
+
* /api/v2/account/limits`. The success envelope's `data` is unwrapped to the
|
|
1941
|
+
* {@link AccountLimits} model (mirrors {@link getCreditsBalance}).
|
|
1942
|
+
*/
|
|
1943
|
+
async getAccountLimits() {
|
|
1944
|
+
return this.request('GET', '/api/v2/account/limits', {
|
|
1945
|
+
deserialize: AccountLimitsFromJSON,
|
|
1946
|
+
});
|
|
1947
|
+
}
|
|
1937
1948
|
// -----------------------------------------------------------------------
|
|
1938
1949
|
// Auth
|
|
1939
1950
|
// -----------------------------------------------------------------------
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route-aware image "Output" model (card YNLrGhNo, contracts tewB37Jg / v2.97.0).
|
|
3
|
+
*
|
|
4
|
+
* The file-first `output()`/`resize()` helpers resolve a single user-facing
|
|
5
|
+
* "Output" operation to the right underlying wire op + options, driven by the
|
|
6
|
+
* contract's `accepted-options/image-output-routes.json` projection. The route is
|
|
7
|
+
* `(input format token, output_format token)`:
|
|
8
|
+
* - `same_format` (output == input) → `source_op: compress` (libcaesium optimiser),
|
|
9
|
+
* wire `output_format: 'original'`;
|
|
10
|
+
* - `format_change` (output != input) → `source_op: convert` (transcoder),
|
|
11
|
+
* wire `output_format: <token>`.
|
|
12
|
+
*
|
|
13
|
+
* Each route cell lists the options the worker HONORS (live) and PLANS (advertised,
|
|
14
|
+
* not yet honored — gated unavailable). Resize (`width`/`height`/`fit`) is
|
|
15
|
+
* **INPUT-keyed**: it lives only on the `same_format[input]` cell but applies on
|
|
16
|
+
* EITHER route, gated by input resizability (raster only — `svg` is vector and
|
|
17
|
+
* carries no resize). So a `png → webp + resize` request reads its resize
|
|
18
|
+
* capability from `same_format.png` and its transcoder options from
|
|
19
|
+
* `format_change.webp`.
|
|
20
|
+
*
|
|
21
|
+
* This hand table MIRRORS the generated projection and is PINNED to it by
|
|
22
|
+
* `output-route-conformance.test.ts` (the watermark-capability-gate precedent) —
|
|
23
|
+
* a contract regen that changes a route's source_op / honored / planned options
|
|
24
|
+
* fails that test. Kept a hand table (not a runtime JSON read) so the gate is
|
|
25
|
+
* browser-safe, exactly like {@link WATERMARK_CAPABILITY}. Mirrored by the PHP
|
|
26
|
+
* `ImageOutputRoutes`.
|
|
27
|
+
*/
|
|
28
|
+
/** The resize option keys — input-keyed, raster-only (see module doc). */
|
|
29
|
+
export declare const RESIZE_KEYS: readonly ["width", "height", "fit"];
|
|
30
|
+
/** Image area cap shared by every resizable route (projection `max_output_pixels`). */
|
|
31
|
+
export declare const MAX_OUTPUT_PIXELS = 16000000;
|
|
32
|
+
/**
|
|
33
|
+
* Output formats reachable via the legacy `compress(output_format=…)` facade
|
|
34
|
+
* (projection `facade_managed_outputs`). Used ONLY as the undetectable-input
|
|
35
|
+
* fallback — a detectable input always routes via `source_op`.
|
|
36
|
+
*/
|
|
37
|
+
export declare const FACADE_MANAGED_OUTPUTS: readonly string[];
|
|
38
|
+
interface RouteCell {
|
|
39
|
+
readonly honored: readonly string[];
|
|
40
|
+
readonly planned: readonly string[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Per-route, per-output-format honored + planned option keys, mirroring
|
|
44
|
+
* `image-output-routes.json` `media.image`. `source_op` is uniform
|
|
45
|
+
* (same_format→compress, format_change→convert) so it is a derivation rule, not
|
|
46
|
+
* a table column. `same_format` is keyed by the INPUT token; `format_change` by
|
|
47
|
+
* the OUTPUT token.
|
|
48
|
+
*/
|
|
49
|
+
export declare const IMAGE_OUTPUT_ROUTES: {
|
|
50
|
+
readonly same_format: Readonly<Record<string, RouteCell>>;
|
|
51
|
+
readonly format_change: Readonly<Record<string, RouteCell>>;
|
|
52
|
+
};
|
|
53
|
+
export type OutputRoute = 'same_format' | 'format_change';
|
|
54
|
+
/** A resolved Output lowering target. */
|
|
55
|
+
export interface ResolvedOutputRoute {
|
|
56
|
+
readonly route: OutputRoute;
|
|
57
|
+
/** The wire op to emit. */
|
|
58
|
+
readonly sourceOp: 'compress' | 'convert';
|
|
59
|
+
/** The wire `output_format` value ('original' for same_format, the token for format_change). */
|
|
60
|
+
readonly outputFormatWire: string;
|
|
61
|
+
/** The input format token the route resolved against (for per-value gating). */
|
|
62
|
+
readonly inputToken: string;
|
|
63
|
+
/** Effective honored option keys (incl. input-keyed resize on format_change). */
|
|
64
|
+
readonly honored: ReadonlySet<string>;
|
|
65
|
+
/** Planned option keys → gate as `feature_not_available`. */
|
|
66
|
+
readonly planned: ReadonlySet<string>;
|
|
67
|
+
}
|
|
68
|
+
/** The bare format token for a MIME type, or undefined if not a known image MIME. */
|
|
69
|
+
export declare function tokenForMime(mime: string): string | undefined;
|
|
70
|
+
/** The bare format token for a filename / path extension, or undefined. */
|
|
71
|
+
export declare function tokenForPath(path: string): string | undefined;
|
|
72
|
+
/** Every image format token the projection knows (for validation / tests). */
|
|
73
|
+
export declare function knownImageTokens(): ReadonlySet<string>;
|
|
74
|
+
/**
|
|
75
|
+
* Resolve an Output request to its wire op + gating sets. Returns undefined when
|
|
76
|
+
* the route is unrepresentable (e.g. converting TO a format no `format_change`
|
|
77
|
+
* cell covers). `outputFormat` undefined → same-format (keep input format).
|
|
78
|
+
*/
|
|
79
|
+
export declare function resolveOutputRoute(inputToken: string, outputFormat: string | undefined): ResolvedOutputRoute | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* Whether a specific VALUE of an option is `availability: 'planned'` for the
|
|
82
|
+
* given input format — the per-value gate (e.g. `metadata: 'keep'` is planned
|
|
83
|
+
* even though the `metadata` key is honored). Reads the generated
|
|
84
|
+
* `compressMetadata` `per_value_availability`; same_format only (the only route
|
|
85
|
+
* where value-level options like `metadata` are honored). Returns false when the
|
|
86
|
+
* option / value / group is unknown (no gate).
|
|
87
|
+
*/
|
|
88
|
+
export declare function isPlannedValue(inputToken: string, optionKey: string, value: unknown): boolean;
|
|
89
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route-aware image "Output" model (card YNLrGhNo, contracts tewB37Jg / v2.97.0).
|
|
3
|
+
*
|
|
4
|
+
* The file-first `output()`/`resize()` helpers resolve a single user-facing
|
|
5
|
+
* "Output" operation to the right underlying wire op + options, driven by the
|
|
6
|
+
* contract's `accepted-options/image-output-routes.json` projection. The route is
|
|
7
|
+
* `(input format token, output_format token)`:
|
|
8
|
+
* - `same_format` (output == input) → `source_op: compress` (libcaesium optimiser),
|
|
9
|
+
* wire `output_format: 'original'`;
|
|
10
|
+
* - `format_change` (output != input) → `source_op: convert` (transcoder),
|
|
11
|
+
* wire `output_format: <token>`.
|
|
12
|
+
*
|
|
13
|
+
* Each route cell lists the options the worker HONORS (live) and PLANS (advertised,
|
|
14
|
+
* not yet honored — gated unavailable). Resize (`width`/`height`/`fit`) is
|
|
15
|
+
* **INPUT-keyed**: it lives only on the `same_format[input]` cell but applies on
|
|
16
|
+
* EITHER route, gated by input resizability (raster only — `svg` is vector and
|
|
17
|
+
* carries no resize). So a `png → webp + resize` request reads its resize
|
|
18
|
+
* capability from `same_format.png` and its transcoder options from
|
|
19
|
+
* `format_change.webp`.
|
|
20
|
+
*
|
|
21
|
+
* This hand table MIRRORS the generated projection and is PINNED to it by
|
|
22
|
+
* `output-route-conformance.test.ts` (the watermark-capability-gate precedent) —
|
|
23
|
+
* a contract regen that changes a route's source_op / honored / planned options
|
|
24
|
+
* fails that test. Kept a hand table (not a runtime JSON read) so the gate is
|
|
25
|
+
* browser-safe, exactly like {@link WATERMARK_CAPABILITY}. Mirrored by the PHP
|
|
26
|
+
* `ImageOutputRoutes`.
|
|
27
|
+
*/
|
|
28
|
+
import { compressMetadata } from '@giveitsmaller/contracts/operations';
|
|
29
|
+
/** The resize option keys — input-keyed, raster-only (see module doc). */
|
|
30
|
+
export const RESIZE_KEYS = ['width', 'height', 'fit'];
|
|
31
|
+
/** Image area cap shared by every resizable route (projection `max_output_pixels`). */
|
|
32
|
+
export const MAX_OUTPUT_PIXELS = 16_000_000;
|
|
33
|
+
/**
|
|
34
|
+
* Output formats reachable via the legacy `compress(output_format=…)` facade
|
|
35
|
+
* (projection `facade_managed_outputs`). Used ONLY as the undetectable-input
|
|
36
|
+
* fallback — a detectable input always routes via `source_op`.
|
|
37
|
+
*/
|
|
38
|
+
export const FACADE_MANAGED_OUTPUTS = ['webp'];
|
|
39
|
+
/** Canonical MIME → bare format token (projection `mime_tokens`). */
|
|
40
|
+
const MIME_TOKEN = {
|
|
41
|
+
'image/avif': 'avif',
|
|
42
|
+
'image/gif': 'gif',
|
|
43
|
+
'image/jpeg': 'jpeg',
|
|
44
|
+
'image/png': 'png',
|
|
45
|
+
'image/svg+xml': 'svg',
|
|
46
|
+
'image/tiff': 'tiff',
|
|
47
|
+
'image/webp': 'webp',
|
|
48
|
+
};
|
|
49
|
+
/** File extension → bare format token (for path / named-blob inputs). */
|
|
50
|
+
const EXT_TOKEN = {
|
|
51
|
+
jpg: 'jpeg', jpeg: 'jpeg', jpe: 'jpeg', jfif: 'jpeg',
|
|
52
|
+
png: 'png', webp: 'webp', gif: 'gif', avif: 'avif',
|
|
53
|
+
tif: 'tiff', tiff: 'tiff', svg: 'svg',
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Per-route, per-output-format honored + planned option keys, mirroring
|
|
57
|
+
* `image-output-routes.json` `media.image`. `source_op` is uniform
|
|
58
|
+
* (same_format→compress, format_change→convert) so it is a derivation rule, not
|
|
59
|
+
* a table column. `same_format` is keyed by the INPUT token; `format_change` by
|
|
60
|
+
* the OUTPUT token.
|
|
61
|
+
*/
|
|
62
|
+
export const IMAGE_OUTPUT_ROUTES = {
|
|
63
|
+
same_format: {
|
|
64
|
+
avif: { honored: ['avif_speed', 'fit', 'height', 'metadata', 'output_format', 'quality', 'width'], planned: [] },
|
|
65
|
+
gif: { honored: ['fit', 'height', 'metadata', 'output_format', 'quality', 'width'], planned: [] },
|
|
66
|
+
jpeg: { honored: ['fit', 'height', 'metadata', 'output_format', 'progressive', 'quality', 'width'], planned: ['lossless'] },
|
|
67
|
+
png: { honored: ['fit', 'height', 'metadata', 'optimization_level', 'output_format', 'quality', 'width'], planned: ['lossy'] },
|
|
68
|
+
svg: { honored: ['metadata', 'output_format', 'quality'], planned: [] },
|
|
69
|
+
tiff: { honored: ['fit', 'height', 'metadata', 'output_format', 'quality', 'width'], planned: [] },
|
|
70
|
+
webp: { honored: ['fit', 'height', 'metadata', 'output_format', 'quality', 'width'], planned: ['lossless'] },
|
|
71
|
+
},
|
|
72
|
+
format_change: {
|
|
73
|
+
avif: { honored: ['output_format', 'quality'], planned: [] },
|
|
74
|
+
gif: { honored: ['output_format'], planned: [] },
|
|
75
|
+
jpeg: { honored: ['background', 'output_format', 'quality'], planned: [] },
|
|
76
|
+
png: { honored: ['output_format'], planned: [] },
|
|
77
|
+
tiff: { honored: ['output_format'], planned: [] },
|
|
78
|
+
webp: { honored: ['output_format', 'quality'], planned: [] },
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
/** The bare format token for a MIME type, or undefined if not a known image MIME. */
|
|
82
|
+
export function tokenForMime(mime) {
|
|
83
|
+
return MIME_TOKEN[mime.split(';')[0].trim().toLowerCase()];
|
|
84
|
+
}
|
|
85
|
+
/** The bare format token for a filename / path extension, or undefined. */
|
|
86
|
+
export function tokenForPath(path) {
|
|
87
|
+
const ext = path.toLowerCase().split('.').pop();
|
|
88
|
+
return ext !== undefined ? EXT_TOKEN[ext] : undefined;
|
|
89
|
+
}
|
|
90
|
+
/** Every image format token the projection knows (for validation / tests). */
|
|
91
|
+
export function knownImageTokens() {
|
|
92
|
+
return new Set(Object.keys(IMAGE_OUTPUT_ROUTES.same_format));
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Resolve an Output request to its wire op + gating sets. Returns undefined when
|
|
96
|
+
* the route is unrepresentable (e.g. converting TO a format no `format_change`
|
|
97
|
+
* cell covers). `outputFormat` undefined → same-format (keep input format).
|
|
98
|
+
*/
|
|
99
|
+
export function resolveOutputRoute(inputToken, outputFormat) {
|
|
100
|
+
const outToken = outputFormat ?? inputToken;
|
|
101
|
+
if (outToken === inputToken) {
|
|
102
|
+
const cell = IMAGE_OUTPUT_ROUTES.same_format[inputToken];
|
|
103
|
+
if (cell === undefined)
|
|
104
|
+
return undefined;
|
|
105
|
+
return {
|
|
106
|
+
route: 'same_format',
|
|
107
|
+
sourceOp: 'compress',
|
|
108
|
+
outputFormatWire: 'original',
|
|
109
|
+
inputToken,
|
|
110
|
+
honored: new Set(cell.honored),
|
|
111
|
+
planned: new Set(cell.planned),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const cell = IMAGE_OUTPUT_ROUTES.format_change[outToken];
|
|
115
|
+
if (cell === undefined)
|
|
116
|
+
return undefined;
|
|
117
|
+
// Input-keyed resize: the format_change cell carries only transcoder options;
|
|
118
|
+
// resize capability comes from the INPUT's same_format cell (raster only).
|
|
119
|
+
const inCell = IMAGE_OUTPUT_ROUTES.same_format[inputToken];
|
|
120
|
+
const resize = inCell ? RESIZE_KEYS.filter((k) => inCell.honored.includes(k)) : [];
|
|
121
|
+
return {
|
|
122
|
+
route: 'format_change',
|
|
123
|
+
sourceOp: 'convert',
|
|
124
|
+
outputFormatWire: outToken,
|
|
125
|
+
inputToken,
|
|
126
|
+
honored: new Set([...cell.honored, ...resize]),
|
|
127
|
+
planned: new Set(cell.planned),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** Input token → its `compress.image*` mime-group name (for per-value availability lookup). */
|
|
131
|
+
function compressGroupForToken(token) {
|
|
132
|
+
if (token === 'jpeg')
|
|
133
|
+
return 'image_jpeg';
|
|
134
|
+
if (token === 'png')
|
|
135
|
+
return 'image_png';
|
|
136
|
+
if (token === 'avif')
|
|
137
|
+
return 'image_avif';
|
|
138
|
+
return 'image'; // webp / gif / svg / tiff
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Whether a specific VALUE of an option is `availability: 'planned'` for the
|
|
142
|
+
* given input format — the per-value gate (e.g. `metadata: 'keep'` is planned
|
|
143
|
+
* even though the `metadata` key is honored). Reads the generated
|
|
144
|
+
* `compressMetadata` `per_value_availability`; same_format only (the only route
|
|
145
|
+
* where value-level options like `metadata` are honored). Returns false when the
|
|
146
|
+
* option / value / group is unknown (no gate).
|
|
147
|
+
*/
|
|
148
|
+
export function isPlannedValue(inputToken, optionKey, value) {
|
|
149
|
+
const group = compressMetadata.mime_groups[compressGroupForToken(inputToken)];
|
|
150
|
+
const opt = group?.options[optionKey];
|
|
151
|
+
if (opt === undefined)
|
|
152
|
+
return false;
|
|
153
|
+
const entry = opt.per_value_availability[String(value)];
|
|
154
|
+
return entry?.availability === 'planned';
|
|
155
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed per-op option interfaces for the ergonomic verbs (card Dhje3Faq).
|
|
3
|
+
* These replace the untyped `Record<string, unknown>` bags so the IDE can offer
|
|
4
|
+
* key completion and `tsc` rejects typos. The KEY SET of each interface is pinned
|
|
5
|
+
* to the contract two ways: (1) the source-level `Equal<...>` assertions below tie
|
|
6
|
+
* each interface to its `*_OPTION_KEYS` tuple at `tsc` time; (2) the wire-key
|
|
7
|
+
* conformance guard ties each tuple (∪ positional-owned) to the generated
|
|
8
|
+
* `OperationMetadata` at test time. Value types are best-effort (per-value/enum
|
|
9
|
+
* sync is out of scope — keys are the contract anchor).
|
|
10
|
+
*
|
|
11
|
+
* Keys a verb owns via a positional argument are EXCLUDED from its interface
|
|
12
|
+
* (`output_format` on convert, `text` on textWatermark) — they are set by the
|
|
13
|
+
* first argument and rejected if supplied in the bag (see `option_validation.ts`).
|
|
14
|
+
*
|
|
15
|
+
* Mirrored by the PHP array-shape docblocks — keep in lockstep.
|
|
16
|
+
*/
|
|
17
|
+
/** 9-grid anchor shared by text/image/video watermark. */
|
|
18
|
+
export type WatermarkAnchor = 'top_left' | 'top_center' | 'top_right' | 'center_left' | 'center' | 'center_right' | 'bottom_left' | 'bottom_center' | 'bottom_right';
|
|
19
|
+
export interface ConvertOptions {
|
|
20
|
+
/** Output quality for lossy image formats (1-100). */
|
|
21
|
+
quality?: number;
|
|
22
|
+
/** Background colour (hex) for transparent images → JPEG. */
|
|
23
|
+
background?: string;
|
|
24
|
+
/** Video Constant Rate Factor (0 best — 51 worst). */
|
|
25
|
+
crf?: number;
|
|
26
|
+
/** Trim from the start, in seconds (video). */
|
|
27
|
+
trim_start?: number;
|
|
28
|
+
/** Trim from the end, in seconds (video). */
|
|
29
|
+
trim_end?: number;
|
|
30
|
+
/** Output frame rate for video → GIF. */
|
|
31
|
+
fps?: number;
|
|
32
|
+
/** Max output width in pixels (video → GIF downscale cap). */
|
|
33
|
+
width?: number;
|
|
34
|
+
/** GIF palette size (2-256). */
|
|
35
|
+
max_colors?: number;
|
|
36
|
+
/** GIF loop count (0 infinite, N>0 N times, -1 once). */
|
|
37
|
+
loop?: number;
|
|
38
|
+
/** GIF dithering method. */
|
|
39
|
+
dither?: 'none' | 'bayer' | 'floyd_steinberg' | 'sierra2' | 'sierra2_4a';
|
|
40
|
+
/** Output bitrate in kbps (lossy audio). */
|
|
41
|
+
bitrate?: 64 | 96 | 128 | 192 | 256 | 320;
|
|
42
|
+
/** Page selection for PDF → image (e.g. '1-5,8'). */
|
|
43
|
+
pages?: string;
|
|
44
|
+
/** Render resolution in DPI for PDF → image. */
|
|
45
|
+
dpi?: number;
|
|
46
|
+
}
|
|
47
|
+
export interface ThumbnailOptions {
|
|
48
|
+
/** Target width in pixels (1-16384). REQUIRED. */
|
|
49
|
+
width: number;
|
|
50
|
+
/** Target height in pixels (1-16384). REQUIRED. */
|
|
51
|
+
height: number;
|
|
52
|
+
/** Resize mode. */
|
|
53
|
+
fit?: 'max' | 'crop' | 'scale';
|
|
54
|
+
/** Output format for the thumbnail. */
|
|
55
|
+
format?: 'jpg' | 'png' | 'webp';
|
|
56
|
+
/** Output quality for lossy thumbnail formats (image input). */
|
|
57
|
+
quality?: number;
|
|
58
|
+
/** Frame timestamp for video input (e.g. '00:00:01'). */
|
|
59
|
+
timestamp?: string;
|
|
60
|
+
/** Document source: a printed page or the cover. */
|
|
61
|
+
source?: 'page' | 'cover';
|
|
62
|
+
/** 1-based page index for document input. */
|
|
63
|
+
page?: number;
|
|
64
|
+
}
|
|
65
|
+
export interface TextWatermarkOptions {
|
|
66
|
+
/** Font size in pixels (8-512). */
|
|
67
|
+
font_size?: number;
|
|
68
|
+
/** Text colour as hex RGB/RGBA (e.g. '#FFFFFF80'). */
|
|
69
|
+
color?: string;
|
|
70
|
+
/** Font family (bundled). */
|
|
71
|
+
font_family?: 'liberation_sans';
|
|
72
|
+
/** Rotation angle in degrees (-360..360). */
|
|
73
|
+
rotation?: number;
|
|
74
|
+
/** Rendering mode. */
|
|
75
|
+
watermark_mode?: 'single' | 'tiled';
|
|
76
|
+
/** Spacing between tiled labels in pixels (tiled mode). */
|
|
77
|
+
tile_spacing?: number;
|
|
78
|
+
/** 9-grid anchor position. */
|
|
79
|
+
anchor?: WatermarkAnchor;
|
|
80
|
+
/** Horizontal offset from the anchor (e.g. '40px' or '5%'). */
|
|
81
|
+
margin_x?: string;
|
|
82
|
+
/** Vertical offset from the anchor. */
|
|
83
|
+
margin_y?: string;
|
|
84
|
+
/** Overlay opacity (0-1). */
|
|
85
|
+
opacity?: number;
|
|
86
|
+
}
|
|
87
|
+
export interface WatermarkOptions {
|
|
88
|
+
/** 9-grid anchor position. */
|
|
89
|
+
anchor?: WatermarkAnchor;
|
|
90
|
+
/** Horizontal offset from the anchor (e.g. '40px' or '5%'). */
|
|
91
|
+
margin_x?: string;
|
|
92
|
+
/** Vertical offset from the anchor. */
|
|
93
|
+
margin_y?: string;
|
|
94
|
+
/** Overlay opacity (0-1). */
|
|
95
|
+
opacity?: number;
|
|
96
|
+
/** Overlay width (e.g. '120px' or '20%'). */
|
|
97
|
+
overlay_width?: string;
|
|
98
|
+
}
|
|
99
|
+
/** Resize mode (contract `fit` enum, v2.97.0). */
|
|
100
|
+
export type OutputFit = 'max' | 'crop' | 'scale';
|
|
101
|
+
/** Metadata policy (contract `metadata` enum, v2.97.0). `keep` is `availability:planned`. */
|
|
102
|
+
export type OutputMetadata = 'all' | 'keep';
|
|
103
|
+
/**
|
|
104
|
+
* Options for the file-first `output()` image transform. The KEY SET is the
|
|
105
|
+
* UNION of every image route's honored + planned option keys (image-output-routes
|
|
106
|
+
* projection); the PER-ROUTE honored/planned narrowing happens in the lowering
|
|
107
|
+
* (`resolveOutputRoute`), so supplying an option not honored on the resolved
|
|
108
|
+
* route (or a planned one) throws pre-upload. `output_format` is set via the
|
|
109
|
+
* positional `format` argument, so it is excluded here. Resize (`width`/`height`/
|
|
110
|
+
* `fit`) is honored on raster routes; `height` is optional (width-only resize).
|
|
111
|
+
*/
|
|
112
|
+
export interface OutputOptions {
|
|
113
|
+
/** Output quality for lossy formats (1-100). Honored: avif/jpeg/webp routes. */
|
|
114
|
+
quality?: number;
|
|
115
|
+
/** Resize target width in px (1-16384; width*height <= 16MP). */
|
|
116
|
+
width?: number;
|
|
117
|
+
/** Resize target height in px (optional — width-only resize preserves aspect). */
|
|
118
|
+
height?: number;
|
|
119
|
+
/** Resize mode (applies when width or height is set). */
|
|
120
|
+
fit?: OutputFit;
|
|
121
|
+
/** Background colour (hex) for transparent images → JPEG. Honored: format_change→jpeg only. */
|
|
122
|
+
background?: string;
|
|
123
|
+
/** Progressive JPEG. Honored: same_format jpeg only. */
|
|
124
|
+
progressive?: boolean;
|
|
125
|
+
/** PNG lossless optimisation effort. Honored: same_format png only. */
|
|
126
|
+
optimization_level?: number;
|
|
127
|
+
/** AVIF encode speed. Honored: same_format avif only. */
|
|
128
|
+
avif_speed?: number;
|
|
129
|
+
/** Metadata policy. Honored: same_format routes. (`keep` value is planned.) */
|
|
130
|
+
metadata?: OutputMetadata;
|
|
131
|
+
/** JPEG/WebP lossless. PLANNED (gated unavailable). */
|
|
132
|
+
lossless?: boolean;
|
|
133
|
+
/** Lossy PNG quantization. PLANNED (gated unavailable; licence-gated). */
|
|
134
|
+
lossy?: boolean;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The user-supplyable option keys per verb (excludes positional-owned keys).
|
|
138
|
+
* Exported for the wire-key conformance guard, which asserts each tuple ∪ its
|
|
139
|
+
* positional-owned keys equals the contract `operationOptionKeys(metadata)`.
|
|
140
|
+
*/
|
|
141
|
+
export declare const VERB_OPTION_KEYS: {
|
|
142
|
+
readonly convert: readonly ["quality", "background", "crf", "trim_start", "trim_end", "fps", "width", "max_colors", "loop", "dither", "bitrate", "pages", "dpi"];
|
|
143
|
+
readonly thumbnail: readonly ["width", "height", "fit", "format", "quality", "timestamp", "source", "page"];
|
|
144
|
+
readonly textWatermark: readonly ["font_size", "color", "font_family", "rotation", "watermark_mode", "tile_spacing", "anchor", "margin_x", "margin_y", "opacity"];
|
|
145
|
+
readonly watermark: readonly ["anchor", "margin_x", "margin_y", "opacity", "overlay_width"];
|
|
146
|
+
readonly output: readonly ["quality", "width", "height", "fit", "background", "progressive", "optimization_level", "avif_speed", "metadata", "lossless", "lossy"];
|
|
147
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed per-op option interfaces for the ergonomic verbs (card Dhje3Faq).
|
|
3
|
+
* These replace the untyped `Record<string, unknown>` bags so the IDE can offer
|
|
4
|
+
* key completion and `tsc` rejects typos. The KEY SET of each interface is pinned
|
|
5
|
+
* to the contract two ways: (1) the source-level `Equal<...>` assertions below tie
|
|
6
|
+
* each interface to its `*_OPTION_KEYS` tuple at `tsc` time; (2) the wire-key
|
|
7
|
+
* conformance guard ties each tuple (∪ positional-owned) to the generated
|
|
8
|
+
* `OperationMetadata` at test time. Value types are best-effort (per-value/enum
|
|
9
|
+
* sync is out of scope — keys are the contract anchor).
|
|
10
|
+
*
|
|
11
|
+
* Keys a verb owns via a positional argument are EXCLUDED from its interface
|
|
12
|
+
* (`output_format` on convert, `text` on textWatermark) — they are set by the
|
|
13
|
+
* first argument and rejected if supplied in the bag (see `option_validation.ts`).
|
|
14
|
+
*
|
|
15
|
+
* Mirrored by the PHP array-shape docblocks — keep in lockstep.
|
|
16
|
+
*/
|
|
17
|
+
const CONVERT_OPTION_KEYS = [
|
|
18
|
+
'quality', 'background', 'crf', 'trim_start', 'trim_end', 'fps', 'width',
|
|
19
|
+
'max_colors', 'loop', 'dither', 'bitrate', 'pages', 'dpi',
|
|
20
|
+
];
|
|
21
|
+
const THUMBNAIL_OPTION_KEYS = [
|
|
22
|
+
'width', 'height', 'fit', 'format', 'quality', 'timestamp', 'source', 'page',
|
|
23
|
+
];
|
|
24
|
+
const TEXT_WATERMARK_OPTION_KEYS = [
|
|
25
|
+
'font_size', 'color', 'font_family', 'rotation', 'watermark_mode',
|
|
26
|
+
'tile_spacing', 'anchor', 'margin_x', 'margin_y', 'opacity',
|
|
27
|
+
];
|
|
28
|
+
const WATERMARK_OPTION_KEYS = [
|
|
29
|
+
'anchor', 'margin_x', 'margin_y', 'opacity', 'overlay_width',
|
|
30
|
+
];
|
|
31
|
+
const OUTPUT_OPTION_KEYS = [
|
|
32
|
+
'quality', 'width', 'height', 'fit', 'background', 'progressive',
|
|
33
|
+
'optimization_level', 'avif_speed', 'metadata', 'lossless', 'lossy',
|
|
34
|
+
];
|
|
35
|
+
const _convertKeysMatch = true;
|
|
36
|
+
const _thumbnailKeysMatch = true;
|
|
37
|
+
const _textWatermarkKeysMatch = true;
|
|
38
|
+
const _watermarkKeysMatch = true;
|
|
39
|
+
const _outputKeysMatch = true;
|
|
40
|
+
// Reference the assertions so `noUnusedLocals` doesn't strip them.
|
|
41
|
+
void _convertKeysMatch;
|
|
42
|
+
void _thumbnailKeysMatch;
|
|
43
|
+
void _textWatermarkKeysMatch;
|
|
44
|
+
void _watermarkKeysMatch;
|
|
45
|
+
void _outputKeysMatch;
|
|
46
|
+
/**
|
|
47
|
+
* The user-supplyable option keys per verb (excludes positional-owned keys).
|
|
48
|
+
* Exported for the wire-key conformance guard, which asserts each tuple ∪ its
|
|
49
|
+
* positional-owned keys equals the contract `operationOptionKeys(metadata)`.
|
|
50
|
+
*/
|
|
51
|
+
export const VERB_OPTION_KEYS = {
|
|
52
|
+
convert: CONVERT_OPTION_KEYS,
|
|
53
|
+
thumbnail: THUMBNAIL_OPTION_KEYS,
|
|
54
|
+
textWatermark: TEXT_WATERMARK_OPTION_KEYS,
|
|
55
|
+
watermark: WATERMARK_OPTION_KEYS,
|
|
56
|
+
output: OUTPUT_OPTION_KEYS,
|
|
57
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type OperationMetadata } from '@giveitsmaller/contracts/operations';
|
|
2
|
+
/**
|
|
3
|
+
* Eager, synchronous, PRE-UPLOAD option-key validation for the ergonomic verbs
|
|
4
|
+
* (card Dhje3Faq). The file-first builders accept verb options as untyped bags;
|
|
5
|
+
* a user typo (`{ quaity: 80 }`) would otherwise flow to the server and 422.
|
|
6
|
+
* This module rejects unknown keys at the verb call — before any upload —
|
|
7
|
+
* mirroring the existing `compress()` optimize-check and the watermark eager
|
|
8
|
+
* gate. The allowed key set is read from the generated `OperationMetadata`
|
|
9
|
+
* sidecars (the same contract-anchored source the wire-key conformance guard
|
|
10
|
+
* uses), so it can never silently drift from the contract.
|
|
11
|
+
*
|
|
12
|
+
* SCOPE: `convert` / `thumbnail` / `textWatermark` / `watermark` only. `compress`
|
|
13
|
+
* is deliberately EXCLUDED — its bag legitimately carries SDK-only keys
|
|
14
|
+
* (`optimize`, `presetOverrides`) and camelCase resolver aliases (`targetSize`,
|
|
15
|
+
* `outputFormat`) that are not `operationOptionKeys(compressMetadata)`; it has its
|
|
16
|
+
* own `unknown_field` validation through the preset resolver.
|
|
17
|
+
*
|
|
18
|
+
* Mirrored by the PHP `OptionValidation` helper — keep the two in lockstep.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* OPERATION-LEVEL contract option keys (the keys valid in `OperationDef.options`):
|
|
22
|
+
* the union of every mime group's `options` plus `direct_options` for
|
|
23
|
+
* media-agnostic ops. Deliberately EXCLUDES `per_input_options` (valid only on a
|
|
24
|
+
* merge input). Promoted from the wire-key conformance guard for runtime reuse.
|
|
25
|
+
*/
|
|
26
|
+
export declare function operationOptionKeys(metadata: OperationMetadata): ReadonlySet<string>;
|
|
27
|
+
/** The ergonomic verbs whose option bags this module key-validates. */
|
|
28
|
+
export type ValidatedVerb = 'convert' | 'thumbnail' | 'textWatermark' | 'watermark' | 'output';
|
|
29
|
+
/** Accessor for the conformance guard (pins these sets to the contract metadata). */
|
|
30
|
+
export declare function allowedKeysFor(verb: ValidatedVerb): ReadonlySet<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Validate a USER-supplied options bag for an ergonomic verb. Throws
|
|
33
|
+
* {@link GislConfigError} (reason `unknown_field`) synchronously, BEFORE any
|
|
34
|
+
* upload or wire-key injection. Call this at the TOP of every verb body, before
|
|
35
|
+
* the `format`-drop / `output_format` / `text` injection.
|
|
36
|
+
*
|
|
37
|
+
* @throws {GislConfigError} reason `unknown_field` when the bag carries a key the
|
|
38
|
+
* verb owns via a positional argument, or a key absent from the op's contract
|
|
39
|
+
* option set.
|
|
40
|
+
*/
|
|
41
|
+
export declare function validateVerbOptions(verb: ValidatedVerb, options: object | null | undefined): void;
|
|
42
|
+
/**
|
|
43
|
+
* Assert thumbnail `width` AND `height` are both present and non-nullish (the
|
|
44
|
+
* contract marks both `required` for image/video/document). The typed signature
|
|
45
|
+
* already enforces this at compile time; this RUNTIME guard catches JS callers and
|
|
46
|
+
* an explicit `undefined`/`null` BEFORE upload. Rejecting `null` (not just
|
|
47
|
+
* `undefined`) keeps TS in lockstep with the PHP `assertThumbnailDimensions`, which
|
|
48
|
+
* must reject `null` because PHP drops null values pre-lower — so a `null` dimension
|
|
49
|
+
* is a pre-upload error in BOTH languages, never a wire `null` that 422s. Mirrored
|
|
50
|
+
* in PHP.
|
|
51
|
+
*
|
|
52
|
+
* @throws {GislConfigError} reason `missing_required_field` naming the absent
|
|
53
|
+
* dimension(s) in `conflictingFields`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function assertThumbnailDimensions(options: {
|
|
56
|
+
width?: unknown;
|
|
57
|
+
height?: unknown;
|
|
58
|
+
} | null | undefined): void;
|