@tablecraft-app/plugin-etcd 1.18.0 → 1.18.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/index.js CHANGED
@@ -41289,6 +41289,18 @@ function createEtcd(settings) {
41289
41289
  if (settings.tls !== null) useTls(etcd, settings.tls);
41290
41290
  return etcd;
41291
41291
  }
41292
+ /**
41293
+ * Stops a watch or keep-alive stream the plugin no longer needs. etcd3 takes an error on a stream
41294
+ * that is still writable for a failed host and closes every gRPC client of the connection
41295
+ * (`stream.writable && this.client.markFailed(resource, err)` in etcd3 1.1.2 lib/rpc.js, see
41296
+ * https://github.com/microsoft/etcd3/blob/v1.1.2/src/rpc.ts); a call queued behind that, such
41297
+ * as the next get over TLS, then fails with "Channel closed before call started". Half-closing
41298
+ * first turns the CANCELLED that `cancel()` raises into a local teardown.
41299
+ */
41300
+ function closeStream(stream) {
41301
+ stream.end();
41302
+ stream.cancel();
41303
+ }
41292
41304
  function errorText(cause) {
41293
41305
  return cause instanceof Error ? cause.message : String(cause);
41294
41306
  }
@@ -42155,7 +42167,7 @@ function namespaceCall(call, mapper, serializable = false) {
42155
42167
  listener(mapResponse(response, mapper));
42156
42168
  } catch (error) {
42157
42169
  for (const handler of errors) handler(error);
42158
- raw.cancel();
42170
+ closeStream(raw);
42159
42171
  }
42160
42172
  } : listener);
42161
42173
  return scoped;
@@ -42943,7 +42955,7 @@ var EtcdLeases = class {
42943
42955
  clearTimeout(timer);
42944
42956
  clearTimeout(timeout);
42945
42957
  if (reason !== null) stoppedReason = reason;
42946
- stream?.cancel();
42958
+ if (stream !== void 0) closeStream(stream);
42947
42959
  pending?.reject(new Error(reason ?? "Lease renewal stopped; the lease was not revoked."));
42948
42960
  pending = void 0;
42949
42961
  release();
@@ -42982,7 +42994,7 @@ var EtcdLeases = class {
42982
42994
  try {
42983
42995
  stream = await this.host.globalCall((etcd) => etcd.leaseClient.leaseKeepAlive());
42984
42996
  if (closed) {
42985
- stream.cancel();
42997
+ closeStream(stream);
42986
42998
  return {
42987
42999
  once,
42988
43000
  close,
@@ -43062,6 +43074,7 @@ var MAX_RETRIES = 2;
43062
43074
  var DECIMAL = /^(0|[1-9]\d*)$/;
43063
43075
  var MAX_CURSOR_LENGTH = 2048;
43064
43076
  var SIGNING_KEY_BYTES = 32;
43077
+ var COMPACTED$1 = /required revision has been compacted/i;
43065
43078
  /** Session-private authentication keeps cursor state bounded without retaining searches. */
43066
43079
  var HistoryState = class {
43067
43080
  #secret = randomBytes(SIGNING_KEY_BYTES);
@@ -43108,6 +43121,23 @@ function positionOf(cursor, key, state) {
43108
43121
  return invalid("invalid cursor. Start a new history search.");
43109
43122
  }
43110
43123
  }
43124
+ async function terminalVersion(call, key, position, deadline) {
43125
+ const read = async (revision) => {
43126
+ const kv = (await call((etcd) => etcd.kv.range({
43127
+ key,
43128
+ ...revision === void 0 ? {} : { revision }
43129
+ }, { deadline }))).kvs[0];
43130
+ return kv?.mod_revision === position.terminal ? kv : null;
43131
+ };
43132
+ const current = await read();
43133
+ if (current !== null) return current;
43134
+ try {
43135
+ return await read(position.snapshot);
43136
+ } catch (cause) {
43137
+ if (COMPACTED$1.test(errorText(cause))) return null;
43138
+ throw cause;
43139
+ }
43140
+ }
43111
43141
  /** Replay ends only at the pinned key's actual terminal event, never at a watch header. */
43112
43142
  async function keyHistory(call, request, state) {
43113
43143
  state.checkOpen();
@@ -43152,7 +43182,7 @@ async function keyHistory(call, request, state) {
43152
43182
  await call(async (etcd) => {
43153
43183
  const stream = await etcd.watchClient.watch({ deadline });
43154
43184
  if (state.signal.aborted || Date.now() >= deadline) {
43155
- stream.cancel();
43185
+ closeStream(stream);
43156
43186
  state.checkOpen();
43157
43187
  return;
43158
43188
  }
@@ -43163,7 +43193,7 @@ async function keyHistory(call, request, state) {
43163
43193
  done = true;
43164
43194
  clearTimeout(timer);
43165
43195
  state.signal.removeEventListener("abort", onClose);
43166
- stream.cancel();
43196
+ closeStream(stream);
43167
43197
  if (error === void 0) resolve();
43168
43198
  else reject(error instanceof Error ? error : new Error(errorText(error)));
43169
43199
  };
@@ -43243,9 +43273,30 @@ async function keyHistory(call, request, state) {
43243
43273
  unavailable: compactRevision,
43244
43274
  next: (BigInt(compactRevision) + 1n).toString()
43245
43275
  };
43246
- if (position.terminal !== null && BigInt(compactRevision) >= BigInt(position.terminal)) {
43247
- reachedEnd = true;
43276
+ const terminalRevision = position.terminal;
43277
+ if (terminalRevision !== null && BigInt(compactRevision) >= BigInt(terminalRevision)) {
43248
43278
  stopReason = "compacted";
43279
+ const terminal = await terminalVersion(call, key, position, deadline);
43280
+ if (terminal !== null && events.at(-1)?.modRevision !== terminalRevision) {
43281
+ const size = terminal.key.length + terminal.value.length;
43282
+ if (size > maxBytes && events.length === 0) throw new PluginError("statementFailed", "etcd history event exceeds the byte limit. Increase maxBytes or inspect the current value.");
43283
+ if (usedBytes + size > maxBytes || events.length >= count) {
43284
+ stopReason = usedBytes + size > maxBytes ? "byteLimit" : "pageLimit";
43285
+ position = {
43286
+ ...position,
43287
+ next: terminalRevision
43288
+ };
43289
+ break;
43290
+ }
43291
+ usedBytes += size;
43292
+ events.push({
43293
+ ...revisionOf(terminal),
43294
+ kind: "PUT",
43295
+ key: keyBytesOf(terminal.key),
43296
+ value: keyBytesOf(terminal.value)
43297
+ });
43298
+ }
43299
+ reachedEnd = true;
43249
43300
  break;
43250
43301
  }
43251
43302
  if (++retries > MAX_RETRIES) {
@@ -43486,7 +43537,7 @@ var EtcdWatcher = class {
43486
43537
  generation++;
43487
43538
  const old = stream;
43488
43539
  stream = void 0;
43489
- old?.cancel();
43540
+ if (old !== void 0) closeStream(old);
43490
43541
  };
43491
43542
  const markGap = (reason, toRevision) => {
43492
43543
  gap = {
@@ -43536,7 +43587,7 @@ var EtcdWatcher = class {
43536
43587
  try {
43537
43588
  const opened = await this.host.call((etcd) => etcd.watchClient.watch());
43538
43589
  if (mine !== generation || state === "stopped" || state === "gap") {
43539
- opened.cancel();
43590
+ closeStream(opened);
43540
43591
  return;
43541
43592
  }
43542
43593
  stream = opened;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablecraft-app/plugin-etcd",
3
- "version": "1.18.0",
3
+ "version": "1.18.2",
4
4
  "description": "The etcd datasource plugin for TableCraft, downloaded by the app when a data source needs it.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "etcd",
3
3
  "name": "etcd",
4
- "version": "1.18.0",
4
+ "version": "1.18.2",
5
5
  "apiVersion": "1.8.0",
6
6
  "entry": "index.js",
7
7
  "capabilities": [