@xlt-token/store-contract 2.1.1 → 2.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.
package/dist/index.cjs CHANGED
@@ -10,6 +10,24 @@ function defineStoreContract(name, createStore) {
10
10
  (0, vitest.expect)((await Promise.all(Array.from({ length: 20 }, (_, index) => store.setIfAbsent("{contract}:lock", String(index), (0, _xlt_token_core.finiteTtl)(30))))).filter(Boolean)).toHaveLength(1);
11
11
  await (0, vitest.expect)(store.get("{contract}:lock")).resolves.toMatchObject({ value: vitest.expect.any(String) });
12
12
  });
13
+ (0, vitest.it)("returns and deletes the entry in one atomic operation", async () => {
14
+ const store = await createStore();
15
+ await store.set("{contract}:consume", "v1", (0, _xlt_token_core.finiteTtl)(60));
16
+ await (0, vitest.expect)(store.getAndDelete("{contract}:consume")).resolves.toMatchObject({ value: "v1" });
17
+ await (0, vitest.expect)(store.getAndDelete("{contract}:consume")).resolves.toBeNull();
18
+ await (0, vitest.expect)(store.get("{contract}:consume")).resolves.toBeNull();
19
+ });
20
+ (0, vitest.it)("returns null when getAndDelete misses", async () => {
21
+ await (0, vitest.expect)((await createStore()).getAndDelete("{contract}:consume-missing")).resolves.toBeNull();
22
+ });
23
+ (0, vitest.it)("keeps one winner for concurrent getAndDelete", async () => {
24
+ const store = await createStore();
25
+ await store.set("{contract}:consume-race", "v1", (0, _xlt_token_core.persistentTtl)());
26
+ const winners = (await Promise.all(Array.from({ length: 20 }, () => store.getAndDelete("{contract}:consume-race")))).filter((entry) => entry !== null);
27
+ (0, vitest.expect)(winners).toHaveLength(1);
28
+ (0, vitest.expect)(winners[0]).toMatchObject({ value: "v1" });
29
+ await (0, vitest.expect)(store.get("{contract}:consume-race")).resolves.toBeNull();
30
+ });
13
31
  (0, vitest.it)("keeps value and ttl unchanged when compareAndSet misses", async () => {
14
32
  const store = await createStore();
15
33
  await store.set("{contract}:cas", "v1", (0, _xlt_token_core.finiteTtl)(60));
package/dist/index.mjs CHANGED
@@ -9,6 +9,24 @@ function defineStoreContract(name, createStore) {
9
9
  expect((await Promise.all(Array.from({ length: 20 }, (_, index) => store.setIfAbsent("{contract}:lock", String(index), finiteTtl(30))))).filter(Boolean)).toHaveLength(1);
10
10
  await expect(store.get("{contract}:lock")).resolves.toMatchObject({ value: expect.any(String) });
11
11
  });
12
+ it("returns and deletes the entry in one atomic operation", async () => {
13
+ const store = await createStore();
14
+ await store.set("{contract}:consume", "v1", finiteTtl(60));
15
+ await expect(store.getAndDelete("{contract}:consume")).resolves.toMatchObject({ value: "v1" });
16
+ await expect(store.getAndDelete("{contract}:consume")).resolves.toBeNull();
17
+ await expect(store.get("{contract}:consume")).resolves.toBeNull();
18
+ });
19
+ it("returns null when getAndDelete misses", async () => {
20
+ await expect((await createStore()).getAndDelete("{contract}:consume-missing")).resolves.toBeNull();
21
+ });
22
+ it("keeps one winner for concurrent getAndDelete", async () => {
23
+ const store = await createStore();
24
+ await store.set("{contract}:consume-race", "v1", persistentTtl());
25
+ const winners = (await Promise.all(Array.from({ length: 20 }, () => store.getAndDelete("{contract}:consume-race")))).filter((entry) => entry !== null);
26
+ expect(winners).toHaveLength(1);
27
+ expect(winners[0]).toMatchObject({ value: "v1" });
28
+ await expect(store.get("{contract}:consume-race")).resolves.toBeNull();
29
+ });
12
30
  it("keeps value and ttl unchanged when compareAndSet misses", async () => {
13
31
  const store = await createStore();
14
32
  await store.set("{contract}:cas", "v1", finiteTtl(60));
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/store-contract.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { finiteTtl, keepTtl, persistentTtl, type XltTokenStore } from \"@xlt-token/core\";\n\nexport function defineStoreContract(\n name: string,\n createStore: () => Promise<XltTokenStore> | XltTokenStore,\n): void {\n describe(name, () => {\n it(\"keeps one winner for concurrent setIfAbsent\", async () => {\n const store = await createStore();\n\n const results = await Promise.all(\n Array.from({ length: 20 }, (_, index) =>\n store.setIfAbsent(\"{contract}:lock\", String(index), finiteTtl(30)),\n ),\n );\n\n expect(results.filter(Boolean)).toHaveLength(1);\n await expect(store.get(\"{contract}:lock\")).resolves.toMatchObject({\n value: expect.any(String),\n });\n });\n\n it(\"keeps value and ttl unchanged when compareAndSet misses\", async () => {\n const store = await createStore();\n\n await store.set(\"{contract}:cas\", \"v1\", finiteTtl(60));\n\n await expect(store.compareAndSet(\"{contract}:cas\", \"wrong\", \"v2\", keepTtl())).resolves.toBe(\n false,\n );\n await expect(store.get(\"{contract}:cas\")).resolves.toMatchObject({ value: \"v1\" });\n });\n\n it(\"updates and deletes only when the current value matches\", async () => {\n const store = await createStore();\n\n await store.set(\"{contract}:mutable\", \"v1\", persistentTtl());\n\n await expect(store.compareAndSet(\"{contract}:mutable\", \"v1\", \"v2\", keepTtl())).resolves.toBe(\n true,\n );\n await expect(store.get(\"{contract}:mutable\")).resolves.toMatchObject({ value: \"v2\" });\n\n await expect(store.compareAndDelete(\"{contract}:mutable\", \"wrong\")).resolves.toBe(false);\n await expect(store.compareAndDelete(\"{contract}:mutable\", \"v2\")).resolves.toBe(true);\n await expect(store.get(\"{contract}:mutable\")).resolves.toBeNull();\n });\n\n it(\"touches ttl without changing value\", async () => {\n const store = await createStore();\n\n await store.set(\"{contract}:touch\", \"v1\", finiteTtl(30));\n\n await expect(store.touch(\"{contract}:touch\", persistentTtl())).resolves.toBe(true);\n await expect(store.get(\"{contract}:touch\")).resolves.toMatchObject({\n value: \"v1\",\n expiresAt: null,\n });\n });\n });\n}\n"],"mappings":";;;;AAGA,SAAgB,oBACd,MACA,aACM;AACN,UAAS,YAAY;AACnB,KAAG,+CAA+C,YAAY;GAC5D,MAAM,QAAQ,MAAM,aAAa;AAQjC,WANgB,MAAM,QAAQ,IAC5B,MAAM,KAAK,EAAE,QAAQ,IAAI,GAAG,GAAG,UAC7B,MAAM,YAAY,mBAAmB,OAAO,MAAM,EAAE,UAAU,GAAG,CAAC,CACnE,CACF,EAEc,OAAO,QAAQ,CAAC,CAAC,aAAa,EAAE;AAC/C,SAAM,OAAO,MAAM,IAAI,kBAAkB,CAAC,CAAC,SAAS,cAAc,EAChE,OAAO,OAAO,IAAI,OAAO,EAC1B,CAAC;IACF;AAEF,KAAG,2DAA2D,YAAY;GACxE,MAAM,QAAQ,MAAM,aAAa;AAEjC,SAAM,MAAM,IAAI,kBAAkB,MAAM,UAAU,GAAG,CAAC;AAEtD,SAAM,OAAO,MAAM,cAAc,kBAAkB,SAAS,MAAM,SAAS,CAAC,CAAC,CAAC,SAAS,KACrF,MACD;AACD,SAAM,OAAO,MAAM,IAAI,iBAAiB,CAAC,CAAC,SAAS,cAAc,EAAE,OAAO,MAAM,CAAC;IACjF;AAEF,KAAG,2DAA2D,YAAY;GACxE,MAAM,QAAQ,MAAM,aAAa;AAEjC,SAAM,MAAM,IAAI,sBAAsB,MAAM,eAAe,CAAC;AAE5D,SAAM,OAAO,MAAM,cAAc,sBAAsB,MAAM,MAAM,SAAS,CAAC,CAAC,CAAC,SAAS,KACtF,KACD;AACD,SAAM,OAAO,MAAM,IAAI,qBAAqB,CAAC,CAAC,SAAS,cAAc,EAAE,OAAO,MAAM,CAAC;AAErF,SAAM,OAAO,MAAM,iBAAiB,sBAAsB,QAAQ,CAAC,CAAC,SAAS,KAAK,MAAM;AACxF,SAAM,OAAO,MAAM,iBAAiB,sBAAsB,KAAK,CAAC,CAAC,SAAS,KAAK,KAAK;AACpF,SAAM,OAAO,MAAM,IAAI,qBAAqB,CAAC,CAAC,SAAS,UAAU;IACjE;AAEF,KAAG,sCAAsC,YAAY;GACnD,MAAM,QAAQ,MAAM,aAAa;AAEjC,SAAM,MAAM,IAAI,oBAAoB,MAAM,UAAU,GAAG,CAAC;AAExD,SAAM,OAAO,MAAM,MAAM,oBAAoB,eAAe,CAAC,CAAC,CAAC,SAAS,KAAK,KAAK;AAClF,SAAM,OAAO,MAAM,IAAI,mBAAmB,CAAC,CAAC,SAAS,cAAc;IACjE,OAAO;IACP,WAAW;IACZ,CAAC;IACF;GACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/store-contract.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { finiteTtl, keepTtl, persistentTtl, type XltTokenStore } from \"@xlt-token/core\";\n\nexport function defineStoreContract(\n name: string,\n createStore: () => Promise<XltTokenStore> | XltTokenStore,\n): void {\n describe(name, () => {\n it(\"keeps one winner for concurrent setIfAbsent\", async () => {\n const store = await createStore();\n\n const results = await Promise.all(\n Array.from({ length: 20 }, (_, index) =>\n store.setIfAbsent(\"{contract}:lock\", String(index), finiteTtl(30)),\n ),\n );\n\n expect(results.filter(Boolean)).toHaveLength(1);\n await expect(store.get(\"{contract}:lock\")).resolves.toMatchObject({\n value: expect.any(String),\n });\n });\n\n it(\"returns and deletes the entry in one atomic operation\", async () => {\n const store = await createStore();\n\n await store.set(\"{contract}:consume\", \"v1\", finiteTtl(60));\n\n await expect(store.getAndDelete(\"{contract}:consume\")).resolves.toMatchObject({\n value: \"v1\",\n });\n await expect(store.getAndDelete(\"{contract}:consume\")).resolves.toBeNull();\n await expect(store.get(\"{contract}:consume\")).resolves.toBeNull();\n });\n\n it(\"returns null when getAndDelete misses\", async () => {\n const store = await createStore();\n\n await expect(store.getAndDelete(\"{contract}:consume-missing\")).resolves.toBeNull();\n });\n\n it(\"keeps one winner for concurrent getAndDelete\", async () => {\n const store = await createStore();\n\n await store.set(\"{contract}:consume-race\", \"v1\", persistentTtl());\n\n const results = await Promise.all(\n Array.from({ length: 20 }, () => store.getAndDelete(\"{contract}:consume-race\")),\n );\n\n const winners = results.filter((entry) => entry !== null);\n expect(winners).toHaveLength(1);\n expect(winners[0]).toMatchObject({ value: \"v1\" });\n await expect(store.get(\"{contract}:consume-race\")).resolves.toBeNull();\n });\n\n it(\"keeps value and ttl unchanged when compareAndSet misses\", async () => {\n const store = await createStore();\n\n await store.set(\"{contract}:cas\", \"v1\", finiteTtl(60));\n\n await expect(store.compareAndSet(\"{contract}:cas\", \"wrong\", \"v2\", keepTtl())).resolves.toBe(\n false,\n );\n await expect(store.get(\"{contract}:cas\")).resolves.toMatchObject({ value: \"v1\" });\n });\n\n it(\"updates and deletes only when the current value matches\", async () => {\n const store = await createStore();\n\n await store.set(\"{contract}:mutable\", \"v1\", persistentTtl());\n\n await expect(store.compareAndSet(\"{contract}:mutable\", \"v1\", \"v2\", keepTtl())).resolves.toBe(\n true,\n );\n await expect(store.get(\"{contract}:mutable\")).resolves.toMatchObject({ value: \"v2\" });\n\n await expect(store.compareAndDelete(\"{contract}:mutable\", \"wrong\")).resolves.toBe(false);\n await expect(store.compareAndDelete(\"{contract}:mutable\", \"v2\")).resolves.toBe(true);\n await expect(store.get(\"{contract}:mutable\")).resolves.toBeNull();\n });\n\n it(\"touches ttl without changing value\", async () => {\n const store = await createStore();\n\n await store.set(\"{contract}:touch\", \"v1\", finiteTtl(30));\n\n await expect(store.touch(\"{contract}:touch\", persistentTtl())).resolves.toBe(true);\n await expect(store.get(\"{contract}:touch\")).resolves.toMatchObject({\n value: \"v1\",\n expiresAt: null,\n });\n });\n });\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xlt-token/store-contract",
3
- "version": "2.1.1",
3
+ "version": "2.4.0",
4
4
  "description": "Shared XltTokenStore contract tests for xlt-token stores",
5
5
  "keywords": [
6
6
  "auth",
@@ -46,7 +46,7 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@xlt-token/core": "^2.1.1",
49
+ "@xlt-token/core": "^2.3.0",
50
50
  "vitest": "^2.0.0"
51
51
  },
52
52
  "devDependencies": {