@haverstack/wire-types 0.40.0 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/auth.d.ts ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Authentication handshake
3
+ * -------------------------------------------------------
4
+ * How a server advertises token issuance, the challenge–response bodies,
5
+ * and the handshake's own error vocabulary.
6
+ * See docs/spec/wire-format.md § Authentication.
7
+ */
8
+ /**
9
+ * How a token can be earned here. Absent means only a scheme arranged out
10
+ * of band, which a client learns at open() rather than as a 404 partway
11
+ * through a handshake. An object so a consent flow can arrive as another
12
+ * entry. See docs/spec/wire-format.md § Advertising it.
13
+ */
14
+ export type DiscoveryAuth = {
15
+ methods: AuthMethod[];
16
+ };
17
+ /** The challenge–response handshake of docs/spec/wire-format.md § Authentication. */
18
+ export declare const AUTH_METHOD_DID_CHALLENGE = "did-challenge";
19
+ export type AuthMethod = typeof AUTH_METHOD_DID_CHALLENGE;
20
+ /** Whether a server advertises the DID challenge–response handshake. */
21
+ export declare function supportsDidChallenge(discovery: {
22
+ auth?: DiscoveryAuth;
23
+ }): boolean;
24
+ /**
25
+ * The two request bodies, re-exported from `@haverstack/core/wire`, whose
26
+ * parsers return them — one definition, as with `parseDate`.
27
+ */
28
+ export type { WireAuthChallengeRequest, WireAuthTokenRequest } from '@haverstack/core/wire';
29
+ /** POST /auth/challenge response. */
30
+ export type WireAuthChallengeResponse = {
31
+ /** Opaque, single-use, base64url-charset. Bound to the requested DID. */
32
+ nonce: string;
33
+ expiresAt: string;
34
+ };
35
+ /**
36
+ * POST /auth/token response. Both identities are always present, and this
37
+ * endpoint always reports them equal — a handshake proves key possession,
38
+ * which says nothing about whom that key may act for. The pair is reported
39
+ * anyway so an issuance path that does delegate needs no new shape.
40
+ * See docs/spec/wire-format.md § Authentication.
41
+ */
42
+ export type WireAuthTokenResponse = {
43
+ token: string;
44
+ expiresAt?: string;
45
+ principalId: string;
46
+ subjectId: string;
47
+ };
48
+ /**
49
+ * Auth failures have their own vocabulary, deliberately outside
50
+ * WireErrorCode: no Stack operation has begun, so none of them is a
51
+ * StackError and none has a class to reconstruct. The split a client acts
52
+ * on is retryable versus fatal — a stale nonce warrants a fresh handshake,
53
+ * a rejected signature never will.
54
+ */
55
+ export type WireAuthErrorCode = 'invalid_did' | 'unknown_nonce' | 'expired_nonce' | 'invalid_signature';
56
+ export type WireAuthError = {
57
+ error: {
58
+ code: WireAuthErrorCode;
59
+ message: string;
60
+ };
61
+ };
62
+ export declare const WIRE_AUTH_ERROR_STATUS: Record<WireAuthErrorCode, number>;
63
+ /** Type guard: does this parsed JSON body look like a WireAuthError? */
64
+ export declare function isWireAuthError(body: unknown): body is WireAuthError;
65
+ /** Whether retrying the handshake from a fresh nonce could succeed. */
66
+ export declare function isRetryableAuthError(code: WireAuthErrorCode): boolean;
67
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB,CAAC;AAEF,qFAAqF;AACrF,eAAO,MAAM,yBAAyB,kBAAkB,CAAC;AAEzD,MAAM,MAAM,UAAU,GAAG,OAAO,yBAAyB,CAAC;AAE1D,wEAAwE;AACxE,wBAAgB,oBAAoB,CAAC,SAAS,EAAE;IAAE,IAAI,CAAC,EAAE,aAAa,CAAA;CAAE,GAAG,OAAO,CAEjF;AAED;;;GAGG;AACH,YAAY,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE5F,qCAAqC;AACrC,MAAM,MAAM,yBAAyB,GAAG;IACtC,yEAAyE;IACzE,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GACzB,aAAa,GACb,eAAe,GACf,eAAe,GACf,mBAAmB,CAAC;AAExB,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE;QACL,IAAI,EAAE,iBAAiB,CAAC;QACxB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,iBAAiB,EAAE,MAAM,CAOpE,CAAC;AAOF,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,aAAa,CAEpE;AAED,uEAAuE;AACvE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAErE"}
package/dist/auth.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Authentication handshake
3
+ * -------------------------------------------------------
4
+ * How a server advertises token issuance, the challenge–response bodies,
5
+ * and the handshake's own error vocabulary.
6
+ * See docs/spec/wire-format.md § Authentication.
7
+ */
8
+ import { hasErrorBody } from './errors.js';
9
+ /** The challenge–response handshake of docs/spec/wire-format.md § Authentication. */
10
+ export const AUTH_METHOD_DID_CHALLENGE = 'did-challenge';
11
+ /** Whether a server advertises the DID challenge–response handshake. */
12
+ export function supportsDidChallenge(discovery) {
13
+ return discovery.auth?.methods?.includes(AUTH_METHOD_DID_CHALLENGE) ?? false;
14
+ }
15
+ export const WIRE_AUTH_ERROR_STATUS = {
16
+ // Malformed input, not a rejected credential — there is nothing here to
17
+ // authenticate yet.
18
+ invalid_did: 400,
19
+ unknown_nonce: 401,
20
+ expired_nonce: 401,
21
+ invalid_signature: 401,
22
+ };
23
+ /** Codes a fresh handshake can resolve. Anything else is fatal to retry. */
24
+ const RETRYABLE_AUTH_CODES = new Set(['unknown_nonce', 'expired_nonce']);
25
+ const KNOWN_AUTH_CODES = new Set(Object.keys(WIRE_AUTH_ERROR_STATUS));
26
+ /** Type guard: does this parsed JSON body look like a WireAuthError? */
27
+ export function isWireAuthError(body) {
28
+ return hasErrorBody(body, KNOWN_AUTH_CODES);
29
+ }
30
+ /** Whether retrying the handshake from a fresh nonce could succeed. */
31
+ export function isRetryableAuthError(code) {
32
+ return RETRYABLE_AUTH_CODES.has(code);
33
+ }
34
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAY3C,qFAAqF;AACrF,MAAM,CAAC,MAAM,yBAAyB,GAAG,eAAe,CAAC;AAIzD,wEAAwE;AACxE,MAAM,UAAU,oBAAoB,CAAC,SAAmC;IACtE,OAAO,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,yBAAyB,CAAC,IAAI,KAAK,CAAC;AAC/E,CAAC;AAiDD,MAAM,CAAC,MAAM,sBAAsB,GAAsC;IACvE,wEAAwE;IACxE,oBAAoB;IACpB,WAAW,EAAE,GAAG;IAChB,aAAa,EAAE,GAAG;IAClB,aAAa,EAAE,GAAG;IAClB,iBAAiB,EAAE,GAAG;CACvB,CAAC;AAEF,4EAA4E;AAC5E,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAAoB,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC,CAAC;AAE5F,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAS,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC;AAE9E,wEAAwE;AACxE,MAAM,UAAU,eAAe,CAAC,IAAa;IAC3C,OAAO,YAAY,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;AAC9C,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,oBAAoB,CAAC,IAAuB;IAC1D,OAAO,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Change feed
3
+ * -------------------------------------------------------
4
+ * How a server advertises the feed, and the frames it streams.
5
+ * See docs/spec/change-feed.md.
6
+ */
7
+ import type { ChangeKind, ChangeOp, DataAssociation, RecordChange } from '@haverstack/core';
8
+ import { type WireChangeActor, type WireRecord } from './records.js';
9
+ /**
10
+ * The change feed a server offers, absent when it offers none. An object
11
+ * rather than a boolean for the same reason `auth` is one: this surface
12
+ * grows entries. See docs/spec/change-feed.md.
13
+ */
14
+ export type DiscoveryChanges = {
15
+ transports: ChangeTransport[];
16
+ /**
17
+ * Whether a resume cursor is honored. `false` is conformant and means
18
+ * every reconnect is answered with a `reset` frame.
19
+ */
20
+ resume: boolean;
21
+ /** Whether `?include=record` is honored. Never honored for a purge. */
22
+ records: boolean;
23
+ };
24
+ /** Server→client streaming over `fetch`. The only transport in this version. */
25
+ export declare const CHANGE_TRANSPORT_SSE = "sse";
26
+ export type ChangeTransport = typeof CHANGE_TRANSPORT_SSE;
27
+ /**
28
+ * Whether a server offers a change feed this client can consume. A client
29
+ * checks this and fails locally rather than learning it as a 404 partway
30
+ * through a connection — the same reason `auth.methods` exists.
31
+ */
32
+ export declare function supportsChangeFeed(discovery: {
33
+ changes?: DiscoveryChanges;
34
+ }): boolean;
35
+ /**
36
+ * One change, as a `record` frame carries it. The envelope describes the
37
+ * change and nothing else: the record's own provenance rides `record`,
38
+ * where a consumer that wants it asks for it. See docs/spec/events.md
39
+ * § Attribution.
40
+ */
41
+ export type WireRecordChange = {
42
+ kind: ChangeKind;
43
+ /** Every aspect this version moved; never empty. See RecordChange.ops. */
44
+ ops: ChangeOp[];
45
+ recordId: string;
46
+ typeId: string;
47
+ version: number;
48
+ updatedAt: string;
49
+ parentId?: string;
50
+ actor?: WireChangeActor;
51
+ /** Present when `ops` includes `associate`. See RecordChange.associationsAdded. */
52
+ associationsAdded?: DataAssociation[];
53
+ /** Present when `ops` includes `dissociate`. See RecordChange.associationsRemoved. */
54
+ associationsRemoved?: DataAssociation[];
55
+ record?: WireRecord;
56
+ cursor?: string;
57
+ };
58
+ /**
59
+ * A change as a server frames it. A purge carries neither the record nor
60
+ * its parent, whatever was asked for: purge is the erasure primitive, and
61
+ * the rule lives here because a server still holds the purged record at
62
+ * emission. See docs/spec/events.md § Purged records carry nothing.
63
+ */
64
+ export declare function serializeChange(c: RecordChange): WireRecordChange;
65
+ /**
66
+ * Frame names. Every frame carries one, and a client MUST ignore a name it
67
+ * does not recognize — which is what makes a new frame an additive, minor
68
+ * change rather than a break. See docs/spec/change-feed.md.
69
+ */
70
+ export declare const CHANGE_FRAME_READY = "ready";
71
+ export declare const CHANGE_FRAME_RECORD = "record";
72
+ export declare const CHANGE_FRAME_RESET = "reset";
73
+ /**
74
+ * Sent first on every connection, before any change. It is what makes
75
+ * subscribe-then-query gap-free: everything after it is in the stream.
76
+ */
77
+ export type WireReadyFrame = {
78
+ /** The head cursor. Absent from a server that mints none (`resume: false`). */
79
+ cursor?: string;
80
+ };
81
+ /**
82
+ * Why a cursor could not be honored. Informational — a client's repair is
83
+ * the same for all three, and it is reconciling by query.
84
+ */
85
+ export type ChangeResetReason = 'cursor_expired' | 'not_supported' | 'overflow';
86
+ /** Your cursor cannot be honored; resynchronize by query. */
87
+ export type WireResetFrame = {
88
+ reason: ChangeResetReason;
89
+ };
90
+ /** Whether a cursor is framable: unreserved base64url characters only. */
91
+ export declare function isValidCursor(cursor: string): boolean;
92
+ //# sourceMappingURL=change-feed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"change-feed.d.ts","sourceRoot":"","sources":["../src/change-feed.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC5F,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,UAAU,EAChB,MAAM,cAAc,CAAC;AAEtB;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B;;;OAGG;IACH,MAAM,EAAE,OAAO,CAAC;IAChB,uEAAuE;IACvE,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,oBAAoB,QAAQ,CAAC;AAE1C,MAAM,MAAM,eAAe,GAAG,OAAO,oBAAoB,CAAC;AAE1D;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE;IAAE,OAAO,CAAC,EAAE,gBAAgB,CAAA;CAAE,GAAG,OAAO,CAErF;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,UAAU,CAAC;IACjB,0EAA0E;IAC1E,GAAG,EAAE,QAAQ,EAAE,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,mFAAmF;IACnF,iBAAiB,CAAC,EAAE,eAAe,EAAE,CAAC;IACtC,sFAAsF;IACtF,mBAAmB,CAAC,EAAE,eAAe,EAAE,CAAC;IACxC,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,YAAY,GAAG,gBAAgB,CAmBjE;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,UAAU,CAAC;AAC1C,eAAO,MAAM,mBAAmB,WAAW,CAAC;AAC5C,eAAO,MAAM,kBAAkB,UAAU,CAAC;AAE1C;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,gBAAgB,GAAG,eAAe,GAAG,UAAU,CAAC;AAEhF,6DAA6D;AAC7D,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,iBAAiB,CAAC;CAC3B,CAAC;AASF,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAErD"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Change feed
3
+ * -------------------------------------------------------
4
+ * How a server advertises the feed, and the frames it streams.
5
+ * See docs/spec/change-feed.md.
6
+ */
7
+ import { serializeChangeActor, serializeRecord, } from './records.js';
8
+ /** Server→client streaming over `fetch`. The only transport in this version. */
9
+ export const CHANGE_TRANSPORT_SSE = 'sse';
10
+ /**
11
+ * Whether a server offers a change feed this client can consume. A client
12
+ * checks this and fails locally rather than learning it as a 404 partway
13
+ * through a connection — the same reason `auth.methods` exists.
14
+ */
15
+ export function supportsChangeFeed(discovery) {
16
+ return discovery.changes?.transports?.includes(CHANGE_TRANSPORT_SSE) ?? false;
17
+ }
18
+ /**
19
+ * A change as a server frames it. A purge carries neither the record nor
20
+ * its parent, whatever was asked for: purge is the erasure primitive, and
21
+ * the rule lives here because a server still holds the purged record at
22
+ * emission. See docs/spec/events.md § Purged records carry nothing.
23
+ */
24
+ export function serializeChange(c) {
25
+ const w = {
26
+ kind: c.kind,
27
+ // Copied, not aliased: a frame is serialized once and delivered to
28
+ // every subscriber, and the array it came from belongs to the emission.
29
+ ops: [...c.ops],
30
+ recordId: c.recordId,
31
+ typeId: c.typeId,
32
+ version: c.version,
33
+ updatedAt: c.updatedAt.toISOString(),
34
+ };
35
+ if (c.actor !== undefined)
36
+ w.actor = serializeChangeActor(c.actor);
37
+ if (c.cursor !== undefined)
38
+ w.cursor = c.cursor;
39
+ if (c.kind === 'purged')
40
+ return w;
41
+ if (c.parentId !== undefined)
42
+ w.parentId = c.parentId;
43
+ if (c.associationsAdded !== undefined)
44
+ w.associationsAdded = c.associationsAdded;
45
+ if (c.associationsRemoved !== undefined)
46
+ w.associationsRemoved = c.associationsRemoved;
47
+ if (c.record !== undefined)
48
+ w.record = serializeRecord(c.record);
49
+ return w;
50
+ }
51
+ /**
52
+ * Frame names. Every frame carries one, and a client MUST ignore a name it
53
+ * does not recognize — which is what makes a new frame an additive, minor
54
+ * change rather than a break. See docs/spec/change-feed.md.
55
+ */
56
+ export const CHANGE_FRAME_READY = 'ready';
57
+ export const CHANGE_FRAME_RECORD = 'record';
58
+ export const CHANGE_FRAME_RESET = 'reset';
59
+ /**
60
+ * A cursor travels in an SSE `id:` field, so a value spanning a line would
61
+ * truncate the frame carrying it. Same charset and same reason as the auth
62
+ * nonce — see docs/spec/wire-format.md § The handshake.
63
+ */
64
+ const CURSOR_FORMAT = /^[A-Za-z0-9_-]+$/;
65
+ /** Whether a cursor is framable: unreserved base64url characters only. */
66
+ export function isValidCursor(cursor) {
67
+ return CURSOR_FORMAT.test(cursor);
68
+ }
69
+ //# sourceMappingURL=change-feed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"change-feed.js","sourceRoot":"","sources":["../src/change-feed.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,oBAAoB,EACpB,eAAe,GAGhB,MAAM,cAAc,CAAC;AAkBtB,gFAAgF;AAChF,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAI1C;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAyC;IAC1E,OAAO,SAAS,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,oBAAoB,CAAC,IAAI,KAAK,CAAC;AAChF,CAAC;AA0BD;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,CAAe;IAC7C,MAAM,CAAC,GAAqB;QAC1B,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,mEAAmE;QACnE,wEAAwE;QACxE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC;QACf,QAAQ,EAAE,CAAC,CAAC,QAAQ;QACpB,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,OAAO,EAAE,CAAC,CAAC,OAAO;QAClB,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,WAAW,EAAE;KACrC,CAAC;IACF,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;QAAE,CAAC,CAAC,KAAK,GAAG,oBAAoB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACnE,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS;QAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC;IAClC,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS;QAAE,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC;IACtD,IAAI,CAAC,CAAC,iBAAiB,KAAK,SAAS;QAAE,CAAC,CAAC,iBAAiB,GAAG,CAAC,CAAC,iBAAiB,CAAC;IACjF,IAAI,CAAC,CAAC,mBAAmB,KAAK,SAAS;QAAE,CAAC,CAAC,mBAAmB,GAAG,CAAC,CAAC,mBAAmB,CAAC;IACvF,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS;QAAE,CAAC,CAAC,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACjE,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAC1C,MAAM,CAAC,MAAM,mBAAmB,GAAG,QAAQ,CAAC;AAC5C,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAsB1C;;;;GAIG;AACH,MAAM,aAAa,GAAG,kBAAkB,CAAC;AAEzC,0EAA0E;AAC1E,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACpC,CAAC"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Discovery
3
+ * -------------------------------------------------------
4
+ * `GET /.well-known/stack` and the protocol version it negotiates. Each
5
+ * optional surface's advertisement lives with that surface — `auth`,
6
+ * `changes`, `installs` — and is only assembled here.
7
+ * See docs/spec/wire-format.md § Discovery.
8
+ */
9
+ import type { StackCapabilities } from '@haverstack/core';
10
+ import type { DiscoveryAuth } from './auth.js';
11
+ import type { DiscoveryChanges } from './change-feed.js';
12
+ import type { DiscoveryInstalls } from './installs.js';
13
+ /**
14
+ * The wire protocol this package describes. Bump the major when a change
15
+ * would make an older client read a response wrongly; bump the minor for
16
+ * additions an older client can ignore. See docs/spec/wire-format.md
17
+ * § Version negotiation.
18
+ */
19
+ export declare const WIRE_PROTOCOL_VERSION = "1.0";
20
+ /** GET /.well-known/stack. See docs/spec/wire-format.md § Discovery. */
21
+ export type DiscoveryResponse = {
22
+ version: string;
23
+ entityId: string;
24
+ timezone?: string;
25
+ capabilities?: DiscoveryCapabilities;
26
+ auth?: DiscoveryAuth;
27
+ changes?: DiscoveryChanges;
28
+ installs?: DiscoveryInstalls;
29
+ };
30
+ /**
31
+ * `capabilities` as it arrives: a foreign server may omit any part, or name
32
+ * a reach or sort field this client has never heard of. Typed loosely on
33
+ * exactly those fields, so the checks normalizeCapabilities() makes read as
34
+ * checks rather than casts. See docs/spec/wire-format.md § Discovery.
35
+ */
36
+ export type DiscoveryCapabilities = {
37
+ filter?: {
38
+ content?: string;
39
+ contentPresent?: boolean;
40
+ contentByType?: number;
41
+ search?: boolean;
42
+ };
43
+ sort?: {
44
+ fields?: string[];
45
+ contentField?: boolean;
46
+ contentFieldByType?: number;
47
+ };
48
+ limits?: {
49
+ attachmentBytes?: number | null;
50
+ contentBytes?: number | null;
51
+ };
52
+ };
53
+ /**
54
+ * Resolves anything absent, malformed or unrecognized to the least capable
55
+ * value it could stand for — one rule, applied once, rather than a default
56
+ * per key at each call site. A `null` limit is the one permissive reading,
57
+ * since the server's own ceiling still answers 413. See
58
+ * docs/spec/adapters.md § Adapter capabilities.
59
+ */
60
+ export declare function normalizeCapabilities(capabilities: DiscoveryCapabilities | undefined): StackCapabilities;
61
+ /** Splits a MAJOR.MINOR protocol version. Returns null if it isn't one. */
62
+ export declare function parseProtocolVersion(version: string): {
63
+ major: number;
64
+ minor: number;
65
+ } | null;
66
+ /**
67
+ * Majors must match; minors never have to. A higher server minor is additive
68
+ * fields this client ignores, and a higher client minor is optional fields
69
+ * the server may omit — neither can make a response read wrongly, which is
70
+ * the only thing a major bump signals.
71
+ */
72
+ export declare function isProtocolCompatible(version: string, against?: string): boolean;
73
+ //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAuC,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC/F,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEvD;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,QAAQ,CAAC;AAE3C,wEAAwE;AACxE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,qBAAqB,CAAC;IACrC,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,MAAM,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAClG,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,YAAY,CAAC,EAAE,OAAO,CAAC;QAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClF,MAAM,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CAC5E,CAAC;AAKF;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,qBAAqB,GAAG,SAAS,GAC9C,iBAAiB,CA2BnB;AAUD,2EAA2E;AAC3E,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAG7F;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,SAAwB,GAAG,OAAO,CAI9F"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Discovery
3
+ * -------------------------------------------------------
4
+ * `GET /.well-known/stack` and the protocol version it negotiates. Each
5
+ * optional surface's advertisement lives with that surface — `auth`,
6
+ * `changes`, `installs` — and is only assembled here.
7
+ * See docs/spec/wire-format.md § Discovery.
8
+ */
9
+ import { NATIVE_SORT_FIELDS } from '@haverstack/core';
10
+ /**
11
+ * The wire protocol this package describes. Bump the major when a change
12
+ * would make an older client read a response wrongly; bump the minor for
13
+ * additions an older client can ignore. See docs/spec/wire-format.md
14
+ * § Version negotiation.
15
+ */
16
+ export const WIRE_PROTOCOL_VERSION = '1.0';
17
+ const CONTENT_FILTER_REACHES = new Set(['none', 'field', 'path']);
18
+ const SORT_FIELDS = new Set(NATIVE_SORT_FIELDS);
19
+ /**
20
+ * Resolves anything absent, malformed or unrecognized to the least capable
21
+ * value it could stand for — one rule, applied once, rather than a default
22
+ * per key at each call site. A `null` limit is the one permissive reading,
23
+ * since the server's own ceiling still answers 413. See
24
+ * docs/spec/adapters.md § Adapter capabilities.
25
+ */
26
+ export function normalizeCapabilities(capabilities) {
27
+ const filter = capabilities?.filter;
28
+ const sort = capabilities?.sort;
29
+ const limits = capabilities?.limits;
30
+ const reach = CONTENT_FILTER_REACHES.has(filter?.content)
31
+ ? filter?.content
32
+ : 'none';
33
+ // The per-Type shapes travel in the POST /records/query body, which a
34
+ // server reaching no content does not serve.
35
+ const cap = (value) => (reach === 'none' ? 0 : capOrZero(value));
36
+ return {
37
+ filter: {
38
+ content: reach,
39
+ contentPresent: filter?.contentPresent === true,
40
+ contentByType: cap(filter?.contentByType),
41
+ search: filter?.search === true,
42
+ },
43
+ sort: {
44
+ fields: (sort?.fields ?? []).filter((f) => SORT_FIELDS.has(f)),
45
+ contentField: sort?.contentField === true,
46
+ contentFieldByType: cap(sort?.contentFieldByType),
47
+ },
48
+ limits: {
49
+ attachmentBytes: finiteOrNull(limits?.attachmentBytes),
50
+ contentBytes: finiteOrNull(limits?.contentBytes),
51
+ },
52
+ };
53
+ }
54
+ /** A cap on keys, or 0 — declined — for anything but a non-negative integer. */
55
+ const capOrZero = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value > 0 ? value : 0;
56
+ /** A ceiling this client can compare against, or null for "cannot pre-check". */
57
+ const finiteOrNull = (value) => typeof value === 'number' && Number.isFinite(value) ? value : null;
58
+ /** Splits a MAJOR.MINOR protocol version. Returns null if it isn't one. */
59
+ export function parseProtocolVersion(version) {
60
+ const match = /^(\d+)\.(\d+)$/.exec(version);
61
+ return match ? { major: Number(match[1]), minor: Number(match[2]) } : null;
62
+ }
63
+ /**
64
+ * Majors must match; minors never have to. A higher server minor is additive
65
+ * fields this client ignores, and a higher client minor is optional fields
66
+ * the server may omit — neither can make a response read wrongly, which is
67
+ * the only thing a major bump signals.
68
+ */
69
+ export function isProtocolCompatible(version, against = WIRE_PROTOCOL_VERSION) {
70
+ const server = parseProtocolVersion(version);
71
+ const client = parseProtocolVersion(against);
72
+ return server !== null && client !== null && server.major === client.major;
73
+ }
74
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Error responses
3
+ * -------------------------------------------------------
4
+ * The round-trip contract for core's typed error taxonomy: a server
5
+ * serializes a caught core error via serializeError(), and APIAdapter
6
+ * reconstructs the same class via deserializeError(). `code` is the
7
+ * authoritative discriminator; status is a transport hint. See
8
+ * docs/spec/wire-format.md § Error responses.
9
+ */
10
+ import type { SchemaDriftViolation, StackErrorCode, ValidationError } from '@haverstack/core';
11
+ /**
12
+ * Alias of core's StackErrorCode: the vocabulary belongs with the classes
13
+ * that carry it, and StackError.code is typed by it, so re-declaring the
14
+ * union here would be a second copy to drift.
15
+ */
16
+ export type WireErrorCode = StackErrorCode;
17
+ export type WireError = {
18
+ error: {
19
+ code: WireErrorCode;
20
+ message: string;
21
+ /** Field-level validation failures. Only present for code: 'validation'. */
22
+ details?: ValidationError[];
23
+ /** ifVersion/If-Match precondition state. Only present for code: 'version_conflict'. */
24
+ versionConflict?: {
25
+ recordId: string;
26
+ expectedVersion: number;
27
+ actualVersion: number;
28
+ };
29
+ /** The rejected defineType() call's target and violations. Only present for code: 'schema_drift'. */
30
+ schemaDrift?: {
31
+ typeId: string;
32
+ violations: SchemaDriftViolation[];
33
+ };
34
+ };
35
+ };
36
+ /** Canonical HTTP status for each wire error code. */
37
+ export declare const WIRE_ERROR_STATUS: Record<WireErrorCode, number>;
38
+ /**
39
+ * Statuses that unambiguously imply a wire error code, for reconstructing
40
+ * an error from status alone when a response has no parseable wire error
41
+ * body. 500 is deliberately excluded — it would misclassify ordinary
42
+ * server bugs as StackMigrationError. See docs/spec/wire-format.md
43
+ * § Wire error body.
44
+ */
45
+ export declare const STATUS_TO_CODE: Partial<Record<number, WireErrorCode>>;
46
+ /** Whether `body` is `{ error: { code, message } }` with a code from `codes`. */
47
+ export declare function hasErrorBody(body: unknown, codes: ReadonlySet<string>): boolean;
48
+ /** Type guard: does this parsed JSON body look like a WireError? */
49
+ export declare function isWireError(body: unknown): body is WireError;
50
+ /**
51
+ * Convert a thrown core error into its wire response. Used by server
52
+ * implementations. Returns null for anything that isn't a StackError —
53
+ * callers fall back to their own generic error handling, so an ordinary bug
54
+ * stays a bare 500 rather than being dressed as a protocol error.
55
+ */
56
+ export declare function serializeError(err: unknown): {
57
+ status: number;
58
+ body: WireError;
59
+ } | null;
60
+ /** Reconstruct the core error a WireError body describes. */
61
+ export declare function deserializeError(body: WireError): Error;
62
+ /**
63
+ * Reconstruct a core error from an HTTP status alone (no usable wire error
64
+ * body). Returns null for statuses with no unambiguous code — callers
65
+ * should fall back to a generic adapter-level error.
66
+ */
67
+ export declare function errorForStatus(status: number, message: string): Error | null;
68
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAeH,OAAO,KAAK,EAAE,oBAAoB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAE9F;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,cAAc,CAAC;AAE3C,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE;QACL,IAAI,EAAE,aAAa,CAAC;QACpB,OAAO,EAAE,MAAM,CAAC;QAChB,4EAA4E;QAC5E,OAAO,CAAC,EAAE,eAAe,EAAE,CAAC;QAC5B,wFAAwF;QACxF,eAAe,CAAC,EAAE;YAChB,QAAQ,EAAE,MAAM,CAAC;YACjB,eAAe,EAAE,MAAM,CAAC;YACxB,aAAa,EAAE,MAAM,CAAC;SACvB,CAAC;QACF,qGAAqG;QACrG,WAAW,CAAC,EAAE;YACZ,MAAM,EAAE,MAAM,CAAC;YACf,UAAU,EAAE,oBAAoB,EAAE,CAAC;SACpC,CAAC;KACH,CAAC;CACH,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAoC3D,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAajE,CAAC;AAIF,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,OAAO,CAO/E;AAED,oEAAoE;AACpE,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,SAAS,CAE5D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAAG,IAAI,CAkBvF;AAED,6DAA6D;AAC7D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,GAAG,KAAK,CA+BvD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAG5E"}