@north-light/sdk 0.3.421 → 0.3.423
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 +7 -3
- package/dist/client.js +23 -18
- package/dist/errors.d.ts +9 -3
- package/dist/errors.js +7 -3
- package/dist/resources/run-watcher.d.ts +9 -0
- package/dist/resources/run-watcher.js +8 -3
- package/dist/resources/runs.d.ts +2 -2
- package/dist/resources/runs.js +6 -3
- package/dist/testing/cloud.d.ts +12 -0
- package/dist/testing/cloud.js +35 -9
- package/dist/version.d.ts +5 -2
- package/dist/version.js +8 -3
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -44,9 +44,13 @@ const client = new Crouter({
|
|
|
44
44
|
});
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
## Runtime
|
|
47
|
+
## Runtime compatibility
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Compatibility between the SDK and a runtime is a **contract level** (`MAJOR.MINOR`), not the package version. The SDK exports it as `CONTRACT_LEVEL` and its manifest carries it as `crouterContract`. The level changes only when the `/v1` surface changes in a way the release check can see or an author declared, so most releases leave it alone.
|
|
50
|
+
|
|
51
|
+
Every runtime answer carries `Crouter-Contract` (the levels it serves) and `Crouter-Runtime-Version`. The SDK sends the level it needs on every request except `/healthz` as `Crouter-Required-Contract`. A runtime satisfies it when it serves the same major and a minor at or above the required one. When it does not, or it names no level on a successful answer, the call throws `RuntimeVersionError` before the SDK reads the response: `code` is `runtime_version_unsupported`, `status` is 426, `retryable` is false, and `requiredContract` and `runtimeContract` name both levels (`sdkVersion` and `runtimeVersion` stay for logging). The hosting router and the runtime refuse the same way *before* any handler runs, so nothing is accepted and no `Idempotency-Key` is recorded (a retry after the roll is a first send). Fix it by rolling the runtime forward, or by pinning an SDK whose `crouterContract` the runtime serves.
|
|
52
|
+
|
|
53
|
+
**`@latest` always works against production.** Every SDK release is published to the `next` dist-tag first; `latest` moves only once production serves the SDK's contract level. `next` may need a newer runtime than production runs. From this contract's first release the SDK also tolerates values it does not know: an unknown run event `type` is passed through to your listener untouched, and an unknown run `status` ends a wait or watch and reports the raw status.
|
|
50
54
|
|
|
51
55
|
Pin `@north-light/sdk` to an exact version. One release is one version set: `@north-light/sdk@X` depends on exactly `@north-light/api@X` and `@north-light/identity@X`, so the pin fixes all three.
|
|
52
56
|
|
|
@@ -56,7 +60,7 @@ import { RuntimeVersionError } from '@north-light/sdk';
|
|
|
56
60
|
try {
|
|
57
61
|
await client.runs.get(runId);
|
|
58
62
|
} catch (error) {
|
|
59
|
-
if (error instanceof RuntimeVersionError) console.error(`runtime ${error.
|
|
63
|
+
if (error instanceof RuntimeVersionError) console.error(`runtime serves contract ${error.runtimeContract ?? '(unnamed)'}, this SDK needs ${error.requiredContract}`);
|
|
60
64
|
else throw error;
|
|
61
65
|
}
|
|
62
66
|
```
|
package/dist/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { APIError, CrtrClient, MINIMUM_RUNTIME_VERSION_HEADER, RUNTIME_VERSION_HEADER,
|
|
1
|
+
import { APIError, CrtrClient, CONTRACT_HEADER, CONTRACT_LEVEL, MINIMUM_RUNTIME_VERSION_HEADER, REQUIRED_CONTRACT_HEADER, RUNTIME_VERSION_HEADER, contractSatisfies, routes, } from '@north-light/api';
|
|
2
2
|
import { APIConnectionError, APIUserAbortError, AuthenticationError, CrouterError, mapError } from './errors.js';
|
|
3
3
|
import { Bash } from './resources/bash.js';
|
|
4
4
|
import { Canvas } from './resources/canvas.js';
|
|
@@ -20,7 +20,7 @@ import { Memory } from './resources/memory.js';
|
|
|
20
20
|
import { Uploads } from './resources/uploads.js';
|
|
21
21
|
import { deadline, heldReadOptions, withinDeadline } from './resources/request.js';
|
|
22
22
|
import { resolveJsonSchema } from './schema.js';
|
|
23
|
-
import { SDK_VERSION } from './version.js';
|
|
23
|
+
import { LEGACY_FLOOR, SDK_VERSION } from './version.js';
|
|
24
24
|
const LOCAL_SOCKET_FETCH = Symbol.for('@north-light/api/local-socket-fetch');
|
|
25
25
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
26
26
|
const POLL_WAIT_SECONDS = 25;
|
|
@@ -515,38 +515,43 @@ function isNodeRuntime() {
|
|
|
515
515
|
return typeof process !== 'undefined' && process.versions?.node !== undefined;
|
|
516
516
|
}
|
|
517
517
|
/**
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
521
|
-
*
|
|
522
|
-
*
|
|
523
|
-
*
|
|
524
|
-
*
|
|
518
|
+
* The contract gate. The SDK sends the one contract level it needs (`Crouter-Required-Contract`) and the frozen
|
|
519
|
+
* legacy floor (`Crouter-Minimum-Runtime-Version`, which a runtime from before the contract level refuses on). Every
|
|
520
|
+
* runtime answer lists the levels it serves in `Crouter-Contract`:
|
|
521
|
+
* - present and satisfying (same major, served minor at or above the required one): the answer passes;
|
|
522
|
+
* - present and not satisfying: replaced by a 426 `runtime_version_unsupported` envelope before anything reads it,
|
|
523
|
+
* which the client raises as `RuntimeVersionError`;
|
|
524
|
+
* - absent on a refusal (not ok): passes through unchanged, since it can come from a router or proxy and carries no
|
|
525
|
+
* runtime data;
|
|
526
|
+
* - absent on a success: a runtime from before the contract level, so the same 426.
|
|
527
|
+
* `/healthz`, the unversioned substrate probe the cold-start wait polls, is exempt.
|
|
525
528
|
*/
|
|
526
529
|
function requireCurrentRuntime(inner) {
|
|
527
530
|
const checked = async (input, init) => {
|
|
528
531
|
const url = new URL(input instanceof Request ? input.url : String(input), 'http://localhost');
|
|
529
|
-
// Say which
|
|
530
|
-
//
|
|
532
|
+
// Say which contract this SDK needs, so the router in front of a hosted runtime, or the runtime itself, refuses an
|
|
533
|
+
// incompatible one before any handler runs. `/healthz` is exempt from both checks.
|
|
531
534
|
let sentInit = init;
|
|
532
535
|
if (url.pathname !== '/healthz') {
|
|
533
536
|
const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined));
|
|
534
|
-
headers.set(MINIMUM_RUNTIME_VERSION_HEADER,
|
|
537
|
+
headers.set(MINIMUM_RUNTIME_VERSION_HEADER, LEGACY_FLOOR);
|
|
538
|
+
headers.set(REQUIRED_CONTRACT_HEADER, CONTRACT_LEVEL);
|
|
535
539
|
sentInit = { ...init, headers };
|
|
536
540
|
}
|
|
537
541
|
const response = await (inner ?? globalThis.fetch)(input, sentInit);
|
|
538
542
|
if (url.pathname === '/healthz')
|
|
539
543
|
return response;
|
|
540
|
-
const
|
|
541
|
-
if (
|
|
544
|
+
const served = response.headers.get(CONTRACT_HEADER);
|
|
545
|
+
if (served === null ? !response.ok : contractSatisfies(CONTRACT_LEVEL, served))
|
|
542
546
|
return response;
|
|
543
547
|
await response.body?.cancel();
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
548
|
+
const runtimeVersion = response.headers.get(RUNTIME_VERSION_HEADER);
|
|
549
|
+
const message = served === null
|
|
550
|
+
? `The runtime did not name its contract level, so it predates the contract header; @north-light/sdk ${SDK_VERSION} requires contract ${CONTRACT_LEVEL} or later. Upgrade the runtime, or pin @north-light/sdk to an older release that matches it.`
|
|
551
|
+
: `The runtime serves contract ${served}; @north-light/sdk ${SDK_VERSION} requires contract ${CONTRACT_LEVEL} or later within the same major. Upgrade the runtime, or pin @north-light/sdk to a release whose crouterContract the runtime serves.`;
|
|
547
552
|
return new Response(JSON.stringify({ error: {
|
|
548
553
|
code: 'runtime_version_unsupported', message, type: 'invalid_request_error', retryable: false,
|
|
549
|
-
details: { sdk_version: SDK_VERSION, runtime_version: runtimeVersion },
|
|
554
|
+
details: { sdk_version: SDK_VERSION, runtime_version: runtimeVersion, required_contract: CONTRACT_LEVEL, runtime_contract: served },
|
|
550
555
|
} }), { status: 426, headers: { 'content-type': 'application/json' } });
|
|
551
556
|
};
|
|
552
557
|
if (inner !== undefined && inner[LOCAL_SOCKET_FETCH] === true) {
|
package/dist/errors.d.ts
CHANGED
|
@@ -6,12 +6,16 @@ export declare class CrouterError extends APIError {
|
|
|
6
6
|
readonly code: SdkErrorCode;
|
|
7
7
|
constructor(message: string, code?: SdkErrorCode);
|
|
8
8
|
}
|
|
9
|
-
/** The runtime
|
|
10
|
-
* sends. Raised before the SDK reads the response; never retryable. Upgrade the runtime (or pin the SDK to the
|
|
9
|
+
/** The runtime does not serve the contract level this SDK requires (or names no level at all), so the SDK's types do not
|
|
10
|
+
* describe what it sends. Raised before the SDK reads the response; never retryable. Upgrade the runtime (or pin the SDK to the
|
|
11
11
|
* runtime's release). `code` is `runtime_version_unsupported`, `status` 426. */
|
|
12
12
|
export declare class RuntimeVersionError extends APIError {
|
|
13
|
-
/** The
|
|
13
|
+
/** The legacy minimum-runtime release the SDK sent (or the SDK's own release when the SDK raised this itself). */
|
|
14
14
|
get sdkVersion(): string;
|
|
15
|
+
/** The contract level the SDK requires (`Crouter-Required-Contract`), or `null` for a refusal that is not about the contract. */
|
|
16
|
+
get requiredContract(): string | null;
|
|
17
|
+
/** The contract levels the runtime serves (`Crouter-Contract`), or `null` when it named none. */
|
|
18
|
+
get runtimeContract(): string | null;
|
|
15
19
|
/** The version the runtime named in `Crouter-Runtime-Version`, or `null` when it sent none (a runtime from before the header existed). */
|
|
16
20
|
get runtimeVersion(): string | null;
|
|
17
21
|
}
|
|
@@ -19,6 +23,8 @@ export declare class RuntimeVersionError extends APIError {
|
|
|
19
23
|
export interface RuntimeVersionDetails {
|
|
20
24
|
sdk_version: string;
|
|
21
25
|
runtime_version: string | null;
|
|
26
|
+
required_contract?: string;
|
|
27
|
+
runtime_contract?: string | null;
|
|
22
28
|
}
|
|
23
29
|
/** The daemon rejected the request: HTTP 400. */
|
|
24
30
|
export declare class BadRequestError extends APIError {
|
package/dist/errors.js
CHANGED
|
@@ -7,12 +7,16 @@ export class CrouterError extends APIError {
|
|
|
7
7
|
this.name = 'CrouterError';
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
-
/** The runtime
|
|
11
|
-
* sends. Raised before the SDK reads the response; never retryable. Upgrade the runtime (or pin the SDK to the
|
|
10
|
+
/** The runtime does not serve the contract level this SDK requires (or names no level at all), so the SDK's types do not
|
|
11
|
+
* describe what it sends. Raised before the SDK reads the response; never retryable. Upgrade the runtime (or pin the SDK to the
|
|
12
12
|
* runtime's release). `code` is `runtime_version_unsupported`, `status` 426. */
|
|
13
13
|
export class RuntimeVersionError extends APIError {
|
|
14
|
-
/** The
|
|
14
|
+
/** The legacy minimum-runtime release the SDK sent (or the SDK's own release when the SDK raised this itself). */
|
|
15
15
|
get sdkVersion() { return this.details.sdk_version; }
|
|
16
|
+
/** The contract level the SDK requires (`Crouter-Required-Contract`), or `null` for a refusal that is not about the contract. */
|
|
17
|
+
get requiredContract() { return this.details.required_contract ?? null; }
|
|
18
|
+
/** The contract levels the runtime serves (`Crouter-Contract`), or `null` when it named none. */
|
|
19
|
+
get runtimeContract() { return this.details.runtime_contract ?? null; }
|
|
16
20
|
/** The version the runtime named in `Crouter-Runtime-Version`, or `null` when it sent none (a runtime from before the header existed). */
|
|
17
21
|
get runtimeVersion() { return this.details.runtime_version; }
|
|
18
22
|
}
|
|
@@ -18,6 +18,15 @@ export type RunEnded = {
|
|
|
18
18
|
runId: string;
|
|
19
19
|
ended: 'gone';
|
|
20
20
|
run: null;
|
|
21
|
+
}
|
|
22
|
+
/** The run is no longer `running` but its `status` is one this SDK does not know (a runtime newer than the SDK added
|
|
23
|
+
* it). It is treated as at rest; `status` is the raw value and `run` the whole answer. */
|
|
24
|
+
| {
|
|
25
|
+
userId: string;
|
|
26
|
+
runId: string;
|
|
27
|
+
ended: 'unknown';
|
|
28
|
+
status: string;
|
|
29
|
+
run: RunObject;
|
|
21
30
|
};
|
|
22
31
|
/** One live-progress reading `RunWatcher` hands to `onActivity`: a `running` run's `activity` and the `runs.get` answer it came from. */
|
|
23
32
|
export interface RunActivityEvent {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { APIError, CrouterError } from '../errors.js';
|
|
2
2
|
import { Follower, troubleResult } from './follower.js';
|
|
3
3
|
const defaultActivityWait = 2;
|
|
4
|
-
const
|
|
4
|
+
const knownResting = new Set(['settled', 'idle', 'waiting_on_user']);
|
|
5
5
|
/**
|
|
6
6
|
* Follows an app's background runs until each comes to rest (`settled`, `idle`, `waiting_on_user`, or deleted),
|
|
7
7
|
* and hands each ending to `onEnded` at least once. Build it with `oauth.watchRuns(...)`, then `start()`.
|
|
@@ -90,7 +90,9 @@ export class RunWatcher {
|
|
|
90
90
|
}
|
|
91
91
|
return troubleResult(this.follower, error, run);
|
|
92
92
|
}
|
|
93
|
-
|
|
93
|
+
// Only `running` keeps the loop going. Any other status, including one a newer runtime added that this SDK does
|
|
94
|
+
// not know, is at rest: looping on it would spin forever.
|
|
95
|
+
if (current.status === 'running') {
|
|
94
96
|
plain = false;
|
|
95
97
|
this.resting.delete(run.runId);
|
|
96
98
|
this.progress(run, current);
|
|
@@ -105,7 +107,10 @@ export class RunWatcher {
|
|
|
105
107
|
this.resting.add(run.runId);
|
|
106
108
|
return 'park';
|
|
107
109
|
}
|
|
108
|
-
|
|
110
|
+
const status = current.status;
|
|
111
|
+
return this.deliver(run, key, knownResting.has(status)
|
|
112
|
+
? { userId: run.userId, runId: run.runId, ended: status, run: current }
|
|
113
|
+
: { userId: run.userId, runId: run.runId, ended: 'unknown', status, run: current });
|
|
109
114
|
}
|
|
110
115
|
}
|
|
111
116
|
/** Hand a running run's activity to `onActivity` once per distinct value, without blocking the follow loop. */
|
package/dist/resources/runs.d.ts
CHANGED
|
@@ -187,8 +187,8 @@ export declare class Runs {
|
|
|
187
187
|
stream(params: RunStartParams, options?: RequestOptions & {
|
|
188
188
|
idempotencyKey?: string;
|
|
189
189
|
}): RunStream;
|
|
190
|
-
/** Long-polls `runs.get` until the run is `settled`, `waiting_on_user`,
|
|
191
|
-
* unchanged (`result` is not parsed). Aborting `signal` rejects with its reason and leaves the run as it is. */
|
|
190
|
+
/** Long-polls `runs.get` until the run is no longer `running` (`settled`, `waiting_on_user`, `idle`, or a status a
|
|
191
|
+
* newer runtime added, which counts as at rest), and returns that response unchanged (`result` is not parsed). Aborting `signal` rejects with its reason and leaves the run as it is. */
|
|
192
192
|
wait(runId: string, options?: RequestOptions): Promise<RunObject>;
|
|
193
193
|
/** Start a run with `output_schema` and wait for it to settle; resolves with the outcome and `output_parsed`
|
|
194
194
|
* typed to the schema. `onQuestion` answers each open question once; without it, a question rejects with
|
package/dist/resources/runs.js
CHANGED
|
@@ -133,15 +133,15 @@ export class Runs {
|
|
|
133
133
|
return this.openEvents(run.run_id, after, signal);
|
|
134
134
|
}, 0, controller.signal);
|
|
135
135
|
}
|
|
136
|
-
/** Long-polls `runs.get` until the run is `settled`, `waiting_on_user`,
|
|
137
|
-
* unchanged (`result` is not parsed). Aborting `signal` rejects with its reason and leaves the run as it is. */
|
|
136
|
+
/** Long-polls `runs.get` until the run is no longer `running` (`settled`, `waiting_on_user`, `idle`, or a status a
|
|
137
|
+
* newer runtime added, which counts as at rest), and returns that response unchanged (`result` is not parsed). Aborting `signal` rejects with its reason and leaves the run as it is. */
|
|
138
138
|
async wait(runId, options = {}) {
|
|
139
139
|
// One deadline for the whole wait: every poll shares it instead of restarting `budgetMs`.
|
|
140
140
|
const bounded = deadline(options) ?? options;
|
|
141
141
|
return abortable(bounded.signal, async () => {
|
|
142
142
|
for (;;) {
|
|
143
143
|
const result = await this.get(runId, { ...bounded, wait: 25 });
|
|
144
|
-
if (result.status
|
|
144
|
+
if (result.status !== 'running')
|
|
145
145
|
return result;
|
|
146
146
|
}
|
|
147
147
|
});
|
|
@@ -170,6 +170,9 @@ export class Runs {
|
|
|
170
170
|
if (current.status === 'idle') {
|
|
171
171
|
throw new APIError(0, 'run_idle', 'the run stopped without settling; send runs.message or runs.cancel', undefined, undefined, { retryable: false, run_id: run.run_id });
|
|
172
172
|
}
|
|
173
|
+
if (current.status !== 'waiting_on_user') {
|
|
174
|
+
throw new APIError(0, 'run_status_unknown', `the run came to rest with a status this SDK does not know (${String(current.status)}); upgrade @north-light/sdk`, undefined, undefined, { retryable: false, run_id: run.run_id });
|
|
175
|
+
}
|
|
173
176
|
const open = current.pending_question_ids.filter((id) => !answered.has(id));
|
|
174
177
|
if (!onQuestion) {
|
|
175
178
|
const questionId = open[0] ?? current.pending_question_ids[0];
|
package/dist/testing/cloud.d.ts
CHANGED
|
@@ -77,6 +77,13 @@ export interface FakeCloudOptions {
|
|
|
77
77
|
runtimeClientId?: string;
|
|
78
78
|
/** The runtime's release; default `SDK_VERSION`. An older one refuses this SDK with 426. */
|
|
79
79
|
runtimeVersion?: string;
|
|
80
|
+
/** The contract levels the runtime serves, one per major (default `[CONTRACT_LEVEL]`). A request whose
|
|
81
|
+
* `Crouter-Required-Contract` no served level satisfies is refused 426 by the runtime. */
|
|
82
|
+
contract?: string | readonly string[];
|
|
83
|
+
/** The level the router in front of the runtime has recorded for it (default: the same as `contract`). `null` models
|
|
84
|
+
* an unknown level: the router forwards and only the runtime enforces. A request the recorded level does not satisfy
|
|
85
|
+
* is refused 426 by the router (origin `router`, no `Crouter-Contract` header) before it reaches the runtime. */
|
|
86
|
+
routedContract?: string | readonly string[] | null;
|
|
80
87
|
/** Idle close of a run's event stream. Default 60 000 ms. */
|
|
81
88
|
streamIdleMs?: number;
|
|
82
89
|
/** Discovery's `account_app_uri_template`; default `<issuer>/account/apps/{client_id}`, null omits it. */
|
|
@@ -263,7 +270,12 @@ export declare class FakeCloud {
|
|
|
263
270
|
private state;
|
|
264
271
|
private runtimeUrlFor;
|
|
265
272
|
private isRuntimeOrigin;
|
|
273
|
+
private servedContract;
|
|
266
274
|
private headers;
|
|
275
|
+
/** The ingress refusals, in the order production applies them, before routing, body parsing, call logging or any
|
|
276
|
+
* interceptor: the router's contract check against the level it recorded, then the runtime's legacy version check,
|
|
277
|
+
* then the runtime's contract check. `null` when the request may proceed. */
|
|
278
|
+
private ingressRefusal;
|
|
267
279
|
private route;
|
|
268
280
|
private runIntercepts;
|
|
269
281
|
private directory;
|
package/dist/testing/cloud.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// FakeCloud: a fake crouter directory and runtime behind one `fetch`, for testing an app's real `OAuth2Client` and
|
|
2
2
|
// `Crouter`. Routing, the call logs, intercepts, users and every runtime operation an app may call.
|
|
3
|
-
import { RUNTIME_VERSION_HEADER, minimumRuntimeRefusal, MINIMUM_RUNTIME_VERSION_HEADER } from '@north-light/api';
|
|
3
|
+
import { CONTRACT_HEADER, CONTRACT_LEVEL, REQUIRED_CONTRACT_HEADER, RUNTIME_VERSION_HEADER, contractRefusal, minimumRuntimeRefusal, MINIMUM_RUNTIME_VERSION_HEADER } from '@north-light/api';
|
|
4
4
|
import { covers } from '@north-light/identity';
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
6
|
import { posix } from 'node:path';
|
|
@@ -416,7 +416,37 @@ export class FakeCloud {
|
|
|
416
416
|
return true;
|
|
417
417
|
return [...this.users.keys()].some((id) => new URL(option(id)).origin === origin);
|
|
418
418
|
}
|
|
419
|
-
|
|
419
|
+
servedContract() {
|
|
420
|
+
const option = this.options.contract ?? CONTRACT_LEVEL;
|
|
421
|
+
return typeof option === 'string' ? option : option.join(', ');
|
|
422
|
+
}
|
|
423
|
+
headers() {
|
|
424
|
+
return { [RUNTIME_VERSION_HEADER]: this.options.runtimeVersion ?? SDK_VERSION, [CONTRACT_HEADER]: this.servedContract() };
|
|
425
|
+
}
|
|
426
|
+
/** The ingress refusals, in the order production applies them, before routing, body parsing, call logging or any
|
|
427
|
+
* interceptor: the router's contract check against the level it recorded, then the runtime's legacy version check,
|
|
428
|
+
* then the runtime's contract check. `null` when the request may proceed. */
|
|
429
|
+
ingressRefusal(request) {
|
|
430
|
+
const required = request.headers.get(REQUIRED_CONTRACT_HEADER) ?? undefined;
|
|
431
|
+
const legacy = request.headers.get(MINIMUM_RUNTIME_VERSION_HEADER) ?? undefined;
|
|
432
|
+
const runtimeVersion = this.options.runtimeVersion ?? SDK_VERSION;
|
|
433
|
+
const refusalResponse = (refusal, origin) => {
|
|
434
|
+
const error = fakeError({ status: refusal.status, code: refusal.code, message: refusal.message, ...(refusal.status === 426 ? { details: refusal.details } : { param: refusal.param }), origin, retryable: false });
|
|
435
|
+
return jsonAnswer(envelopeBody(error), error.status, origin === 'runtime' ? this.headers() : {});
|
|
436
|
+
};
|
|
437
|
+
const routed = this.options.routedContract === undefined ? this.servedContract() : this.options.routedContract;
|
|
438
|
+
if (routed !== null) {
|
|
439
|
+
const refusal = contractRefusal(required, typeof routed === 'string' ? routed : routed.join(', '), { legacyMinimum: legacy, runtimeVersion: null });
|
|
440
|
+
if (refusal !== null)
|
|
441
|
+
return refusalResponse(refusal, 'router');
|
|
442
|
+
}
|
|
443
|
+
const legacyRefusal = minimumRuntimeRefusal(legacy, runtimeVersion);
|
|
444
|
+
if (legacyRefusal !== null) {
|
|
445
|
+
return refusalResponse(legacyRefusal, 'runtime');
|
|
446
|
+
}
|
|
447
|
+
const refusal = contractRefusal(required, this.servedContract(), { legacyMinimum: legacy, runtimeVersion });
|
|
448
|
+
return refusal === null ? null : refusalResponse(refusal, 'runtime');
|
|
449
|
+
}
|
|
420
450
|
async route(input, init) {
|
|
421
451
|
const request = new Request(input, init);
|
|
422
452
|
const signal = init?.signal ?? (input instanceof Request ? input.signal : null);
|
|
@@ -551,6 +581,9 @@ export class FakeCloud {
|
|
|
551
581
|
}
|
|
552
582
|
// ---- runtime -----------------------------------------------------------------------------------------------------
|
|
553
583
|
async runtime(url, request, signal, claims, sub) {
|
|
584
|
+
const refused = this.ingressRefusal(request);
|
|
585
|
+
if (refused !== null)
|
|
586
|
+
return refused;
|
|
554
587
|
const route = ROUTES.find(([method, pattern]) => method === request.method && pattern.test(url.pathname));
|
|
555
588
|
if (!route)
|
|
556
589
|
return jsonAnswer(envelopeBody(fakeError({ status: 404, code: 'not_found', message: 'unknown operation' })), 404, this.headers());
|
|
@@ -589,13 +622,6 @@ export class FakeCloud {
|
|
|
589
622
|
return fail(fakeError({ status: scripted, code: scripted >= 500 ? 'server_error' : 'invalid_request', message: `scripted ${scripted}`, retryable: false }));
|
|
590
623
|
return fail(scripted);
|
|
591
624
|
}
|
|
592
|
-
const minimum = minimumRuntimeRefusal(request.headers.get(MINIMUM_RUNTIME_VERSION_HEADER) ?? undefined, this.options.runtimeVersion ?? SDK_VERSION);
|
|
593
|
-
if (minimum) {
|
|
594
|
-
const error = fakeError({ status: minimum.status, code: minimum.code, message: minimum.message, ...(minimum.status === 426 ? { details: minimum.details } : { param: minimum.param }), origin: minimum.status === 426 ? 'router' : 'runtime', retryable: false });
|
|
595
|
-
call.status = error.status;
|
|
596
|
-
// A router refusal names no runtime release.
|
|
597
|
-
return error.status === 426 ? jsonAnswer(envelopeBody(error), 426, {}) : fail(error);
|
|
598
|
-
}
|
|
599
625
|
try {
|
|
600
626
|
if (!user?.refreshToken || claims === null)
|
|
601
627
|
throw refuse(401, 'unauthorized', 'a directory access token is required');
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
/** This SDK's release version.
|
|
2
|
-
export declare const SDK_VERSION = "0.3.
|
|
1
|
+
/** This SDK's release version. Informational only: compatibility is the contract level, not this number. */
|
|
2
|
+
export declare const SDK_VERSION = "0.3.423";
|
|
3
|
+
/** The release sent in `Crouter-Minimum-Runtime-Version` on every request. It is frozen, so a runtime from before the
|
|
4
|
+
* contract level existed (which only knows this header) refuses this SDK, while every later runtime accepts it. */
|
|
5
|
+
export declare const LEGACY_FLOOR = "0.3.423";
|
package/dist/version.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
//
|
|
2
|
-
/** This SDK's release version.
|
|
3
|
-
export const SDK_VERSION = '0.3.
|
|
1
|
+
// SDK_VERSION is written by scripts/release-version.mjs on every release; do not edit by hand.
|
|
2
|
+
/** This SDK's release version. Informational only: compatibility is the contract level, not this number. */
|
|
3
|
+
export const SDK_VERSION = '0.3.423';
|
|
4
|
+
// LEGACY_FLOOR is written once by `scripts/contract.mjs release`, at the first release that carries the contract level
|
|
5
|
+
// (the `0.0.0` placeholder is replaced there and never again); do not edit by hand.
|
|
6
|
+
/** The release sent in `Crouter-Minimum-Runtime-Version` on every request. It is frozen, so a runtime from before the
|
|
7
|
+
* contract level existed (which only knows this header) refuses this SDK, while every later runtime accepts it. */
|
|
8
|
+
export const LEGACY_FLOOR = '0.3.423';
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/sdk",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.423",
|
|
4
|
+
"crouterContract": "1.0",
|
|
4
5
|
"description": "Typed Node and browser client for running crouter agents through the crtrd /v1 API.",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"main": "./dist/index.js",
|
|
@@ -36,11 +37,12 @@
|
|
|
36
37
|
],
|
|
37
38
|
"sideEffects": false,
|
|
38
39
|
"scripts": {
|
|
39
|
-
"build": "tsc -p tsconfig.json && chmod 755 dist/keygen-cli.js"
|
|
40
|
+
"build": "tsc -p tsconfig.json && chmod 755 dist/keygen-cli.js",
|
|
41
|
+
"prepublishOnly": "node -e \"if (process.env.npm_config_tag !== 'next') { console.error('@north-light/sdk publishes only with --tag next; scripts/promote-sdk.mjs is the only mover of latest.'); process.exit(1); }\""
|
|
40
42
|
},
|
|
41
43
|
"dependencies": {
|
|
42
|
-
"@north-light/api": "0.3.
|
|
43
|
-
"@north-light/identity": "0.3.
|
|
44
|
+
"@north-light/api": "0.3.423",
|
|
45
|
+
"@north-light/identity": "0.3.423",
|
|
44
46
|
"jose": "^6.2.1"
|
|
45
47
|
},
|
|
46
48
|
"peerDependencies": {
|