@spooled/sdk 1.0.35 → 1.0.36
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 +49 -4
- package/dist/{index-B96n0WUe.d.cts → index-CNhKw4vH.d.cts} +2 -2
- package/dist/{index-B96n0WUe.d.ts → index-CNhKw4vH.d.ts} +2 -2
- package/dist/index.cjs +98 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +98 -12
- package/dist/index.js.map +1 -1
- package/dist/worker/index.cjs +1 -1
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +1 -1
- package/dist/worker/index.d.ts +1 -1
- package/dist/worker/index.js +1 -1
- package/dist/worker/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -321,7 +321,10 @@ All operations automatically enforce tier-based limits:
|
|
|
321
321
|
- ✅ Webhook creation
|
|
322
322
|
|
|
323
323
|
When a plan quota or limit is exceeded, you'll receive an `HTTP 429` response with
|
|
324
|
-
`code: "QUOTA_EXCEEDED"
|
|
324
|
+
`code: "QUOTA_EXCEEDED"`. The SDK reliably preserves the HTTP status, error code, message,
|
|
325
|
+
request ID, and rate-limit headers. `error.details` is populated only when the server nests
|
|
326
|
+
metadata under a JSON `details` field; quota fields sent at the top level of the server payload
|
|
327
|
+
(such as `resource`, `current`, `limit`, and `plan`) are not currently exposed by the SDK.
|
|
325
328
|
|
|
326
329
|
```typescript
|
|
327
330
|
import { RateLimitError } from '@spooled/sdk';
|
|
@@ -330,9 +333,10 @@ try {
|
|
|
330
333
|
await client.jobs.create({ /* ... */ });
|
|
331
334
|
} catch (error) {
|
|
332
335
|
if (error instanceof RateLimitError && error.code === 'QUOTA_EXCEEDED') {
|
|
333
|
-
// Plan quota reached (HTTP 429); details carry resource/current/limit/plan
|
|
334
336
|
console.log(`Plan limit: ${error.message}`);
|
|
335
|
-
console.log(
|
|
337
|
+
console.log(`Request ID: ${error.requestId ?? 'not provided'}`);
|
|
338
|
+
// Present only if this server response included a nested `details` object.
|
|
339
|
+
if (error.details) console.log('Details:', error.details);
|
|
336
340
|
}
|
|
337
341
|
}
|
|
338
342
|
```
|
|
@@ -406,12 +410,13 @@ const { jobs } = await grpcClient.queue.dequeue({
|
|
|
406
410
|
leaseDurationSecs: 300
|
|
407
411
|
});
|
|
408
412
|
|
|
409
|
-
// Process and complete
|
|
413
|
+
// Process and complete. Echo leaseId when present so current servers can fence stale executions; legacy servers may omit it and then use worker-ID-only fencing.
|
|
410
414
|
for (const job of jobs) {
|
|
411
415
|
await processJob(job);
|
|
412
416
|
await grpcClient.queue.complete({
|
|
413
417
|
jobId: job.id,
|
|
414
418
|
workerId,
|
|
419
|
+
leaseId: job.leaseId ?? undefined,
|
|
415
420
|
result: { success: true }
|
|
416
421
|
});
|
|
417
422
|
}
|
|
@@ -441,6 +446,46 @@ import type {
|
|
|
441
446
|
} from '@spooled/sdk';
|
|
442
447
|
```
|
|
443
448
|
|
|
449
|
+
## Development and releases
|
|
450
|
+
|
|
451
|
+
Before opening a pull request or tagging a release, run the same checks used by the package and publish workflow:
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
npm ci
|
|
455
|
+
npm run lint
|
|
456
|
+
npm run typecheck
|
|
457
|
+
npm run build
|
|
458
|
+
npm test
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Production-safe API verification is separate and credentialed: `npm run verify:production`. Integration tests use `npm run test:integration` against a local or isolated test backend.
|
|
462
|
+
|
|
463
|
+
Maintainers release by pushing a matching `vX.Y.Z` tag. The tag workflow verifies all same-artifact version surfaces before publishing through npm Trusted Publishing (OIDC) with provenance.
|
|
464
|
+
|
|
465
|
+
### Maintainer release checklist (advisory)
|
|
466
|
+
|
|
467
|
+
This checklist records evidence and does not block ordinary commits, experiments, or compatibility research. A mismatch among values that identify the same npm artifact is a release error and cannot be waived for publication. Publishing the SDK is not a service deployment; consumers report new metadata only after upgrading and restarting or redeploying their applications.
|
|
468
|
+
|
|
469
|
+
Before pushing `vX.Y.Z`:
|
|
470
|
+
|
|
471
|
+
- [ ] Set `package.json.version` to `X.Y.Z`.
|
|
472
|
+
- [ ] Regenerate `package-lock.json`; verify both top-level `version` and `packages[""].version` are `X.Y.Z`.
|
|
473
|
+
- [ ] Set `SDK_VERSION` and the default User-Agent in `src/config.ts` to `X.Y.Z`.
|
|
474
|
+
- [ ] Confirm `SpooledWorker` derives its default registration version from `SDK_VERSION`.
|
|
475
|
+
- [ ] Add `## [X.Y.Z] - YYYY-MM-DD` to `CHANGELOG.md` and review version-bearing worker documentation.
|
|
476
|
+
- [ ] Run `npm ci`, lint, typecheck, build, tests, and `npm pack --dry-run`.
|
|
477
|
+
- [ ] Review the packed file list and compiled output; confirm the package contains no local logs, caches, credentials, or unintended generated files.
|
|
478
|
+
- [ ] Record any cross-repository compatibility difference separately; backend, SDK, and application versions are not required to match numerically.
|
|
479
|
+
|
|
480
|
+
Publish and verify:
|
|
481
|
+
|
|
482
|
+
- [ ] Create one immutable `vX.Y.Z` tag on the reviewed commit and never move or recreate a pushed tag. Fix forward with a new patch version after external visibility.
|
|
483
|
+
- [ ] Record the tag commit and publish workflow URL.
|
|
484
|
+
- [ ] Verify `npm view @spooled/sdk@X.Y.Z version gitHead dist.integrity dist.attestations --json` against the tag and commit.
|
|
485
|
+
- [ ] Confirm `latest` points to `X.Y.Z` when intended and inspect the published tarball's `SDK_VERSION`, User-Agent, and worker registration default.
|
|
486
|
+
- [ ] Confirm npm provenance names this repository, `.github/workflows/publish.yml`, `refs/tags/vX.Y.Z`, and the release commit.
|
|
487
|
+
- [ ] Record registry publication separately from consumer rollout, backend deployment, or documentation deployment.
|
|
488
|
+
|
|
444
489
|
## License
|
|
445
490
|
|
|
446
491
|
Apache-2.0 - see [LICENSE](LICENSE) for details.
|
|
@@ -107,11 +107,11 @@ declare const DEFAULT_CONFIG: {
|
|
|
107
107
|
successThreshold: number;
|
|
108
108
|
timeout: number;
|
|
109
109
|
};
|
|
110
|
-
readonly userAgent: "@spooled/sdk-nodejs/1.0.
|
|
110
|
+
readonly userAgent: "@spooled/sdk-nodejs/1.0.36";
|
|
111
111
|
readonly autoRefreshToken: true;
|
|
112
112
|
};
|
|
113
113
|
/** SDK version */
|
|
114
|
-
declare const SDK_VERSION = "1.0.
|
|
114
|
+
declare const SDK_VERSION = "1.0.36";
|
|
115
115
|
/** API version prefix */
|
|
116
116
|
declare const API_VERSION = "v1";
|
|
117
117
|
/**
|
|
@@ -107,11 +107,11 @@ declare const DEFAULT_CONFIG: {
|
|
|
107
107
|
successThreshold: number;
|
|
108
108
|
timeout: number;
|
|
109
109
|
};
|
|
110
|
-
readonly userAgent: "@spooled/sdk-nodejs/1.0.
|
|
110
|
+
readonly userAgent: "@spooled/sdk-nodejs/1.0.36";
|
|
111
111
|
readonly autoRefreshToken: true;
|
|
112
112
|
};
|
|
113
113
|
/** SDK version */
|
|
114
|
-
declare const SDK_VERSION = "1.0.
|
|
114
|
+
declare const SDK_VERSION = "1.0.36";
|
|
115
115
|
/** API version prefix */
|
|
116
116
|
declare const API_VERSION = "v1";
|
|
117
117
|
/**
|
package/dist/index.cjs
CHANGED
|
@@ -49,10 +49,10 @@ var DEFAULT_CONFIG = {
|
|
|
49
49
|
successThreshold: 3,
|
|
50
50
|
timeout: 3e4
|
|
51
51
|
},
|
|
52
|
-
userAgent: "@spooled/sdk-nodejs/1.0.
|
|
52
|
+
userAgent: "@spooled/sdk-nodejs/1.0.36",
|
|
53
53
|
autoRefreshToken: true
|
|
54
54
|
};
|
|
55
|
-
var SDK_VERSION = "1.0.
|
|
55
|
+
var SDK_VERSION = "1.0.36";
|
|
56
56
|
var API_VERSION = "v1";
|
|
57
57
|
var API_BASE_PATH = `/api/${API_VERSION}`;
|
|
58
58
|
function normalizeCredential(value) {
|
|
@@ -774,15 +774,17 @@ var HttpClient = class {
|
|
|
774
774
|
*/
|
|
775
775
|
resolveRetryConfig(method, options) {
|
|
776
776
|
const base = this.config.retry;
|
|
777
|
-
if (base.retryOn) {
|
|
778
|
-
return base;
|
|
779
|
-
}
|
|
780
777
|
if (IDEMPOTENT_METHODS.has(method) || this.hasIdempotencyKey(options)) {
|
|
781
778
|
return base;
|
|
782
779
|
}
|
|
783
780
|
return {
|
|
784
781
|
...base,
|
|
785
|
-
retryOn: (error) =>
|
|
782
|
+
retryOn: (error, attempt) => {
|
|
783
|
+
if (error instanceof RateLimitError) {
|
|
784
|
+
return base.retryOn ? base.retryOn(error, attempt) : true;
|
|
785
|
+
}
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
786
788
|
};
|
|
787
789
|
}
|
|
788
790
|
/**
|
|
@@ -1964,8 +1966,9 @@ var WebSocketRealtimeClient = class {
|
|
|
1964
1966
|
};
|
|
1965
1967
|
this.ws.onclose = (event) => {
|
|
1966
1968
|
this.options.debug(`WebSocket closed: ${event.code} ${event.reason}`);
|
|
1969
|
+
const wasConnecting = this.state === "connecting";
|
|
1967
1970
|
this.handleDisconnect();
|
|
1968
|
-
if (
|
|
1971
|
+
if (wasConnecting) {
|
|
1969
1972
|
reject(new Error(`WebSocket connection failed: ${event.reason || "Unknown error"}`));
|
|
1970
1973
|
}
|
|
1971
1974
|
};
|
|
@@ -2127,6 +2130,11 @@ var WebSocketRealtimeClient = class {
|
|
|
2127
2130
|
await this.connect();
|
|
2128
2131
|
} catch (error) {
|
|
2129
2132
|
this.options.debug("Reconnect failed", error);
|
|
2133
|
+
if (this.options.autoReconnect && this.reconnectAttempts < this.options.maxReconnectAttempts) {
|
|
2134
|
+
this.scheduleReconnect();
|
|
2135
|
+
} else {
|
|
2136
|
+
this.setState("disconnected");
|
|
2137
|
+
}
|
|
2130
2138
|
}
|
|
2131
2139
|
}, delay);
|
|
2132
2140
|
}
|
|
@@ -2241,7 +2249,7 @@ var SseRealtimeClient = class {
|
|
|
2241
2249
|
}
|
|
2242
2250
|
let token;
|
|
2243
2251
|
try {
|
|
2244
|
-
token = await this.options.tokenProvider();
|
|
2252
|
+
token = await this.options.tokenProvider(this.reconnectAttempts > 0);
|
|
2245
2253
|
} catch (error) {
|
|
2246
2254
|
this.setState("disconnected");
|
|
2247
2255
|
throw new Error(`Failed to acquire realtime token: ${error}`);
|
|
@@ -2411,6 +2419,11 @@ var SseRealtimeClient = class {
|
|
|
2411
2419
|
await this.connect(this.filter || void 0);
|
|
2412
2420
|
} catch (error) {
|
|
2413
2421
|
this.options.debug("SSE reconnect failed", error);
|
|
2422
|
+
if (this.options.autoReconnect && this.reconnectAttempts < this.options.maxReconnectAttempts) {
|
|
2423
|
+
this.scheduleReconnect();
|
|
2424
|
+
} else {
|
|
2425
|
+
this.setState("disconnected");
|
|
2426
|
+
}
|
|
2414
2427
|
}
|
|
2415
2428
|
}, delay);
|
|
2416
2429
|
}
|
|
@@ -2682,6 +2695,77 @@ function createJobStream(call, options) {
|
|
|
2682
2695
|
}
|
|
2683
2696
|
|
|
2684
2697
|
// src/grpc/client.ts
|
|
2698
|
+
function encodeStruct(value) {
|
|
2699
|
+
if (value == null) {
|
|
2700
|
+
return void 0;
|
|
2701
|
+
}
|
|
2702
|
+
return {
|
|
2703
|
+
fields: Object.fromEntries(
|
|
2704
|
+
Object.entries(value).map(([key, entry]) => [key, encodeValue(entry)])
|
|
2705
|
+
)
|
|
2706
|
+
};
|
|
2707
|
+
}
|
|
2708
|
+
function encodeValue(value) {
|
|
2709
|
+
if (value == null) {
|
|
2710
|
+
return { nullValue: 0 };
|
|
2711
|
+
}
|
|
2712
|
+
if (Array.isArray(value)) {
|
|
2713
|
+
return { listValue: { values: value.map(encodeValue) } };
|
|
2714
|
+
}
|
|
2715
|
+
switch (typeof value) {
|
|
2716
|
+
case "number":
|
|
2717
|
+
return { numberValue: value };
|
|
2718
|
+
case "string":
|
|
2719
|
+
return { stringValue: value };
|
|
2720
|
+
case "boolean":
|
|
2721
|
+
return { boolValue: value };
|
|
2722
|
+
case "object":
|
|
2723
|
+
return { structValue: encodeStruct(value) };
|
|
2724
|
+
default:
|
|
2725
|
+
return { stringValue: String(value) };
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
function decodeStruct(value) {
|
|
2729
|
+
if (!value || typeof value !== "object" || !("fields" in value)) {
|
|
2730
|
+
return value;
|
|
2731
|
+
}
|
|
2732
|
+
const fields = value.fields ?? {};
|
|
2733
|
+
return Object.fromEntries(Object.entries(fields).map(([key, entry]) => [key, decodeValue(entry)]));
|
|
2734
|
+
}
|
|
2735
|
+
function decodeValue(value) {
|
|
2736
|
+
if ("nullValue" in value) {
|
|
2737
|
+
return null;
|
|
2738
|
+
}
|
|
2739
|
+
if ("numberValue" in value) {
|
|
2740
|
+
return value.numberValue;
|
|
2741
|
+
}
|
|
2742
|
+
if ("stringValue" in value) {
|
|
2743
|
+
return value.stringValue;
|
|
2744
|
+
}
|
|
2745
|
+
if ("boolValue" in value) {
|
|
2746
|
+
return value.boolValue;
|
|
2747
|
+
}
|
|
2748
|
+
if ("structValue" in value) {
|
|
2749
|
+
return decodeStruct(value.structValue);
|
|
2750
|
+
}
|
|
2751
|
+
if ("listValue" in value) {
|
|
2752
|
+
return (value.listValue.values ?? []).map(decodeValue);
|
|
2753
|
+
}
|
|
2754
|
+
return void 0;
|
|
2755
|
+
}
|
|
2756
|
+
function encodeEnqueueRequest(params) {
|
|
2757
|
+
return { ...params, payload: encodeStruct(params.payload) };
|
|
2758
|
+
}
|
|
2759
|
+
function encodeCompleteRequest(params) {
|
|
2760
|
+
return { ...params, result: encodeStruct(params.result) };
|
|
2761
|
+
}
|
|
2762
|
+
function decodeJob(job) {
|
|
2763
|
+
return {
|
|
2764
|
+
...job,
|
|
2765
|
+
payload: decodeStruct(job.payload),
|
|
2766
|
+
result: decodeStruct(job.result)
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2685
2769
|
function promisify(client, method, request, metadata) {
|
|
2686
2770
|
return new Promise((resolve, reject) => {
|
|
2687
2771
|
method.call(client, request, metadata, (error, response) => {
|
|
@@ -2709,19 +2793,20 @@ var GrpcQueueOperations = class {
|
|
|
2709
2793
|
* Enqueue a new job
|
|
2710
2794
|
*/
|
|
2711
2795
|
async enqueue(params) {
|
|
2712
|
-
return promisify(this.client, this.client.Enqueue, params, this.metadata);
|
|
2796
|
+
return promisify(this.client, this.client.Enqueue, encodeEnqueueRequest(params), this.metadata);
|
|
2713
2797
|
}
|
|
2714
2798
|
/**
|
|
2715
2799
|
* Dequeue a job (for workers)
|
|
2716
2800
|
*/
|
|
2717
2801
|
async dequeue(params) {
|
|
2718
|
-
|
|
2802
|
+
const response = await promisify(this.client, this.client.Dequeue, params, this.metadata);
|
|
2803
|
+
return { ...response, jobs: response.jobs.map(decodeJob) };
|
|
2719
2804
|
}
|
|
2720
2805
|
/**
|
|
2721
2806
|
* Complete a job successfully
|
|
2722
2807
|
*/
|
|
2723
2808
|
async complete(params) {
|
|
2724
|
-
return promisify(this.client, this.client.Complete, params, this.metadata);
|
|
2809
|
+
return promisify(this.client, this.client.Complete, encodeCompleteRequest(params), this.metadata);
|
|
2725
2810
|
}
|
|
2726
2811
|
/**
|
|
2727
2812
|
* Fail a job
|
|
@@ -2739,7 +2824,8 @@ var GrpcQueueOperations = class {
|
|
|
2739
2824
|
* Get a job by ID
|
|
2740
2825
|
*/
|
|
2741
2826
|
async getJob(jobId) {
|
|
2742
|
-
|
|
2827
|
+
const response = await promisify(this.client, this.client.GetJob, { jobId }, this.metadata);
|
|
2828
|
+
return { ...response, job: response.job ? decodeJob(response.job) : response.job };
|
|
2743
2829
|
}
|
|
2744
2830
|
/**
|
|
2745
2831
|
* Get queue statistics
|