@giveitsmaller/sdk 0.4.0 → 0.7.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.
Files changed (50) hide show
  1. package/dist/_audit.js +67 -0
  2. package/dist/builder.d.ts +406 -0
  3. package/dist/builder.js +706 -0
  4. package/dist/client.d.ts +96 -2
  5. package/dist/client.js +968 -33
  6. package/dist/credentials.d.ts +61 -0
  7. package/dist/credentials.js +200 -0
  8. package/dist/ergonomic/preset_resolver.d.ts +75 -0
  9. package/dist/ergonomic/preset_resolver.js +568 -0
  10. package/dist/ergonomic/presets/_translate.d.ts +11 -0
  11. package/dist/ergonomic/presets/_translate.js +35 -0
  12. package/dist/ergonomic/presets/audio_compress.d.ts +16 -0
  13. package/dist/ergonomic/presets/audio_compress.js +45 -0
  14. package/dist/ergonomic/presets/document_epub_compress.d.ts +14 -0
  15. package/dist/ergonomic/presets/document_epub_compress.js +34 -0
  16. package/dist/ergonomic/presets/document_odf_compress.d.ts +14 -0
  17. package/dist/ergonomic/presets/document_odf_compress.js +34 -0
  18. package/dist/ergonomic/presets/document_office_compress.d.ts +16 -0
  19. package/dist/ergonomic/presets/document_office_compress.js +40 -0
  20. package/dist/ergonomic/presets/document_pdf_compress.d.ts +14 -0
  21. package/dist/ergonomic/presets/document_pdf_compress.js +35 -0
  22. package/dist/ergonomic/presets/image_compress.d.ts +43 -0
  23. package/dist/ergonomic/presets/image_compress.js +95 -0
  24. package/dist/ergonomic/presets/index.d.ts +77 -0
  25. package/dist/ergonomic/presets/index.js +216 -0
  26. package/dist/ergonomic/presets/video_compress.d.ts +30 -0
  27. package/dist/ergonomic/presets/video_compress.js +83 -0
  28. package/dist/errors.d.ts +251 -1
  29. package/dist/errors.js +268 -0
  30. package/dist/generated/sdk_spec/enums.d.ts +195 -0
  31. package/dist/generated/sdk_spec/enums.js +127 -0
  32. package/dist/generated/sdk_spec/errors.d.ts +16 -0
  33. package/dist/generated/sdk_spec/errors.js +473 -0
  34. package/dist/generated/sdk_spec/index.d.ts +4 -0
  35. package/dist/generated/sdk_spec/index.js +7 -0
  36. package/dist/generated/sdk_spec/presets.d.ts +6 -0
  37. package/dist/generated/sdk_spec/presets.js +157 -0
  38. package/dist/generated/sdk_spec/version.d.ts +3 -0
  39. package/dist/generated/sdk_spec/version.js +6 -0
  40. package/dist/gisl.d.ts +112 -0
  41. package/dist/gisl.js +266 -0
  42. package/dist/index.d.ts +17 -7
  43. package/dist/index.js +33 -3
  44. package/dist/merge.d.ts +142 -0
  45. package/dist/merge.js +411 -0
  46. package/dist/sse.d.ts +20 -1
  47. package/dist/sse.js +62 -3
  48. package/dist/types.d.ts +144 -14
  49. package/dist/types.js +18 -0
  50. package/package.json +2 -2
@@ -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;