@tablecraft-app/plugin-redis 1.5.1 → 1.7.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/index.js CHANGED
@@ -52106,6 +52106,10 @@ var KEY_COLUMN = {
52106
52106
  name: "key",
52107
52107
  type: null
52108
52108
  };
52109
+ var FIELD_COLUMN = {
52110
+ name: "field",
52111
+ type: null
52112
+ };
52109
52113
  var MEMBER_COLUMN = {
52110
52114
  name: "member",
52111
52115
  type: null
@@ -52216,24 +52220,62 @@ async function* yieldRows(rows) {
52216
52220
  for (const row of rows) yield row;
52217
52221
  return Promise.resolve();
52218
52222
  }
52219
- /** A command's reply as an `ExecuteResult`: a scalar as `reply`, anything else as rows. */
52220
- function resultOf(reply, options = {}) {
52223
+ /**
52224
+ * A command's reply as an `ExecuteResult`: a scalar as `reply`, anything else as rows. A bulk
52225
+ * string, or the nil of a command that reads one key, is a value read (decision 119).
52226
+ */
52227
+ function resultOf(reply, options = {}, readsKey = false) {
52221
52228
  const table = tableOf(reply, options);
52222
- if (table === null) return {
52223
- columns: [],
52224
- rows: yieldRows([]),
52225
- rowsAffected: null,
52226
- reply: {
52227
- value: cellOf(reply, options),
52228
- text: replyText(reply)
52229
- }
52230
- };
52229
+ if (table === null) {
52230
+ const read = reply instanceof Uint8Array || readsKey && (reply === null || reply === void 0);
52231
+ return {
52232
+ columns: [],
52233
+ rows: yieldRows([]),
52234
+ rowsAffected: null,
52235
+ reply: {
52236
+ value: cellOf(reply, options),
52237
+ text: replyText(reply),
52238
+ ...read ? { read } : {}
52239
+ }
52240
+ };
52241
+ }
52231
52242
  return {
52232
52243
  columns: table.columns,
52233
52244
  rows: yieldRows(table.rows),
52234
52245
  rowsAffected: null
52235
52246
  };
52236
52247
  }
52248
+ function membersOf(reply) {
52249
+ return Array.isArray(reply) || reply instanceof Set ? [...reply] : null;
52250
+ }
52251
+ /**
52252
+ * A reply holding a whole key in the columns a key tab reads that type with (decision 119), so an
52253
+ * edit maps to the key the same way: list positions count from 0 as `LSET` does. Null when the
52254
+ * reply is not that shape, or the type is not one a console result edits as rows.
52255
+ */
52256
+ function wholeKeyResult(reply, type, options = {}) {
52257
+ const result = (columns, rows) => ({
52258
+ columns,
52259
+ rows: yieldRows(rows),
52260
+ rowsAffected: null
52261
+ });
52262
+ const members = membersOf(reply);
52263
+ switch (type) {
52264
+ case "hash":
52265
+ if (!(reply instanceof Map)) return null;
52266
+ return result([FIELD_COLUMN, VALUE_COLUMN], [...reply].map(([field, value]) => [cellOf(field, options), cellOf(value, options)]));
52267
+ case "set": return members === null ? null : result([MEMBER_COLUMN], members.map((member) => [cellOf(member, options)]));
52268
+ case "list": return members === null ? null : result([INDEX_COLUMN, VALUE_COLUMN], members.map((element, index) => [index, cellOf(element, options)]));
52269
+ case "zset":
52270
+ if (members === null) return null;
52271
+ if (members.length > 0 && members.every(isScoredPair)) return result([MEMBER_COLUMN, SCORE_COLUMN], members.map((element) => {
52272
+ const [member, score] = element;
52273
+ return [cellOf(member, options), score];
52274
+ }));
52275
+ return result([MEMBER_COLUMN], members.map((member) => [cellOf(member, options)]));
52276
+ default: return null;
52277
+ }
52278
+ }
52237
52279
  /** One reply per primary, each row led by the node it came from (decision 114). */
52238
52280
  function fannedOutResult(replies, options = {}) {
52239
52281
  const tables = replies.map(({ node, reply }) => ({
@@ -55096,6 +55138,33 @@ var RedisSessionMonitor = class {
55096
55138
  });
55097
55139
  }
55098
55140
  };
55141
+ var FIRST = "0";
55142
+ var LAST = "-1";
55143
+ var STREAM_START = "-";
55144
+ var STREAM_END = "+";
55145
+ /** The one key a read-only command reads, or null for writes, keyless and multi-key commands. */
55146
+ function keyRead(table, words, args) {
55147
+ if (!flagsOf(table, words).includes("readonly")) return null;
55148
+ const keys = keyArguments(table, words, args);
55149
+ return keys === "movable" || keys.length !== 1 ? null : keys[0] ?? null;
55150
+ }
55151
+ /**
55152
+ * Whether the command's reply holds the whole of a key of `type` (decision 119): `GET`, `HGETALL`,
55153
+ * `SMEMBERS`, `LRANGE k 0 -1`, `ZRANGE k 0 -1 [WITHSCORES]` and `XRANGE k - +`, with no other
55154
+ * option that narrows or reorders them.
55155
+ */
55156
+ function readsWholeKey(words, type) {
55157
+ const [name, , from, to, option] = words;
55158
+ switch (type) {
55159
+ case "string": return name === "GET" && words.length === 2;
55160
+ case "hash": return name === "HGETALL" && words.length === 2;
55161
+ case "set": return name === "SMEMBERS" && words.length === 2;
55162
+ case "list": return name === "LRANGE" && words.length === 4 && from === FIRST && to === LAST;
55163
+ case "zset": return name === "ZRANGE" && from === FIRST && to === LAST && (words.length === 4 || words.length === 5 && option === "WITHSCORES");
55164
+ case "stream": return name === "XRANGE" && words.length === 4 && from === STREAM_START && to === STREAM_END;
55165
+ default: return false;
55166
+ }
55167
+ }
55099
55168
  // ../../plugins/redis/src/session.ts
55100
55169
  var CLOSE_GRACE_MS = 3e3;
55101
55170
  var BUSY_MESSAGE = "Another command is still running in this session.";
@@ -55235,10 +55304,18 @@ var RedisSession = class RedisSession {
55235
55304
  const refusal = readOnlyRefusal(await this.commandTable(), words);
55236
55305
  if (refusal !== null) throw new PluginError("statementFailed", refusal);
55237
55306
  }
55238
- const routed = await this.#run(tokens.map((token) => token.bytes), words, options?.node);
55307
+ const args = tokens.map((token) => token.bytes);
55308
+ const routed = await this.#run(args, words, options?.node);
55239
55309
  if (words[0] === "SELECT" && !routed.fanned) this.#database = Number(words[1]);
55240
- const wire = { fullBytes: options?.fullBytes === true };
55241
- return routed.fanned ? fannedOutResult(routed.replies, wire) : resultOf(routed.reply, wire);
55310
+ if (routed.fanned) return fannedOutResult(routed.replies, { fullBytes: options?.fullBytes === true });
55311
+ const key = routed.reply === "QUEUED" ? null : keyRead(await this.commandTable(), words, args);
55312
+ const source = key === null ? null : await this.#sourceOf(key, words);
55313
+ const wire = { fullBytes: options?.fullBytes === true || key !== null };
55314
+ const result = (source?.whole === true ? wholeKeyResult(routed.reply, source.type, wire) : null) ?? resultOf(routed.reply, wire, key !== null);
55315
+ return source === null ? result : {
55316
+ ...result,
55317
+ source
55318
+ };
55242
55319
  } finally {
55243
55320
  this.#running = false;
55244
55321
  }
@@ -55352,6 +55429,21 @@ var RedisSession = class RedisSession {
55352
55429
  this.#queue = run.catch(() => void 0);
55353
55430
  return run;
55354
55431
  }
55432
+ /** The key the command read, typed by `TYPE` (decision 119); null when it is gone or `TYPE` is refused. */
55433
+ async #sourceOf(key, words) {
55434
+ try {
55435
+ const type = keyTypeOf(String(await this.#router.onKey(key, ["TYPE", key])));
55436
+ if (type === null) return null;
55437
+ return {
55438
+ database: this.#database,
55439
+ key: keyBytesOf(key),
55440
+ type,
55441
+ whole: readsWholeKey(words, type)
55442
+ };
55443
+ } catch {
55444
+ return null;
55445
+ }
55446
+ }
55355
55447
  #run(args, words, node) {
55356
55448
  return this.#failures(() => this.#router.run(args, words, node));
55357
55449
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tablecraft-app/plugin-redis",
3
- "version": "1.5.1",
3
+ "version": "1.7.0",
4
4
  "description": "The Redis datasource plugin for TableCraft, downloaded by the app when a data source needs it.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -24,6 +24,6 @@
24
24
  },
25
25
  "tableCraft": {
26
26
  "id": "redis",
27
- "apiVersion": "1.2.0"
27
+ "apiVersion": "1.3.0"
28
28
  }
29
29
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "id": "redis",
3
3
  "name": "Redis",
4
- "version": "1.5.1",
5
- "apiVersion": "1.2.0",
4
+ "version": "1.7.0",
5
+ "apiVersion": "1.3.0",
6
6
  "entry": "index.js",
7
7
  "capabilities": [
8
8
  "commands",