@giveitsmaller/sdk 0.6.0 → 0.8.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/_audit.js +71 -0
- package/dist/builder.d.ts +406 -0
- package/dist/builder.js +706 -0
- package/dist/client.d.ts +10 -0
- package/dist/client.js +42 -6
- package/dist/credentials.d.ts +61 -0
- package/dist/credentials.js +200 -0
- package/dist/ergonomic/preset_resolver.d.ts +75 -0
- package/dist/ergonomic/preset_resolver.js +568 -0
- package/dist/ergonomic/presets/_translate.d.ts +11 -0
- package/dist/ergonomic/presets/_translate.js +35 -0
- package/dist/ergonomic/presets/audio_compress.d.ts +16 -0
- package/dist/ergonomic/presets/audio_compress.js +45 -0
- package/dist/ergonomic/presets/document_epub_compress.d.ts +14 -0
- package/dist/ergonomic/presets/document_epub_compress.js +34 -0
- package/dist/ergonomic/presets/document_odf_compress.d.ts +14 -0
- package/dist/ergonomic/presets/document_odf_compress.js +34 -0
- package/dist/ergonomic/presets/document_office_compress.d.ts +16 -0
- package/dist/ergonomic/presets/document_office_compress.js +40 -0
- package/dist/ergonomic/presets/document_pdf_compress.d.ts +14 -0
- package/dist/ergonomic/presets/document_pdf_compress.js +35 -0
- package/dist/ergonomic/presets/image_compress.d.ts +43 -0
- package/dist/ergonomic/presets/image_compress.js +95 -0
- package/dist/ergonomic/presets/index.d.ts +77 -0
- package/dist/ergonomic/presets/index.js +216 -0
- package/dist/ergonomic/presets/video_compress.d.ts +30 -0
- package/dist/ergonomic/presets/video_compress.js +83 -0
- package/dist/errors.d.ts +196 -1
- package/dist/errors.js +216 -0
- package/dist/file-first.d.ts +284 -0
- package/dist/file-first.js +445 -0
- package/dist/generated/sdk_spec/enums.d.ts +195 -0
- package/dist/generated/sdk_spec/enums.js +127 -0
- package/dist/generated/sdk_spec/errors.d.ts +16 -0
- package/dist/generated/sdk_spec/errors.js +523 -0
- package/dist/generated/sdk_spec/index.d.ts +4 -0
- package/dist/generated/sdk_spec/index.js +7 -0
- package/dist/generated/sdk_spec/presets.d.ts +6 -0
- package/dist/generated/sdk_spec/presets.js +157 -0
- package/dist/generated/sdk_spec/version.d.ts +3 -0
- package/dist/generated/sdk_spec/version.js +6 -0
- package/dist/gisl.d.ts +122 -0
- package/dist/gisl.js +283 -0
- package/dist/http-downloader.d.ts +9 -0
- package/dist/http-downloader.js +55 -0
- package/dist/index.d.ts +20 -5
- package/dist/index.js +45 -4
- package/dist/merge.d.ts +142 -0
- package/dist/merge.js +411 -0
- package/dist/types.d.ts +12 -14
- package/dist/types.js +18 -0
- package/package.json +3 -3
package/dist/client.d.ts
CHANGED
|
@@ -197,6 +197,16 @@ export declare class GislClient {
|
|
|
197
197
|
* previous response to revalidate — a 304 surfaces as
|
|
198
198
|
* `{ notModified: true, etag, lastModified }` so callers can keep
|
|
199
199
|
* using their cached copy.
|
|
200
|
+
*
|
|
201
|
+
* NOTE: this schema describes available types/options/availability — it does
|
|
202
|
+
* NOT carry per-tier processing-class size/duration caps (the response shape
|
|
203
|
+
* has no `processing_class` / `per_tier_constraints`). The typed operation
|
|
204
|
+
* metadata's `AvailabilityEntry.constraints` is the conservative baseline
|
|
205
|
+
* only. Per-tier caps are enforced server-side: the caps that applied to an
|
|
206
|
+
* upload are reported on a successful `UploadResponse.constraintsApplied`,
|
|
207
|
+
* and an exceeded limit throws `GislUploadCapExceededError` (the 422 path
|
|
208
|
+
* carries a typed payload; the 413 absolute-cap path is a plain envelope).
|
|
209
|
+
* There is no read-ahead per-tier-cap API today.
|
|
200
210
|
*/
|
|
201
211
|
getSchema(options?: GetSchemaOptions): Promise<GetSchemaResult>;
|
|
202
212
|
/**
|
package/dist/client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { open, stat } from 'node:fs/promises';
|
|
2
2
|
import { basename } from 'node:path';
|
|
3
|
-
import { AudioWatermarkDecodeRequestToJSON, AudioWatermarkDecodeResponseFromJSON, ExternalImportCreatedResponseFromJSON, ExternalImportRequestToJSON, LoginUser200ResponseDataFromJSON, CreditsBalanceResponseFromJSON, CreditsUsageResponseFromJSON, UploadResponseFromJSON, UploadProbeResponseFromJSON, MultipartInitiateResponseFromJSON, MultipartInitiateRequestMetadataHintToJSON, MultipartCompleteResponseFromJSON, MultipartCompleteRequestToJSON, WorkflowCancelResponseFromJSON, WorkflowCreateResponseFromJSON, WorkflowResumeResponseFromJSON, WorkflowStatusResponseFromJSON, WorkflowDownloadResponseFromJSON, MetadataResponseFromJSON, OperationsSchemaResponseFromJSON, RetryResponseFromJSON, WorkflowStatus, AuthErrorResponseFromJSON, AuthErrorType, BalanceExhaustedResponseFromJSON, BalanceExhaustedResponseRequiredActionEnum, FeatureNotAvailableResponseFromJSON, FeatureTierRestrictedResponseFromJSON, TierRestrictionKind, TierRestrictionResponseFromJSON, UserTier, WorkflowExpiredResponseFromJSON, UploadSizeExceedsTierResponseFromJSON, UploadDurationExceedsTierResponseFromJSON, UploadConstraintsAppliedProcessingClassPreAssignmentEnum, UploadThresholdsSingleShotMaxBytesEnum, UploadThresholdsMultipartChunkSizeEnum, UploadThresholdsMultipartConcurrencyDefaultEnum, } from '@giveitsmaller/contracts/openapi';
|
|
4
|
-
import { GislAbortError, GislApiError, GislAuthError, GislBalanceExhaustedError, GislError, GislFeatureNotAvailableError, GislFeatureTierRestrictedError, GislMultipartPartCountError, GislMultipartPartError, GislMultipartSessionNotFoundError, GislMultipartSessionOwnershipError, GislMultipartSessionAuthRequiredError, GislTierRestrictedError, GislTimeoutError, GislUploadCapExceededError, GislValidationError, GislWorkflowExpiredError, } from './errors.js';
|
|
3
|
+
import { AudioWatermarkDecodeRequestToJSON, AudioWatermarkDecodeResponseFromJSON, ExternalImportCreatedResponseFromJSON, ExternalImportRequestToJSON, LoginUser200ResponseDataFromJSON, CreditsBalanceResponseFromJSON, CreditsUsageResponseFromJSON, UploadResponseFromJSON, UploadProbeResponseFromJSON, MultipartInitiateResponseFromJSON, MultipartInitiateRequestMetadataHintToJSON, MultipartCompleteResponseFromJSON, MultipartCompleteRequestToJSON, WorkflowCancelResponseFromJSON, WorkflowCreateResponseFromJSON, WorkflowResumeResponseFromJSON, WorkflowStatusResponseFromJSON, WorkflowDownloadResponseFromJSON, MetadataResponseFromJSON, OperationsSchemaResponseFromJSON, RetryResponseFromJSON, WorkflowStatus, AuthErrorResponseFromJSON, AuthErrorType, BalanceExhaustedResponseFromJSON, BalanceExhaustedResponseRequiredActionEnum, FeatureNotAvailableResponseFromJSON, FeatureTierRestrictedResponseFromJSON, TierRestrictionKind, TierRestrictionResponseFromJSON, UserTier, WorkflowExpiredResponseFromJSON, ProbePendingResponseFromJSON, UploadSizeExceedsTierResponseFromJSON, UploadDurationExceedsTierResponseFromJSON, UploadConstraintsAppliedProcessingClassPreAssignmentEnum, UploadThresholdsSingleShotMaxBytesEnum, UploadThresholdsMultipartChunkSizeEnum, UploadThresholdsMultipartConcurrencyDefaultEnum, } from '@giveitsmaller/contracts/openapi';
|
|
4
|
+
import { GislAbortError, GislApiError, GislAuthError, GislBalanceExhaustedError, GislError, GislFeatureNotAvailableError, GislFeatureTierRestrictedError, GislMultipartPartCountError, GislMultipartPartError, GislMultipartSessionNotFoundError, GislMultipartSessionOwnershipError, GislMultipartSessionAuthRequiredError, GislTierRestrictedError, GislTimeoutError, GislProbePendingError, GislUploadCapExceededError, GislValidationError, GislWorkflowExpiredError, } from './errors.js';
|
|
5
5
|
import { parseSseStream } from './sse.js';
|
|
6
6
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
7
7
|
// SDK-internal aliases derived from the contract-pinned UploadThresholds enums
|
|
@@ -399,10 +399,19 @@ export class GislClient {
|
|
|
399
399
|
locale: json.locale,
|
|
400
400
|
messageParams: json.message_params,
|
|
401
401
|
};
|
|
402
|
+
// Human-readable text comes from `message` (the I26 localised field).
|
|
403
|
+
// `error` is the stable, never-localised SCREAMING_SNAKE machine code —
|
|
404
|
+
// NOT display text. Surfacing `error` as the thrown error's `.message`
|
|
405
|
+
// regressed consumers that render the human string (x9Lbf6uy). Fall back
|
|
406
|
+
// to `error` when `message` is absent (deployed contract guarantees
|
|
407
|
+
// `message` on conforming error envelopes). Machine dispatch keys off
|
|
408
|
+
// `error_type` (below), unchanged.
|
|
409
|
+
const status = response.status;
|
|
410
|
+
const errorMessage = json.message ?? json.error ?? 'Unknown error';
|
|
402
411
|
// Validation-details branch first — preserve existing shape so callers
|
|
403
412
|
// matching on `instanceof GislValidationError` keep working.
|
|
404
413
|
if (isValidationDetails(json.details)) {
|
|
405
|
-
throw new GislValidationError(response.status,
|
|
414
|
+
throw new GislValidationError(response.status, errorMessage, json.details, path, i18n);
|
|
406
415
|
}
|
|
407
416
|
// Dispatch by (status, error_type) onto the structured envelope shapes
|
|
408
417
|
// emitted by the v2 contracts. Each branch builds the typed payload via
|
|
@@ -416,8 +425,6 @@ export class GislClient {
|
|
|
416
425
|
// fall through to the base `GislApiError` rather than handing the
|
|
417
426
|
// caller silently-corrupted typed metadata.
|
|
418
427
|
const errorType = json.error_type;
|
|
419
|
-
const status = response.status;
|
|
420
|
-
const errorMessage = json.error ?? 'Unknown error';
|
|
421
428
|
// Build the typed payload via FromJSON, then validate that all
|
|
422
429
|
// required typed fields are well-formed. FromJSON does not throw on
|
|
423
430
|
// missing required fields — for example `workflow_expired` without
|
|
@@ -463,6 +470,22 @@ export class GislClient {
|
|
|
463
470
|
if (status === 422 && errorType === 'workflow_expired') {
|
|
464
471
|
tryThrowStructured(WorkflowExpiredResponseFromJSON, GislWorkflowExpiredError, (p) => isValidDate(p.expiredAt));
|
|
465
472
|
}
|
|
473
|
+
// Probe-pending 422 on POST /api/workflows (per contracts av1J0rEF).
|
|
474
|
+
// Recovery: caller polls /api/uploads/{id}/probe until terminal, then
|
|
475
|
+
// retries the workflow-create. `payload.jobRef` names which job.
|
|
476
|
+
//
|
|
477
|
+
// Defensive against v2.15.3 generator-bug: the openapi-generator-emitted
|
|
478
|
+
// `instanceOfProbePendingResponse` checks camelCase fields against the
|
|
479
|
+
// raw snake_case wire — fails dispatch when used at the CreateWorkflow
|
|
480
|
+
// 422 union top-level. We branch on the already-parsed `error_type`
|
|
481
|
+
// here (the SDK-side snake_case envelope read at line 659) and
|
|
482
|
+
// validate the payload shape directly on the raw JSON before
|
|
483
|
+
// ProbePendingResponseFromJSON converts snake_case → camelCase.
|
|
484
|
+
// Robust to BOTH the current v2.15.3 broken dispatch AND any future
|
|
485
|
+
// v2.15.4 fix that lands a discriminator.
|
|
486
|
+
if (status === 422 && errorType === 'probe_pending') {
|
|
487
|
+
tryThrowStructured(ProbePendingResponseFromJSON, GislProbePendingError, (p) => typeof p.jobRef === 'string' && p.jobRef.length > 0);
|
|
488
|
+
}
|
|
466
489
|
// Upload cap errors. `GislUploadCapExceededError` takes an extra `kind`
|
|
467
490
|
// arg so it cannot use `tryThrowStructured` (whose ErrorClass signature
|
|
468
491
|
// is fixed) — this local helper applies the SAME defense-in-depth
|
|
@@ -1680,6 +1703,16 @@ export class GislClient {
|
|
|
1680
1703
|
* previous response to revalidate — a 304 surfaces as
|
|
1681
1704
|
* `{ notModified: true, etag, lastModified }` so callers can keep
|
|
1682
1705
|
* using their cached copy.
|
|
1706
|
+
*
|
|
1707
|
+
* NOTE: this schema describes available types/options/availability — it does
|
|
1708
|
+
* NOT carry per-tier processing-class size/duration caps (the response shape
|
|
1709
|
+
* has no `processing_class` / `per_tier_constraints`). The typed operation
|
|
1710
|
+
* metadata's `AvailabilityEntry.constraints` is the conservative baseline
|
|
1711
|
+
* only. Per-tier caps are enforced server-side: the caps that applied to an
|
|
1712
|
+
* upload are reported on a successful `UploadResponse.constraintsApplied`,
|
|
1713
|
+
* and an exceeded limit throws `GislUploadCapExceededError` (the 422 path
|
|
1714
|
+
* carries a typed payload; the 413 absolute-cap path is a plain envelope).
|
|
1715
|
+
* There is no read-ahead per-tier-cap API today.
|
|
1683
1716
|
*/
|
|
1684
1717
|
async getSchema(options = {}) {
|
|
1685
1718
|
const params = new URLSearchParams();
|
|
@@ -1714,7 +1747,10 @@ export class GislClient {
|
|
|
1714
1747
|
let errorMessage = 'Unknown error';
|
|
1715
1748
|
try {
|
|
1716
1749
|
const errJson = (await response.json());
|
|
1717
|
-
|
|
1750
|
+
// Prefer the human `message`; `error` is the machine code (x9Lbf6uy).
|
|
1751
|
+
if (errJson.message)
|
|
1752
|
+
errorMessage = errJson.message;
|
|
1753
|
+
else if (errJson.error)
|
|
1718
1754
|
errorMessage = errJson.error;
|
|
1719
1755
|
}
|
|
1720
1756
|
catch {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential + endpoint resolution for the ergonomic-layer `gisl.create()`
|
|
3
|
+
* factory. Implements an AWS-style short-circuiting chain:
|
|
4
|
+
* API key: explicit arg → `GISL_API_KEY` env → `~/.gisl/credentials` profile
|
|
5
|
+
* Endpoint: explicit arg → `GISL_ENVIRONMENT` / `GISL_BASE_URL` env → prod default
|
|
6
|
+
*
|
|
7
|
+
* Sources are tried in order; the first hit wins. Malformed profile files
|
|
8
|
+
* surface as `GislConfigError` naming the offending profile (NEVER the value
|
|
9
|
+
* — credential values must not appear in error messages).
|
|
10
|
+
*
|
|
11
|
+
* Browser-safe: profile-file lookups dynamic-import `node:fs/promises` only
|
|
12
|
+
* when a profile path is requested AND a Node runtime is detected; missing
|
|
13
|
+
* fs in the browser yields the typed
|
|
14
|
+
* `GislConfigError('profile_unavailable_in_runtime')`.
|
|
15
|
+
*/
|
|
16
|
+
export declare const GISL_API_KEY_ENV = "GISL_API_KEY";
|
|
17
|
+
export declare const GISL_BASE_URL_ENV = "GISL_BASE_URL";
|
|
18
|
+
export declare const GISL_ENVIRONMENT_ENV = "GISL_ENVIRONMENT";
|
|
19
|
+
/**
|
|
20
|
+
* Named environments → base URLs. Kept colocated with the resolver so the
|
|
21
|
+
* mapping table doesn't leak into `gisl.ts`.
|
|
22
|
+
*/
|
|
23
|
+
export declare const ENVIRONMENT_ENDPOINTS: {
|
|
24
|
+
readonly prod: "https://api.giveitsmaller.com";
|
|
25
|
+
readonly staging: "https://api.staging.giveitsmaller.com";
|
|
26
|
+
};
|
|
27
|
+
export type Environment = keyof typeof ENVIRONMENT_ENDPOINTS;
|
|
28
|
+
export declare const DEFAULT_ENDPOINT: "https://api.giveitsmaller.com";
|
|
29
|
+
export interface ResolveCredentialsOptions {
|
|
30
|
+
/** Explicit API key — highest precedence. */
|
|
31
|
+
readonly apiKey?: string;
|
|
32
|
+
/** Profile name from `~/.gisl/credentials`. Default: `'default'`. */
|
|
33
|
+
readonly profile?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Cookie-mode flag. When `true`, the caller is authenticating via
|
|
36
|
+
* session cookie (browser SPA flow) and a missing apiKey is NOT a
|
|
37
|
+
* configuration error — `resolveApiKey` returns `null` without
|
|
38
|
+
* throwing, and the ergonomic-layer factory accepts it.
|
|
39
|
+
*/
|
|
40
|
+
readonly useSessionCookie?: boolean;
|
|
41
|
+
/** Override the profile-file path (testing). */
|
|
42
|
+
readonly profilePath?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface ResolveEndpointOptions {
|
|
45
|
+
readonly baseUrl?: string;
|
|
46
|
+
readonly environment?: Environment;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the API key via the credential chain. Returns the resolved key,
|
|
50
|
+
* or `null` if no source produced one. The ergonomic-layer factory is
|
|
51
|
+
* responsible for deciding whether `null` is an error (default: yes, throw
|
|
52
|
+
* `GislMissingCredentialsError`) or acceptable (anonymous / cookie-mode).
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveApiKey(opts?: ResolveCredentialsOptions): Promise<string | null>;
|
|
55
|
+
/**
|
|
56
|
+
* Resolve the base URL. Explicit `baseUrl` wins; otherwise an explicit
|
|
57
|
+
* `environment` name; otherwise the `GISL_BASE_URL` / `GISL_ENVIRONMENT`
|
|
58
|
+
* env vars; otherwise the prod default. Never throws — the chain always
|
|
59
|
+
* resolves to a usable URL.
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveEndpoint(opts?: ResolveEndpointOptions): string;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential + endpoint resolution for the ergonomic-layer `gisl.create()`
|
|
3
|
+
* factory. Implements an AWS-style short-circuiting chain:
|
|
4
|
+
* API key: explicit arg → `GISL_API_KEY` env → `~/.gisl/credentials` profile
|
|
5
|
+
* Endpoint: explicit arg → `GISL_ENVIRONMENT` / `GISL_BASE_URL` env → prod default
|
|
6
|
+
*
|
|
7
|
+
* Sources are tried in order; the first hit wins. Malformed profile files
|
|
8
|
+
* surface as `GislConfigError` naming the offending profile (NEVER the value
|
|
9
|
+
* — credential values must not appear in error messages).
|
|
10
|
+
*
|
|
11
|
+
* Browser-safe: profile-file lookups dynamic-import `node:fs/promises` only
|
|
12
|
+
* when a profile path is requested AND a Node runtime is detected; missing
|
|
13
|
+
* fs in the browser yields the typed
|
|
14
|
+
* `GislConfigError('profile_unavailable_in_runtime')`.
|
|
15
|
+
*/
|
|
16
|
+
import { GislConfigError } from './errors.js';
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Environment constants
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
export const GISL_API_KEY_ENV = 'GISL_API_KEY';
|
|
21
|
+
export const GISL_BASE_URL_ENV = 'GISL_BASE_URL';
|
|
22
|
+
export const GISL_ENVIRONMENT_ENV = 'GISL_ENVIRONMENT';
|
|
23
|
+
/**
|
|
24
|
+
* Named environments → base URLs. Kept colocated with the resolver so the
|
|
25
|
+
* mapping table doesn't leak into `gisl.ts`.
|
|
26
|
+
*/
|
|
27
|
+
export const ENVIRONMENT_ENDPOINTS = {
|
|
28
|
+
prod: 'https://api.giveitsmaller.com',
|
|
29
|
+
staging: 'https://api.staging.giveitsmaller.com',
|
|
30
|
+
};
|
|
31
|
+
export const DEFAULT_ENDPOINT = ENVIRONMENT_ENDPOINTS.prod;
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Public resolvers
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the API key via the credential chain. Returns the resolved key,
|
|
37
|
+
* or `null` if no source produced one. The ergonomic-layer factory is
|
|
38
|
+
* responsible for deciding whether `null` is an error (default: yes, throw
|
|
39
|
+
* `GislMissingCredentialsError`) or acceptable (anonymous / cookie-mode).
|
|
40
|
+
*/
|
|
41
|
+
export async function resolveApiKey(opts = {}) {
|
|
42
|
+
// 1. Explicit wins.
|
|
43
|
+
if (typeof opts.apiKey === 'string' && opts.apiKey.length > 0) {
|
|
44
|
+
return opts.apiKey;
|
|
45
|
+
}
|
|
46
|
+
// 2. Environment variable.
|
|
47
|
+
const envKey = readEnv(GISL_API_KEY_ENV);
|
|
48
|
+
if (envKey !== null && envKey.length > 0) {
|
|
49
|
+
return envKey;
|
|
50
|
+
}
|
|
51
|
+
// 3. Shared-config profile (`~/.gisl/credentials`). Skipped silently
|
|
52
|
+
// when running in a non-Node runtime (browser, edge) — the resolver
|
|
53
|
+
// returns `null` and the caller decides whether that's fatal.
|
|
54
|
+
if (!isNodeRuntime()) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const profileName = opts.profile ?? 'default';
|
|
58
|
+
const profilePath = opts.profilePath ?? defaultProfilePath();
|
|
59
|
+
if (profilePath === null) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const profileEntries = await readProfile(profilePath, profileName);
|
|
63
|
+
if (profileEntries === null) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const profileKey = profileEntries.api_key;
|
|
67
|
+
if (typeof profileKey === 'string' && profileKey.length > 0) {
|
|
68
|
+
return profileKey;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Resolve the base URL. Explicit `baseUrl` wins; otherwise an explicit
|
|
74
|
+
* `environment` name; otherwise the `GISL_BASE_URL` / `GISL_ENVIRONMENT`
|
|
75
|
+
* env vars; otherwise the prod default. Never throws — the chain always
|
|
76
|
+
* resolves to a usable URL.
|
|
77
|
+
*/
|
|
78
|
+
export function resolveEndpoint(opts = {}) {
|
|
79
|
+
if (typeof opts.baseUrl === 'string' && opts.baseUrl.length > 0) {
|
|
80
|
+
return opts.baseUrl;
|
|
81
|
+
}
|
|
82
|
+
if (typeof opts.environment === 'string') {
|
|
83
|
+
const explicitEnv = ENVIRONMENT_ENDPOINTS[opts.environment];
|
|
84
|
+
if (explicitEnv !== undefined) {
|
|
85
|
+
return explicitEnv;
|
|
86
|
+
}
|
|
87
|
+
// Codex r2 medium 23a17c1dbf75 — fail-closed on an unknown explicit
|
|
88
|
+
// environment name. A typo or JS-side caller previously silently fell
|
|
89
|
+
// through to env-var / prod default, which can mis-route a staging-
|
|
90
|
+
// intended request to production. The env-var path still allows
|
|
91
|
+
// unknown values to fall through (low-impact since it's an env config,
|
|
92
|
+
// not a code-level arg).
|
|
93
|
+
throw new GislConfigError(`Unknown environment '${opts.environment}'. Valid values: ${Object.keys(ENVIRONMENT_ENDPOINTS).join(', ')}.`);
|
|
94
|
+
}
|
|
95
|
+
const envBaseUrl = readEnv(GISL_BASE_URL_ENV);
|
|
96
|
+
if (envBaseUrl !== null && envBaseUrl.length > 0) {
|
|
97
|
+
return envBaseUrl;
|
|
98
|
+
}
|
|
99
|
+
const envEnvironment = readEnv(GISL_ENVIRONMENT_ENV);
|
|
100
|
+
if (envEnvironment !== null) {
|
|
101
|
+
const envMapped = ENVIRONMENT_ENDPOINTS[envEnvironment];
|
|
102
|
+
if (envMapped !== undefined) {
|
|
103
|
+
return envMapped;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return DEFAULT_ENDPOINT;
|
|
107
|
+
}
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Internals
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
function isNodeRuntime() {
|
|
112
|
+
return (typeof process !== 'undefined' &&
|
|
113
|
+
process.versions !== undefined &&
|
|
114
|
+
typeof process.versions.node === 'string');
|
|
115
|
+
}
|
|
116
|
+
function readEnv(name) {
|
|
117
|
+
if (!isNodeRuntime()) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const value = process.env[name];
|
|
121
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
122
|
+
}
|
|
123
|
+
function defaultProfilePath() {
|
|
124
|
+
const homeDir = readEnv('HOME') ?? readEnv('USERPROFILE');
|
|
125
|
+
if (homeDir === null) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
// Use forward-slash join — Node accepts it on every platform.
|
|
129
|
+
return `${homeDir}/.gisl/credentials`;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Read a single profile section from an INI-format credentials file.
|
|
133
|
+
* Returns `null` when the file does not exist; throws `GislConfigError`
|
|
134
|
+
* when the file exists but is malformed or the requested profile is
|
|
135
|
+
* absent. Never includes credential VALUES in error messages.
|
|
136
|
+
*/
|
|
137
|
+
async function readProfile(path, profileName) {
|
|
138
|
+
let raw;
|
|
139
|
+
try {
|
|
140
|
+
// Lazy import keeps `node:fs` out of browser bundles.
|
|
141
|
+
const fs = await import('node:fs/promises');
|
|
142
|
+
raw = await fs.readFile(path, 'utf8');
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
// ENOENT → no file = no credentials from this source (not an error).
|
|
146
|
+
const code = err.code;
|
|
147
|
+
if (code === 'ENOENT' || code === 'ENOTDIR') {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
throw new GislConfigError(`Failed to read shared credentials file at ${path}: ${code ?? 'unknown error'}`);
|
|
151
|
+
}
|
|
152
|
+
const parsed = parseIni(raw, path);
|
|
153
|
+
const entries = parsed[profileName];
|
|
154
|
+
if (entries === undefined) {
|
|
155
|
+
throw new GislConfigError(`Profile '${profileName}' not found in shared credentials file at ${path}`);
|
|
156
|
+
}
|
|
157
|
+
return entries;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Minimal INI parser sufficient for AWS-style credentials files: section
|
|
161
|
+
* headers `[name]`, `key = value` lines, `#` and `;` comments. Whitespace
|
|
162
|
+
* around `=` is trimmed. No nested sections, no interpolation, no quoting.
|
|
163
|
+
* Throws `GislConfigError` on malformed lines, naming only the LINE NUMBER
|
|
164
|
+
* and PROFILE — never the value.
|
|
165
|
+
*/
|
|
166
|
+
function parseIni(raw, sourcePath) {
|
|
167
|
+
const sections = {};
|
|
168
|
+
let currentSection = null;
|
|
169
|
+
const lines = raw.split(/\r?\n/);
|
|
170
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
171
|
+
const line = lines[i].trim();
|
|
172
|
+
if (line === '' || line.startsWith('#') || line.startsWith(';')) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (line.startsWith('[') && line.endsWith(']')) {
|
|
176
|
+
currentSection = line.slice(1, -1).trim();
|
|
177
|
+
if (currentSection === '') {
|
|
178
|
+
throw new GislConfigError(`Empty section header at line ${i + 1} in ${sourcePath}`);
|
|
179
|
+
}
|
|
180
|
+
if (sections[currentSection] === undefined) {
|
|
181
|
+
sections[currentSection] = {};
|
|
182
|
+
}
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (currentSection === null) {
|
|
186
|
+
throw new GislConfigError(`Key-value line at line ${i + 1} in ${sourcePath} is outside any section header`);
|
|
187
|
+
}
|
|
188
|
+
const eqIndex = line.indexOf('=');
|
|
189
|
+
if (eqIndex === -1) {
|
|
190
|
+
throw new GislConfigError(`Malformed line ${i + 1} in ${sourcePath} (expected key=value)`);
|
|
191
|
+
}
|
|
192
|
+
const key = line.slice(0, eqIndex).trim();
|
|
193
|
+
if (key === '') {
|
|
194
|
+
throw new GislConfigError(`Empty key at line ${i + 1} in ${sourcePath}`);
|
|
195
|
+
}
|
|
196
|
+
const value = line.slice(eqIndex + 1).trim();
|
|
197
|
+
sections[currentSection][key] = value;
|
|
198
|
+
}
|
|
199
|
+
return sections;
|
|
200
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ResolvedOptions } from '../builder.js';
|
|
2
|
+
import type { OptimizeFor } from '../generated/sdk_spec/enums.js';
|
|
3
|
+
import { type PresetDefaults, type PresetMedia, type PresetOp } from './presets/index.js';
|
|
4
|
+
/** Bumped on any change to a `*PresetOptions.shippedDefaultsFor(...)` cell value. */
|
|
5
|
+
export declare const PRESET_VERSION = "1.0";
|
|
6
|
+
/**
|
|
7
|
+
* Inputs to {@link resolveCompressOptions}. `media` selects which leaf
|
|
8
|
+
* DTO drives sdkDefault + clientDefault lookups + invalid-combo
|
|
9
|
+
* validations. `op` is currently always `'compress'`; future ops will
|
|
10
|
+
* extend the union.
|
|
11
|
+
*/
|
|
12
|
+
export interface ResolveCompressOptionsInput {
|
|
13
|
+
readonly media: PresetMedia;
|
|
14
|
+
readonly op: PresetOp;
|
|
15
|
+
/** Defaults registered via `gisl.create({ presetDefaults: ... })`. */
|
|
16
|
+
readonly presetDefaults?: PresetDefaults;
|
|
17
|
+
/**
|
|
18
|
+
* Scoped defaults attached via `client.withPresetDefaults(...)` (T4c —
|
|
19
|
+
* `ULAlOP6j`). Layered between `presetDefaults` and `presetOverrides`
|
|
20
|
+
* in the resolver chain. The derived client closes over this
|
|
21
|
+
* reference; `undefined` for clients that never went through a
|
|
22
|
+
* `withPresetDefaults` call.
|
|
23
|
+
*/
|
|
24
|
+
readonly scopedPresetDefaults?: PresetDefaults;
|
|
25
|
+
/** Per-call `presetOverrides` argument from the operation builder. */
|
|
26
|
+
readonly presetOverrides?: Readonly<Record<string, unknown>>;
|
|
27
|
+
/**
|
|
28
|
+
* `optimize` selects the preset level. When unset, NO shipped
|
|
29
|
+
* defaults apply (resolver layer 1 contributes nothing); the
|
|
30
|
+
* resulting `resolvedOptions.preset` is `null`.
|
|
31
|
+
*/
|
|
32
|
+
readonly optimize?: OptimizeFor;
|
|
33
|
+
/**
|
|
34
|
+
* Explicit per-call knobs the caller passed alongside `optimize` —
|
|
35
|
+
* e.g. `{ autoOrient: true }`. These are the highest-precedence
|
|
36
|
+
* layer.
|
|
37
|
+
*/
|
|
38
|
+
readonly explicitOptions: Readonly<Record<string, unknown>>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Output of {@link resolveCompressOptions}. `wireOptions` is the
|
|
42
|
+
* snake_case payload ready for the operation argument; `resolvedOptions`
|
|
43
|
+
* is the introspection projection surfaced on `Result.resolvedOptions`.
|
|
44
|
+
*/
|
|
45
|
+
export interface ResolveCompressOptionsOutput {
|
|
46
|
+
readonly wireOptions: Record<string, unknown>;
|
|
47
|
+
readonly resolvedOptions: ResolvedOptions;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Parse a `targetSize` value into a positive integer byte count.
|
|
51
|
+
*
|
|
52
|
+
* - Integer input passes through after non-negative + finite checks.
|
|
53
|
+
* - String input must match `<number><unit?>` with unit in
|
|
54
|
+
* `B|KB|MB|GB|TB` (case-insensitive). Multipliers are BINARY
|
|
55
|
+
* (1 KB = 1024). Decimal fractions allowed in the magnitude
|
|
56
|
+
* (`'1.5GB'` → `1.5 * 2^30` rounded down to integer bytes).
|
|
57
|
+
*
|
|
58
|
+
* Throws {@link GislConfigError} with `reason: 'invalid_target_size'`
|
|
59
|
+
* on any other input — including negative numbers, infinities, missing
|
|
60
|
+
* magnitude, unknown unit, or zero magnitude.
|
|
61
|
+
*
|
|
62
|
+
* @internal — exported for unit tests.
|
|
63
|
+
*/
|
|
64
|
+
export declare function _parseTargetSize(value: unknown): number;
|
|
65
|
+
/**
|
|
66
|
+
* Resolve the wire payload + introspection projection for a compress
|
|
67
|
+
* operation call. Throws {@link GislConfigError} before any network
|
|
68
|
+
* round-trip when the merged options violate a documented constraint.
|
|
69
|
+
*
|
|
70
|
+
* Layers are applied in fixed order:
|
|
71
|
+
* SDK shipped → client default → scoped (T4c) → callPresetOverride → explicit.
|
|
72
|
+
*
|
|
73
|
+
* `optimize` unset ⇒ layer 1 contributes nothing; `resolvedOptions.preset = null`.
|
|
74
|
+
*/
|
|
75
|
+
export declare function resolveCompressOptions(input: ResolveCompressOptionsInput): ResolveCompressOptionsOutput;
|