@tablecraft-app/plugin-api 1.5.0 → 1.6.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/errors.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Failures the host tells apart; anything else a plugin throws is reported as `statementFailed`. */
2
- export declare const PLUGIN_ERROR_CODES: readonly ["cancelled", "connectionFailed", "statementFailed", "introspectionFailed", "definitionNotFound", "explainUnsupported", "connectionLost", "sessionBusy", "conflict"];
2
+ export declare const PLUGIN_ERROR_CODES: readonly ["cancelled", "connectionFailed", "statementFailed", "introspectionFailed", "definitionNotFound", "explainUnsupported", "connectionLost", "sessionBusy", "conflict", "timedOut"];
3
3
  export type PluginErrorCode = (typeof PLUGIN_ERROR_CODES)[number];
4
4
  /**
5
5
  * Where the database located a failure, relative to the statement text the plugin received:
package/dist/errors.js CHANGED
@@ -23,6 +23,11 @@ export const PLUGIN_ERROR_CODES = [
23
23
  * nothing (decision 115); the host offers to reload or to overwrite.
24
24
  */
25
25
  "conflict",
26
+ /**
27
+ * The server stopped the statement at the limit `ExecuteOptions.serverTimeoutMs` set (decision
28
+ * 129); the message names the server's setting.
29
+ */
30
+ "timedOut",
26
31
  ];
27
32
  /** What a plugin throws when the host should react to the kind of failure, not just show it. */
28
33
  export class PluginError extends Error {
package/dist/explain.d.ts CHANGED
@@ -44,6 +44,8 @@ export interface ExplainedPlan {
44
44
  export interface ExplainOptions {
45
45
  /** Run the statement and report what it did (Explain Analyse), not only how it would run. */
46
46
  readonly analyze: boolean;
47
+ /** `ExecuteOptions.serverTimeoutMs` for the statement `explain` runs (decision 129). */
48
+ readonly serverTimeoutMs?: number;
47
49
  }
48
50
  /**
49
51
  * Whether a statement can be explained, decided without a database. `writes` says the statement
package/dist/index.d.ts CHANGED
@@ -5,9 +5,9 @@ export { ConflictError, DefinitionNotFoundError, type ErrorPosition, errorPositi
5
5
  export { type ExplainedPlan, type Explainability, type ExplainOptions, type PlanNode, type PlanProperty, } from "./explain.js";
6
6
  export { KEY_TYPES, type KeyApplied, type KeyApplyOptions, type KeyBytes, type KeyChange, type KeyContents, type KeyDatabase, type KeyDetails, type KeyExpiry, type KeyInfo, type KeyName, type KeyReadRequest, type KeyScan, type KeyScanRequest, type KeySource, type Keyspace, type KeyType, } from "./keyspace.js";
7
7
  export { OBJECT_KINDS, type CatalogObject, type CheckDesign, type ColumnDesign, type DdlContext, type DdlScript, type ForeignKeyDesign, type IndexDesign, type ObjectChange, type ObjectChangeKind, type ObjectEditor, type ObjectFeatures, type ObjectKind, type TableDesign, } from "./objects.js";
8
- export { CONNECTION_FIELD_GROUPS, CONNECTION_FIELD_KINDS, CONNECTION_URL_FORMS, CONNECTION_URL_PARTS, connectionFieldSchema, connectionUrlSchema, MANIFEST_FILE, networkSpecSchema, pluginManifestSchema, TLS_FIELD_ROLES, tlsSpecSchema, type ConnectionField, type ConnectionFieldGroup, type ConnectionFieldKind, type ConnectionUrl, type ConnectionUrlForm, type ConnectionUrlPart, type NetworkSpec, type PluginManifest, type TlsFieldRole, type TlsSpec, } from "./manifest.js";
8
+ export { BRAND_ID, brandSchema, CONNECTION_FIELD_GROUPS, CONNECTION_FIELD_KINDS, CONNECTION_URL_FORMS, CONNECTION_URL_PARTS, connectionFieldSchema, connectionUrlSchema, MANIFEST_FILE, networkSpecSchema, pluginManifestSchema, TLS_FIELD_ROLES, tlsSpecSchema, type Brand, type ConnectionField, type ConnectionFieldGroup, type ConnectionFieldKind, type ConnectionUrl, type ConnectionUrlForm, type ConnectionUrlPart, type NetworkSpec, type PluginManifest, type TlsFieldRole, type TlsSpec, } from "./manifest.js";
9
9
  export { ConnectionUrlError, formatConnectionUrl, parseConnectionUrl, type ConnectionForm, type ConnectionUrlSpec, type ConnectionValue, type ParsedConnectionUrl, } from "./url.js";
10
- export { type BoundStatement, CAPABILITIES, type Capability, type Column, type ColumnOrigin, type CommandLanguage, type CommandReply, type ConnectOptions, type DatasourcePlugin, type ExecuteOptions, type ExecuteResult, type FormatOptions, FORMAT_STYLES, type FormatStyle, GENERATED_STATEMENTS, type GeneratedStatement, IMPORT_KINDS, type ImportKind, type InferredColumn, type InsertRow, type InsertTarget, KEYWORD_CASES, type NamespaceSwitch, NODE_ALL_PRIMARIES, NODE_AUTO, NODE_ROLES, type NodeRole, type ServerInfo, type ServerInfoSection, type ServerNode, type SlowCommand, switchableTo, type NewColumn, type KeywordCase, PARAMETER_STYLES, type ParameterStyle, type ParameterValue, type RoutineArgument, type RoutineCall, type Row, type SavepointStatements, type SelectSource, omittedMessages, SERVER_MESSAGE_LEVELS, SERVER_MESSAGE_LIMIT, type ServerMessage, type ServerMessageLevel, type SqlParameter, type Session, type SqlLanguage, STATEMENT_KINDS, type StatementClass, type StatementKind, type StatementSpan, type StatementStream, type StreamedStatement, type TlsInfo, TRANSACTION_MODES, type TransactionMode, type Transactions, } from "./plugin.js";
10
+ export { type BoundStatement, CAPABILITIES, type Capability, type Column, type ColumnOrigin, type CommandLanguage, type CommandReply, type ConnectOptions, type DatasourcePlugin, type ExecuteOptions, type ExecuteResult, type FormatOptions, FORMAT_STYLES, type FormatStyle, GENERATED_STATEMENTS, type GeneratedStatement, IMPORT_KINDS, type ImportKind, type InferredColumn, type InsertRow, type InsertTarget, KEYWORD_CASES, type NamespaceSwitch, NODE_ALL_PRIMARIES, NODE_AUTO, NODE_ROLES, type NodeRole, type ServerInfo, type ServerInfoSection, type ServerNode, type ServerTimeout, type SlowCommand, switchableTo, type NewColumn, type KeywordCase, PARAMETER_STYLES, type ParameterStyle, type ParameterValue, type RoutineArgument, type RoutineCall, type Row, type SavepointStatements, type SelectSource, omittedMessages, SERVER_MESSAGE_LEVELS, SERVER_MESSAGE_LIMIT, type ServerMessage, type ServerMessageLevel, type SqlParameter, type Session, type SqlLanguage, STATEMENT_KINDS, type StatementClass, type StatementKind, type StatementSpan, type StatementStream, type StreamedStatement, type TlsInfo, TRANSACTION_MODES, type TransactionMode, type Transactions, } from "./plugin.js";
11
11
  export { SERVER_SESSION_STATES, type ServerSession, type ServerSessionState, type SessionListing, type SessionMonitor, type SessionTarget, } from "./sessions.js";
12
12
  export { TLS_CHAIN_CODES, TLS_HOST_CODE, homeExpanded, tlsFailureMessage, tlsFileMessage, tlsKeyIsEncrypted, tlsKeyMessage, type TlsVerification, } from "./tls.js";
13
13
  export { BYTES_PREVIEW_LENGTH, toBytesValue, toWireInteger, toWireValue, type WireValueOptions, type BigIntValue, type BytesValue, type DateValue, type JsonValue, type TypeClass, type WireValue, } from "./values.js";
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ export { ConflictError, DefinitionNotFoundError, errorPositionOf, isPluginError,
5
5
  export {} from "./explain.js";
6
6
  export { KEY_TYPES, } from "./keyspace.js";
7
7
  export { OBJECT_KINDS, } from "./objects.js";
8
- export { CONNECTION_FIELD_GROUPS, CONNECTION_FIELD_KINDS, CONNECTION_URL_FORMS, CONNECTION_URL_PARTS, connectionFieldSchema, connectionUrlSchema, MANIFEST_FILE, networkSpecSchema, pluginManifestSchema, TLS_FIELD_ROLES, tlsSpecSchema, } from "./manifest.js";
8
+ export { BRAND_ID, brandSchema, CONNECTION_FIELD_GROUPS, CONNECTION_FIELD_KINDS, CONNECTION_URL_FORMS, CONNECTION_URL_PARTS, connectionFieldSchema, connectionUrlSchema, MANIFEST_FILE, networkSpecSchema, pluginManifestSchema, TLS_FIELD_ROLES, tlsSpecSchema, } from "./manifest.js";
9
9
  export { ConnectionUrlError, formatConnectionUrl, parseConnectionUrl, } from "./url.js";
10
10
  export { CAPABILITIES, FORMAT_STYLES, GENERATED_STATEMENTS, IMPORT_KINDS, KEYWORD_CASES, NODE_ALL_PRIMARIES, NODE_AUTO, NODE_ROLES, switchableTo, PARAMETER_STYLES, omittedMessages, SERVER_MESSAGE_LEVELS, SERVER_MESSAGE_LIMIT, STATEMENT_KINDS, TRANSACTION_MODES, } from "./plugin.js";
11
11
  export { SERVER_SESSION_STATES, } from "./sessions.js";
@@ -106,6 +106,14 @@ export type TlsSpec = z.infer<typeof tlsSpecSchema>;
106
106
  /** The roles of `TlsSpec` that name a field, in the order the dialog draws them. */
107
107
  export declare const TLS_FIELD_ROLES: readonly ["mode", "caFile", "certFile", "keyFile", "keyPassphrase"];
108
108
  export type TlsFieldRole = (typeof TLS_FIELD_ROLES)[number];
109
+ /** A simple-icons slug, the one public catalogue of database product ids (decision 128). */
110
+ export declare const BRAND_ID: RegExp;
111
+ /** A database product a plugin speaks; `id` is a simple-icons slug, `name` what a row shows. */
112
+ export declare const brandSchema: z.ZodObject<{
113
+ id: z.ZodString;
114
+ name: z.ZodString;
115
+ }, z.core.$strict>;
116
+ export type Brand = z.infer<typeof brandSchema>;
109
117
  export declare const pluginManifestSchema: z.ZodObject<{
110
118
  id: z.ZodString;
111
119
  name: z.ZodString;
@@ -131,8 +139,13 @@ export declare const pluginManifestSchema: z.ZodObject<{
131
139
  keyValue: "keyValue";
132
140
  nodes: "nodes";
133
141
  serverInfo: "serverInfo";
142
+ serverTimeout: "serverTimeout";
134
143
  }>>;
135
144
  highlight: z.ZodString;
145
+ brands: z.ZodOptional<z.ZodArray<z.ZodObject<{
146
+ id: z.ZodString;
147
+ name: z.ZodString;
148
+ }, z.core.$strict>>>;
136
149
  connection: z.ZodObject<{
137
150
  fields: z.ZodArray<z.ZodObject<{
138
151
  key: z.ZodString;
package/dist/manifest.js CHANGED
@@ -157,6 +157,15 @@ function urlSetsDeclared(connection) {
157
157
  return Object.entries(url.sets).every(([scheme, values]) => url.schemes.includes(scheme) &&
158
158
  Object.keys(values).every((key) => connection.fields.some((field) => field.key === key)));
159
159
  }
160
+ /** A simple-icons slug, the one public catalogue of database product ids (decision 128). */
161
+ export const BRAND_ID = /^[a-z0-9_]+$/;
162
+ /** A database product a plugin speaks; `id` is a simple-icons slug, `name` what a row shows. */
163
+ export const brandSchema = z.strictObject({
164
+ id: z
165
+ .string()
166
+ .regex(BRAND_ID, { message: "expected a simple-icons slug: lower-case letters, digits and underscores" }),
167
+ name: z.string().min(1),
168
+ });
160
169
  function includesAny(capabilities, ...wanted) {
161
170
  return wanted.some((capability) => capabilities.includes(capability));
162
171
  }
@@ -171,6 +180,14 @@ export const pluginManifestSchema = z
171
180
  capabilities: z.array(z.enum(CAPABILITIES)),
172
181
  /** The highlighter the console uses for this plugin's dialect (decision 008). */
173
182
  highlight: z.string().min(1),
183
+ /** The database products the plugin speaks, its default first (decision 128). Absent: rows name the plugin. */
184
+ brands: z
185
+ .array(brandSchema)
186
+ .min(1)
187
+ .refine((brands) => new Set(brands.map((brand) => brand.id)).size === brands.length, {
188
+ message: "brand ids must be unique",
189
+ })
190
+ .optional(),
174
191
  /** The connection form, readable without running plugin code (decision 019). */
175
192
  connection: z
176
193
  .strictObject({
package/dist/plugin.d.ts CHANGED
@@ -11,7 +11,7 @@ import type { TypeClass, WireValue } from "./values.js";
11
11
  * What a plugin can do. The UI branches on these, never on a plugin id (decision 007), and a
12
12
  * capability without contract-suite coverage cannot be declared (decision 013).
13
13
  */
14
- export declare const CAPABILITIES: readonly ["sql", "cancel", "schemaTree", "transactions", "ddl", "explain", "explainAnalyze", "currentNamespace", "keepAlive", "bulkInsert", "objects", "serverMessages", "sessions", "routineCall", "commands", "keyValue", "nodes", "serverInfo"];
14
+ export declare const CAPABILITIES: readonly ["sql", "cancel", "schemaTree", "transactions", "ddl", "explain", "explainAnalyze", "currentNamespace", "keepAlive", "bulkInsert", "objects", "serverMessages", "sessions", "routineCall", "commands", "keyValue", "nodes", "serverInfo", "serverTimeout"];
15
15
  export type Capability = (typeof CAPABILITIES)[number];
16
16
  /** Statements `SqlLanguage.generate` writes from a catalog relation (plan 010). */
17
17
  export declare const GENERATED_STATEMENTS: readonly ["drop", "select", "insert", "update"];
@@ -128,6 +128,25 @@ export interface ExecuteOptions {
128
128
  * many times and answers with the last run; any other plugin never receives it.
129
129
  */
130
130
  readonly repeat?: number;
131
+ /**
132
+ * Have the server itself stop the statement after this many milliseconds, at least 1 (decision
133
+ * 129), through its own setting, never above a limit the server already had. The host sends it
134
+ * only to a session whose `serverTimeout` is not null. Absent: the plugin puts back the
135
+ * server's own value. A statement the server stops rejects with `timedOut`.
136
+ */
137
+ readonly serverTimeoutMs?: number;
138
+ }
139
+ /** What the connected server stops by itself once `ExecuteOptions.serverTimeoutMs` passes (decision 129). */
140
+ export interface ServerTimeout {
141
+ /** The server's own name for it, for messages: `max_execution_time`, `statement_timeout`. */
142
+ readonly setting: string;
143
+ /** `reads`: only `SELECT` (and `SELECT … FOR UPDATE`); `all`: every statement. */
144
+ readonly covers: "reads" | "all";
145
+ /**
146
+ * `statement`: from the start until the last row is sent, so a result read slowly is cut too;
147
+ * `serverWork`: only while the server works on it.
148
+ */
149
+ readonly counts: "statement" | "serverWork";
131
150
  }
132
151
  /** `ExecuteOptions.node`: the plugin routes the command by its keys (decision 114). */
133
152
  export declare const NODE_AUTO = "auto";
@@ -275,6 +294,8 @@ export interface Session {
275
294
  close(): Promise<void>;
276
295
  /** What the server reports about itself, such as `17.4`; null when it reports nothing. */
277
296
  readonly serverVersion: string | null;
297
+ /** The manifest `brands` id of the product this session reached; absent when the plugin declares no brands or cannot tell. */
298
+ readonly serverBrand?: string;
278
299
  /**
279
300
  * True when another session on the same configuration sees a different database, as with
280
301
  * SQLite's `:memory:`; the engine then never re-runs this session's statements elsewhere.
@@ -285,6 +306,12 @@ export interface Session {
285
306
  * (decision 072).
286
307
  */
287
308
  readonly tls?: TlsInfo | null;
309
+ /**
310
+ * Present exactly when the plugin declares `serverTimeout` (decision 129): what this server stops
311
+ * by itself under `ExecuteOptions.serverTimeoutMs`, read once at connect; null when this server
312
+ * version has no such setting.
313
+ */
314
+ readonly serverTimeout?: ServerTimeout | null;
288
315
  }
289
316
  /** What a TLS handshake agreed, as Node's socket names it: `TLSv1.3`, `TLS_AES_256_GCM_SHA384`. */
290
317
  export interface TlsInfo {
package/dist/plugin.js CHANGED
@@ -21,6 +21,7 @@ export const CAPABILITIES = [
21
21
  "keyValue",
22
22
  "nodes",
23
23
  "serverInfo",
24
+ "serverTimeout",
24
25
  ];
25
26
  /** Statements `SqlLanguage.generate` writes from a catalog relation (plan 010). */
26
27
  export const GENERATED_STATEMENTS = ["drop", "select", "insert", "update"];
@@ -9,6 +9,18 @@ export interface ContractFixture {
9
9
  readonly statements?: {
10
10
  /** A statement that runs for `seconds`; required when the plugin declares `cancel`. */
11
11
  readonly sleep?: (seconds: number) => string;
12
+ /**
13
+ * A read that runs at least `seconds` on an idle server and then ends by itself, which the
14
+ * server's own limit stops with an error; required when a session reports a `serverTimeout`
15
+ * (decision 129). Not `sleep`, which MySQL answers with 1 instead of an error when the limit
16
+ * cuts it.
17
+ */
18
+ readonly slowRead?: (seconds: number) => string;
19
+ /**
20
+ * Sets the session's own statement limit to `ms`, as a DBA's per-user default would; required
21
+ * with `slowRead`.
22
+ */
23
+ readonly sessionTimeout?: (ms: number) => string;
12
24
  /**
13
25
  * A write that succeeds on a writable session and leaves the fixture unchanged, such as an
14
26
  * update setting a column to itself; the read-only case is skipped without one.
@@ -179,6 +179,16 @@ const SLEEP_SECONDS = 30;
179
179
  const CANCEL_DELAY_MS = 500;
180
180
  /** Well under `SLEEP_SECONDS`, so a cancel that did nothing fails the test instead of passing late. */
181
181
  const CANCEL_DEADLINE_MS = 10_000;
182
+ /** The limit the `serverTimeout` cases ask for; servers check theirs about every 100 ms. */
183
+ const SERVER_LIMIT_MS = 1500;
184
+ /** Far past `SERVER_LIMIT_MS`, so a limit that did nothing fails the test instead of passing late. */
185
+ const SLOW_READ_SECONDS = 8;
186
+ /** Long enough to show a read ran past `SERVER_LIMIT_MS` with no limit, short enough to wait for. */
187
+ const SHORT_READ_SECONDS = 3;
188
+ /** A limit the slow read never reaches, for the cases where something else must stop it first. */
189
+ const UNREACHED_LIMIT_MS = 2 * SLOW_READ_SECONDS * 1000;
190
+ /** When a stopped statement must have rejected: the limit plus the server's slack. */
191
+ const SERVER_LIMIT_DEADLINE_MS = 5_000;
182
192
  const KEEPALIVE_DELAY_MS = 1000;
183
193
  /** How long a listing may take to show what another connection just did. */
184
194
  const LISTING_DEADLINE_MS = 10_000;
@@ -360,6 +370,15 @@ export function runPluginContract(plugin, fixture) {
360
370
  return Promise.resolve();
361
371
  });
362
372
  });
373
+ const brands = fixture.manifest.brands;
374
+ it.runIf(brands !== undefined)("givenBrands_whenSessionOpens_thenServerBrandIsDeclared", async () => {
375
+ await withSession(plugin, fixture, (session) => {
376
+ if (session.serverBrand !== undefined) {
377
+ expect(brands?.map((brand) => brand.id)).toContain(session.serverBrand);
378
+ }
379
+ return Promise.resolve();
380
+ });
381
+ });
363
382
  it("givenSession_whenConnected_thenTlsIsNullOrDescribed", async () => {
364
383
  await withSession(plugin, fixture, (session) => {
365
384
  if (session.tls !== undefined && session.tls !== null) {
@@ -2143,6 +2162,76 @@ export function runPluginContract(plugin, fixture) {
2143
2162
  });
2144
2163
  });
2145
2164
  });
2165
+ describe.runIf(plugin.capabilities.includes("serverTimeout"))("serverTimeout", () => {
2166
+ function slowRead(seconds) {
2167
+ const read = fixture.statements?.slowRead;
2168
+ if (read === undefined)
2169
+ throw new Error("A server with a serverTimeout needs fixture.statements.slowRead.");
2170
+ return read(seconds);
2171
+ }
2172
+ /** Runs `sql` to its last row, so a limit that strikes while rows stream counts too. */
2173
+ async function runToEnd(session, sql, serverTimeoutMs) {
2174
+ const options = serverTimeoutMs === undefined ? undefined : { serverTimeoutMs };
2175
+ await collect((await session.execute(sql, undefined, options)).rows);
2176
+ }
2177
+ /** Runs `use` on a session whose server enforces a limit; a server without one has nothing to check. */
2178
+ async function withLimitedSession(use) {
2179
+ await withSession(plugin, fixture, async (session) => {
2180
+ if (session.serverTimeout === null || session.serverTimeout === undefined)
2181
+ return;
2182
+ await use(session);
2183
+ });
2184
+ }
2185
+ it("givenSession_whenInspected_thenReportsServerTimeout", async () => {
2186
+ await withSession(plugin, fixture, (session) => {
2187
+ expect(session.serverTimeout).not.toBeUndefined();
2188
+ if (session.serverTimeout !== null && session.serverTimeout !== undefined) {
2189
+ expect(session.serverTimeout.setting).toMatch(/\S/);
2190
+ }
2191
+ return Promise.resolve();
2192
+ });
2193
+ });
2194
+ it("givenServerTimeout_whenSlowReadPastLimit_thenRejectsTimedOut", async () => {
2195
+ await withLimitedSession(async (session) => {
2196
+ const started = Date.now();
2197
+ const code = await rejectionCode(runToEnd(session, slowRead(SLOW_READ_SECONDS), SERVER_LIMIT_MS));
2198
+ expect(code).toBe("timedOut");
2199
+ expect(Date.now() - started).toBeLessThan(SERVER_LIMIT_DEADLINE_MS);
2200
+ await expectAlive(session);
2201
+ });
2202
+ });
2203
+ it("givenServerTimeoutThenNone_whenSlowRead_thenRunsPastIt", async () => {
2204
+ await withLimitedSession(async (session) => {
2205
+ await runToEnd(session, selectOne, SERVER_LIMIT_MS);
2206
+ const started = Date.now();
2207
+ await runToEnd(session, slowRead(SHORT_READ_SECONDS));
2208
+ expect(Date.now() - started).toBeGreaterThan(SERVER_LIMIT_MS);
2209
+ });
2210
+ });
2211
+ it("givenOwnLowerLimit_whenHigherAsked_thenOwnStops", async () => {
2212
+ await withLimitedSession(async (session) => {
2213
+ const own = fixture.statements?.sessionTimeout;
2214
+ if (own === undefined)
2215
+ throw new Error("A server with a serverTimeout needs fixture.statements.sessionTimeout.");
2216
+ await runToEnd(session, own(SERVER_LIMIT_MS));
2217
+ const started = Date.now();
2218
+ const code = await rejectionCode(runToEnd(session, slowRead(SLOW_READ_SECONDS), UNREACHED_LIMIT_MS));
2219
+ expect(code).toBe("timedOut");
2220
+ expect(Date.now() - started).toBeLessThan(SERVER_LIMIT_DEADLINE_MS);
2221
+ });
2222
+ });
2223
+ it("givenServerTimeout_whenCancelled_thenCancelledNotTimedOut", async () => {
2224
+ await withLimitedSession(async (session) => {
2225
+ const started = Date.now();
2226
+ const running = rejectionCode(runToEnd(session, slowRead(SLOW_READ_SECONDS), UNREACHED_LIMIT_MS));
2227
+ await delay(CANCEL_DELAY_MS);
2228
+ await session.cancel?.();
2229
+ expect(await running).toBe("cancelled");
2230
+ expect(Date.now() - started).toBeLessThan(CANCEL_DEADLINE_MS);
2231
+ await expectAlive(session);
2232
+ });
2233
+ });
2234
+ });
2146
2235
  describe.runIf(plugin.capabilities.includes("keepAlive"))("keepAlive", () => {
2147
2236
  it("givenKeepAlive_whenConnectedWithDelay_thenPingResolvesAndSessionWorks", async () => {
2148
2237
  await withSession(plugin, fixture, async (session) => {
package/dist/version.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * The plugin contract's version, by semver (decision 106): a host loads a plugin built for the same
3
3
  * major and an older or equal minor. Typed `string` so a bump does not change the API report.
4
4
  */
5
- export const API_VERSION = "1.5.0";
5
+ export const API_VERSION = "1.6.0";
6
6
  const SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
7
7
  /** `major.minor.patch` and nothing else; pre-release and build tags are not accepted. */
8
8
  export function parseVersion(text) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablecraft-app/plugin-api",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "The datasource plugin contract for TableCraft",
5
5
  "license": "MIT",
6
6
  "type": "module",