@hasna-internal/kai-storage 0.1.1-rc.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/LICENSE +21 -0
- package/README.i18n.yaml +6 -0
- package/README.md +32 -0
- package/README.zh.md +32 -0
- package/lib/index.js +140 -0
- package/lib/invariant.js +25 -0
- package/lib/types/backend.d.ts +98 -0
- package/lib/types/error.d.ts +21 -0
- package/lib/types/index.d.ts +64 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/registry.d.ts +34 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
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.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/storage/storage/README.md
|
|
5
|
+
README.md: bf827220afe0d8b1cbc53b3a12e6d0034004e1d2
|
|
6
|
+
README.zh.md: fb52c96e38c17b76ef0e6b70a79177911a088fb7
|
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @hasna-internal/kai-storage
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
Storage hub (`ctx.storage`) for non-session data: a named backend registry plus mounted data-form facilities. The hub performs no IO itself — backends own media, and data forms own semantics. The [storage family overview](../README.md) maps those packages; the [domain KV storage Agent Note](../../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.md) records the design rationale.
|
|
6
|
+
|
|
7
|
+
## Shape
|
|
8
|
+
|
|
9
|
+
- `ctx.storage.backend` — name → backend table. Multiple backends stay mounted side by side (`json`, `sqlite`); which backend serves a consumer is that consumer's configuration (the domain layer's route table), never a hub-global choice. `register()` returns the disposer; duplicate names and unknown lookups fail loud.
|
|
10
|
+
- `ctx.storage.mount(form, facility)` / `ctx.storage.form(form)` — data-form mounting. `StorageForms` is merge-extensible; the domain layer merges `domain` and is reached as `ctx.storage.domain`.
|
|
11
|
+
- A backend owns one medium and exposes the data-shape facets it supports. `kv` is the current facet; `src/backend.ts` owns its exact contract.
|
|
12
|
+
|
|
13
|
+
## Model Experience
|
|
14
|
+
|
|
15
|
+
### Backend and form registrations
|
|
16
|
+
|
|
17
|
+
#### What the model sees
|
|
18
|
+
|
|
19
|
+
Nothing. `ctx.storage` is a host-side registration table; the hub registers no tools, injects no prompts, and writes no session events.
|
|
20
|
+
|
|
21
|
+
#### Token effect
|
|
22
|
+
|
|
23
|
+
Zero direct tokens on every request.
|
|
24
|
+
|
|
25
|
+
#### KV Cache effect
|
|
26
|
+
|
|
27
|
+
Independent of live requests: the hub never touches a request prefix, so it cannot invalidate provider cache reuse.
|
|
28
|
+
|
|
29
|
+
## Known Limitations and Deferred Work
|
|
30
|
+
|
|
31
|
+
- **`kv` is the only data shape** — backends currently have one facet to implement.
|
|
32
|
+
- **Forms resolve lazily** — reading `ctx.storage.domain` before the domain plugin mounts throws `form-not-mounted`; assemblies order plugins accordingly (misconfiguration fails loud rather than silently deferring).
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @hasna-internal/kai-storage
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
非会话数据的存储中心(`ctx.storage`):具名后端注册表加已挂载的数据形式设施。中心自身不执行 IO:后端拥有介质,数据形式拥有语义。[存储家族概述](../README.zh.md)列出了这些包;[领域 KV 存储 Agent Note](../../../.agents/notes/proposed/architecture/2026-07-24-domain-kv-storage-and-workspace.zh.md)记录了设计理由。
|
|
6
|
+
|
|
7
|
+
## 结构
|
|
8
|
+
|
|
9
|
+
- `ctx.storage.backend`:名称 → 后端表。多个后端并排保持挂载(`json`、`sqlite`);为消费方提供服务的后端由该消费方自身的配置决定(领域层的路由表),绝非中心的全局选择。`register()` 返回资源释放函数;注册重复名称或查找未知名称时都会明确报错。
|
|
10
|
+
- `ctx.storage.mount(form, facility)`/`ctx.storage.form(form)`:数据形式挂载。`StorageForms` 可通过合并扩展;领域层合并 `domain`,并通过 `ctx.storage.domain` 访问。
|
|
11
|
+
- 后端拥有一种介质,并公开其支持的数据形状**分面**。当前分面为 `kv`;`src/backend.ts` 负责定义其确切约定。
|
|
12
|
+
|
|
13
|
+
## 模型体验
|
|
14
|
+
|
|
15
|
+
### 后端与形式注册
|
|
16
|
+
|
|
17
|
+
#### 模型看到的内容
|
|
18
|
+
|
|
19
|
+
无。`ctx.storage` 是主机侧注册表;中心不注册工具、不注入提示词,也不写入会话事件。
|
|
20
|
+
|
|
21
|
+
#### Token 影响
|
|
22
|
+
|
|
23
|
+
每次请求都不会直接增加 token。
|
|
24
|
+
|
|
25
|
+
#### KV Cache 影响
|
|
26
|
+
|
|
27
|
+
与实时请求相互独立:中心绝不触碰请求前缀,因此无法使提供方缓存复用失效。
|
|
28
|
+
|
|
29
|
+
## 已知限制与暂缓事项
|
|
30
|
+
|
|
31
|
+
- **`kv` 是唯一的数据形状**:后端目前只有一个分面需要实现。
|
|
32
|
+
- **数据形式按需解析**:在领域插件挂载前读取 `ctx.storage.domain` 会抛出 `form-not-mounted`;组装会按相应顺序排列插件(错误配置会明确报错,而不是静默推迟处理)。
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Service } from "@deepseek-ai/cordis";
|
|
2
|
+
//#region lib/types/error.js
|
|
3
|
+
/**
|
|
4
|
+
* Error vocabulary for the storage hub and its backends.
|
|
5
|
+
* @module @hasna-internal/kai-storage/src/error
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Error thrown by the hub and by backend implementations. The `code` is the
|
|
9
|
+
* stable contract consumers may switch on; `message` is diagnostic prose.
|
|
10
|
+
*/
|
|
11
|
+
var StorageError = class extends Error {
|
|
12
|
+
code;
|
|
13
|
+
name = "StorageError";
|
|
14
|
+
/**
|
|
15
|
+
* @param code - Stable discriminant for the failure class.
|
|
16
|
+
* @param message - Human-readable diagnostic detail.
|
|
17
|
+
* @param options - Standard error options (`cause`).
|
|
18
|
+
*/
|
|
19
|
+
constructor(code, message, options) {
|
|
20
|
+
super(message, options);
|
|
21
|
+
this.code = code;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region lib/types/registry.js
|
|
26
|
+
/**
|
|
27
|
+
* Named backend registry of the storage hub.
|
|
28
|
+
* @module @hasna-internal/kai-storage/src/registry
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Mutable name → backend table. Multiple backends stay mounted side by side;
|
|
32
|
+
* which backend serves which consumer is the consumer's configuration
|
|
33
|
+
* (e.g. the domain layer's route table), never a hub-global choice.
|
|
34
|
+
*/
|
|
35
|
+
var BackendRegistry = class {
|
|
36
|
+
backends = /* @__PURE__ */ new Map();
|
|
37
|
+
/**
|
|
38
|
+
* Register a named backend. Registration is an effect: the returned
|
|
39
|
+
* disposer removes the name. Disposal does NOT close the backend — the
|
|
40
|
+
* owning plugin closes it after unregistering.
|
|
41
|
+
* @param name - Backend name, e.g. `json` or `sqlite`.
|
|
42
|
+
* @param backend - The backend instance.
|
|
43
|
+
* @returns the disposer that unregisters the name.
|
|
44
|
+
*/
|
|
45
|
+
register(name, backend) {
|
|
46
|
+
if (this.backends.has(name)) throw new StorageError("duplicate-backend", `storage backend '${name}' is already registered`);
|
|
47
|
+
this.backends.set(name, backend);
|
|
48
|
+
return () => {
|
|
49
|
+
if (this.backends.get(name) === backend) this.backends.delete(name);
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Resolve a backend by name.
|
|
54
|
+
* @param name - Registered backend name.
|
|
55
|
+
* @returns the backend.
|
|
56
|
+
*/
|
|
57
|
+
get(name) {
|
|
58
|
+
const backend = this.backends.get(name);
|
|
59
|
+
if (!backend) throw new StorageError("backend-not-found", `storage backend '${name}' is not registered (registered: ${[...this.backends.keys()].join(", ") || "none"})`);
|
|
60
|
+
return backend;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Registered backend names, for diagnostics.
|
|
64
|
+
* @returns a snapshot array of names.
|
|
65
|
+
*/
|
|
66
|
+
names() {
|
|
67
|
+
return [...this.backends.keys()];
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region lib/types/backend.js
|
|
72
|
+
/**
|
|
73
|
+
* Backend-facing vocabulary of the storage hub: a backend owns one medium
|
|
74
|
+
* (a file-tree root, a database file) and exposes operation groups over it.
|
|
75
|
+
* This module defines the normative contract text for backend implementers; the shared
|
|
76
|
+
* conformance suite in `tests/contract.ts` checks every rule.
|
|
77
|
+
* @module @hasna-internal/kai-storage/src/backend
|
|
78
|
+
*/
|
|
79
|
+
/** Allowed format for unit and table names: safe as a file name and as a SQL identifier segment without escaping. */
|
|
80
|
+
const UNIT_NAME_RE = /^[a-z][a-z0-9_]*$/;
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region lib/types/index.js
|
|
83
|
+
/**
|
|
84
|
+
* Storage hub (`ctx.storage`): a named backend registry plus mounted
|
|
85
|
+
* data-form facilities. The hub itself performs no IO — backends own media,
|
|
86
|
+
* data forms (the domain layer first) own semantics.
|
|
87
|
+
* @module @hasna-internal/kai-storage
|
|
88
|
+
*/
|
|
89
|
+
/**
|
|
90
|
+
* Derive the Cordis lifecycle service that one named backend plugin provides.
|
|
91
|
+
* Domain-form providers inject these keys so activation cannot race backend
|
|
92
|
+
* registration even though callers continue resolving backends through the
|
|
93
|
+
* storage registry.
|
|
94
|
+
* @param name - Backend registry name.
|
|
95
|
+
* @returns the corresponding lifecycle-only service key.
|
|
96
|
+
*/
|
|
97
|
+
function storageBackendServiceKey(name) {
|
|
98
|
+
return `storage.backend.${name}`;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The storage hub service. Backends register under `backend`; data forms
|
|
102
|
+
* mount under their `StorageForms` key and are reached as `ctx.storage.<form>`.
|
|
103
|
+
*/
|
|
104
|
+
var Storage = class extends Service {
|
|
105
|
+
/** Named backend table; multiple backends stay mounted side by side. */
|
|
106
|
+
backend = new BackendRegistry();
|
|
107
|
+
forms = /* @__PURE__ */ new Map();
|
|
108
|
+
constructor(ctx) {
|
|
109
|
+
super(ctx, "storage");
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Mount a data-form facility on the hub. Mounting is an effect: the
|
|
113
|
+
* returned disposer unmounts the form.
|
|
114
|
+
* @param form - Form key declared in {@link StorageForms}.
|
|
115
|
+
* @param facility - The facility instance to expose.
|
|
116
|
+
* @returns the disposer that unmounts the form.
|
|
117
|
+
*/
|
|
118
|
+
mount(form, facility) {
|
|
119
|
+
if (this.forms.has(form)) throw new StorageError("duplicate-mount", `storage form '${String(form)}' is already mounted`);
|
|
120
|
+
this.forms.set(form, facility);
|
|
121
|
+
return () => {
|
|
122
|
+
if (this.forms.get(form) === facility) this.forms.delete(form);
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Resolve a mounted data form.
|
|
127
|
+
* @param form - Form key declared in {@link StorageForms}.
|
|
128
|
+
* @returns the mounted facility.
|
|
129
|
+
*/
|
|
130
|
+
form(form) {
|
|
131
|
+
if (!this.forms.has(form)) throw new StorageError("form-not-mounted", `storage form '${String(form)}' is not mounted`);
|
|
132
|
+
return this.forms.get(form);
|
|
133
|
+
}
|
|
134
|
+
/** Domain data form; present once the domain layer plugin is loaded. */
|
|
135
|
+
get domain() {
|
|
136
|
+
return this.form("domain");
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
//#endregion
|
|
140
|
+
export { BackendRegistry, Storage, Storage as default, StorageError, UNIT_NAME_RE, storageBackendServiceKey };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/**
|
|
3
|
+
* Package-owned invariant companion for `@hasna-internal/kai-storage`.
|
|
4
|
+
* @module @hasna-internal/kai-storage/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = "@hasna-internal/kai-storage";
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = "storage-invariant";
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
const inject = ["invariants"];
|
|
11
|
+
/**
|
|
12
|
+
* No runtime invariant: the hub is a pure registration table (names →
|
|
13
|
+
* backends, forms → facilities) whose consistency is fully enforced at the
|
|
14
|
+
* call sites (duplicate/missing entries fail loud synchronously); it owns no
|
|
15
|
+
* event stream or mutable medium to cross-check.
|
|
16
|
+
*/
|
|
17
|
+
const install = () => {};
|
|
18
|
+
/**
|
|
19
|
+
* Register this package's invariant companion.
|
|
20
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
21
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
22
|
+
*/
|
|
23
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
24
|
+
//#endregion
|
|
25
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend-facing vocabulary of the storage hub: a backend owns one medium
|
|
3
|
+
* (a file-tree root, a database file) and exposes operation groups over it.
|
|
4
|
+
* This module defines the normative contract text for backend implementers; the shared
|
|
5
|
+
* conformance suite in `tests/contract.ts` checks every rule.
|
|
6
|
+
* @module @hasna-internal/kai-storage/src/backend
|
|
7
|
+
*/
|
|
8
|
+
/** Allowed format for unit and table names: safe as a file name and as a SQL identifier segment without escaping. */
|
|
9
|
+
export declare const UNIT_NAME_RE: RegExp;
|
|
10
|
+
/**
|
|
11
|
+
* One registered backend. A backend owns exactly one medium and shares its
|
|
12
|
+
* lifecycle across all facets; facets are optional members — a backend that
|
|
13
|
+
* cannot serve a data kind simply omits it, and resolution fails loud instead.
|
|
14
|
+
*/
|
|
15
|
+
export interface StorageBackend {
|
|
16
|
+
/** Key-value operations; absent when this backend cannot serve them. */
|
|
17
|
+
readonly kv?: KvFacet;
|
|
18
|
+
/**
|
|
19
|
+
* Drain in-flight writes across all open units and release the medium.
|
|
20
|
+
* Idempotent; concurrent and repeated calls resolve once teardown finishes.
|
|
21
|
+
* @returns resolution after the medium is released.
|
|
22
|
+
*/
|
|
23
|
+
close(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
/** The key-value data shape: whole-unit snapshots plus per-record durable writes. */
|
|
26
|
+
export interface KvFacet {
|
|
27
|
+
/**
|
|
28
|
+
* Open one unit, creating it when the medium holds no trace of it yet
|
|
29
|
+
* (materialization may defer to the first write, but {@link KvUnit.loadAll}
|
|
30
|
+
* must immediately serve the empty shape). A version already stamped on the
|
|
31
|
+
* medium that differs from `descriptor.version` rejects with
|
|
32
|
+
* `version-mismatch`; a medium that cannot be parsed as this unit rejects
|
|
33
|
+
* with `malformed-medium`. Opening the same unit name twice without closing
|
|
34
|
+
* is a caller bug and rejects.
|
|
35
|
+
* @param descriptor - Static identity and shape of the unit to open.
|
|
36
|
+
* @returns the opened unit.
|
|
37
|
+
*/
|
|
38
|
+
open(descriptor: KvUnitDescriptor): Promise<KvUnit>;
|
|
39
|
+
}
|
|
40
|
+
/** Static identity and shape of one KV unit, projected from its owner's spec. */
|
|
41
|
+
export interface KvUnitDescriptor {
|
|
42
|
+
/** Unit name; must match {@link UNIT_NAME_RE}. Also the file-name / SQL-identifier segment. */
|
|
43
|
+
readonly name: string;
|
|
44
|
+
/** Unit format version; a non-negative integer stamped on the medium at first materialization. */
|
|
45
|
+
readonly version: number;
|
|
46
|
+
/** Table names; each must match {@link UNIT_NAME_RE}. */
|
|
47
|
+
readonly tables: readonly string[];
|
|
48
|
+
/** Whether this unit carries the global singleton slot. */
|
|
49
|
+
readonly hasGlobal: boolean;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* One opened unit. Values are opaque JSON to this layer: no schema, no
|
|
53
|
+
* events, no domain meaning. The unit does NOT serialize concurrent writes —
|
|
54
|
+
* write ordering is the caller's responsibility (the domain layer runs one
|
|
55
|
+
* write chain per unit); the unit only guarantees that each single call is
|
|
56
|
+
* atomic on the medium and durable once resolved (a crash after resolution
|
|
57
|
+
* followed by a re-open observes the write). Any call after {@link close}
|
|
58
|
+
* rejects with `closed`.
|
|
59
|
+
*/
|
|
60
|
+
export interface KvUnit {
|
|
61
|
+
/**
|
|
62
|
+
* Read the full current snapshot.
|
|
63
|
+
* @returns every table's records keyed by table name, plus the global
|
|
64
|
+
* singleton (`null` when never written or not declared).
|
|
65
|
+
*/
|
|
66
|
+
loadAll(): Promise<{
|
|
67
|
+
tables: Record<string, Record<string, unknown>>;
|
|
68
|
+
global: unknown;
|
|
69
|
+
}>;
|
|
70
|
+
/**
|
|
71
|
+
* Upsert one record durably. Overwrite semantics: an existing key is replaced.
|
|
72
|
+
* @param table - Declared table name.
|
|
73
|
+
* @param key - Record key; any string is safe (keys never reach file paths).
|
|
74
|
+
* @param value - Opaque JSON-serializable record.
|
|
75
|
+
* @returns resolution after durability.
|
|
76
|
+
*/
|
|
77
|
+
putRecord(table: string, key: string, value: unknown): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Delete one record durably. Idempotent: a missing key is a no-op.
|
|
80
|
+
* @param table - Declared table name.
|
|
81
|
+
* @param key - Record key.
|
|
82
|
+
* @returns resolution after durability.
|
|
83
|
+
*/
|
|
84
|
+
deleteRecord(table: string, key: string): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Write the global singleton durably. Only valid when the descriptor
|
|
87
|
+
* declared `hasGlobal`.
|
|
88
|
+
* @param value - Opaque JSON-serializable value.
|
|
89
|
+
* @returns resolution after durability.
|
|
90
|
+
*/
|
|
91
|
+
setGlobal(value: unknown): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Drain this unit's in-flight writes and release it. Idempotent.
|
|
94
|
+
* @returns resolution after the unit is released.
|
|
95
|
+
*/
|
|
96
|
+
close(): Promise<void>;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=backend.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error vocabulary for the storage hub and its backends.
|
|
3
|
+
* @module @hasna-internal/kai-storage/src/error
|
|
4
|
+
*/
|
|
5
|
+
/** Discriminant codes carried by every {@link StorageError}. */
|
|
6
|
+
export type StorageErrorCode = 'backend-not-found' | 'form-not-mounted' | 'duplicate-backend' | 'duplicate-mount' | 'version-mismatch' | 'malformed-medium' | 'closed';
|
|
7
|
+
/**
|
|
8
|
+
* Error thrown by the hub and by backend implementations. The `code` is the
|
|
9
|
+
* stable contract consumers may switch on; `message` is diagnostic prose.
|
|
10
|
+
*/
|
|
11
|
+
export declare class StorageError extends Error {
|
|
12
|
+
readonly code: StorageErrorCode;
|
|
13
|
+
readonly name = "StorageError";
|
|
14
|
+
/**
|
|
15
|
+
* @param code - Stable discriminant for the failure class.
|
|
16
|
+
* @param message - Human-readable diagnostic detail.
|
|
17
|
+
* @param options - Standard error options (`cause`).
|
|
18
|
+
*/
|
|
19
|
+
constructor(code: StorageErrorCode, message: string, options?: ErrorOptions);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage hub (`ctx.storage`): a named backend registry plus mounted
|
|
3
|
+
* data-form facilities. The hub itself performs no IO — backends own media,
|
|
4
|
+
* data forms (the domain layer first) own semantics.
|
|
5
|
+
* @module @hasna-internal/kai-storage
|
|
6
|
+
*/
|
|
7
|
+
import { Context, Service } from '@deepseek-ai/cordis';
|
|
8
|
+
import { BackendRegistry } from './registry.ts';
|
|
9
|
+
export { BackendRegistry } from './registry.ts';
|
|
10
|
+
export { StorageError } from './error.ts';
|
|
11
|
+
export type { StorageErrorCode } from './error.ts';
|
|
12
|
+
export { UNIT_NAME_RE } from './backend.ts';
|
|
13
|
+
export type { StorageBackend, KvFacet, KvUnit, KvUnitDescriptor } from './backend.ts';
|
|
14
|
+
/**
|
|
15
|
+
* Derive the Cordis lifecycle service that one named backend plugin provides.
|
|
16
|
+
* Domain-form providers inject these keys so activation cannot race backend
|
|
17
|
+
* registration even though callers continue resolving backends through the
|
|
18
|
+
* storage registry.
|
|
19
|
+
* @param name - Backend registry name.
|
|
20
|
+
* @returns the corresponding lifecycle-only service key.
|
|
21
|
+
*/
|
|
22
|
+
export declare function storageBackendServiceKey(name: string): string;
|
|
23
|
+
declare module '@deepseek-ai/cordis' {
|
|
24
|
+
interface Context {
|
|
25
|
+
storage: Storage;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Data forms mountable on the hub, keyed by form name. Form owners extend
|
|
30
|
+
* this map via declaration merging (the domain layer merges
|
|
31
|
+
* `domain: DomainFacility`) and mount the facility in their `apply`.
|
|
32
|
+
*/
|
|
33
|
+
export interface StorageForms {
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The storage hub service. Backends register under `backend`; data forms
|
|
37
|
+
* mount under their `StorageForms` key and are reached as `ctx.storage.<form>`.
|
|
38
|
+
*/
|
|
39
|
+
export declare class Storage extends Service {
|
|
40
|
+
/** Named backend table; multiple backends stay mounted side by side. */
|
|
41
|
+
readonly backend: BackendRegistry;
|
|
42
|
+
private readonly forms;
|
|
43
|
+
constructor(ctx: Context);
|
|
44
|
+
/**
|
|
45
|
+
* Mount a data-form facility on the hub. Mounting is an effect: the
|
|
46
|
+
* returned disposer unmounts the form.
|
|
47
|
+
* @param form - Form key declared in {@link StorageForms}.
|
|
48
|
+
* @param facility - The facility instance to expose.
|
|
49
|
+
* @returns the disposer that unmounts the form.
|
|
50
|
+
*/
|
|
51
|
+
mount<K extends keyof StorageForms>(form: K, facility: StorageForms[K]): () => void;
|
|
52
|
+
/**
|
|
53
|
+
* Resolve a mounted data form.
|
|
54
|
+
* @param form - Form key declared in {@link StorageForms}.
|
|
55
|
+
* @returns the mounted facility.
|
|
56
|
+
*/
|
|
57
|
+
form<K extends keyof StorageForms>(form: K): StorageForms[K];
|
|
58
|
+
/** Domain data form; present once the domain layer plugin is loaded. */
|
|
59
|
+
get domain(): StorageForms extends {
|
|
60
|
+
domain: infer D;
|
|
61
|
+
} ? D : never;
|
|
62
|
+
}
|
|
63
|
+
export default Storage;
|
|
64
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@hasna-internal/kai-storage`.
|
|
3
|
+
* @module @hasna-internal/kai-storage/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "storage-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
+
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Named backend registry of the storage hub.
|
|
3
|
+
* @module @hasna-internal/kai-storage/src/registry
|
|
4
|
+
*/
|
|
5
|
+
import type { StorageBackend } from './backend.ts';
|
|
6
|
+
/**
|
|
7
|
+
* Mutable name → backend table. Multiple backends stay mounted side by side;
|
|
8
|
+
* which backend serves which consumer is the consumer's configuration
|
|
9
|
+
* (e.g. the domain layer's route table), never a hub-global choice.
|
|
10
|
+
*/
|
|
11
|
+
export declare class BackendRegistry {
|
|
12
|
+
private readonly backends;
|
|
13
|
+
/**
|
|
14
|
+
* Register a named backend. Registration is an effect: the returned
|
|
15
|
+
* disposer removes the name. Disposal does NOT close the backend — the
|
|
16
|
+
* owning plugin closes it after unregistering.
|
|
17
|
+
* @param name - Backend name, e.g. `json` or `sqlite`.
|
|
18
|
+
* @param backend - The backend instance.
|
|
19
|
+
* @returns the disposer that unregisters the name.
|
|
20
|
+
*/
|
|
21
|
+
register(name: string, backend: StorageBackend): () => void;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a backend by name.
|
|
24
|
+
* @param name - Registered backend name.
|
|
25
|
+
* @returns the backend.
|
|
26
|
+
*/
|
|
27
|
+
get(name: string): StorageBackend;
|
|
28
|
+
/**
|
|
29
|
+
* Registered backend names, for diagnostics.
|
|
30
|
+
* @returns a snapshot array of names.
|
|
31
|
+
*/
|
|
32
|
+
names(): string[];
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=registry.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hasna-internal/kai-storage",
|
|
3
|
+
"description": "Storage hub (ctx.storage): named backend registry plus mounted data-form facilities for the DeepSeek Harness",
|
|
4
|
+
"version": "0.1.1-rc.2",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/storage/storage"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./src/*": "./src/*",
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"lib/index.js",
|
|
30
|
+
"lib/invariant.js",
|
|
31
|
+
"lib/types/**/*.d.ts"
|
|
32
|
+
],
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
36
|
+
"@hasna-internal/kai-invariants": "^0.1.1-rc.2"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@hasna-internal/kai-invariants": "^0.1.1-rc.2",
|
|
40
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
41
|
+
}
|
|
42
|
+
}
|