@giveitsmaller/sdk 0.6.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 (48) hide show
  1. package/dist/_audit.js +60 -0
  2. package/dist/builder.d.ts +406 -0
  3. package/dist/builder.js +706 -0
  4. package/dist/client.d.ts +10 -0
  5. package/dist/client.js +28 -2
  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 +147 -1
  29. package/dist/errors.js +161 -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 +15 -5
  43. package/dist/index.js +32 -4
  44. package/dist/merge.d.ts +142 -0
  45. package/dist/merge.js +411 -0
  46. package/dist/types.d.ts +12 -14
  47. package/dist/types.js +18 -0
  48. package/package.json +2 -2
package/dist/merge.js ADDED
@@ -0,0 +1,411 @@
1
+ /**
2
+ * Merge-compose layer for the SDK ergonomic surface (T3 / cuecCmb5).
3
+ *
4
+ * `client.merge(...assets, options?)` returns a `MergeBuilder`. The builder
5
+ * separates WHAT (the asset set) from ORDER (the timeline):
6
+ *
7
+ * - `merge(a, b, c)` declares the asset set — each unique input is uploaded
8
+ * ONCE per run, even if referenced multiple times in the sequence.
9
+ * - `.sequence(...refs)` defines the play order. References may repeat
10
+ * freely; entries may be bare asset refs or `clip(ref, opts)` objects
11
+ * carrying per-position options.
12
+ * - No `.sequence(...)` => play in declared order, no transitions.
13
+ *
14
+ * Wire-truth boundaries (lowering.md §sequences):
15
+ * - Video merge per-input options: `transition`, `crossfadeDuration` only.
16
+ * - Audio merge per-input options: `transition`, `crossfadeDuration`,
17
+ * `gapDuration` only.
18
+ * - Image merge has NO per-input options today — `clip(ref)` is reuse/order
19
+ * only. Per-position transitions on image merges throw locally as
20
+ * `GislPerInputOptionsNotSupportedError`.
21
+ * - No per-clip `trimStart`/`trimEnd` today (contracts ticket iZzn5QrS
22
+ * tracks the fix). Workaround: pre-trim each clip via a chained
23
+ * `compress(file, trimStart, trimEnd)`.
24
+ *
25
+ * Local validation runs BEFORE any upload — undeclared refs and unused
26
+ * assets both fail fast so the caller saves bandwidth on typo'd composes.
27
+ */
28
+ import { uploadSource } from './types.js';
29
+ import { GislConfigError, GislPerInputOptionsNotSupportedError, GislTimeoutError, GislUndeclaredAssetError, GislUnusedAssetError, } from './errors.js';
30
+ import { _checkAborted, _consumeSseToTerminal, _parseMaxWait, _pollToTerminal, _projectResult, } from './builder.js';
31
+ /**
32
+ * Construct a path-asset. Bare-string arguments to `merge(...)` are
33
+ * implicitly wrapped via this helper.
34
+ */
35
+ export function asset(path) {
36
+ return { type: 'path', path };
37
+ }
38
+ /**
39
+ * Wrap an already-uploaded file_id as a merge asset. Use this when the
40
+ * SAME logical file should be referenced from multiple merge runs with
41
+ * guaranteed-single-upload semantics.
42
+ */
43
+ export function handle(fileId) {
44
+ return { type: 'handle', fileId };
45
+ }
46
+ /**
47
+ * Construct a clip entry for `.sequence(...)`. The asset MUST already
48
+ * be in the merge's declared asset set.
49
+ */
50
+ export function clip(ref, options = {}) {
51
+ return { type: 'clip', asset: ref, options };
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // MergeBuilder
55
+ // ---------------------------------------------------------------------------
56
+ /**
57
+ * Captures the (declared assets, options) for a merge. `.sequence(...)`
58
+ * pins the play order; without it, the declared order is used as-is
59
+ * with no per-input options.
60
+ *
61
+ * Local validation runs at `.run()`/`.submit()` time (BEFORE any upload)
62
+ * and throws one of `GislUndeclaredAssetError`, `GislUnusedAssetError`,
63
+ * or `GislPerInputOptionsNotSupportedError` if the compose is invalid.
64
+ */
65
+ export class MergeBuilder {
66
+ client;
67
+ assets;
68
+ opOptions;
69
+ sequenceEntries = null;
70
+ constructor(client, assets, opOptions) {
71
+ this.client = client;
72
+ this.assets = assets;
73
+ this.opOptions = opOptions;
74
+ }
75
+ /**
76
+ * Pin the merge play order. Each entry must reference an asset that
77
+ * was declared in the parent `merge(...)` call. Repeats are allowed
78
+ * and deduped on upload (one upload per unique declared asset).
79
+ */
80
+ sequence(...entries) {
81
+ this.sequenceEntries = entries;
82
+ return this;
83
+ }
84
+ async run(options) {
85
+ const deadline = Date.now() + _parseMaxWait(options.maxWait);
86
+ const signal = options.signal;
87
+ const onProgress = options.onProgress;
88
+ const useSSE = options.useSSE ?? true;
89
+ // 1. Validate locally BEFORE any upload.
90
+ const plan = this.planSequence();
91
+ // 2. Upload each unique asset exactly ONCE. Pass the deadline so the
92
+ // upload loop can abort mid-batch on a slow connection.
93
+ const uploadedByAssetId = await this.uploadUniqueAssets(plan.uniqueAssets, {
94
+ signal,
95
+ onProgress,
96
+ deadline,
97
+ });
98
+ _checkAborted(signal);
99
+ if (Date.now() >= deadline) {
100
+ throw new GislTimeoutError(`Upload(s) completed but maxWait elapsed before merge workflow could be created`);
101
+ }
102
+ // 3. Build the merge JobDefinitionPayload (multi-input).
103
+ const payload = this.buildPayload(plan, uploadedByAssetId);
104
+ const created = await this.client.createWorkflow(payload);
105
+ _checkAborted(signal);
106
+ // 4. Wait to terminal status.
107
+ const finalStatus = await this.awaitTerminal({
108
+ workflowId: created.workflowId,
109
+ deadline,
110
+ signal,
111
+ onProgress,
112
+ useSSE,
113
+ pollIntervalMs: options.pollIntervalMs,
114
+ });
115
+ // 5. Fetch downloads + project.
116
+ if (Date.now() >= deadline) {
117
+ throw new GislTimeoutError(`Merge workflow ${created.workflowId} reached terminal status but maxWait elapsed before downloads could be fetched`);
118
+ }
119
+ const downloads = await this.client.getWorkflowDownloads(created.workflowId);
120
+ return _projectResult(finalStatus, downloads.downloads, this.opOptionsForResolved());
121
+ }
122
+ async submit(options) {
123
+ const plan = this.planSequence();
124
+ const uploadedByAssetId = await this.uploadUniqueAssets(plan.uniqueAssets, {});
125
+ const payload = this.buildPayload(plan, uploadedByAssetId);
126
+ payload.callback_url = options.webhook;
127
+ const created = await this.client.createWorkflow(payload);
128
+ const handle = {
129
+ workflowId: created.workflowId,
130
+ ...(created.webhookSecret != null ? { webhookSecret: created.webhookSecret } : {}),
131
+ };
132
+ return handle;
133
+ }
134
+ // ---------------------------------------------------------------------------
135
+ /**
136
+ * Resolve the declared assets + sequence (or fall back to declared order),
137
+ * dedupe by identity, and run the local validators. The returned plan
138
+ * carries the SEQUENCE (positional entries) + the UNIQUE assets to upload.
139
+ */
140
+ planSequence() {
141
+ const declaredIds = this.assets.map(assetIdentity);
142
+ const declaredSet = new Map();
143
+ for (let i = 0; i < this.assets.length; i += 1) {
144
+ const id = declaredIds[i];
145
+ if (!declaredSet.has(id))
146
+ declaredSet.set(id, this.assets[i]);
147
+ }
148
+ // Use the explicit sequence if set; otherwise the declared order as-is.
149
+ const rawEntries = this.sequenceEntries ?? this.assets.map((a) => a);
150
+ // Validate per-entry: undeclared ref + image-merge-clip-with-opts.
151
+ const mediaKind = this.inferMediaKind();
152
+ const positions = [];
153
+ const refIds = new Set();
154
+ for (const entry of rawEntries) {
155
+ const isClip = entry !== null && typeof entry === 'object' && 'type' in entry && entry.type === 'clip';
156
+ const assetRef = isClip ? entry.asset : entry;
157
+ const id = assetIdentity(assetRef);
158
+ if (!declaredSet.has(id)) {
159
+ throw new GislUndeclaredAssetError(id, Array.from(declaredSet.keys()));
160
+ }
161
+ refIds.add(id);
162
+ if (isClip) {
163
+ const opts = entry.options;
164
+ const hasOpts = opts.transition !== undefined ||
165
+ opts.crossfadeDuration !== undefined ||
166
+ opts.gapDuration !== undefined;
167
+ if (hasOpts && mediaKind === 'image') {
168
+ throw new GislPerInputOptionsNotSupportedError('image');
169
+ }
170
+ positions.push({ assetId: id, options: opts });
171
+ }
172
+ else {
173
+ positions.push({ assetId: id, options: {} });
174
+ }
175
+ }
176
+ // Unused-asset check.
177
+ if (this.sequenceEntries !== null && this.opOptions.allowUnusedAssets !== true) {
178
+ const unused = Array.from(declaredSet.keys()).filter((id) => !refIds.has(id));
179
+ if (unused.length > 0)
180
+ throw new GislUnusedAssetError(unused);
181
+ }
182
+ // Codex r1 medium edb1bb641d81 — when an explicit sequence is set,
183
+ // restrict the upload set to only the referenced assets. This prevents
184
+ // wasted uploads of declared-but-unsequenced assets (e.g. when the
185
+ // user passes allowUnusedAssets: true).
186
+ const uploadSet = this.sequenceEntries === null
187
+ ? declaredSet
188
+ : new Map(Array.from(declaredSet.entries()).filter(([id]) => refIds.has(id)));
189
+ // Codex r1 medium 5c86b67c979b — enforce merge schema input bounds
190
+ // (min_inputs: 2, max_inputs: 10 per generated/typescript/operations/merge.ts).
191
+ // Validate sequence position count, NOT unique-asset count: the merge job
192
+ // sends N inputs where N = position count (repeats included).
193
+ if (positions.length < 2) {
194
+ throw new GislConfigError(`merge requires at least 2 inputs (got ${positions.length}). Declare more assets or check the sequence.`);
195
+ }
196
+ if (positions.length > 10) {
197
+ throw new GislConfigError(`merge accepts at most 10 inputs (got ${positions.length}). Reduce the sequence or split the merge.`);
198
+ }
199
+ return { mediaKind, positions, uniqueAssets: uploadSet };
200
+ }
201
+ inferMediaKind() {
202
+ if (this.opOptions.mediaKind !== undefined)
203
+ return this.opOptions.mediaKind;
204
+ const first = this.assets[0];
205
+ if (first === undefined)
206
+ return 'video';
207
+ if (first.type === 'path' && typeof first.path === 'string') {
208
+ const lower = first.path.toLowerCase();
209
+ if (/\.(jpe?g|png|webp|avif|gif|heic|tiff?)$/.test(lower))
210
+ return 'image';
211
+ if (/\.(mp3|wav|flac|aac|ogg|m4a)$/.test(lower))
212
+ return 'audio';
213
+ return 'video';
214
+ }
215
+ if (first.type === 'path' && first.path instanceof Blob) {
216
+ if (first.path.type.startsWith('image/'))
217
+ return 'image';
218
+ if (first.path.type.startsWith('audio/'))
219
+ return 'audio';
220
+ return 'video';
221
+ }
222
+ return 'video';
223
+ }
224
+ async uploadUniqueAssets(uniqueAssets, opts) {
225
+ const uploaded = new Map();
226
+ for (const [id, a] of uniqueAssets) {
227
+ // Codex r1 medium 797b4113431f — check the deadline between uploads
228
+ // so a multi-file merge doesn't keep uploading past `maxWait`.
229
+ if (opts.deadline !== undefined && Date.now() >= opts.deadline) {
230
+ throw new GislTimeoutError(`maxWait elapsed mid-upload (after ${uploaded.size} of ${uniqueAssets.size} merge assets)`);
231
+ }
232
+ if (a.type === 'handle') {
233
+ uploaded.set(id, a.fileId);
234
+ continue;
235
+ }
236
+ const uploadOpts = {};
237
+ if (opts.signal !== undefined)
238
+ uploadOpts.signal = opts.signal;
239
+ if (opts.onProgress !== undefined) {
240
+ uploadOpts.onProgress = (uploadedBytes, totalBytes) => {
241
+ opts.onProgress?.({ phase: 'upload', uploadedBytes, totalBytes });
242
+ };
243
+ }
244
+ const resp = await this.client.uploadFile(a.path, uploadOpts);
245
+ uploaded.set(id, resp.fileId);
246
+ }
247
+ return uploaded;
248
+ }
249
+ buildPayload(plan, uploadedByAssetId) {
250
+ const inputs = plan.positions.map((pos) => {
251
+ const fileId = uploadedByAssetId.get(pos.assetId);
252
+ if (fileId === undefined) {
253
+ // Defensive — planSequence should have rejected this.
254
+ throw new Error(`Asset '${pos.assetId}' was never uploaded — internal builder bug`);
255
+ }
256
+ // Codex r1 HIGH 502c6bf232c2 — per_input_options goes on EACH
257
+ // JobInputV2Payload (per-input entry), NOT on operations[0].options.
258
+ // Skip emission for image merges (planSequence already rejects opts
259
+ // on image-merge clips). Project per ClipOptions per media kind
260
+ // (codex r1 medium 128404fa16a9 — gapDuration is audio-only).
261
+ const wireOpts = plan.mediaKind === 'image'
262
+ ? {}
263
+ : wirePerInputOptions(pos.options, plan.mediaKind);
264
+ const input = { source: uploadSource(fileId) };
265
+ if (Object.keys(wireOpts).length > 0) {
266
+ input.per_input_options = wireOpts;
267
+ }
268
+ return input;
269
+ });
270
+ // Merge-level options (excluding the SDK-side mediaKind/allowUnusedAssets).
271
+ const mergeOpts = wireMergeOptions(this.opOptions, plan.mediaKind);
272
+ const job = {
273
+ id: 'merge',
274
+ inputs,
275
+ operations: [{ type: 'merge', options: mergeOpts }],
276
+ };
277
+ return { jobs: [job] };
278
+ }
279
+ opOptionsForResolved() {
280
+ // Strip the SDK-only fields before exposing on resolvedOptions.applied.
281
+ const { mediaKind: _m, allowUnusedAssets: _a, ...rest } = this.opOptions;
282
+ void _m;
283
+ void _a;
284
+ return { ...rest };
285
+ }
286
+ async awaitTerminal(args) {
287
+ if (args.useSSE) {
288
+ try {
289
+ return await _consumeSseToTerminal(this.client, args);
290
+ }
291
+ catch (err) {
292
+ if (err instanceof GislTimeoutError)
293
+ throw err;
294
+ if (err instanceof DOMException && err.name === 'AbortError')
295
+ throw err;
296
+ }
297
+ }
298
+ return await _pollToTerminal(this.client, args);
299
+ }
300
+ }
301
+ /**
302
+ * Per-Blob identity tokens — referential dedupe via WeakMap. Two distinct
303
+ * Blob objects with the same size + MIME would otherwise hash to the
304
+ * SAME identity (silent data loss; code-reviewer P1 conf 8). Reference
305
+ * identity guarantees same-Blob = same-upload and different-Blob = two
306
+ * uploads, irrespective of content sniffing.
307
+ */
308
+ const _blobTokens = new WeakMap();
309
+ let _blobCounter = 0;
310
+ /**
311
+ * Asset identity for dedupe. Handles use their fileId; paths use a
312
+ * trim+trailing-separator-strip normalised string (NOT case-folded —
313
+ * case-insensitive dedupe would silently merge `A.mp4` and `a.mp4` on a
314
+ * case-sensitive filesystem; code-reviewer P1 conf 7). Blobs use
315
+ * referential identity via a WeakMap-backed token. Bare-string path
316
+ * dedupe is best-effort — use `handle()` for guaranteed reuse.
317
+ */
318
+ function assetIdentity(a) {
319
+ if (a.type === 'handle')
320
+ return `handle:${a.fileId}`;
321
+ if (a.path instanceof Blob) {
322
+ let token = _blobTokens.get(a.path);
323
+ if (token === undefined) {
324
+ _blobCounter += 1;
325
+ token = `${_blobCounter}`;
326
+ _blobTokens.set(a.path, token);
327
+ }
328
+ return `blob:${token}`;
329
+ }
330
+ // Codex r2 medium bb500566a683 — dedupe by the EXACT caller-provided
331
+ // string. Previous trim+trailing-slash-strip would collapse
332
+ // `'clip.mp4'` and `'clip.mp4 '` into one upload while uploadFile later
333
+ // received the original string. Exact-string dedupe = upload identity
334
+ // matches dedupe identity. Best-effort = "two identical strings dedupe;
335
+ // anything else is a separate upload" — predictable.
336
+ return `path:${a.path}`;
337
+ }
338
+ function wireMergeOptions(opts, mediaKind) {
339
+ const out = {};
340
+ if (opts.transition !== undefined)
341
+ out.transition = opts.transition;
342
+ if (opts.crossfadeDuration !== undefined)
343
+ out.crossfade_duration = opts.crossfadeDuration;
344
+ // Codex r2 medium ab2422e56ea0 — merge-level `gap_duration` is on
345
+ // MergeAudioOptions only (not MergeVideoOptions or MergeImageOptions).
346
+ // Drop it for non-audio merges instead of shipping an invalid payload.
347
+ if (opts.gapDuration !== undefined && mediaKind === 'audio')
348
+ out.gap_duration = opts.gapDuration;
349
+ if (opts.normalizeAudio !== undefined)
350
+ out.normalize_audio = opts.normalizeAudio;
351
+ if (opts.codec !== undefined)
352
+ out.codec = opts.codec;
353
+ if (opts.crf !== undefined)
354
+ out.crf = opts.crf;
355
+ if (opts.preset !== undefined)
356
+ out.preset = opts.preset;
357
+ if (opts.targetSize !== undefined) {
358
+ out.target_size_bytes = typeof opts.targetSize === 'number'
359
+ ? opts.targetSize
360
+ : parseSizeString(opts.targetSize);
361
+ out.encoding_mode = 'target_size';
362
+ }
363
+ if (opts.transitionDuration !== undefined)
364
+ out.transition_duration = opts.transitionDuration;
365
+ if (opts.fps !== undefined)
366
+ out.fps = opts.fps;
367
+ if (opts.durationPerImage !== undefined)
368
+ out.duration_per_image = opts.durationPerImage;
369
+ if (opts.loopCount !== undefined)
370
+ out.loop_count = opts.loopCount;
371
+ if (opts.output !== undefined)
372
+ out.output_type = opts.output;
373
+ if (opts.outputType !== undefined)
374
+ out.output_type = opts.outputType;
375
+ if (opts.videoFormat !== undefined)
376
+ out.video_format = opts.videoFormat;
377
+ return out;
378
+ }
379
+ /**
380
+ * Project a ClipOptions into the wire-shape per_input_options object.
381
+ * Codex r1 medium 128404fa16a9 — `gap_duration` is on AUDIO per-input
382
+ * only (`MergeAudioPerInputOptions`), NOT video. Splitting by mediaKind
383
+ * here keeps the wire payload honest and prevents the server from
384
+ * silently rejecting/ignoring an out-of-spec field.
385
+ */
386
+ function wirePerInputOptions(opts, mediaKind) {
387
+ const out = {};
388
+ if (opts.transition !== undefined)
389
+ out.transition = opts.transition;
390
+ if (opts.crossfadeDuration !== undefined)
391
+ out.crossfade_duration = opts.crossfadeDuration;
392
+ if (opts.gapDuration !== undefined && mediaKind === 'audio') {
393
+ out.gap_duration = opts.gapDuration;
394
+ }
395
+ return out;
396
+ }
397
+ function parseSizeString(s) {
398
+ const m = /^(\d+(?:\.\d+)?)\s*(KB|MB|GB|B)?$/i.exec(s.trim());
399
+ if (m === null)
400
+ throw new TypeError(`Invalid targetSize string '${s}'`);
401
+ const n = Number(m[1]);
402
+ const unit = (m[2] ?? 'B').toUpperCase();
403
+ switch (unit) {
404
+ case 'B': return Math.round(n);
405
+ case 'KB': return Math.round(n * 1_000);
406
+ case 'MB': return Math.round(n * 1_000_000);
407
+ case 'GB': return Math.round(n * 1_000_000_000);
408
+ /* istanbul ignore next */
409
+ default: throw new TypeError(`Unknown size unit '${unit}'`);
410
+ }
411
+ }
package/dist/types.d.ts CHANGED
@@ -94,21 +94,19 @@ export interface JobDefinitionPayload {
94
94
  operations: OperationDef[];
95
95
  /** Per-job hide-intermediates promotion flag per ADR-0003. */
96
96
  deliver?: boolean;
97
- /**
98
- * Per-job opt-out of the "compress required in every chain" gate.
99
- * When `true`, the server accepts a chain that doesn't terminate in a
100
- * `compress` operation — required for chains that observe multi-output
101
- * fan-out (e.g. convert PDF -> N images per ADR-0009 §D2) without
102
- * collapsing the N outputs through a trailing chained compress.
103
- *
104
- * Accepted by the API at `compression/src/Jobs/.../JobDefinition.php`
105
- * (`skipCompression`) and validated against the chain-ordering rule at
106
- * `Job::validateChainOrdering`. Currently undocumented in
107
- * `contracts/openapi/api.yaml` JobDefinition schema — spec follow-up
108
- * pending; the SDK exposes the field to unblock e2e A8-FLIP.
109
- */
110
- skip_compression?: boolean;
111
97
  }
98
+ /**
99
+ * Single source of truth for JobDefinitionPayload's top-level wire keys.
100
+ * Read by `contract-drift-fields.test.ts` to cross-check against the spec
101
+ * at `JobDefinition`. Not re-exported from `index.ts`; this is reachable
102
+ * only via deep imports and should not be treated as public API.
103
+ *
104
+ * The V1-only `skip_compression` field is deliberately absent (eQnUMW68);
105
+ * the runtime drift test will fail if the spec re-introduces it OR adds
106
+ * any other V1-leak field to the V2 JobDefinition schema.
107
+ * @internal
108
+ */
109
+ export declare const JOB_DEFINITION_PAYLOAD_KEYS: readonly ["id", "source", "inputs", "operations", "deliver"];
112
110
  export type ExternalDestinationPayload = {
113
111
  type: 'connection';
114
112
  connection_id: string;
package/dist/types.js CHANGED
@@ -15,6 +15,24 @@ export function externalImportSource(externalSourceId) {
15
15
  export function connectionSource(connectionId, path) {
16
16
  return { type: 'connection', connection_id: connectionId, path };
17
17
  }
18
+ /**
19
+ * Single source of truth for JobDefinitionPayload's top-level wire keys.
20
+ * Read by `contract-drift-fields.test.ts` to cross-check against the spec
21
+ * at `JobDefinition`. Not re-exported from `index.ts`; this is reachable
22
+ * only via deep imports and should not be treated as public API.
23
+ *
24
+ * The V1-only `skip_compression` field is deliberately absent (eQnUMW68);
25
+ * the runtime drift test will fail if the spec re-introduces it OR adds
26
+ * any other V1-leak field to the V2 JobDefinition schema.
27
+ * @internal
28
+ */
29
+ export const JOB_DEFINITION_PAYLOAD_KEYS = Object.freeze([
30
+ 'id',
31
+ 'source',
32
+ 'inputs',
33
+ 'operations',
34
+ 'deliver',
35
+ ]);
18
36
  /**
19
37
  * Single source of truth for WorkflowCreatePayload's top-level wire keys.
20
38
  * Read by `contract-drift-fields.test.ts` to cross-check against the spec at
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@giveitsmaller/sdk",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Node.js SDK for the GISL (Give It Smaller) file compression and processing API",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  "node": ">=18"
20
20
  },
21
21
  "dependencies": {
22
- "@giveitsmaller/contracts": "^0.4.0"
22
+ "@giveitsmaller/contracts": "^0.8.0"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@types/node": "^22",