@kumwe/studio-testkit 0.1.0-alpha.7 → 0.1.0-beta.2
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 +122 -23
- package/THIRD_PARTY_NOTICES.md +9 -0
- package/conformance/authoring-web/non-drag-move-equivalence.json +199 -0
- package/conformance/renderer-web/blob-default-deny.json +62 -0
- package/conformance/renderer-web/chart-fallback.json +54 -0
- package/conformance/renderer-web/complete-catalog.json +649 -0
- package/conformance/renderer-web/escaped-heading.json +43 -0
- package/conformance/renderer-web/interactive-behaviors.json +284 -0
- package/conformance/renderer-web/presentation-capabilities.json +104 -0
- package/conformance/renderer-web/responsive-grid.json +48 -0
- package/conformance/renderer-web/security-fallbacks.json +184 -0
- package/conformance/rich-text/advanced.first-party.json +67 -0
- package/corpus-manifest.json +571 -37
- package/dist/contextual-conformance.d.ts +81 -0
- package/dist/contextual-conformance.d.ts.map +1 -0
- package/dist/contextual-conformance.js +1 -0
- package/dist/host-testbed.d.ts +68 -5
- package/dist/host-testbed.d.ts.map +1 -1
- package/dist/host-testbed.js +1 -547
- package/dist/http-authoring-contract.d.ts +3 -0
- package/dist/http-authoring-contract.d.ts.map +1 -0
- package/dist/http-authoring-contract.js +1 -0
- package/dist/http-host-adapter.d.ts +12 -64
- package/dist/http-host-adapter.d.ts.map +1 -1
- package/dist/http-host-adapter.js +1 -256
- package/dist/http-host-responder.d.ts +58 -0
- package/dist/http-host-responder.d.ts.map +1 -0
- package/dist/http-host-responder.js +1 -0
- package/dist/index.d.ts +88 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -140
- package/dist/web-conformance.d.ts +112 -0
- package/dist/web-conformance.d.ts.map +1 -0
- package/dist/web-conformance.js +1 -0
- package/fixtures/authoring-message-catalog.en.json +1093 -0
- package/fixtures/authoring-save.plan.example.json +48 -0
- package/fixtures/authoring-session.example.json +212 -0
- package/fixtures/authoring-target.example.json +34 -0
- package/fixtures/host-capabilities.example.json +2 -2
- package/fixtures/host-operations.example.json +77 -0
- package/fixtures/plugin.example.json +12 -0
- package/fixtures/preview.render.example.json +1 -0
- package/fixtures/reusable-content-type.example.json +26 -0
- package/fixtures/studio-config.example.json +3 -3
- package/fixtures/studio-deployment.hosted.example.json +152 -0
- package/fixtures/studio-deployment.standalone.example.json +9 -0
- package/invalid/authoring-save.plan-missing-coordinates.json +16 -0
- package/invalid/authoring-save.plan-missing-successor-context.json +28 -0
- package/invalid/authoring-save.request-plan-missing-successor-context.json +26 -0
- package/invalid/authoring-session.missing-entry-state.json +9 -0
- package/invalid/authoring-target.missing-resource-types.json +24 -0
- package/invalid/binding-projection-vector.unknown-status.json +85 -0
- package/invalid/host-capabilities.invalid-protocol-version.json +1 -1
- package/invalid/host-error.retry-delay-without-retry.json +16 -0
- package/invalid/host-error.retry-delay-wrong-category.json +16 -0
- package/invalid/host-error.revision-without-conflict.json +16 -0
- package/invalid/host-sequence.pending-expectation.json +52 -0
- package/invalid/host-sequence.preview-render-missing-request-id.json +50 -0
- package/invalid/preview-message.activated-unknown-interaction.json +1 -1
- package/invalid/preview-message.duplicate-marker.json +24 -0
- package/invalid/preview-message.invalid-digest.json +1 -0
- package/invalid/preview-message.invalid-marker-grammar.json +19 -0
- package/invalid/preview-message.missing-marker-map.json +20 -0
- package/invalid/preview-message.missing-render-request-id.json +18 -0
- package/invalid/preview-message.unknown-type.json +13 -0
- package/invalid/preview-message.viewport-height-too-small.json +13 -0
- package/invalid/preview-message.viewport-unsafe-role.json +13 -0
- package/invalid/reusable-content-type.entry-values.json +32 -0
- package/invalid/studio-chart.script-config.json +10 -0
- package/invalid/studio-deployment.long-lived-token.json +24 -0
- package/invalid/studio-deployment.missing-required.json +5 -0
- package/invalid/studio-deployment.unknown-member.json +15 -0
- package/invalid/studio-drawing.svg-payload.json +10 -0
- package/invalid/studio-money.float.json +8 -0
- package/invalid/studio-presentation.css-injection.json +8 -0
- package/invalid/studio-table.html-cell.json +8 -0
- package/package.json +8 -4
- package/studio-release.json +35 -0
- package/vectors/authoring-http/transport-matrix.json +237 -0
- package/vectors/binding-projection/app-content-fields.resolved.json +286 -0
- package/vectors/binding-projection/field-removed.diagnostic.json +135 -0
- package/vectors/binding-projection/model-revision-mismatch.diagnostic.json +124 -0
- package/vectors/host/artifact.dependencies.result.json +2 -2
- package/vectors/host/artifact.load.stored.json +2 -2
- package/vectors/host/artifact.load.unknown.error.json +2 -2
- package/vectors/host/artifact.publish.accepted.json +2 -2
- package/vectors/host/artifact.publish.forbidden.error.json +1 -1
- package/vectors/host/artifact.publish.stale.error.json +2 -2
- package/vectors/host/artifact.save.accepted.json +2 -2
- package/vectors/host/artifact.save.forbidden.error.json +1 -1
- package/vectors/host/artifact.save.stale.error.json +2 -2
- package/vectors/host/artifact.unpublish.stale.error.json +2 -2
- package/vectors/host/envelope.malformed-context.error.json +2 -2
- package/vectors/host/envelope.protocol-version.error.json +1 -1
- package/vectors/host/envelope.stale-generation.error.json +2 -2
- package/vectors/host/localization.messages.unknown-locale.error.json +2 -2
- package/vectors/host/media.abort-upload.unknown.error.json +1 -1
- package/vectors/host/media.authorize-upload.granted.json +1 -1
- package/vectors/host/media.authorize-upload.path-filename.error.json +1 -1
- package/vectors/host/media.authorize-upload.too-large.error.json +1 -1
- package/vectors/host/media.complete-upload.unknown.error.json +1 -1
- package/vectors/host/media.get.unknown.json +2 -2
- package/vectors/host/media.import-external.private-host.error.json +1 -1
- package/vectors/host/media.list.cursor.error.json +2 -2
- package/vectors/host/media.list.limit.error.json +2 -2
- package/vectors/host/media.upload-status.unknown.error.json +1 -1
- package/vectors/host/model.get.stored.json +37 -0
- package/vectors/host/model.list.authorized.json +31 -0
- package/vectors/host/permission.explain.withheld.json +2 -2
- package/vectors/host/permission.refresh.snapshot.json +2 -2
- package/vectors/host/recovery.load.absent.json +2 -2
- package/vectors/host/telemetry.emit.accepted.json +2 -2
- package/vectors/host/telemetry.emit.non-primitive.error.json +2 -2
- package/vectors/host-sequence/artifact.publish.changed-intent.sequence.json +75 -0
- package/vectors/host-sequence/artifact.publish.idempotent-replay.sequence.json +111 -0
- package/vectors/host-sequence/preview.cancel.cross-context.sequence.json +78 -0
- package/vectors/host-sequence/preview.render.cancelled.sequence.json +75 -0
- package/vectors/host-sequence/recovery.store.canonical-number.sequence.json +72 -0
- package/vectors/host-sequence/recovery.store.changed-context.sequence.json +61 -0
- package/vectors/host-sequence/recovery.store.rate-limited.sequence.json +93 -0
- package/vectors/host-sequence/recovery.store.resource-scope.sequence.json +61 -0
- package/vectors/host-sequence/recovery.store.wrong-operation-id.sequence.json +56 -0
- package/vectors/preview/canonical-preorder.json +102 -0
- package/vectors/preview/empty-draft.json +36 -0
- package/vectors/schema-profile/alternative-limit.accepted.json +95 -0
- package/vectors/schema-profile/alternative-limit.rejected.json +92 -0
- package/vectors/schema-profile/array-composition.accepted.json +35 -0
- package/vectors/schema-profile/byte-limit.accepted.json +30 -0
- package/vectors/schema-profile/byte-limit.rejected.json +26 -0
- package/vectors/schema-profile/closed-object.accepted.json +38 -0
- package/vectors/schema-profile/decimal-multiple.accepted.json +54 -0
- package/vectors/schema-profile/depth-limit.accepted.json +90 -0
- package/vectors/schema-profile/depth-limit.rejected.json +88 -0
- package/vectors/schema-profile/description-limit.accepted.json +29 -0
- package/vectors/schema-profile/description-limit.rejected.json +25 -0
- package/vectors/schema-profile/diagnostic-precedence.rejected.json +26 -0
- package/vectors/schema-profile/duplicate-dependent-required.rejected.json +21 -0
- package/vectors/schema-profile/duplicate-required.rejected.json +18 -0
- package/vectors/schema-profile/empty-combinator.rejected.json +17 -0
- package/vectors/schema-profile/empty-enum.rejected.json +17 -0
- package/vectors/schema-profile/enum-limit.accepted.json +89 -0
- package/vectors/schema-profile/enum-limit.rejected.json +84 -0
- package/vectors/schema-profile/examples-limit.accepted.json +35 -0
- package/vectors/schema-profile/examples-limit.rejected.json +31 -0
- package/vectors/schema-profile/format-keyword.rejected.json +17 -0
- package/vectors/schema-profile/forward-reference-path-precedence.rejected.json +20 -0
- package/vectors/schema-profile/invalid-pointer.rejected.json +18 -0
- package/vectors/schema-profile/json-depth-limit.accepted.json +278 -0
- package/vectors/schema-profile/json-depth-limit.rejected.json +276 -0
- package/vectors/schema-profile/json-item-limit.accepted.json +686 -0
- package/vectors/schema-profile/json-item-limit.rejected.json +682 -0
- package/vectors/schema-profile/json-property-limit.accepted.json +1031 -0
- package/vectors/schema-profile/json-property-limit.rejected.json +1028 -0
- package/vectors/schema-profile/local-reference.accepted.json +28 -0
- package/vectors/schema-profile/memoized-diagnostic.accepted.json +31 -0
- package/vectors/schema-profile/node-limit.accepted.json +1097 -0
- package/vectors/schema-profile/node-limit.rejected.json +1094 -0
- package/vectors/schema-profile/non-object-root.rejected.json +9 -0
- package/vectors/schema-profile/non-schema-reference.rejected.json +21 -0
- package/vectors/schema-profile/object-key-limit.accepted.json +32 -0
- package/vectors/schema-profile/object-key-limit.rejected.json +28 -0
- package/vectors/schema-profile/open-root.rejected.json +13 -0
- package/vectors/schema-profile/percent-encoded-pointer.rejected.json +26 -0
- package/vectors/schema-profile/property-name-limit.accepted.json +543 -0
- package/vectors/schema-profile/property-name-limit.rejected.json +536 -0
- package/vectors/schema-profile/recursive-diagnostic-precedence.rejected.json +30 -0
- package/vectors/schema-profile/recursive-reference.rejected.json +24 -0
- package/vectors/schema-profile/reference-fanout.accepted.json +662 -0
- package/vectors/schema-profile/reference-length-limit.accepted.json +45 -0
- package/vectors/schema-profile/reference-length-limit.rejected.json +39 -0
- package/vectors/schema-profile/reference-limit.accepted.json +416 -0
- package/vectors/schema-profile/reference-limit.rejected.json +415 -0
- package/vectors/schema-profile/reference-vs-keyword-precedence.rejected.json +22 -0
- package/vectors/schema-profile/reserved-pointer.rejected.json +26 -0
- package/vectors/schema-profile/root-diagnostic-precedence.rejected.json +16 -0
- package/vectors/schema-profile/root-vs-reference-precedence.rejected.json +19 -0
- package/vectors/schema-profile/schema-map-limit.accepted.json +539 -0
- package/vectors/schema-profile/schema-map-limit.rejected.json +536 -0
- package/vectors/schema-profile/semantic-recursion-precedence.rejected.json +21 -0
- package/vectors/schema-profile/semantic-reference-precedence.rejected.json +21 -0
- package/vectors/schema-profile/title-limit.accepted.json +29 -0
- package/vectors/schema-profile/title-limit.rejected.json +25 -0
- package/vectors/schema-profile/unresolved-reference.rejected.json +17 -0
- package/vectors/schema-profile/unsafe-dependent-required.rejected.json +24 -0
- package/vectors/schema-profile/unsafe-member.rejected.json +17 -0
- package/vectors/schema-profile/unsafe-required.rejected.json +17 -0
- package/dist/host-testbed.js.map +0 -1
- package/dist/http-host-adapter.js.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -1,74 +1,22 @@
|
|
|
1
|
+
import { type HttpAuthenticationResolver, type HttpFetchLike, type HttpRequestInit, type HttpSchemaValidator, type HttpTimeoutHandle } from '@kumwe/studio-core';
|
|
1
2
|
import { type HostAdapter } from '@kumwe/studio-protocol';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* response class so implementations from any runtime satisfy it.
|
|
6
|
-
*/
|
|
7
|
-
export interface HttpResponseLike {
|
|
8
|
-
status: number;
|
|
9
|
-
text(): Promise<string>;
|
|
10
|
-
}
|
|
11
|
-
/** The request shape the adapter hands to the injected transport. */
|
|
12
|
-
export interface HttpRequestInit {
|
|
13
|
-
body: string;
|
|
14
|
-
headers: Record<string, string>;
|
|
15
|
-
method: 'POST';
|
|
16
|
-
/** Opaque abort signal produced by the injected timeout factory, if any. */
|
|
17
|
-
signal?: unknown;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* The injected fetch-like transport — the adapter's portability seam.
|
|
21
|
-
*
|
|
22
|
-
* Testkit source is deliberately free of host globals (no fetch, no DOM, no
|
|
23
|
-
* Node), so the embedder supplies the platform transport: a Node test injects
|
|
24
|
-
* a `node:http`-backed implementation, a browser host would inject the
|
|
25
|
-
* platform fetch, and either stays outside this package. The function must
|
|
26
|
-
* resolve with the terminal HTTP response (any status) and reject only for
|
|
27
|
-
* transport-level failures: network refusal, connection loss, or an abort
|
|
28
|
-
* raised by the injected timeout signal.
|
|
29
|
-
*/
|
|
30
|
-
export type HttpFetchLike = (url: string, init: HttpRequestInit) => Promise<HttpResponseLike>;
|
|
31
|
-
/**
|
|
32
|
-
* An abort signal handle minted per request by the injected timeout factory.
|
|
33
|
-
* `signal` is passed through to the transport untouched; `release` (when
|
|
34
|
-
* present) is invoked once the request settles so timer-backed factories can
|
|
35
|
-
* clean up.
|
|
36
|
-
*/
|
|
37
|
-
export interface HttpTimeoutHandle {
|
|
38
|
-
release?(): void;
|
|
39
|
-
signal: unknown;
|
|
40
|
-
}
|
|
3
|
+
export { HTTP_HOST_OPERATION_ROUTES } from '@kumwe/studio-core';
|
|
4
|
+
export type { ConfiguredHttpHostAdapterOptions, HttpAuthenticationConfiguration, HttpAuthenticationRequest, HttpAuthenticationResolver, HttpBearerTokenAuthentication, HttpFetchLike, HttpHeaderTokenAuthentication, HttpHostOperationEndpoints, HttpHostOperationRoute, HttpHostRoutingConfiguration, HttpHostTransportConfiguration, HttpRequestInit, HttpResponseLike, HttpSameOriginSessionAuthentication, HttpTimeoutHandle, } from '@kumwe/studio-core';
|
|
5
|
+
/** Historical conventional-route options retained only for test conformance. */
|
|
41
6
|
export interface HttpHostAdapterOptions {
|
|
42
|
-
/**
|
|
43
|
-
* Second half of the portability seam: mints the per-request timeout
|
|
44
|
-
* signal. When omitted, requests carry no deadline. A rejection whose
|
|
45
|
-
* reason is named `AbortError` or `TimeoutError` is mapped to the canonical
|
|
46
|
-
* deadline failure.
|
|
47
|
-
*/
|
|
48
7
|
createTimeoutSignal?: (timeoutMilliseconds: number) => HttpTimeoutHandle;
|
|
8
|
+
credentials?: HttpRequestInit['credentials'];
|
|
49
9
|
fetchImplementation: HttpFetchLike;
|
|
50
|
-
|
|
10
|
+
maximumResponseBytes?: number;
|
|
11
|
+
requestHeaders?: () => Readonly<Record<string, string>>;
|
|
12
|
+
resolveAuthentication?: HttpAuthenticationResolver;
|
|
51
13
|
timeoutMilliseconds?: number;
|
|
14
|
+
validateSchema?: HttpSchemaValidator;
|
|
52
15
|
}
|
|
53
16
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* `
|
|
57
|
-
* `{ arguments, context }` JSON body.
|
|
58
|
-
*
|
|
59
|
-
* Success responses (2xx) must carry a `HostPortResult` JSON body. Failure
|
|
60
|
-
* responses that carry a guard-conforming `HostPortError` body are re-thrown
|
|
61
|
-
* as that canonical error, so a host-authored category (with its safe
|
|
62
|
-
* revision on conflicts) crosses the transport intact. Everything else is
|
|
63
|
-
* mapped onto the canonical host error categories without disclosing
|
|
64
|
-
* transport details: network refusal and deadline expiry become
|
|
65
|
-
* `unavailable`, HTTP statuses map by class (401 `unauthenticated`, 403
|
|
66
|
-
* `forbidden`, 404 `not-found`, 409 `conflict`, 413 `limit-exceeded`, 422
|
|
67
|
-
* `validation-failed`, 429 `rate-limited`, 5xx `internal`/`unavailable`), and
|
|
68
|
-
* a malformed body — unparseable JSON, a result without `value`, or an error
|
|
69
|
-
* document the guard rejects — becomes `internal`. Every rejection is a
|
|
70
|
-
* `TestbedHostError` whose `error` satisfies `isHostPortError`, and no
|
|
71
|
-
* message ever echoes response bodies, addresses, or underlying reasons.
|
|
17
|
+
* Backward-compatible testkit facade over the production core transport.
|
|
18
|
+
* Canonical `HostPortFailure` rejections are translated to the historical
|
|
19
|
+
* `TestbedHostError` subclass without duplicating any HTTP behavior.
|
|
72
20
|
*/
|
|
73
21
|
export declare function createHttpHostAdapter(baseUrl: string, options: HttpHostAdapterOptions): HostAdapter;
|
|
74
22
|
//# sourceMappingURL=http-host-adapter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-host-adapter.d.ts","sourceRoot":"","sources":["../src/http-host-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"http-host-adapter.d.ts","sourceRoot":"","sources":["../src/http-host-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,0BAA0B,EAC/B,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAG7E,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAEhE,YAAY,EACV,gCAAgC,EAChC,+BAA+B,EAC/B,yBAAyB,EACzB,0BAA0B,EAC1B,6BAA6B,EAC7B,aAAa,EACb,6BAA6B,EAC7B,0BAA0B,EAC1B,sBAAsB,EACtB,4BAA4B,EAC5B,8BAA8B,EAC9B,eAAe,EACf,gBAAgB,EAChB,mCAAmC,EACnC,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAE5B,gFAAgF;AAChF,MAAM,WAAW,sBAAsB;IACrC,mBAAmB,CAAC,EAAE,CAAC,mBAAmB,EAAE,MAAM,KAAK,iBAAiB,CAAC;IACzE,WAAW,CAAC,EAAE,eAAe,CAAC,aAAa,CAAC,CAAC;IAC7C,mBAAmB,EAAE,aAAa,CAAC;IACnC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACxD,qBAAqB,CAAC,EAAE,0BAA0B,CAAC;IACnD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,mBAAmB,CAAC;CACtC;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,sBAAsB,GAC9B,WAAW,CAiEb"}
|
|
@@ -1,256 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { TestbedHostError } from './host-testbed.js';
|
|
3
|
-
/**
|
|
4
|
-
* A `HostAdapter` that speaks JSON over an injected fetch-like transport to a
|
|
5
|
-
* host server: every port operation becomes
|
|
6
|
-
* `POST {baseUrl}/ports/{port}/{operation}` with a
|
|
7
|
-
* `{ arguments, context }` JSON body.
|
|
8
|
-
*
|
|
9
|
-
* Success responses (2xx) must carry a `HostPortResult` JSON body. Failure
|
|
10
|
-
* responses that carry a guard-conforming `HostPortError` body are re-thrown
|
|
11
|
-
* as that canonical error, so a host-authored category (with its safe
|
|
12
|
-
* revision on conflicts) crosses the transport intact. Everything else is
|
|
13
|
-
* mapped onto the canonical host error categories without disclosing
|
|
14
|
-
* transport details: network refusal and deadline expiry become
|
|
15
|
-
* `unavailable`, HTTP statuses map by class (401 `unauthenticated`, 403
|
|
16
|
-
* `forbidden`, 404 `not-found`, 409 `conflict`, 413 `limit-exceeded`, 422
|
|
17
|
-
* `validation-failed`, 429 `rate-limited`, 5xx `internal`/`unavailable`), and
|
|
18
|
-
* a malformed body — unparseable JSON, a result without `value`, or an error
|
|
19
|
-
* document the guard rejects — becomes `internal`. Every rejection is a
|
|
20
|
-
* `TestbedHostError` whose `error` satisfies `isHostPortError`, and no
|
|
21
|
-
* message ever echoes response bodies, addresses, or underlying reasons.
|
|
22
|
-
*/
|
|
23
|
-
export function createHttpHostAdapter(baseUrl, options) {
|
|
24
|
-
const base = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
25
|
-
const timeoutMilliseconds = options.timeoutMilliseconds ?? 10_000;
|
|
26
|
-
let correlationSerial = 0;
|
|
27
|
-
function createError(failure) {
|
|
28
|
-
correlationSerial += 1;
|
|
29
|
-
const error = {
|
|
30
|
-
category: failure.category,
|
|
31
|
-
contractVersion: STUDIO_CONTRACT_VERSION,
|
|
32
|
-
correlationId: `http-transport-${correlationSerial}`,
|
|
33
|
-
kind: 'host-error',
|
|
34
|
-
message: { defaultMessage: failure.defaultMessage, key: failure.key },
|
|
35
|
-
retryable: failure.retryable,
|
|
36
|
-
};
|
|
37
|
-
return new TestbedHostError(error);
|
|
38
|
-
}
|
|
39
|
-
async function call(portName, operation, callArguments, context) {
|
|
40
|
-
const handle = options.createTimeoutSignal?.(timeoutMilliseconds);
|
|
41
|
-
let response;
|
|
42
|
-
try {
|
|
43
|
-
response = await options.fetchImplementation(`${base}/ports/${portName}/${operation}`, {
|
|
44
|
-
body: JSON.stringify({ arguments: callArguments, context }),
|
|
45
|
-
headers: { 'content-type': 'application/json' },
|
|
46
|
-
method: 'POST',
|
|
47
|
-
...(handle === undefined ? {} : { signal: handle.signal }),
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
catch (reason) {
|
|
51
|
-
throw createError(classifyTransportFailure(reason));
|
|
52
|
-
}
|
|
53
|
-
finally {
|
|
54
|
-
handle?.release?.();
|
|
55
|
-
}
|
|
56
|
-
let bodyText;
|
|
57
|
-
try {
|
|
58
|
-
bodyText = await response.text();
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
throw createError(malformedResponse());
|
|
62
|
-
}
|
|
63
|
-
if (response.status >= 200 && response.status < 300) {
|
|
64
|
-
const parsed = parseJson(bodyText);
|
|
65
|
-
if (!isHostPortResult(parsed)) {
|
|
66
|
-
throw createError(malformedResponse());
|
|
67
|
-
}
|
|
68
|
-
const result = {
|
|
69
|
-
...(parsed.revision === undefined ? {} : { revision: parsed.revision }),
|
|
70
|
-
value: parsed.value,
|
|
71
|
-
};
|
|
72
|
-
return result;
|
|
73
|
-
}
|
|
74
|
-
const parsed = parseJson(bodyText);
|
|
75
|
-
if (isHostPortError(parsed)) {
|
|
76
|
-
// The host authored a canonical error; transport it verbatim.
|
|
77
|
-
throw new TestbedHostError(parsed);
|
|
78
|
-
}
|
|
79
|
-
throw createError(statusFailure(response.status));
|
|
80
|
-
}
|
|
81
|
-
return {
|
|
82
|
-
artifact: {
|
|
83
|
-
dependencies(reference, context) {
|
|
84
|
-
return call('artifact', 'dependencies', { reference: asJson(reference) }, context);
|
|
85
|
-
},
|
|
86
|
-
load(reference, context) {
|
|
87
|
-
return call('artifact', 'load', { reference: asJson(reference) }, context);
|
|
88
|
-
},
|
|
89
|
-
publish(reference, context) {
|
|
90
|
-
return call('artifact', 'publish', { reference: asJson(reference) }, context);
|
|
91
|
-
},
|
|
92
|
-
save(document, context) {
|
|
93
|
-
return call('artifact', 'save', { document: asJson(document) }, context);
|
|
94
|
-
},
|
|
95
|
-
unpublish(reference, context) {
|
|
96
|
-
return call('artifact', 'unpublish', { reference: asJson(reference) }, context);
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
localization: {
|
|
100
|
-
messages(locale, namespaces, context) {
|
|
101
|
-
return call('localization', 'messages', { locale, namespaces }, context);
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
media: {
|
|
105
|
-
abortUpload(uploadId, context) {
|
|
106
|
-
return call('media', 'abort-upload', { uploadId }, context);
|
|
107
|
-
},
|
|
108
|
-
authorizeUpload(request, context) {
|
|
109
|
-
return call('media', 'authorize-upload', { request: asJson(request) }, context);
|
|
110
|
-
},
|
|
111
|
-
completeUpload(uploadId, context) {
|
|
112
|
-
return call('media', 'complete-upload', { uploadId }, context);
|
|
113
|
-
},
|
|
114
|
-
get(assetId, context) {
|
|
115
|
-
return call('media', 'get', { assetId }, context);
|
|
116
|
-
},
|
|
117
|
-
importExternal(url, context) {
|
|
118
|
-
return call('media', 'import-external', { url }, context);
|
|
119
|
-
},
|
|
120
|
-
list(query, context) {
|
|
121
|
-
return call('media', 'list', { query: asJson(query) }, context);
|
|
122
|
-
},
|
|
123
|
-
uploadStatus(assetId, context) {
|
|
124
|
-
return call('media', 'upload-status', { assetId }, context);
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
permission: {
|
|
128
|
-
explain(operation, context) {
|
|
129
|
-
return call('permission', 'explain', { operation }, context);
|
|
130
|
-
},
|
|
131
|
-
refresh(context) {
|
|
132
|
-
return call('permission', 'refresh', {}, context);
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
preview: {
|
|
136
|
-
cancel(draftDigest, context) {
|
|
137
|
-
return call('preview', 'cancel', { draftDigest }, context);
|
|
138
|
-
},
|
|
139
|
-
render(payload, context) {
|
|
140
|
-
return call('preview', 'render', { payload: asJson(payload) }, context);
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
recovery: {
|
|
144
|
-
discard(context) {
|
|
145
|
-
return call('recovery', 'discard', {}, context);
|
|
146
|
-
},
|
|
147
|
-
load(context) {
|
|
148
|
-
return call('recovery', 'load', {}, context);
|
|
149
|
-
},
|
|
150
|
-
store(envelope, context) {
|
|
151
|
-
return call('recovery', 'store', { envelope }, context);
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
resource: {
|
|
155
|
-
search(query, context) {
|
|
156
|
-
return call('resource', 'search', { query: asJson(query) }, context);
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
telemetry: {
|
|
160
|
-
emit(event, context) {
|
|
161
|
-
return call('telemetry', 'emit', { event: asJson(event) }, context);
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
/** Serializable typed values cross the wire as their JSON projections. */
|
|
167
|
-
function asJson(value) {
|
|
168
|
-
return JSON.parse(JSON.stringify(value));
|
|
169
|
-
}
|
|
170
|
-
function parseJson(text) {
|
|
171
|
-
try {
|
|
172
|
-
return JSON.parse(text);
|
|
173
|
-
}
|
|
174
|
-
catch {
|
|
175
|
-
return undefined;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
function isHostPortResult(value) {
|
|
179
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
180
|
-
return false;
|
|
181
|
-
}
|
|
182
|
-
const record = value;
|
|
183
|
-
if (!Object.hasOwn(record, 'value')) {
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
const revision = record.revision;
|
|
187
|
-
if (revision !== undefined && (typeof revision !== 'string' || revision.length === 0)) {
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
return Object.keys(record).every((key) => key === 'value' || key === 'revision');
|
|
191
|
-
}
|
|
192
|
-
function classifyTransportFailure(reason) {
|
|
193
|
-
const name = typeof reason === 'object' && reason !== null && 'name' in reason ? reason.name : undefined;
|
|
194
|
-
if (name === 'AbortError' || name === 'TimeoutError') {
|
|
195
|
-
// The injected deadline signal fired before the host answered.
|
|
196
|
-
return {
|
|
197
|
-
category: 'unavailable',
|
|
198
|
-
defaultMessage: 'The host did not respond within the transport deadline.',
|
|
199
|
-
key: 'studio.testkit/http-timeout',
|
|
200
|
-
retryable: true,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
return {
|
|
204
|
-
category: 'unavailable',
|
|
205
|
-
defaultMessage: 'The host could not be reached.',
|
|
206
|
-
key: 'studio.testkit/http-unreachable',
|
|
207
|
-
retryable: true,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
function malformedResponse() {
|
|
211
|
-
return {
|
|
212
|
-
category: 'internal',
|
|
213
|
-
defaultMessage: 'The host response could not be interpreted.',
|
|
214
|
-
key: 'studio.testkit/http-malformed-response',
|
|
215
|
-
retryable: false,
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
function statusFailure(status) {
|
|
219
|
-
const category = categoryForStatus(status);
|
|
220
|
-
return {
|
|
221
|
-
category,
|
|
222
|
-
defaultMessage: 'The host rejected the request.',
|
|
223
|
-
key: `studio.testkit/http-status-${category}`,
|
|
224
|
-
retryable: category === 'rate-limited' || category === 'unavailable',
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
function categoryForStatus(status) {
|
|
228
|
-
switch (status) {
|
|
229
|
-
case 401:
|
|
230
|
-
return 'unauthenticated';
|
|
231
|
-
case 403:
|
|
232
|
-
return 'forbidden';
|
|
233
|
-
case 404:
|
|
234
|
-
return 'not-found';
|
|
235
|
-
case 408:
|
|
236
|
-
return 'unavailable';
|
|
237
|
-
case 409:
|
|
238
|
-
return 'conflict';
|
|
239
|
-
case 413:
|
|
240
|
-
return 'limit-exceeded';
|
|
241
|
-
case 422:
|
|
242
|
-
return 'validation-failed';
|
|
243
|
-
case 429:
|
|
244
|
-
return 'rate-limited';
|
|
245
|
-
case 502:
|
|
246
|
-
case 503:
|
|
247
|
-
case 504:
|
|
248
|
-
return 'unavailable';
|
|
249
|
-
default:
|
|
250
|
-
if (status >= 400 && status < 500) {
|
|
251
|
-
return 'invalid-request';
|
|
252
|
-
}
|
|
253
|
-
return 'internal';
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
//# sourceMappingURL=http-host-adapter.js.map
|
|
1
|
+
import{HTTP_HOST_OPERATION_ROUTES as e,createHttpHostAdapter as t}from"@kumwe/studio-core";import{isHostPortFailure as n}from"@kumwe/studio-protocol";import{TestbedHostError as r}from"./host-testbed.js";export{HTTP_HOST_OPERATION_ROUTES}from"@kumwe/studio-core";export function createHttpHostAdapter(n,r){let i=r.createTimeoutSignal,s=a(n),l=Object.fromEntries(e.map(e=>[e,`${s}/ports/${e}`])),u=r.timeoutMilliseconds??1e4,d=r.maximumResponseBytes??67108864;if(!Number.isSafeInteger(u)||u<=0)throw TypeError(`timeoutMilliseconds must be a positive safe integer.`);if(!Number.isSafeInteger(d)||d<=0)throw TypeError(`maximumResponseBytes must be a positive safe integer.`);let f=async(e,t)=>{let n;try{n=o(t.headers,r.requestHeaders?.())}catch{let e=Error(`The testkit HTTP request headers are invalid.`);throw e.name=`InvalidRequestError`,e}let i=await r.fetchImplementation(e,{...t,credentials:r.credentials??`same-origin`,headers:n});return{...i.contentType===void 0?{}:{contentType:i.contentType},status:i.status,text:()=>i.text(d)}};return c(t({authentication:{credentials:`omit`,expiresAt:`1970-01-01T00:10:00Z`,headerName:`X-Studio-Testkit-Transport`,issuedAt:`1970-01-01T00:00:00Z`,kind:`header-token`,token:`conformance`},kind:`http`,maximumResponseBytes:Math.min(67108864,Math.max(1024,d)),requestTimeoutMilliseconds:1e4,routing:{endpoints:l,kind:`operation-map`}},{...i===void 0?{}:{createTimeoutSignal:()=>i(u)},currentTimeMilliseconds:()=>0,fetchImplementation:f,...r.resolveAuthentication===void 0?{}:{resolveAuthentication:r.resolveAuthentication},...r.validateSchema===void 0?{}:{validateSchema:r.validateSchema}}))}function a(e){if(e===``)return``;if(e.trim()!==e||/\s/u.test(e)||e.includes(`?`)||e.includes(`#`)||e.includes(`\\`))throw TypeError(`baseUrl must not contain whitespace, a query, or a fragment.`);let t=e.endsWith(`/`)?e.slice(0,-1):e,n;if(t.startsWith(`https://`)||t.startsWith(`http://`)){let e=t.indexOf(`://`)+3,r=t.indexOf(`/`,e),i=r===-1?t.slice(e):t.slice(e,r);if(i.length===0||i.includes(`@`))throw TypeError(`baseUrl must contain a host and must not embed credentials.`);n=r===-1?``:t.slice(r)}else{if(!t.startsWith(`/`)||t.startsWith(`//`))throw TypeError(`baseUrl must be an HTTP(S) URL or a same-origin absolute path.`);n=t}if(n.includes(`//`))throw TypeError(`baseUrl path must be normalized without empty segments.`);for(let e of n.split(`/`))if(e===`.`||e===`..`||/%2e/iu.test(e))throw TypeError(`baseUrl path must not contain dot segments.`);return t}function o(e,t){let n={...e};for(let[e,r]of Object.entries(t??{})){let t=e.toLowerCase();if(e.length>100||!/^[!#$%&'*+.^_`|~0-9a-z-]+$/u.test(t)||r.length>8192||r.trim()!==r||s(r)||t===`accept`||t===`content-type`||t===`x-studio-operation`||Object.hasOwn(n,t))throw TypeError(`Invalid HTTP request header.`);n[t]=r}return n}function s(e){for(let t=0;t<e.length;t+=1){let n=e.charCodeAt(t);if(n<=8||n>=10&&n<=31||n===127||n>255)return!0}return!1}function c(e){return Object.fromEntries(Object.entries(e).map(([e,t])=>[e,l(t)]))}function l(e){return new Proxy(e,{get(e,t,i){let a=Reflect.get(e,t,i);return typeof a==`function`?(...t)=>Promise.resolve(Reflect.apply(a,e,t)).catch(e=>{throw n(e)&&!(e instanceof r)?new r(e.error):e}):a}})}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type AuthoringPort, type QualifiedName } from '@kumwe/studio-protocol';
|
|
2
|
+
import { type AuthoringHttpRoute, type HttpSchemaValidator } from './http-authoring-contract.js';
|
|
3
|
+
export interface HttpResponderRequest {
|
|
4
|
+
/** Raw JSON body after the HTTP server has decoded transport bytes as UTF-8. */
|
|
5
|
+
body: string;
|
|
6
|
+
/** Header names are matched case-insensitively. Duplicate spellings fail closed. */
|
|
7
|
+
headers: Readonly<Record<string, string | undefined>>;
|
|
8
|
+
method: string;
|
|
9
|
+
/** Pathname only. A query string is not a Studio port route. */
|
|
10
|
+
path: string;
|
|
11
|
+
}
|
|
12
|
+
export interface HttpResponderResponse {
|
|
13
|
+
body: string;
|
|
14
|
+
headers: Readonly<Record<string, string>>;
|
|
15
|
+
status: number;
|
|
16
|
+
}
|
|
17
|
+
export interface HttpTransportSecurityDecision {
|
|
18
|
+
/** Authentication was derived from trusted transport evidence, never the JSON body. */
|
|
19
|
+
authenticated: boolean;
|
|
20
|
+
/** A CSRF token, strict same-origin proof, or non-browser service credential bound this call. */
|
|
21
|
+
requestIntegrity: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface HttpTransportSecurityInput {
|
|
24
|
+
capability: QualifiedName;
|
|
25
|
+
headers: Readonly<Record<string, string | undefined>>;
|
|
26
|
+
method: 'POST';
|
|
27
|
+
mutating: boolean;
|
|
28
|
+
path: string;
|
|
29
|
+
route: AuthoringHttpRoute;
|
|
30
|
+
}
|
|
31
|
+
export type HttpTransportSecurityVerifier = (input: HttpTransportSecurityInput) => HttpTransportSecurityDecision | Promise<HttpTransportSecurityDecision>;
|
|
32
|
+
/**
|
|
33
|
+
* Validates one value against the exact canonical schema reference supplied
|
|
34
|
+
* by the responder. A PHP host performs the equivalent call in its PHP JSON
|
|
35
|
+
* Schema library; the reference responder deliberately does not own Ajv.
|
|
36
|
+
*/
|
|
37
|
+
export interface AuthoringHttpResponderOptions {
|
|
38
|
+
/** Defaults to 64 MiB; hosts may set a stricter deployment-specific limit. */
|
|
39
|
+
maximumRequestBytes?: number;
|
|
40
|
+
/** Defaults to `/ports`. */
|
|
41
|
+
routePrefix?: string;
|
|
42
|
+
validateSchema: HttpSchemaValidator;
|
|
43
|
+
/** Required fail-closed authentication plus CSRF/origin/service-integrity boundary. */
|
|
44
|
+
verifyTransportSecurity: HttpTransportSecurityVerifier;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Creates a platform-neutral reference responder for all seven contextual
|
|
48
|
+
* authoring routes. It receives and returns plain values, so an HTTP server,
|
|
49
|
+
* Service Worker, browser mock, or PHP interoperability test can bind it
|
|
50
|
+
* without importing a server framework.
|
|
51
|
+
*
|
|
52
|
+
* The responder validates transport method/content type/size, performs the
|
|
53
|
+
* trusted authentication and request-integrity check before parsing JSON,
|
|
54
|
+
* validates the exact operation schema, checks capability and resource
|
|
55
|
+
* context binding, and only then invokes the host-authoritative port.
|
|
56
|
+
*/
|
|
57
|
+
export declare function createAuthoringHttpResponder(authoring: AuthoringPort, options: AuthoringHttpResponderOptions): (request: HttpResponderRequest) => Promise<HttpResponderResponse>;
|
|
58
|
+
//# sourceMappingURL=http-host-responder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-host-responder.d.ts","sourceRoot":"","sources":["../src/http-host-responder.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,aAAa,EAalB,KAAK,aAAa,EACnB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACzB,MAAM,8BAA8B,CAAC;AAEtC,MAAM,WAAW,oBAAoB;IACnC,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;IACb,oFAAoF;IACpF,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,6BAA6B;IAC5C,uFAAuF;IACvF,aAAa,EAAE,OAAO,CAAC;IACvB,iGAAiG;IACjG,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,aAAa,CAAC;IAC1B,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IACtD,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,kBAAkB,CAAC;CAC3B;AAED,MAAM,MAAM,6BAA6B,GAAG,CAC1C,KAAK,EAAE,0BAA0B,KAC9B,6BAA6B,GAAG,OAAO,CAAC,6BAA6B,CAAC,CAAC;AAE5E;;;;GAIG;AACH,MAAM,WAAW,6BAA6B;IAC5C,8EAA8E;IAC9E,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,4BAA4B;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,mBAAmB,CAAC;IACpC,uFAAuF;IACvF,uBAAuB,EAAE,6BAA6B,CAAC;CACxD;AA8CD;;;;;;;;;;GAUG;AACH,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,aAAa,EACxB,OAAO,EAAE,6BAA6B,GACrC,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,qBAAqB,CAAC,CA0JnE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isHostPortFailure as e,STUDIO_CONTRACT_VERSION as t}from"@kumwe/studio-protocol";import{AUTHORING_HTTP_OPERATIONS as n,AUTHORING_HTTP_OPERATIONS_BY_ROUTE as r}from"./http-authoring-contract.js";const i=Object.freeze({"cache-control":`no-store`,"content-type":`application/json; charset=utf-8`,"x-content-type-options":`nosniff`}),a=Object.freeze({"authoring/list-types":(e,t)=>e.listTypes(t.arguments.query,t.context),"authoring/plan-save":(e,t)=>e.planSave(t.arguments.intent,t.context),"authoring/resolve-target":(e,t)=>e.resolveTarget(t.arguments.request,t.context),"authoring/save-as-new-type":(e,t)=>e.saveAsNewType(t.arguments.request,t.context),"authoring/save-item":(e,t)=>e.saveItem(t.arguments.request,t.context),"authoring/save-new-type-version":(e,t)=>e.saveNewTypeVersion(t.arguments.request,t.context),"authoring/start":(e,t)=>e.start(t.arguments.request,t.context)});export function createAuthoringHttpResponder(n,r){let i=r.maximumRequestBytes??67108864;if(!Number.isSafeInteger(i)||i<=0)throw TypeError(`maximumRequestBytes must be a positive safe integer.`);let o=c(r.routePrefix??`/ports`),y=0,b=(e,n,r,i=!1)=>(y+=1,h({category:e,contractVersion:t,correlationId:`http-responder/${y}`,kind:`host-error`,message:{defaultMessage:r,key:n},retryable:i},m(e)));return async t=>{let c=s(t.path,o);if(c===void 0)return b(`not-found`,`studio.testkit/http-route-not-found`,`The requested Studio host operation is unavailable.`);if(t.method!==`POST`)return _(b(`invalid-request`,`studio.testkit/http-method-invalid`,`Studio host operations require POST.`));if(!u(l(t.headers,`content-type`)))return b(`invalid-request`,`studio.testkit/http-content-type-invalid`,`Studio host operations require an application/json content type.`);if(v(t.body)>i)return b(`limit-exceeded`,`studio.testkit/http-request-too-large`,`The Studio host request exceeds the configured transport limit.`);let y;try{y=await r.verifyTransportSecurity({capability:c.capability,headers:t.headers,method:`POST`,mutating:c.mutating,path:t.path,route:c.route})}catch{return b(`internal`,`studio.testkit/http-security-check-failed`,`The host could not verify request security.`)}if(!y.authenticated)return b(`unauthenticated`,`studio.testkit/http-unauthenticated`,`Authentication is required for Studio authoring.`);if(!y.requestIntegrity)return b(`forbidden`,`studio.testkit/http-request-integrity-failed`,`The Studio authoring request failed request-integrity verification.`);let x=d(t.body);if(x===void 0)return b(`invalid-request`,`studio.testkit/http-request-malformed`,`The Studio host request body is not valid JSON.`);let S;try{S=r.validateSchema(c.requestSchema,x)}catch{return b(`internal`,`studio.testkit/http-schema-validator-failed`,`The host could not validate the Studio request.`)}if(!S||!f(x)||x.context.operationId!==c.capability)return b(`invalid-request`,`studio.testkit/http-request-invalid`,`The Studio host request does not match the operation contract.`);if(!p(c.route,x))return b(`invalid-request`,`studio.testkit/http-resource-context-mismatch`,`The operation resource context does not match the request envelope.`);let C;try{C=await a[c.route](n,x)}catch(t){return e(t)?h(t.error,m(t.error.category)):b(`internal`,`studio.testkit/http-host-failure`,`The host could not complete the Studio operation.`)}try{S=r.validateSchema(c.responseSchema,C)}catch{S=!1}return S?g(200,C):b(`internal`,`studio.testkit/http-response-invalid`,`The host produced an invalid Studio operation result.`)}}function s(e,t){if(!e.startsWith(`${t}/`)||e.includes(`?`)||e.includes(`#`))return;let n=e.slice(t.length+1);return r.get(n)}function c(e){if(!e.startsWith(`/`)||e.includes(`?`)||e.includes(`#`))throw TypeError(`routePrefix must be an absolute pathname without a query or fragment.`);let t=e.endsWith(`/`)?e.slice(0,-1):e;if(t.length===0||t.includes(`//`))throw TypeError(`routePrefix must identify one normalized absolute pathname.`);return t}function l(e,t){let n=Object.entries(e).filter(([e])=>e.toLowerCase()===t);return n.length===1?n[0]?.[1]:void 0}function u(e){return e?.split(`;`,1)[0]?.trim().toLowerCase()===`application/json`}function d(e){try{return JSON.parse(e)}catch{return}}function f(e){if(typeof e!=`object`||!e||Array.isArray(e))return!1;let t=e;return typeof t.arguments==`object`&&t.arguments!==null&&!Array.isArray(t.arguments)&&typeof t.context==`object`&&t.context!==null&&!Array.isArray(t.context)}function p(e,t){let r;switch(e){case n.resolveTarget.route:case n.start.route:r=t.arguments.request?.resourceContext;break;case n.listTypes.route:r=t.arguments.query?.resourceContext;break;default:return!0}return typeof r==`object`&&!!r&&!Array.isArray(r)&&r.key===t.context.resourceContextKey}function m(e){switch(e){case`unauthenticated`:return 401;case`forbidden`:return 403;case`not-found`:return 404;case`conflict`:return 409;case`limit-exceeded`:return 413;case`validation-failed`:return 422;case`rate-limited`:return 429;case`unavailable`:return 503;case`internal`:return 500;case`cancelled`:case`incompatible`:case`invalid-request`:return 400}}function h(e,t){return g(t,e)}function g(e,t){return{body:JSON.stringify(t),headers:i,status:e}}function _(e){return{...e,headers:{...e.headers,allow:`POST`}}}function v(e){let t=0;for(let n of e){let e=n.codePointAt(0)??0;t+=e<=127?1:e<=2047?2:e<=65535?3:4}return t}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
|
|
3
|
-
export {
|
|
1
|
+
import { type StudioSchemaProfileErrorCode } from '@kumwe/studio-core';
|
|
2
|
+
import { type BlockDefinition, type BlockSlotDefinition, type BlockType, type BlueprintDocument, type BlueprintBlockLock, type BlueprintNode, type ContentFieldKind, type ContentModelDocument, type DiagnosticLocation, type FieldDefinition, type JsonSchema, type StudioConfiguration, type StudioDiagnostic } from '@kumwe/studio-protocol';
|
|
3
|
+
export { createHostRequestContextFixture, createTestbedHost, TestbedHostError, type HostRequestContextFixtureOptions, type TestbedAuthoringOperation, type TestbedAuthoringOptions, type TestbedAuthoringPlanFixture, type TestbedAuthoringSaveFixture, type TestbedAuthoringSaveRequest, type TestbedAuthoringStartFixture, type TestbedAuthoringTargetFixture, type TestbedAuthoringTypeFixture, type TestbedControls, type TestbedHost, type TestbedHostOptions, type TestbedPortName, type TestbedRateLimitPolicy, } from './host-testbed.js';
|
|
4
|
+
export { createHttpHostAdapter, HTTP_HOST_OPERATION_ROUTES, type ConfiguredHttpHostAdapterOptions, type HttpAuthenticationConfiguration, type HttpAuthenticationRequest, type HttpAuthenticationResolver, type HttpBearerTokenAuthentication, type HttpFetchLike, type HttpHeaderTokenAuthentication, type HttpHostAdapterOptions, type HttpHostOperationEndpoints, type HttpHostOperationRoute, type HttpHostRoutingConfiguration, type HttpHostTransportConfiguration, type HttpRequestInit, type HttpResponseLike, type HttpSameOriginSessionAuthentication, type HttpTimeoutHandle, } from './http-host-adapter.js';
|
|
5
|
+
export { AUTHORING_HTTP_OPERATIONS, AUTHORING_HTTP_OPERATIONS_BY_ROUTE, AUTHORING_HTTP_SCHEMA_ID, type AuthoringHttpOperationContract, type AuthoringHttpRoute, type HttpSchemaValidator, } from './http-authoring-contract.js';
|
|
6
|
+
export { createAuthoringHttpResponder, type AuthoringHttpResponderOptions, type HttpResponderRequest, type HttpResponderResponse, type HttpTransportSecurityDecision, type HttpTransportSecurityInput, type HttpTransportSecurityVerifier, } from './http-host-responder.js';
|
|
7
|
+
export { runAuthoringWebVector, type AuthoringWebAction, type AuthoringWebCommandObservation, type AuthoringWebConformanceAdapter, type AuthoringWebConformanceSession, type AuthoringWebGiven, type AuthoringWebLane, type AuthoringWebLaneResult, type AuthoringWebObservation, type AuthoringWebRegion, type AuthoringWebVector, type AuthoringWebVectorResult, } from './web-conformance.js';
|
|
8
|
+
export { runContextualAuthoringStrideVector, type ContextualAuthoringCommandAssertion, type ContextualAuthoringConformanceAdapter, type ContextualAuthoringConformanceSession, type ContextualAuthoringLaunchAssertion, type ContextualAuthoringSaveAssertion, type ContextualAuthoringSaveRequest, type ContextualAuthoringStrideResult, type ContextualAuthoringStrideVector, } from './contextual-conformance.js';
|
|
4
9
|
export interface BlueprintFixtureOptions {
|
|
5
10
|
blockLocks?: BlueprintBlockLock[];
|
|
6
11
|
id?: string;
|
|
@@ -19,6 +24,86 @@ export interface StudioConfigurationFixtureOptions {
|
|
|
19
24
|
mode?: 'blueprint' | 'content' | 'model';
|
|
20
25
|
sessionState?: 'editable' | 'read-only';
|
|
21
26
|
}
|
|
27
|
+
export interface SchemaProfileVectorInstance {
|
|
28
|
+
value: unknown;
|
|
29
|
+
valid: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** Portable input consumed by `studio.profile/binding-projection-v1`. */
|
|
32
|
+
export interface BindingProjectionVectorInput {
|
|
33
|
+
blockDefinitions: readonly BlockDefinition[];
|
|
34
|
+
blueprint: BlueprintDocument;
|
|
35
|
+
model: ContentModelDocument;
|
|
36
|
+
}
|
|
37
|
+
export interface BindingProjectionVectorCandidateResult {
|
|
38
|
+
cardinality: FieldDefinition['cardinality'];
|
|
39
|
+
control?: string;
|
|
40
|
+
fieldPath: string[];
|
|
41
|
+
itemKind?: Exclude<ContentFieldKind, 'collection'>;
|
|
42
|
+
kind: ContentFieldKind;
|
|
43
|
+
}
|
|
44
|
+
export interface BindingProjectionVectorPortResult {
|
|
45
|
+
bindingSourceKind?: string;
|
|
46
|
+
boundFieldPath?: string[];
|
|
47
|
+
candidates: BindingProjectionVectorCandidateResult[];
|
|
48
|
+
multiple?: boolean;
|
|
49
|
+
port: string;
|
|
50
|
+
required?: boolean;
|
|
51
|
+
status: 'invalid' | 'non-field-source' | 'resolved' | 'unbound';
|
|
52
|
+
valueType?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface BindingProjectionVectorRunResult {
|
|
55
|
+
diagnostics: {
|
|
56
|
+
code: string;
|
|
57
|
+
location?: DiagnosticLocation;
|
|
58
|
+
severity: StudioDiagnostic['severity'];
|
|
59
|
+
}[];
|
|
60
|
+
model: BlueprintDocument['model'];
|
|
61
|
+
nodes: {
|
|
62
|
+
nodeId: string;
|
|
63
|
+
ports: BindingProjectionVectorPortResult[];
|
|
64
|
+
}[];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Replays a binding vector without consulting its expected projection. The
|
|
68
|
+
* normalized result intentionally excludes localized labels and prose so a
|
|
69
|
+
* non-TypeScript host compares stable coordinates, controls, outcomes, and
|
|
70
|
+
* diagnostic locations rather than an English rendering.
|
|
71
|
+
*/
|
|
72
|
+
export declare function runBindingProjectionVector(vector: BindingProjectionVectorInput): BindingProjectionVectorRunResult;
|
|
73
|
+
export interface SchemaProfileVectorInput {
|
|
74
|
+
expect: {
|
|
75
|
+
outcome: 'accepted' | 'rejected';
|
|
76
|
+
};
|
|
77
|
+
schema: unknown;
|
|
78
|
+
}
|
|
79
|
+
export interface SchemaProfileVectorInstanceResult {
|
|
80
|
+
diagnostic?: {
|
|
81
|
+
instancePath: string;
|
|
82
|
+
keyword: string;
|
|
83
|
+
};
|
|
84
|
+
value: unknown;
|
|
85
|
+
valid: boolean;
|
|
86
|
+
}
|
|
87
|
+
export type SchemaProfileVectorRunResult = {
|
|
88
|
+
instances: SchemaProfileVectorInstanceResult[];
|
|
89
|
+
outcome: 'accepted';
|
|
90
|
+
} | {
|
|
91
|
+
code: StudioSchemaProfileErrorCode;
|
|
92
|
+
outcome: 'rejected';
|
|
93
|
+
schemaPath: string;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Replay a portable `studio.profile/schema-property` vector against the
|
|
97
|
+
* reference implementation. Only each case's input `value` is read from the
|
|
98
|
+
* accepted expectation; expected verdicts and diagnostics are not consulted.
|
|
99
|
+
* Callers compare this independently produced outcome with the published
|
|
100
|
+
* `expect` member.
|
|
101
|
+
*/
|
|
102
|
+
export declare function runSchemaProfileVector(vector: SchemaProfileVectorInput & {
|
|
103
|
+
expect: {
|
|
104
|
+
instances?: SchemaProfileVectorInstance[];
|
|
105
|
+
};
|
|
106
|
+
}): SchemaProfileVectorRunResult;
|
|
22
107
|
export declare function createBlueprintFixture(options?: BlueprintFixtureOptions): BlueprintDocument;
|
|
23
108
|
export declare function defineTestBlock(options: TestBlockOptions): BlockDefinition;
|
|
24
109
|
export declare function createStudioConfigurationFixture(options?: StudioConfigurationFixtureOptions): StudioConfiguration;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|