@archont561/qgis-sdk 0.3.0 → 0.4.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.
@@ -0,0 +1,280 @@
1
+ import { QgisSdkError2 } from "./chunk-pdj2fxjb.js";
2
+ declare const MAX_SAFE_INTEGER2: number;
3
+ declare const MIN_SAFE_INTEGER2: number;
4
+ declare const PLUGIN_DESCRIPTION_SCHEMA_VERSION2: 1;
5
+ type ValidationDirection2 = "request" | "result";
6
+ interface SerializedValidationIssue2 {
7
+ readonly path: (string | number)[];
8
+ readonly code: string;
9
+ }
10
+ interface SerializedContractValidationError2 {
11
+ readonly code: string;
12
+ readonly direction: ValidationDirection2;
13
+ readonly issues: SerializedValidationIssue2[];
14
+ }
15
+ interface ActionDescription2 {
16
+ readonly id: string;
17
+ readonly title: string;
18
+ readonly handler: string;
19
+ readonly request: Readonly<Record<string, unknown>>;
20
+ readonly result: Readonly<Record<string, unknown>>;
21
+ }
22
+ /** The event kinds a stream may declare; the frame carries the same three. */
23
+ type StreamEventKind2 = "data" | "progress" | "state";
24
+ /**
25
+ * A declared stream (decision-28): a request in, one payload schema per
26
+ * declared event kind out. There is no `result`: the reply to a subscription is
27
+ * the protocol's acknowledgement, not something an author declares.
28
+ */
29
+ interface StreamDescription2 {
30
+ readonly id: string;
31
+ readonly title: string;
32
+ readonly handler: string;
33
+ readonly request: Readonly<Record<string, unknown>>;
34
+ readonly events: Readonly<Partial<Record<StreamEventKind2, Readonly<Record<string, unknown>>>>>;
35
+ }
36
+ interface PluginDescription2 {
37
+ readonly schema_version: 1;
38
+ readonly id: string;
39
+ readonly title: string;
40
+ readonly actions: readonly ActionDescription2[];
41
+ /** Declared streams; a description from a host that predates them is empty. */
42
+ readonly streams: readonly StreamDescription2[];
43
+ }
44
+ /**
45
+ * Return whether `value` is a finite integer within the JavaScript-safe wire range.
46
+ */
47
+ declare function isSafeJsonInteger2(value: unknown): value is number;
48
+ /**
49
+ * A declaration cannot be represented or parsed in the versioned contract.
50
+ */
51
+ declare class QgisSdkDeclarationError2 extends QgisSdkError2 {
52
+ constructor(message: string);
53
+ }
54
+ /**
55
+ * Safe, structured location and category for a rejected contract value.
56
+ */
57
+ declare class QgisSdkValidationIssue2 {
58
+ readonly path: readonly (string | number)[];
59
+ readonly code: string;
60
+ constructor(path: readonly (string | number)[], code: string);
61
+ toJSON(): SerializedValidationIssue2;
62
+ }
63
+ /**
64
+ * A request or result did not satisfy its declared wire contract.
65
+ */
66
+ declare class QgisSdkContractValidationError2 extends QgisSdkError2 {
67
+ readonly direction: ValidationDirection2;
68
+ readonly issues: readonly QgisSdkValidationIssue2[];
69
+ constructor(direction: ValidationDirection2, issues: readonly QgisSdkValidationIssue2[]);
70
+ toJSON(): SerializedContractValidationError2;
71
+ }
72
+ /**
73
+ * Parse and validate a version 1 `PluginDescription` emitted by `describe_plugin`.
74
+ */
75
+ declare function parsePluginDescription2(input: string | Readonly<Record<string, unknown>>): PluginDescription2;
76
+ declare const BRIDGE_PROTOCOL_VERSION2: 1;
77
+ declare const MAX_CALL_ID_LENGTH2: number;
78
+ /** A failure reply, as the host serialized it. */
79
+ interface BridgeFailure2 {
80
+ readonly code: string;
81
+ readonly message: string;
82
+ }
83
+ /** A reply to one invocation: exactly one of `value` or `error` is present. */
84
+ type InvokeReply2 = {
85
+ readonly protocol: 1;
86
+ readonly call_id: string;
87
+ readonly ok: true;
88
+ readonly value: unknown;
89
+ } | {
90
+ readonly protocol: 1;
91
+ readonly call_id: string;
92
+ readonly ok: false;
93
+ readonly error: BridgeFailure2;
94
+ };
95
+ /** Why a subscription stopped. */
96
+ type StreamTerminalKind2 = "complete" | "error" | "cancelled" | "gap" | "disposed";
97
+ /** One event on a subscription. */
98
+ interface BridgeStreamEvent2 {
99
+ readonly kind: StreamEventKind2;
100
+ readonly payload: unknown;
101
+ }
102
+ /** The end of a subscription, as the host described it. */
103
+ interface BridgeStreamTerminal2 {
104
+ readonly kind: StreamTerminalKind2;
105
+ readonly message?: string;
106
+ readonly dropped?: number;
107
+ }
108
+ /** A frame carrying one event. */
109
+ interface BridgeEventFrame2 {
110
+ readonly protocol: 1;
111
+ readonly subscription_id: string;
112
+ readonly seq: number;
113
+ readonly event: BridgeStreamEvent2;
114
+ }
115
+ /** The frame that ends a subscription. */
116
+ interface BridgeTerminalFrame2 {
117
+ readonly protocol: 1;
118
+ readonly subscription_id: string;
119
+ readonly seq: number;
120
+ readonly terminal: BridgeStreamTerminal2;
121
+ }
122
+ /** Anything the host may send for a subscription. */
123
+ type BridgeStreamFrame2 = BridgeEventFrame2 | BridgeTerminalFrame2;
124
+ /** The transport a client speaks through; a QWebChannel adapter or a script. */
125
+ interface WireTransport2 {
126
+ send(message: unknown): void;
127
+ subscribe(listener: (message: unknown) => void): () => void;
128
+ }
129
+ /** Options for {@link BridgeClient}. */
130
+ interface BridgeClientOptions2 {
131
+ readonly timeoutMs?: number;
132
+ }
133
+ /** Options for one invocation. */
134
+ interface InvokeOptions2 {
135
+ readonly signal?: AbortSignal;
136
+ }
137
+ /**
138
+ * An envelope or reply does not match the versioned bridge protocol.
139
+ */
140
+ declare class QgisSdkBridgeProtocolError2 extends QgisSdkError2 {
141
+ constructor(message: string);
142
+ }
143
+ /**
144
+ * The host did not answer in time. The call may still have run, so this is an
145
+ * unknown outcome: retrying automatically is forbidden, deciding is the caller's.
146
+ */
147
+ declare class QgisSdkBridgeTimeoutError2 extends QgisSdkError2 {
148
+ readonly callId: string;
149
+ constructor(callId: string, timeoutMs: number);
150
+ }
151
+ /**
152
+ * The caller aborted locally. Distinct from the host cancelling the operation,
153
+ * which arrives as a failure reply carrying the `cancelled` code.
154
+ */
155
+ declare class QgisSdkBridgeAbortedError2 extends QgisSdkError2 {
156
+ readonly callId: string;
157
+ constructor(callId: string);
158
+ }
159
+ /**
160
+ * The client was closed, so pending calls can no longer be answered.
161
+ */
162
+ declare class QgisSdkBridgeClosedError2 extends QgisSdkError2 {
163
+ constructor();
164
+ }
165
+ /**
166
+ * A subscription's frames were not the sequence the host promised.
167
+ *
168
+ * The client does not try to reassemble: a missed frame is a fact about the
169
+ * subscription, not something to paper over.
170
+ */
171
+ declare class QgisSdkBridgeSequenceError2 extends QgisSdkError2 {
172
+ readonly subscriptionId: string;
173
+ readonly expected: number;
174
+ readonly received: number;
175
+ constructor(subscriptionId: string, expected: number, received: number);
176
+ }
177
+ /**
178
+ * The subscription ended with a terminal frame the caller may want to inspect.
179
+ */
180
+ declare class QgisSdkBridgeStreamEndedError2 extends QgisSdkError2 {
181
+ readonly subscriptionId: string;
182
+ readonly terminal: BridgeStreamTerminal2;
183
+ constructor(subscriptionId: string, terminal: BridgeStreamTerminal2);
184
+ }
185
+ /**
186
+ * The host refused or failed the operation; `failure.code` is the contract.
187
+ */
188
+ declare class QgisSdkBridgeRemoteError2 extends QgisSdkError2 {
189
+ readonly failure: BridgeFailure2;
190
+ readonly callId: string;
191
+ constructor(callId: string, failure: BridgeFailure2);
192
+ }
193
+ /**
194
+ * Parse one reply, refusing anything the host could not legitimately have sent.
195
+ */
196
+ declare function parseInvokeReply2(raw: unknown): InvokeReply2;
197
+ /**
198
+ * Parse one stream frame, refusing anything the host could not legitimately
199
+ * have sent.
200
+ *
201
+ * Exactly one of `event` and `terminal` has to be present: a frame that carries
202
+ * both leaves the client guessing which one wins, and a client that guesses is
203
+ * a client that will eventually deliver an event after the end of a stream.
204
+ */
205
+ declare function parseStreamFrame2(raw: unknown): BridgeStreamFrame2;
206
+ /** Callbacks a subscription reports through. */
207
+ interface SubscriptionHandlers2 {
208
+ readonly onEvent?: (event: BridgeStreamEvent2) => void;
209
+ readonly onTerminal?: (terminal: BridgeStreamTerminal2) => void;
210
+ }
211
+ /** A live subscription, as the caller holds it. */
212
+ interface BridgeSubscription2 {
213
+ readonly id: string;
214
+ readonly closed: boolean;
215
+ close(): void;
216
+ }
217
+ /**
218
+ * Correlate invocations with replies over an injected transport.
219
+ *
220
+ * The client never retries: a timeout, an abort and a closed client are three
221
+ * different answers, and each is reported as itself.
222
+ */
223
+ declare class BridgeClient2 {
224
+ private readonly transport;
225
+ private readonly timeoutMs;
226
+ private readonly pending;
227
+ private readonly subscriptions;
228
+ private readonly disposals;
229
+ private readonly unsubscribe;
230
+ private counter;
231
+ private closed;
232
+ private unmatched;
233
+ private breaks;
234
+ private lastProtocolError;
235
+ constructor(transport: WireTransport2, options?: BridgeClientOptions2);
236
+ /** Whether {@link close} has run; a closed client answers nothing. */
237
+ get isClosed(): boolean;
238
+ /** Replies that named no pending call: a late duplicate, or a stray message. */
239
+ get unmatchedReplies(): number;
240
+ /** Number of calls still waiting for a reply. */
241
+ get pendingCalls(): number;
242
+ /**
243
+ * Invoke one operation and settle with its value or with a typed failure.
244
+ */
245
+ invoke(operation: string, payload?: Readonly<Record<string, unknown>>, options?: InvokeOptions2): Promise<unknown>;
246
+ /**
247
+ * Open a subscription and resolve once the host has acknowledged it.
248
+ *
249
+ * Frames are matched to the subscription by id and by sequence; a frame that
250
+ * does not continue the sequence ends the subscription instead of being
251
+ * delivered out of order.
252
+ */
253
+ subscribe(operation: string, payload?: Readonly<Record<string, unknown>>, handlers?: SubscriptionHandlers2, options?: InvokeOptions2): Promise<BridgeSubscription2>;
254
+ /**
255
+ * Ask the host for the handshake: the contract this view may use.
256
+ *
257
+ * The host bound the view before its first message, so this is a call, not
258
+ * a binding. The value is what `Dispatcher.handshake` builds - protocol,
259
+ * generation, the plugin description, the host schema version and the
260
+ * operations this view is authorized for.
261
+ */
262
+ handshake(options?: InvokeOptions2): Promise<unknown>;
263
+ /** Subscriptions still open; a leak check for callers and tests. */
264
+ get activeSubscriptions(): readonly string[];
265
+ /** Frames that did not continue their subscription's sequence. */
266
+ get sequenceBreaks(): number;
267
+ private send;
268
+ /**
269
+ * Refuse further calls, reject what is still pending, and stop listening.
270
+ */
271
+ close(): void;
272
+ private settle;
273
+ private handle;
274
+ private handleFrame;
275
+ private endSubscription;
276
+ private disposeSubscription;
277
+ /** The last malformed message this client refused, if any. */
278
+ get protocolError(): Error | null;
279
+ }
280
+ export { MAX_SAFE_INTEGER2, MIN_SAFE_INTEGER2, PLUGIN_DESCRIPTION_SCHEMA_VERSION2, ValidationDirection2, SerializedValidationIssue2, SerializedContractValidationError2, ActionDescription2, StreamEventKind2, StreamDescription2, PluginDescription2, isSafeJsonInteger2, QgisSdkDeclarationError2, QgisSdkValidationIssue2, QgisSdkContractValidationError2, parsePluginDescription2, BRIDGE_PROTOCOL_VERSION2, MAX_CALL_ID_LENGTH2, BridgeFailure2, InvokeReply2, StreamTerminalKind2, BridgeStreamEvent2, BridgeStreamTerminal2, BridgeEventFrame2, BridgeTerminalFrame2, BridgeStreamFrame2, WireTransport2, BridgeClientOptions2, InvokeOptions2, QgisSdkBridgeProtocolError2, QgisSdkBridgeTimeoutError2, QgisSdkBridgeAbortedError2, QgisSdkBridgeClosedError2, QgisSdkBridgeSequenceError2, QgisSdkBridgeStreamEndedError2, QgisSdkBridgeRemoteError2, parseInvokeReply2, parseStreamFrame2, SubscriptionHandlers2, BridgeSubscription2, BridgeClient2 };
package/dist/testing.d.ts CHANGED
@@ -1,25 +1,7 @@
1
- /**
2
- * Base class for expected SDK errors across the bridge boundary.
3
- */
4
- declare class QgisSdkError extends Error {
5
- readonly code: string;
6
- constructor(message: string, code?: string);
7
- }
8
- /** The kinds of event a stream may carry; only `progress` may be coalesced. */
9
- type StreamEventKind2 = "data" | "progress" | "state";
10
- /** Why a subscription stopped. */
11
- type StreamTerminalKind = "complete" | "error" | "cancelled" | "gap" | "disposed";
12
- /** The end of a subscription, as the host described it. */
13
- interface BridgeStreamTerminal {
14
- readonly kind: StreamTerminalKind;
15
- readonly message?: string;
16
- readonly dropped?: number;
17
- }
18
- /** The transport a client speaks through; a QWebChannel adapter or a script. */
19
- interface WireTransport {
20
- send(message: unknown): void;
21
- subscribe(listener: (message: unknown) => void): () => void;
22
- }
1
+ import { StreamEventKind2, BridgeStreamTerminal2, WireTransport2 } from "./shared/chunk-wtv4vt4q.js";
2
+ import { HostOperationId2 } from "./shared/chunk-khemhx86.js";
3
+ import { OperationShape2, StreamShape2, BridgeSchema2 } from "./shared/chunk-jjx14tgd.js";
4
+ import { QgisSdkError2 } from "./shared/chunk-pdj2fxjb.js";
23
5
  /** A scripted host failure, returned from an expectation's handler. */
24
6
  interface ScriptedFault {
25
7
  readonly fault: {
@@ -43,7 +25,8 @@ type ScriptedReply<Res> = Res | ScriptedFault | typeof SILENCE | Promise<Res | S
43
25
  /** A message the client sent, as the harness recorded it. */
44
26
  interface RecordedCall {
45
27
  readonly callId: string;
46
- readonly kind: "invoke" | "subscribe" | "unsubscribe";
28
+ readonly kind: "invoke" | "subscribe" | "unsubscribe" | "handshake";
29
+ /** Empty for a handshake, which names no operation. */
47
30
  readonly operation: string;
48
31
  readonly payload?: Readonly<Record<string, unknown>>;
49
32
  readonly subscriptionId?: string;
@@ -58,7 +41,7 @@ interface ScriptedStream {
58
41
  /** `false` once the host ended it or the page closed it. */
59
42
  readonly open: boolean;
60
43
  emit(kind: StreamEventKind2, payload: unknown): void;
61
- end(terminal: BridgeStreamTerminal): void;
44
+ end(terminal: BridgeStreamTerminal2): void;
62
45
  /** Advance the sequence without sending, so the next frame is a gap. */
63
46
  skip(frames: number): void;
64
47
  }
@@ -67,15 +50,26 @@ interface SubscribeOptions {
67
50
  /** Delay the acknowledgement until this promise settles. */
68
51
  readonly ack?: () => Promise<void>;
69
52
  }
53
+ /** Options for {@link createBridgeHarness}. */
54
+ interface HarnessOptions {
55
+ /**
56
+ * The host operation ids the scripted handshake advertises; every curated
57
+ * id by default. A narrowed list is how a test proves a page respects
58
+ * `qgis.host.granted` (TASK-66 AC #5). A call outside it is still answered
59
+ * by whatever the test scripted - typically `fault("not_authorized", ...)` -
60
+ * because that refusal is the host's, not the harness's.
61
+ */
62
+ readonly granted?: readonly HostOperationId2[];
63
+ }
70
64
  /**
71
65
  * A test's script went wrong: an unmet or unexpected call, a leaked
72
66
  * subscription, a handler that threw, or an envelope that is not protocol 1.
73
67
  */
74
- declare class QgisSdkBridgeHarnessError extends QgisSdkError {
68
+ declare class QgisSdkBridgeHarnessError extends QgisSdkError2 {
75
69
  constructor(message: string);
76
70
  }
77
71
  /** The harness a test holds; also the transport its client is built on. */
78
- interface BridgeHarness<Op extends string = string> extends WireTransport {
72
+ interface BridgeHarness<Op extends string = string> extends WireTransport2 {
79
73
  /** Every message the client sent, in order. */
80
74
  readonly calls: readonly RecordedCall[];
81
75
  /** Calls no expectation covered; each was answered `unexpected_call`. */
@@ -102,8 +96,36 @@ interface BridgeHarness<Op extends string = string> extends WireTransport {
102
96
  assertSatisfied(): void;
103
97
  }
104
98
  /**
105
- * Create a harness; `Op` narrows the operations a test may expect, typically
106
- * the generated module's `OperationId`.
99
+ * The harness {@link createBridgeHarness} returns for a schema: the same
100
+ * harness, with `expectInvoke` and `expectSubscribe` typed by the schema's
101
+ * `Operations` and `Streams` - a payload is the declared request, a reply the
102
+ * declared result - and by the curated host ids, whose shapes the host module
103
+ * owns.
104
+ */
105
+ interface SchemaBridgeHarness<
106
+ O,
107
+ S
108
+ > extends Omit<BridgeHarness<Extract<keyof O | keyof S, string> | HostOperationId2>, "expectInvoke" | "expectSubscribe"> {
109
+ expectInvoke<K extends Extract<keyof O, string>>(operation: K, handler: (payload: O[K] extends OperationShape2 ? O[K]["request"] : never) => ScriptedReply<O[K] extends OperationShape2 ? O[K]["result"] : never>): void;
110
+ expectInvoke<
111
+ Req extends object = Readonly<Record<string, unknown>>,
112
+ Res = unknown
113
+ >(operation: HostOperationId2, handler: (payload: Req) => ScriptedReply<Res>): void;
114
+ expectSubscribe<K extends Extract<keyof S, string>>(operation: K, script: SubscribeScript<S[K] extends StreamShape2 ? S[K]["request"] : never>, options?: SubscribeOptions): void;
115
+ expectSubscribe<Req extends object = Readonly<Record<string, unknown>>>(operation: HostOperationId2, script: SubscribeScript<Req>, options?: SubscribeOptions): void;
116
+ }
117
+ /**
118
+ * Create a harness.
119
+ *
120
+ * Given the generated `bridge.schema.ts` default export, the harness is typed
121
+ * by it: `expectInvoke("sites.summary", (payload) => ...)` sees the declared
122
+ * request and must answer with the declared result, and the handshake it
123
+ * answers advertises the schema's operations. Without one, `Op` narrows the
124
+ * operations a test may expect, typically a generated `OperationId`.
107
125
  */
108
- declare function createBridgeHarness<Op extends string = string>(): BridgeHarness<Op>;
109
- export { BridgeHarness, QgisSdkBridgeHarnessError, RecordedCall, SILENCE, ScriptedFault, ScriptedRefusal, ScriptedReply, ScriptedStream, SubscribeOptions, SubscribeScript, createBridgeHarness, fault };
126
+ declare function createBridgeHarness<Op extends string = string>(options?: HarnessOptions): BridgeHarness<Op>;
127
+ declare function createBridgeHarness<
128
+ O,
129
+ S
130
+ >(schema: BridgeSchema2<O, S>, options?: HarnessOptions): SchemaBridgeHarness<O, S>;
131
+ export { BridgeHarness, HarnessOptions, QgisSdkBridgeHarnessError, RecordedCall, SILENCE, SchemaBridgeHarness, ScriptedFault, ScriptedRefusal, ScriptedReply, ScriptedStream, SubscribeOptions, SubscribeScript, createBridgeHarness, fault };