@fedify/redis 2.4.0-dev.2164 → 2.4.0-dev.2190

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/kv.cjs CHANGED
@@ -30,6 +30,22 @@ let node_buffer = require("node:buffer");
30
30
  function expirySeconds(ttl) {
31
31
  return Math.max(1, Math.ceil(ttl.total("second")));
32
32
  }
33
+ const casScript = `
34
+ local current = redis.call('GET', KEYS[1])
35
+ if ARGV[1] == '1' then
36
+ if current then return 0 end
37
+ elseif not current or current ~= ARGV[2] then
38
+ return 0
39
+ end
40
+ if ARGV[3] == '1' then
41
+ redis.call('DEL', KEYS[1])
42
+ elseif ARGV[5] ~= '0' then
43
+ redis.call('SETEX', KEYS[1], ARGV[5], ARGV[4])
44
+ else
45
+ redis.call('SET', KEYS[1], ARGV[4])
46
+ end
47
+ return 1
48
+ `;
33
49
  /**
34
50
  * A key–value store that uses Redis as the underlying storage.
35
51
  *
@@ -105,6 +121,20 @@ var RedisKvStore = class {
105
121
  const serializedKey = this.#serializeKey(key);
106
122
  await this.#redis.del(serializedKey);
107
123
  }
124
+ /**
125
+ * {@inheritDoc KvStore.cas}
126
+ *
127
+ * Values are compared as encoded bytes. A custom {@link Codec} must encode
128
+ * equal values identically; with {@link JsonCodec}, object property order
129
+ * affects the comparison. The `ttl` option follows {@link set}: a
130
+ * fractional second is rounded up and a non-positive duration uses one
131
+ * second.
132
+ * @since 2.4.0
133
+ */
134
+ async cas(key, expectedValue, newValue, options) {
135
+ const absent = node_buffer.Buffer.alloc(0);
136
+ return await this.#redis.eval(casScript, 1, this.#serializeKey(key), expectedValue === void 0 ? "1" : "0", expectedValue === void 0 ? absent : this.#codec.encode(expectedValue), newValue === void 0 ? "1" : "0", newValue === void 0 ? absent : this.#codec.encode(newValue), options?.ttl == null ? 0 : expirySeconds(options.ttl)) === 1;
137
+ }
108
138
  #deserializeKey(redisKey) {
109
139
  return redisKey.slice(this.#keyPrefixStr.length).split("::").map((p) => p.replaceAll("_:", ":"));
110
140
  }
package/dist/kv.d.cts CHANGED
@@ -16,6 +16,7 @@ interface RedisKvStoreOptions {
16
16
  /**
17
17
  * The codec to use for encoding and decoding values in the key–value store.
18
18
  * Defaults to {@link JsonCodec}.
19
+ * For {@link RedisKvStore.cas}, equal values must encode to identical bytes.
19
20
  */
20
21
  readonly codec?: Codec;
21
22
  }
@@ -67,6 +68,17 @@ declare class RedisKvStore implements KvStore {
67
68
  set(key: KvKey, value: unknown, options?: KvStoreSetOptions | undefined): Promise<void>;
68
69
  delete(key: KvKey): Promise<void>;
69
70
  /**
71
+ * {@inheritDoc KvStore.cas}
72
+ *
73
+ * Values are compared as encoded bytes. A custom {@link Codec} must encode
74
+ * equal values identically; with {@link JsonCodec}, object property order
75
+ * affects the comparison. The `ttl` option follows {@link set}: a
76
+ * fractional second is rounded up and a non-positive duration uses one
77
+ * second.
78
+ * @since 2.4.0
79
+ */
80
+ cas(key: KvKey, expectedValue: unknown, newValue: unknown, options?: KvStoreSetOptions): Promise<boolean>;
81
+ /**
70
82
  * {@inheritDoc KvStore.list}
71
83
  * @since 1.10.0
72
84
  */
package/dist/kv.d.ts CHANGED
@@ -16,6 +16,7 @@ interface RedisKvStoreOptions {
16
16
  /**
17
17
  * The codec to use for encoding and decoding values in the key–value store.
18
18
  * Defaults to {@link JsonCodec}.
19
+ * For {@link RedisKvStore.cas}, equal values must encode to identical bytes.
19
20
  */
20
21
  readonly codec?: Codec;
21
22
  }
@@ -67,6 +68,17 @@ declare class RedisKvStore implements KvStore {
67
68
  set(key: KvKey, value: unknown, options?: KvStoreSetOptions | undefined): Promise<void>;
68
69
  delete(key: KvKey): Promise<void>;
69
70
  /**
71
+ * {@inheritDoc KvStore.cas}
72
+ *
73
+ * Values are compared as encoded bytes. A custom {@link Codec} must encode
74
+ * equal values identically; with {@link JsonCodec}, object property order
75
+ * affects the comparison. The `ttl` option follows {@link set}: a
76
+ * fractional second is rounded up and a non-positive duration uses one
77
+ * second.
78
+ * @since 2.4.0
79
+ */
80
+ cas(key: KvKey, expectedValue: unknown, newValue: unknown, options?: KvStoreSetOptions): Promise<boolean>;
81
+ /**
70
82
  * {@inheritDoc KvStore.list}
71
83
  * @since 1.10.0
72
84
  */
package/dist/kv.js CHANGED
@@ -29,6 +29,22 @@ import { Buffer } from "node:buffer";
29
29
  function expirySeconds(ttl) {
30
30
  return Math.max(1, Math.ceil(ttl.total("second")));
31
31
  }
32
+ const casScript = `
33
+ local current = redis.call('GET', KEYS[1])
34
+ if ARGV[1] == '1' then
35
+ if current then return 0 end
36
+ elseif not current or current ~= ARGV[2] then
37
+ return 0
38
+ end
39
+ if ARGV[3] == '1' then
40
+ redis.call('DEL', KEYS[1])
41
+ elseif ARGV[5] ~= '0' then
42
+ redis.call('SETEX', KEYS[1], ARGV[5], ARGV[4])
43
+ else
44
+ redis.call('SET', KEYS[1], ARGV[4])
45
+ end
46
+ return 1
47
+ `;
32
48
  /**
33
49
  * A key–value store that uses Redis as the underlying storage.
34
50
  *
@@ -104,6 +120,20 @@ var RedisKvStore = class {
104
120
  const serializedKey = this.#serializeKey(key);
105
121
  await this.#redis.del(serializedKey);
106
122
  }
123
+ /**
124
+ * {@inheritDoc KvStore.cas}
125
+ *
126
+ * Values are compared as encoded bytes. A custom {@link Codec} must encode
127
+ * equal values identically; with {@link JsonCodec}, object property order
128
+ * affects the comparison. The `ttl` option follows {@link set}: a
129
+ * fractional second is rounded up and a non-positive duration uses one
130
+ * second.
131
+ * @since 2.4.0
132
+ */
133
+ async cas(key, expectedValue, newValue, options) {
134
+ const absent = Buffer.alloc(0);
135
+ return await this.#redis.eval(casScript, 1, this.#serializeKey(key), expectedValue === void 0 ? "1" : "0", expectedValue === void 0 ? absent : this.#codec.encode(expectedValue), newValue === void 0 ? "1" : "0", newValue === void 0 ? absent : this.#codec.encode(newValue), options?.ttl == null ? 0 : expirySeconds(options.ttl)) === 1;
136
+ }
107
137
  #deserializeKey(redisKey) {
108
138
  return redisKey.slice(this.#keyPrefixStr.length).split("::").map((p) => p.replaceAll("_:", ":"));
109
139
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/redis",
3
- "version": "2.4.0-dev.2164+db2b8571",
3
+ "version": "2.4.0-dev.2190+be3a8be0",
4
4
  "description": "Redis drivers for Fedify",
5
5
  "keywords": [
6
6
  "fedify",
@@ -79,7 +79,7 @@
79
79
  },
80
80
  "peerDependencies": {
81
81
  "ioredis": "^5.8.2",
82
- "@fedify/fedify": "^2.4.0-dev.2164+db2b8571"
82
+ "@fedify/fedify": "^2.4.0-dev.2190+be3a8be0"
83
83
  },
84
84
  "devDependencies": {
85
85
  "@std/async": "npm:@jsr/std__async@^1.0.13",
@@ -88,7 +88,7 @@
88
88
  "tsdown": "^0.22.0",
89
89
  "typescript": "^6.0.0",
90
90
  "@fedify/fixture": "^2.0.0",
91
- "@fedify/testing": "^2.4.0-dev.2164+db2b8571"
91
+ "@fedify/testing": "^2.4.0-dev.2190+be3a8be0"
92
92
  },
93
93
  "scripts": {
94
94
  "build:self": "tsdown",