@zeltjs/kv 0.0.1 → 0.5.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 9wick / Kohei Kido
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,45 +1,32 @@
1
1
  # @zeltjs/kv
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ [![Documentation](https://img.shields.io/badge/docs-zeltjs.com-blue)](https://zeltjs.com)
4
4
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
5
+ Key-value store abstraction for Zelt applications.
6
6
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
7
+ **[Read the Documentation](https://zeltjs.com)**
8
8
 
9
- ## Purpose
9
+ ## Installation
10
10
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@zeltjs/kv`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
11
+ ```bash
12
+ npm install @zeltjs/kv @zeltjs/core
13
+ ```
15
14
 
16
- ## What is OIDC Trusted Publishing?
15
+ ## Usage
17
16
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
17
+ ```typescript
18
+ import { MemoryKVAdaptor } from '@zeltjs/kv';
19
+ import type { KVStore } from '@zeltjs/kv';
19
20
 
20
- ## Setup Instructions
21
+ const kv: KVStore = new MemoryKVAdaptor();
21
22
 
22
- To properly configure OIDC trusted publishing for this package:
23
+ await kv.set('user:1', { name: 'Alice' });
24
+ const user = await kv.get('user:1');
23
25
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
26
+ await kv.set('session:abc', { userId: '1' }, { ttl: 3600 });
27
+ ```
28
28
 
29
- ## DO NOT USE THIS PACKAGE
29
+ ## Adaptors
30
30
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
36
-
37
- ## More Information
38
-
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
42
-
43
- ---
44
-
45
- **Maintained for OIDC setup purposes only**
31
+ - `MemoryKVAdaptor` - In-memory store (for testing/development)
32
+ - `@zeltjs/redis` - Redis-backed store
@@ -0,0 +1,2 @@
1
+ import { t as MemoryKVAdaptor } from "../index-BKwavBZX.js";
2
+ export { MemoryKVAdaptor };
@@ -0,0 +1,2 @@
1
+ import { t as MemoryKVAdaptor } from "../adaptor-memory-B2nsNQ-e.js";
2
+ export { MemoryKVAdaptor };
@@ -0,0 +1,99 @@
1
+ import { i as ZeltKVInvalidTtlError, n as serialize, r as joinPrefix, t as deserialize } from "./serialize-DwL5y2vb.js";
2
+ import { Injectable, LifecycleManager, inject } from "@zeltjs/core";
3
+ //#region src/adaptor-memory/memory-kv.adaptor.ts
4
+ /** @throws {ZeltKVInvalidTtlError} */
5
+ function validateTtl(ttlSec) {
6
+ if (ttlSec !== void 0 && ttlSec <= 0) throw new ZeltKVInvalidTtlError({ ttlSec });
7
+ }
8
+ function makeEntry(raw, ttlSec) {
9
+ return ttlSec !== void 0 ? {
10
+ raw,
11
+ expiresAt: Date.now() + ttlSec * 1e3
12
+ } : { raw };
13
+ }
14
+ var MemoryKVAdaptor = @Injectable() class {
15
+ constructor(lifecycle = inject(LifecycleManager)) {
16
+ this.data = /* @__PURE__ */ new Map();
17
+ this.gcInterval = setInterval(() => this.gc(), 6e4);
18
+ this.gcInterval.unref();
19
+ lifecycle.register(this);
20
+ }
21
+ gc() {
22
+ const now = Date.now();
23
+ for (const [key, entry] of this.data) if (entry.expiresAt !== void 0 && entry.expiresAt <= now) this.data.delete(key);
24
+ }
25
+ async startup() {}
26
+ async shutdown() {
27
+ clearInterval(this.gcInterval);
28
+ }
29
+ namespace(prefix) {
30
+ return new MemoryKVStore(this.data, prefix);
31
+ }
32
+ };
33
+ var MemoryKVStore = class MemoryKVStore {
34
+ constructor(data, prefix) {
35
+ this.data = data;
36
+ this.prefix = prefix;
37
+ }
38
+ k(key) {
39
+ return this.prefix + key;
40
+ }
41
+ current(key) {
42
+ const entry = this.data.get(this.k(key));
43
+ if (!entry) return void 0;
44
+ if (entry.expiresAt !== void 0 && entry.expiresAt <= Date.now()) {
45
+ this.data.delete(this.k(key));
46
+ return;
47
+ }
48
+ return entry;
49
+ }
50
+ async get(key) {
51
+ const entry = this.current(key);
52
+ return entry ? deserialize(entry.raw) : void 0;
53
+ }
54
+ async set(key, value, opts) {
55
+ validateTtl(opts?.ttlSec);
56
+ const raw = serialize(value);
57
+ this.data.set(this.k(key), makeEntry(raw, opts?.ttlSec));
58
+ }
59
+ async del(key) {
60
+ this.data.delete(this.k(key));
61
+ }
62
+ async has(key) {
63
+ return this.current(key) !== void 0;
64
+ }
65
+ async expire(key, ttlSec) {
66
+ validateTtl(ttlSec);
67
+ const entry = this.current(key);
68
+ if (!entry) return false;
69
+ entry.expiresAt = Date.now() + ttlSec * 1e3;
70
+ return true;
71
+ }
72
+ namespace(sub) {
73
+ return new MemoryKVStore(this.data, joinPrefix(this.prefix, sub));
74
+ }
75
+ async incr(key, by = 1, opts) {
76
+ validateTtl(opts?.ttlSec);
77
+ const k = this.k(key);
78
+ const entry = this.current(key);
79
+ if (!entry) {
80
+ const raw = serialize(by);
81
+ this.data.set(k, makeEntry(raw, opts?.ttlSec));
82
+ return by;
83
+ }
84
+ const next = (deserialize(entry.raw) ?? 0) + by;
85
+ entry.raw = serialize(next);
86
+ return next;
87
+ }
88
+ async setnx(key, value, opts) {
89
+ validateTtl(opts?.ttlSec);
90
+ if (this.current(key)) return false;
91
+ const raw = serialize(value);
92
+ this.data.set(this.k(key), makeEntry(raw, opts?.ttlSec));
93
+ return true;
94
+ }
95
+ };
96
+ //#endregion
97
+ export { MemoryKVAdaptor as t };
98
+
99
+ //# sourceMappingURL=adaptor-memory-B2nsNQ-e.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adaptor-memory-B2nsNQ-e.js","names":[],"sources":["../src/adaptor-memory/memory-kv.adaptor.ts"],"sourcesContent":["import type { Lifecycle } from '@zeltjs/core';\nimport { Injectable, inject, LifecycleManager } from '@zeltjs/core';\n\nimport { ZeltKVInvalidTtlError } from '../errors';\nimport { joinPrefix } from '../namespace';\nimport { deserialize, serialize } from '../serialize';\nimport type { AtomicKVAdaptor, AtomicKVStore, Defined, NonEmptyString, SetOptions } from '../types';\n\ntype Entry = {\n raw: string;\n /** epoch ms。undefined は永続 */\n expiresAt?: number;\n};\n\n/** @throws {ZeltKVInvalidTtlError} */\nfunction validateTtl(ttlSec: number | undefined): void {\n if (ttlSec !== undefined && ttlSec <= 0) throw new ZeltKVInvalidTtlError({ ttlSec });\n}\n\nfunction makeEntry(raw: string, ttlSec?: number): Entry {\n return ttlSec !== undefined ? { raw, expiresAt: Date.now() + ttlSec * 1000 } : { raw };\n}\n\n@Injectable()\nexport class MemoryKVAdaptor implements AtomicKVAdaptor, Lifecycle {\n private readonly data = new Map<string, Entry>();\n private readonly gcInterval: ReturnType<typeof setInterval>;\n\n constructor(lifecycle = inject(LifecycleManager)) {\n this.gcInterval = setInterval(() => this.gc(), 60_000);\n this.gcInterval.unref();\n lifecycle.register(this);\n }\n\n private gc(): void {\n const now = Date.now();\n for (const [key, entry] of this.data) {\n if (entry.expiresAt !== undefined && entry.expiresAt <= now) {\n this.data.delete(key);\n }\n }\n }\n\n async startup(): Promise<void> {}\n\n async shutdown(): Promise<void> {\n clearInterval(this.gcInterval);\n }\n\n namespace<const S extends string>(prefix: NonEmptyString<S>): AtomicKVStore {\n return new MemoryKVStore(this.data, prefix);\n }\n}\n\nclass MemoryKVStore implements AtomicKVStore {\n constructor(\n private readonly data: Map<string, Entry>,\n private readonly prefix: string,\n ) {}\n\n private k(key: string): string {\n return this.prefix + key;\n }\n\n private current(key: string): Entry | undefined {\n const entry = this.data.get(this.k(key));\n if (!entry) return undefined;\n if (entry.expiresAt !== undefined && entry.expiresAt <= Date.now()) {\n this.data.delete(this.k(key));\n return undefined;\n }\n return entry;\n }\n\n async get<T>(key: string): Promise<T | undefined> {\n const entry = this.current(key);\n return entry ? deserialize<T>(entry.raw) : undefined;\n }\n\n async set<T extends Defined>(key: string, value: T, opts?: SetOptions): Promise<void> {\n validateTtl(opts?.ttlSec);\n const raw = serialize(value);\n this.data.set(this.k(key), makeEntry(raw, opts?.ttlSec));\n }\n\n async del(key: string): Promise<void> {\n this.data.delete(this.k(key));\n }\n\n async has(key: string): Promise<boolean> {\n return this.current(key) !== undefined;\n }\n\n async expire(key: string, ttlSec: number): Promise<boolean> {\n validateTtl(ttlSec);\n const entry = this.current(key);\n if (!entry) return false;\n entry.expiresAt = Date.now() + ttlSec * 1000;\n return true;\n }\n\n namespace<const S extends string>(sub: NonEmptyString<S>): AtomicKVStore {\n return new MemoryKVStore(this.data, joinPrefix(this.prefix, sub));\n }\n\n async incr(key: string, by = 1, opts?: { ttlSec?: number }): Promise<number> {\n validateTtl(opts?.ttlSec);\n const k = this.k(key);\n const entry = this.current(key);\n if (!entry) {\n const raw = serialize(by);\n this.data.set(k, makeEntry(raw, opts?.ttlSec));\n return by;\n }\n const next = (deserialize<number>(entry.raw) ?? 0) + by;\n entry.raw = serialize(next);\n return next;\n }\n\n async setnx<T extends Defined>(key: string, value: T, opts?: SetOptions): Promise<boolean> {\n validateTtl(opts?.ttlSec);\n if (this.current(key)) return false;\n const raw = serialize(value);\n this.data.set(this.k(key), makeEntry(raw, opts?.ttlSec));\n return true;\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { n as AtomicKVStore, o as NonEmptyString, t as AtomicKVAdaptor } from "../types-PctPMJzq.js";
2
+ import { RedisService } from "@zeltjs/redis";
3
+
4
+ //#region src/adaptor-redis/redis-kv-adaptor.d.ts
5
+ declare class RedisKVAdaptor implements AtomicKVAdaptor {
6
+ private readonly redis;
7
+ constructor(redis?: RedisService);
8
+ namespace<const S extends string>(prefix: NonEmptyString<S>): AtomicKVStore;
9
+ }
10
+ //#endregion
11
+ export { RedisKVAdaptor };
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/adaptor-redis/redis-kv-adaptor.ts"],"mappings":";;;;cAOa,cAAA,YAA0B,eAAA;EAAA,iBACR,KAAA;cAAA,KAAA,GAAK,YAAA;EAElC,SAAA,wBAAA,CAAkC,MAAA,EAAQ,cAAA,CAAe,CAAA,IAAK,aAAA;AAAA"}
@@ -0,0 +1,75 @@
1
+ import { i as ZeltKVInvalidTtlError, n as serialize, r as joinPrefix, t as deserialize } from "../serialize-DwL5y2vb.js";
2
+ import { inject } from "@zeltjs/core";
3
+ import { RedisService } from "@zeltjs/redis";
4
+ //#region src/adaptor-redis/lua-scripts.ts
5
+ const INCR_WITH_TTL_LUA = `
6
+ if ARGV[2] ~= '' then
7
+ redis.call('SET', KEYS[1], 0, 'EX', ARGV[2], 'NX')
8
+ end
9
+ return redis.call('INCRBY', KEYS[1], ARGV[1])
10
+ `;
11
+ //#endregion
12
+ //#region src/adaptor-redis/redis-kv-store.ts
13
+ /** @throws {ZeltKVInvalidTtlError} */
14
+ const validateTtl = (ttlSec) => {
15
+ if (ttlSec !== void 0 && ttlSec <= 0) throw new ZeltKVInvalidTtlError({ ttlSec });
16
+ };
17
+ const incrWithTtl = async (client, key, by, ttlSec) => {
18
+ const ttlArg = ttlSec !== void 0 ? String(ttlSec) : "";
19
+ return client.eval(INCR_WITH_TTL_LUA, 1, key, by, ttlArg);
20
+ };
21
+ var RedisKVStore = class RedisKVStore {
22
+ constructor(client, prefix) {
23
+ this.client = client;
24
+ this.prefix = prefix;
25
+ }
26
+ k(key) {
27
+ return this.prefix + key;
28
+ }
29
+ async get(key) {
30
+ const raw = await this.client.get(this.k(key));
31
+ return raw === null ? void 0 : deserialize(raw);
32
+ }
33
+ async set(key, value, opts) {
34
+ validateTtl(opts?.ttlSec);
35
+ const json = serialize(value);
36
+ if (opts?.ttlSec !== void 0) await this.client.set(this.k(key), json, "EX", opts.ttlSec);
37
+ else await this.client.set(this.k(key), json);
38
+ }
39
+ async del(key) {
40
+ await this.client.del(this.k(key));
41
+ }
42
+ async has(key) {
43
+ return await this.client.exists(this.k(key)) === 1;
44
+ }
45
+ async expire(key, ttlSec) {
46
+ validateTtl(ttlSec);
47
+ return await this.client.expire(this.k(key), ttlSec) === 1;
48
+ }
49
+ namespace(sub) {
50
+ return new RedisKVStore(this.client, joinPrefix(this.prefix, sub));
51
+ }
52
+ async incr(key, by = 1, opts) {
53
+ validateTtl(opts?.ttlSec);
54
+ return incrWithTtl(this.client, this.k(key), by, opts?.ttlSec);
55
+ }
56
+ async setnx(key, value, opts) {
57
+ validateTtl(opts?.ttlSec);
58
+ const json = serialize(value);
59
+ return (opts?.ttlSec !== void 0 ? await this.client.set(this.k(key), json, "EX", opts.ttlSec, "NX") : await this.client.set(this.k(key), json, "NX")) === "OK";
60
+ }
61
+ };
62
+ //#endregion
63
+ //#region src/adaptor-redis/redis-kv-adaptor.ts
64
+ var RedisKVAdaptor = class {
65
+ constructor(redis = inject(RedisService)) {
66
+ this.redis = redis;
67
+ }
68
+ namespace(prefix) {
69
+ return new RedisKVStore(this.redis.client, prefix);
70
+ }
71
+ };
72
+ //#endregion
73
+ export { RedisKVAdaptor };
74
+
75
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/adaptor-redis/lua-scripts.ts","../../src/adaptor-redis/redis-kv-store.ts","../../src/adaptor-redis/redis-kv-adaptor.ts"],"sourcesContent":["export const INCR_WITH_TTL_LUA = `\n if ARGV[2] ~= '' then\n redis.call('SET', KEYS[1], 0, 'EX', ARGV[2], 'NX')\n end\n return redis.call('INCRBY', KEYS[1], ARGV[1])\n`;\n","import type Redis from 'ioredis';\nimport { ZeltKVInvalidTtlError } from '../errors';\nimport { joinPrefix } from '../namespace';\nimport { deserialize, serialize } from '../serialize';\nimport type { AtomicKVStore, Defined, NonEmptyString, SetOptions } from '../types';\n\nimport { INCR_WITH_TTL_LUA } from './lua-scripts';\n\n/** @throws {ZeltKVInvalidTtlError} */\nconst validateTtl = (ttlSec: number | undefined): void => {\n if (ttlSec !== undefined && ttlSec <= 0) throw new ZeltKVInvalidTtlError({ ttlSec });\n};\n\nconst incrWithTtl = async (\n client: Redis,\n key: string,\n by: number,\n ttlSec: number | undefined,\n): Promise<number> => {\n const ttlArg = ttlSec !== undefined ? String(ttlSec) : '';\n return client.eval(INCR_WITH_TTL_LUA, 1, key, by, ttlArg) as Promise<number>;\n};\n\nexport class RedisKVStore implements AtomicKVStore {\n constructor(\n private readonly client: Redis,\n private readonly prefix: string,\n ) {}\n\n private k(key: string): string {\n return this.prefix + key;\n }\n\n async get<T>(key: string): Promise<T | undefined> {\n const raw = await this.client.get(this.k(key));\n return raw === null ? undefined : deserialize<T>(raw);\n }\n\n async set<T extends Defined>(key: string, value: T, opts?: SetOptions): Promise<void> {\n validateTtl(opts?.ttlSec);\n const json = serialize(value);\n if (opts?.ttlSec !== undefined) {\n await this.client.set(this.k(key), json, 'EX', opts.ttlSec);\n } else {\n await this.client.set(this.k(key), json);\n }\n }\n\n async del(key: string): Promise<void> {\n await this.client.del(this.k(key));\n }\n\n async has(key: string): Promise<boolean> {\n const n = await this.client.exists(this.k(key));\n return n === 1;\n }\n\n async expire(key: string, ttlSec: number): Promise<boolean> {\n validateTtl(ttlSec);\n const n = await this.client.expire(this.k(key), ttlSec);\n return n === 1;\n }\n\n namespace<const S extends string>(sub: NonEmptyString<S>): AtomicKVStore {\n return new RedisKVStore(this.client, joinPrefix(this.prefix, sub));\n }\n\n async incr(key: string, by = 1, opts?: { ttlSec?: number }): Promise<number> {\n validateTtl(opts?.ttlSec);\n return incrWithTtl(this.client, this.k(key), by, opts?.ttlSec);\n }\n\n async setnx<T extends Defined>(key: string, value: T, opts?: SetOptions): Promise<boolean> {\n validateTtl(opts?.ttlSec);\n const json = serialize(value);\n const result =\n opts?.ttlSec !== undefined\n ? await this.client.set(this.k(key), json, 'EX', opts.ttlSec, 'NX')\n : await this.client.set(this.k(key), json, 'NX');\n return result === 'OK';\n }\n}\n","import { inject } from '@zeltjs/core';\nimport { RedisService } from '@zeltjs/redis';\n\nimport type { AtomicKVAdaptor, AtomicKVStore, NonEmptyString } from '../types';\n\nimport { RedisKVStore } from './redis-kv-store';\n\nexport class RedisKVAdaptor implements AtomicKVAdaptor {\n constructor(private readonly redis = inject(RedisService)) {}\n\n namespace<const S extends string>(prefix: NonEmptyString<S>): AtomicKVStore {\n return new RedisKVStore(this.redis.client, prefix);\n }\n}\n"],"mappings":";;;;AAAA,MAAa,oBAAoB;;;;;;;;;ACSjC,MAAM,eAAe,WAAqC;AACxD,KAAI,WAAW,KAAA,KAAa,UAAU,EAAG,OAAM,IAAI,sBAAsB,EAAE,QAAQ,CAAC;;AAGtF,MAAM,cAAc,OAClB,QACA,KACA,IACA,WACoB;CACpB,MAAM,SAAS,WAAW,KAAA,IAAY,OAAO,OAAO,GAAG;AACvD,QAAO,OAAO,KAAK,mBAAmB,GAAG,KAAK,IAAI,OAAO;;AAG3D,IAAa,eAAb,MAAa,aAAsC;CACjD,YACE,QACA,QACA;AAFiB,OAAA,SAAA;AACA,OAAA,SAAA;;CAGnB,EAAU,KAAqB;AAC7B,SAAO,KAAK,SAAS;;CAGvB,MAAM,IAAO,KAAqC;EAChD,MAAM,MAAM,MAAM,KAAK,OAAO,IAAI,KAAK,EAAE,IAAI,CAAC;AAC9C,SAAO,QAAQ,OAAO,KAAA,IAAY,YAAe,IAAI;;CAGvD,MAAM,IAAuB,KAAa,OAAU,MAAkC;AACpF,cAAY,MAAM,OAAO;EACzB,MAAM,OAAO,UAAU,MAAM;AAC7B,MAAI,MAAM,WAAW,KAAA,EACnB,OAAM,KAAK,OAAO,IAAI,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,KAAK,OAAO;MAE3D,OAAM,KAAK,OAAO,IAAI,KAAK,EAAE,IAAI,EAAE,KAAK;;CAI5C,MAAM,IAAI,KAA4B;AACpC,QAAM,KAAK,OAAO,IAAI,KAAK,EAAE,IAAI,CAAC;;CAGpC,MAAM,IAAI,KAA+B;AAEvC,SAAO,MADS,KAAK,OAAO,OAAO,KAAK,EAAE,IAAI,CAAC,KAClC;;CAGf,MAAM,OAAO,KAAa,QAAkC;AAC1D,cAAY,OAAO;AAEnB,SAAO,MADS,KAAK,OAAO,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,KAC1C;;CAGf,UAAkC,KAAuC;AACvE,SAAO,IAAI,aAAa,KAAK,QAAQ,WAAW,KAAK,QAAQ,IAAI,CAAC;;CAGpE,MAAM,KAAK,KAAa,KAAK,GAAG,MAA6C;AAC3E,cAAY,MAAM,OAAO;AACzB,SAAO,YAAY,KAAK,QAAQ,KAAK,EAAE,IAAI,EAAE,IAAI,MAAM,OAAO;;CAGhE,MAAM,MAAyB,KAAa,OAAU,MAAqC;AACzF,cAAY,MAAM,OAAO;EACzB,MAAM,OAAO,UAAU,MAAM;AAK7B,UAHE,MAAM,WAAW,KAAA,IACb,MAAM,KAAK,OAAO,IAAI,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,KAAK,QAAQ,KAAK,GACjE,MAAM,KAAK,OAAO,IAAI,KAAK,EAAE,IAAI,EAAE,MAAM,KAAK,MAClC;;;;;ACxEtB,IAAa,iBAAb,MAAuD;CACrD,YAAY,QAAyB,OAAO,aAAa,EAAE;AAA9B,OAAA,QAAA;;CAE7B,UAAkC,QAA0C;AAC1E,SAAO,IAAI,aAAa,KAAK,MAAM,QAAQ,OAAO"}
@@ -0,0 +1,16 @@
1
+ import { n as AtomicKVStore, o as NonEmptyString, t as AtomicKVAdaptor } from "./types-PctPMJzq.js";
2
+ import { Lifecycle, LifecycleManager } from "@zeltjs/core";
3
+
4
+ //#region src/adaptor-memory/memory-kv.adaptor.d.ts
5
+ declare class MemoryKVAdaptor implements AtomicKVAdaptor, Lifecycle {
6
+ private readonly data;
7
+ private readonly gcInterval;
8
+ constructor(lifecycle?: LifecycleManager);
9
+ private gc;
10
+ startup(): Promise<void>;
11
+ shutdown(): Promise<void>;
12
+ namespace<const S extends string>(prefix: NonEmptyString<S>): AtomicKVStore;
13
+ }
14
+ //#endregion
15
+ export { MemoryKVAdaptor as t };
16
+ //# sourceMappingURL=index-BKwavBZX.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-BKwavBZX.d.ts","names":[],"sources":["../src/adaptor-memory/memory-kv.adaptor.ts"],"mappings":";;;;cAwBa,eAAA,YAA2B,eAAA,EAAiB,SAAA;EAAA,iBACtC,IAAA;EAAA,iBACA,UAAA;cAEL,SAAA,GAAS,gBAAA;EAAA,QAMb,EAAA;EASF,OAAA,CAAA,GAAW,OAAA;EAEX,QAAA,CAAA,GAAY,OAAA;EAIlB,SAAA,wBAAA,CAAkC,MAAA,EAAQ,cAAA,CAAe,CAAA,IAAK,aAAA;AAAA"}
@@ -0,0 +1,23 @@
1
+ import { a as KVStore, i as KVAdaptor, n as AtomicKVStore, o as NonEmptyString, r as Defined, s as SetOptions, t as AtomicKVAdaptor } from "./types-PctPMJzq.js";
2
+ import { t as MemoryKVAdaptor } from "./index-BKwavBZX.js";
3
+
4
+ //#region src/errors.d.ts
5
+ declare const ZeltKVInvalidTtlError: new (context: {
6
+ ttlSec: number;
7
+ }, cause?: unknown) => Error & {
8
+ readonly name: string;
9
+ readonly context: {
10
+ ttlSec: number;
11
+ };
12
+ };
13
+ declare const isZeltKVInvalidTtlError: (err: unknown) => err is InstanceType<typeof ZeltKVInvalidTtlError>;
14
+ //#endregion
15
+ //#region src/namespace.d.ts
16
+ declare const joinPrefix: (a: string, b: string) => string;
17
+ //#endregion
18
+ //#region src/serialize.d.ts
19
+ declare const serialize: (value: Defined) => string;
20
+ declare const deserialize: <T>(raw: string | null) => T | undefined;
21
+ //#endregion
22
+ export { type AtomicKVAdaptor, type AtomicKVStore, type Defined, type KVAdaptor, type KVStore, MemoryKVAdaptor as MemoryKV, MemoryKVAdaptor, MemoryKVAdaptor as MemoryKVService, type NonEmptyString, type SetOptions, ZeltKVInvalidTtlError, deserialize, isZeltKVInvalidTtlError, joinPrefix, serialize };
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/errors.ts","../src/namespace.ts","../src/serialize.ts"],"mappings":";;;;cAEa,qBAAA,OAAqB,OAAA;;uBAGjC,KAAA;EAAA;;;;;cAEY,uBAAA,GACX,GAAA,cACC,GAAA,IAAO,YAAA,QAAoB,qBAAA;;;cCTjB,UAAA,GAAc,CAAA,UAAW,CAAA;;;cCEzB,SAAA,GAAa,KAAA,EAAO,OAAA;AAAA,cAIpB,WAAA,MAAkB,GAAA,oBAAqB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { a as isZeltKVInvalidTtlError, i as ZeltKVInvalidTtlError, n as serialize, r as joinPrefix, t as deserialize } from "./serialize-DwL5y2vb.js";
2
+ import { t as MemoryKVAdaptor } from "./adaptor-memory-B2nsNQ-e.js";
3
+ export { MemoryKVAdaptor as MemoryKV, MemoryKVAdaptor, MemoryKVAdaptor as MemoryKVService, ZeltKVInvalidTtlError, deserialize, isZeltKVInvalidTtlError, joinPrefix, serialize };
@@ -0,0 +1,20 @@
1
+ import { defineError } from "@zeltjs/core/internal-bridge/errors";
2
+ //#region src/errors.ts
3
+ const ZeltKVInvalidTtlError = defineError("ZeltKVInvalidTtlError", (ctx) => `ttlSec must be > 0, got ${ctx.ttlSec}`);
4
+ const isZeltKVInvalidTtlError = (err) => {
5
+ return err instanceof ZeltKVInvalidTtlError;
6
+ };
7
+ //#endregion
8
+ //#region src/namespace.ts
9
+ const joinPrefix = (a, b) => a + b;
10
+ //#endregion
11
+ //#region src/serialize.ts
12
+ const serialize = (value) => JSON.stringify(value);
13
+ const deserialize = (raw) => {
14
+ if (raw === null) return void 0;
15
+ return JSON.parse(raw);
16
+ };
17
+ //#endregion
18
+ export { isZeltKVInvalidTtlError as a, ZeltKVInvalidTtlError as i, serialize as n, joinPrefix as r, deserialize as t };
19
+
20
+ //# sourceMappingURL=serialize-DwL5y2vb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize-DwL5y2vb.js","names":[],"sources":["../src/errors.ts","../src/namespace.ts","../src/serialize.ts"],"sourcesContent":["import { defineError } from '@zeltjs/core/internal-bridge/errors';\n\nexport const ZeltKVInvalidTtlError = defineError(\n 'ZeltKVInvalidTtlError',\n (ctx: { ttlSec: number }) => `ttlSec must be > 0, got ${ctx.ttlSec}`,\n);\n\nexport const isZeltKVInvalidTtlError = (\n err: unknown,\n): err is InstanceType<typeof ZeltKVInvalidTtlError> => {\n return err instanceof ZeltKVInvalidTtlError;\n};\n","export const joinPrefix = (a: string, b: string): string => a + b;\n","import type { Defined } from './types';\n\nexport const serialize = (value: Defined): string => JSON.stringify(value);\n\n// deserialize stays sync without Result because we control the input (always\n// a string we previously serialized via JSON.stringify, plus driver-controlled null sentinel).\nexport const deserialize = <T>(raw: string | null): T | undefined => {\n if (raw === null) return undefined;\n return JSON.parse(raw) as T;\n};\n"],"mappings":";;AAEA,MAAa,wBAAwB,YACnC,0BACC,QAA4B,2BAA2B,IAAI,SAC7D;AAED,MAAa,2BACX,QACsD;AACtD,QAAO,eAAe;;;;ACVxB,MAAa,cAAc,GAAW,MAAsB,IAAI;;;ACEhE,MAAa,aAAa,UAA2B,KAAK,UAAU,MAAM;AAI1E,MAAa,eAAkB,QAAsC;AACnE,KAAI,QAAQ,KAAM,QAAO,KAAA;AACzB,QAAO,KAAK,MAAM,IAAI"}
@@ -0,0 +1,40 @@
1
+ //#region src/types.d.ts
2
+ /** 空文字を型レベルで禁止 */
3
+ type NonEmptyString<S extends string> = S extends '' ? never : S;
4
+ /** undefined を除外した値型 */
5
+ type Defined = {} | null;
6
+ /** Top-level adaptor. namespace で view を取り出すまで data ops は不可。 */
7
+ interface KVAdaptor {
8
+ namespace<const S extends string>(prefix: NonEmptyString<S>): KVStore;
9
+ }
10
+ /** atomic 操作対応の adaptor。namespace の戻り値が AtomicKVStore */
11
+ interface AtomicKVAdaptor extends KVAdaptor {
12
+ namespace<const S extends string>(prefix: NonEmptyString<S>): AtomicKVStore;
13
+ }
14
+ /** namespaced view。実際の data ops はここ */
15
+ interface KVStore {
16
+ get<T>(key: string): Promise<T | undefined>;
17
+ set<T extends Defined>(key: string, value: T, opts?: SetOptions): Promise<void>;
18
+ del(key: string): Promise<void>;
19
+ has(key: string): Promise<boolean>;
20
+ /** TTL 延長 (session touch / lock extend 用)。key 不在時は false。 */
21
+ expire(key: string, ttlSec: number): Promise<boolean>;
22
+ /** 子 namespace。チェーン可能。 */
23
+ namespace<const S extends string>(prefix: NonEmptyString<S>): KVStore;
24
+ }
25
+ /** atomic 操作対応 view */
26
+ interface AtomicKVStore extends KVStore {
27
+ /** atomic incr。最初の incr 時のみ TTL をセット。 */
28
+ incr(key: string, by?: number, opts?: {
29
+ ttlSec?: number;
30
+ }): Promise<number>;
31
+ /** atomic set if not exists。set されたら true、既存なら false。 */
32
+ setnx<T extends Defined>(key: string, value: T, opts?: SetOptions): Promise<boolean>;
33
+ namespace<const S extends string>(prefix: NonEmptyString<S>): AtomicKVStore;
34
+ }
35
+ type SetOptions = {
36
+ ttlSec?: number;
37
+ };
38
+ //#endregion
39
+ export { KVStore as a, KVAdaptor as i, AtomicKVStore as n, NonEmptyString as o, Defined as r, SetOptions as s, AtomicKVAdaptor as t };
40
+ //# sourceMappingURL=types-PctPMJzq.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-PctPMJzq.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;KACY,cAAA,qBAAmC,CAAA,sBAAuB,CAAA;;KAI1D,OAAA;;UAGK,SAAA;EACf,SAAA,yBAAkC,MAAA,EAAQ,cAAA,CAAe,CAAA,IAAK,OAAA;AAAA;;UAI/C,eAAA,SAAwB,SAAA;EACvC,SAAA,yBAAkC,MAAA,EAAQ,cAAA,CAAe,CAAA,IAAK,aAAA;AAAA;;UAI/C,OAAA;EACf,GAAA,IAAO,GAAA,WAAc,OAAA,CAAQ,CAAA;EAC7B,GAAA,WAAc,OAAA,EAAS,GAAA,UAAa,KAAA,EAAO,CAAA,EAAG,IAAA,GAAO,UAAA,GAAa,OAAA;EAClE,GAAA,CAAI,GAAA,WAAc,OAAA;EAClB,GAAA,CAAI,GAAA,WAAc,OAAA;EAbuC;EAezD,MAAA,CAAO,GAAA,UAAa,MAAA,WAAiB,OAAA;EAfyB;EAiB9D,SAAA,yBAAkC,MAAA,EAAQ,cAAA,CAAe,CAAA,IAAK,OAAA;AAAA;;UAI/C,aAAA,SAAsB,OAAA;EArBK;EAuB1C,IAAA,CAAK,GAAA,UAAa,EAAA,WAAa,IAAA;IAAS,MAAA;EAAA,IAAoB,OAAA;EAvBS;EAyBrE,KAAA,WAAgB,OAAA,EAAS,GAAA,UAAa,KAAA,EAAO,CAAA,EAAG,IAAA,GAAO,UAAA,GAAa,OAAA;EACpE,SAAA,yBAAkC,MAAA,EAAQ,cAAA,CAAe,CAAA,IAAK,aAAA;AAAA;AAAA,KAGpD,UAAA;EACV,MAAA;AAAA"}
package/package.json CHANGED
@@ -1,10 +1,55 @@
1
1
  {
2
2
  "name": "@zeltjs/kv",
3
- "version": "0.0.1",
4
- "description": "OIDC trusted publishing setup package for @zeltjs/kv",
5
- "keywords": [
6
- "oidc",
7
- "trusted-publishing",
8
- "setup"
9
- ]
10
- }
3
+ "version": "0.5.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/zeltjs/zelt.git",
9
+ "directory": "packages/kv"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "import": "./dist/index.js"
18
+ },
19
+ "./adaptor-memory": {
20
+ "types": "./dist/adaptor-memory/index.d.ts",
21
+ "import": "./dist/adaptor-memory/index.js"
22
+ },
23
+ "./adaptor-redis": {
24
+ "types": "./dist/adaptor-redis/index.d.ts",
25
+ "import": "./dist/adaptor-redis/index.js"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist"
30
+ ],
31
+ "peerDependencies": {
32
+ "vitest": ">=4 <5",
33
+ "@zeltjs/core": "0.5.0",
34
+ "@zeltjs/redis": "0.5.0"
35
+ },
36
+ "peerDependenciesMeta": {
37
+ "@zeltjs/redis": {
38
+ "optional": true
39
+ }
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "22.19.17",
43
+ "ioredis": "5.10.1",
44
+ "@zeltjs/core": "0.5.0",
45
+ "@zeltjs/redis": "0.5.0"
46
+ },
47
+ "volta": {
48
+ "extends": "../../package.json"
49
+ },
50
+ "scripts": {
51
+ "build": "tsdown",
52
+ "test": "vitest run",
53
+ "typecheck": "tsc -b"
54
+ }
55
+ }