@mushinjs/plugin-storage 0.0.0-stage → 0.2.0-next.20261112165327
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/dist/index.d.ts +30 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -3
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Setobox
|
|
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/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Context, Service, z } from "@mushinjs/core";
|
|
2
|
+
declare module '@mushinjs/core' {
|
|
3
|
+
interface Context {
|
|
4
|
+
storage: Storage;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* JSON documents under the data directory. A key such as `agent/abc` maps to `data/agent/abc.json`;
|
|
9
|
+
* logs use `.jsonl` files through `append` and `tail`.
|
|
10
|
+
*/
|
|
11
|
+
export declare class Storage extends Service {
|
|
12
|
+
static Config: z.ZodObject<{
|
|
13
|
+
dir: z.ZodDefault<z.ZodString>;
|
|
14
|
+
}, z.core.$strip>;
|
|
15
|
+
readonly dir: string;
|
|
16
|
+
constructor(ctx: Context, config: z.infer<typeof Storage.Config>);
|
|
17
|
+
private file;
|
|
18
|
+
get<T>(key: string): Promise<T | undefined>;
|
|
19
|
+
set(key: string, value: unknown): Promise<void>;
|
|
20
|
+
delete(key: string): Promise<void>;
|
|
21
|
+
/** Keys of the JSON documents directly under a prefix, such as `agent`. */
|
|
22
|
+
list(prefix: string): Promise<string[]>;
|
|
23
|
+
/** Appends one JSON line to `<key>.jsonl`. */
|
|
24
|
+
append(key: string, value: unknown): Promise<void>;
|
|
25
|
+
/** The last `limit` entries of `<key>.jsonl`, oldest first. */
|
|
26
|
+
tail<T>(key: string, limit: number): Promise<T[]>;
|
|
27
|
+
/** Path of a storage key relative to the data directory, for display. */
|
|
28
|
+
describe(key: string): string;
|
|
29
|
+
}
|
|
30
|
+
export { Storage as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { appendFile, mkdir, readFile, readdir, rm } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { Service, field, readJson, writeJsonAtomic, z } from "@mushinjs/core";
|
|
4
|
+
// src/index.ts
|
|
5
|
+
const keyPattern = /^[A-Za-z0-9_-]+(\/[A-Za-z0-9_.-]+)*$/;
|
|
6
|
+
/**
|
|
7
|
+
* JSON documents under the data directory. A key such as `agent/abc` maps to `data/agent/abc.json`;
|
|
8
|
+
* logs use `.jsonl` files through `append` and `tail`.
|
|
9
|
+
*/
|
|
10
|
+
var Storage = class extends Service {
|
|
11
|
+
static Config = z.object({ dir: z.string().default("data").meta(field("Data directory", "Relative to the instance directory", { "zh-CN": {
|
|
12
|
+
title: "数据目录",
|
|
13
|
+
description: "相对于实例目录"
|
|
14
|
+
} })) });
|
|
15
|
+
dir;
|
|
16
|
+
constructor(ctx, config) {
|
|
17
|
+
super(ctx, "storage");
|
|
18
|
+
this.dir = resolve(ctx.baseDir ?? process.cwd(), config.dir);
|
|
19
|
+
}
|
|
20
|
+
file(key, ext) {
|
|
21
|
+
if (!keyPattern.test(key) || key.split("/").includes("..")) throw new Error(`invalid storage key "${key}"`);
|
|
22
|
+
return join(this.dir, ...key.split("/")) + ext;
|
|
23
|
+
}
|
|
24
|
+
async get(key) {
|
|
25
|
+
return readJson(this.file(key, ".json"));
|
|
26
|
+
}
|
|
27
|
+
async set(key, value) {
|
|
28
|
+
return writeJsonAtomic(this.file(key, ".json"), value);
|
|
29
|
+
}
|
|
30
|
+
async delete(key) {
|
|
31
|
+
await rm(this.file(key, ".json"), { force: true });
|
|
32
|
+
}
|
|
33
|
+
/** Keys of the JSON documents directly under a prefix, such as `agent`. */
|
|
34
|
+
async list(prefix) {
|
|
35
|
+
const dir = join(this.dir, ...prefix.split("/"));
|
|
36
|
+
return (await readdir(dir).catch(() => [])).filter((name) => name.endsWith(".json")).map((name) => `${prefix}/${name.slice(0, -5)}`).sort();
|
|
37
|
+
}
|
|
38
|
+
/** Appends one JSON line to `<key>.jsonl`. */
|
|
39
|
+
async append(key, value) {
|
|
40
|
+
const file = this.file(key, ".jsonl");
|
|
41
|
+
await mkdir(dirname(file), { recursive: true });
|
|
42
|
+
await appendFile(file, JSON.stringify(value) + "\n", "utf8");
|
|
43
|
+
}
|
|
44
|
+
/** The last `limit` entries of `<key>.jsonl`, oldest first. */
|
|
45
|
+
async tail(key, limit) {
|
|
46
|
+
let text;
|
|
47
|
+
try {
|
|
48
|
+
text = await readFile(this.file(key, ".jsonl"), "utf8");
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error?.code === "ENOENT") return [];
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
return text.split("\n").filter(Boolean).slice(-limit).flatMap((line) => {
|
|
54
|
+
try {
|
|
55
|
+
return [JSON.parse(line)];
|
|
56
|
+
} catch {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/** Path of a storage key relative to the data directory, for display. */
|
|
62
|
+
describe(key) {
|
|
63
|
+
return relative(this.dir, this.file(key, ".json")).split(sep).join("/");
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
export { Storage, Storage as default };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { appendFile, mkdir, readdir, readFile, rm } from 'node:fs/promises'\r\nimport { dirname, join, relative, resolve, sep } from 'node:path'\r\nimport { Context, field, readJson, Service, writeJsonAtomic, z } from '@mushinjs/core'\r\n\r\ndeclare module '@mushinjs/core' {\r\n interface Context {\r\n storage: Storage\r\n }\r\n}\r\n\r\nconst keyPattern = /^[A-Za-z0-9_-]+(\\/[A-Za-z0-9_.-]+)*$/\r\n\r\n/**\r\n * JSON documents under the data directory. A key such as `agent/abc` maps to `data/agent/abc.json`;\r\n * logs use `.jsonl` files through `append` and `tail`.\r\n */\r\nexport class Storage extends Service {\r\n static Config = z.object({\r\n dir: z.string().default('data').meta(field('Data directory', 'Relative to the instance directory', {\r\n 'zh-CN': { title: '数据目录', description: '相对于实例目录' },\r\n })),\r\n })\r\n\r\n readonly dir: string\r\n\r\n constructor(ctx: Context, config: z.infer<typeof Storage.Config>) {\r\n super(ctx, 'storage')\r\n this.dir = resolve(ctx.baseDir ?? process.cwd(), config.dir)\r\n }\r\n\r\n private file(key: string, ext: '.json' | '.jsonl') {\r\n if (!keyPattern.test(key) || key.split('/').includes('..')) {\r\n throw new Error(`invalid storage key \"${key}\"`)\r\n }\r\n return join(this.dir, ...key.split('/')) + ext\r\n }\r\n\r\n async get<T>(key: string): Promise<T | undefined> {\r\n return readJson<T>(this.file(key, '.json'))\r\n }\r\n\r\n async set(key: string, value: unknown) {\r\n return writeJsonAtomic(this.file(key, '.json'), value)\r\n }\r\n\r\n async delete(key: string) {\r\n await rm(this.file(key, '.json'), { force: true })\r\n }\r\n\r\n /** Keys of the JSON documents directly under a prefix, such as `agent`. */\r\n async list(prefix: string) {\r\n const dir = join(this.dir, ...prefix.split('/'))\r\n const names = await readdir(dir).catch(() => [] as string[])\r\n return names\r\n .filter((name) => name.endsWith('.json'))\r\n .map((name) => `${prefix}/${name.slice(0, -'.json'.length)}`)\r\n .sort()\r\n }\r\n\r\n /** Appends one JSON line to `<key>.jsonl`. */\r\n async append(key: string, value: unknown) {\r\n const file = this.file(key, '.jsonl')\r\n await mkdir(dirname(file), { recursive: true })\r\n await appendFile(file, JSON.stringify(value) + '\\n', 'utf8')\r\n }\r\n\r\n /** The last `limit` entries of `<key>.jsonl`, oldest first. */\r\n async tail<T>(key: string, limit: number): Promise<T[]> {\r\n let text: string\r\n try {\r\n text = await readFile(this.file(key, '.jsonl'), 'utf8')\r\n } catch (error: any) {\r\n if (error?.code === 'ENOENT') return []\r\n throw error\r\n }\r\n const lines = text.split('\\n').filter(Boolean)\r\n return lines.slice(-limit).flatMap((line) => {\r\n try {\r\n return [JSON.parse(line) as T]\r\n } catch {\r\n return []\r\n }\r\n })\r\n }\r\n\r\n /** Path of a storage key relative to the data directory, for display. */\r\n describe(key: string) {\r\n return relative(this.dir, this.file(key, '.json')).split(sep).join('/')\r\n }\r\n}\r\n\r\nexport default Storage\r\n"],"mappings":";;;;AAUA,MAAM,aAAa;;;;;AAMnB,IAAa,UAAb,cAA6B,QAAQ;CACnC,OAAO,SAAS,EAAE,OAAO,EACvB,KAAK,EAAE,OAAO,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,KAAK,MAAM,kBAAkB,sCAAsC,EACjG,SAAS;EAAE,OAAO;EAAQ,aAAa;CAAU,EACnD,CAAC,CAAC,EACJ,CAAC;CAED;CAEA,YAAY,KAAc,QAAwC;EAChE,MAAM,KAAK,SAAS;EACpB,KAAK,MAAM,QAAQ,IAAI,WAAW,QAAQ,IAAI,GAAG,OAAO,GAAG;CAC7D;CAEA,KAAa,KAAa,KAAyB;EACjD,IAAI,CAAC,WAAW,KAAK,GAAG,KAAK,IAAI,MAAM,GAAG,CAAC,CAAC,SAAS,IAAI,GACvD,MAAM,IAAI,MAAM,wBAAwB,IAAI,EAAE;EAEhD,OAAO,KAAK,KAAK,KAAK,GAAG,IAAI,MAAM,GAAG,CAAC,IAAI;CAC7C;CAEA,MAAM,IAAO,KAAqC;EAChD,OAAO,SAAY,KAAK,KAAK,KAAK,OAAO,CAAC;CAC5C;CAEA,MAAM,IAAI,KAAa,OAAgB;EACrC,OAAO,gBAAgB,KAAK,KAAK,KAAK,OAAO,GAAG,KAAK;CACvD;CAEA,MAAM,OAAO,KAAa;EACxB,MAAM,GAAG,KAAK,KAAK,KAAK,OAAO,GAAG,EAAE,OAAO,KAAK,CAAC;CACnD;;CAGA,MAAM,KAAK,QAAgB;EACzB,MAAM,MAAM,KAAK,KAAK,KAAK,GAAG,OAAO,MAAM,GAAG,CAAC;EAE/C,QAAO,MADa,QAAQ,GAAG,CAAC,CAAC,YAAY,CAAC,CAAa,EAAA,CAExD,QAAQ,SAAS,KAAK,SAAS,OAAO,CAAC,CAAC,CACxC,KAAK,SAAS,GAAG,OAAO,GAAG,KAAK,MAAM,GAAG,EAAe,GAAG,CAAC,CAC5D,KAAK;CACV;;CAGA,MAAM,OAAO,KAAa,OAAgB;EACxC,MAAM,OAAO,KAAK,KAAK,KAAK,QAAQ;EACpC,MAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EAC9C,MAAM,WAAW,MAAM,KAAK,UAAU,KAAK,IAAI,MAAM,MAAM;CAC7D;;CAGA,MAAM,KAAQ,KAAa,OAA6B;EACtD,IAAI;EACJ,IAAI;GACF,OAAO,MAAM,SAAS,KAAK,KAAK,KAAK,QAAQ,GAAG,MAAM;EACxD,SAAS,OAAY;GACnB,IAAI,OAAO,SAAS,UAAU,OAAO,CAAC;GACtC,MAAM;EACR;EAEA,OADc,KAAK,MAAM,IAAI,CAAC,CAAC,OAAO,OAC3B,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,SAAS;GAC3C,IAAI;IACF,OAAO,CAAC,KAAK,MAAM,IAAI,CAAM;GAC/B,QAAQ;IACN,OAAO,CAAC;GACV;EACF,CAAC;CACH;;CAGA,SAAS,KAAa;EACpB,OAAO,SAAS,KAAK,KAAK,KAAK,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,GAAG;CACxE;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mushinjs/plugin-storage",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0-next.20261112165327",
|
|
4
|
+
"description": "JSON document storage",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/mushinjs/mushin.git",
|
|
10
|
+
"directory": "packages/plugin-storage"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"mushin-plugin"
|
|
27
|
+
],
|
|
28
|
+
"mushin": {
|
|
29
|
+
"kind": "app",
|
|
30
|
+
"essential": true,
|
|
31
|
+
"displayName": {
|
|
32
|
+
"en": "Storage",
|
|
33
|
+
"zh-CN": "存储"
|
|
34
|
+
},
|
|
35
|
+
"description": {
|
|
36
|
+
"en": "Stores providers, bots and sessions as JSON files",
|
|
37
|
+
"zh-CN": "以 JSON 文件保存提供商、机器人与会话"
|
|
38
|
+
},
|
|
39
|
+
"provides": [
|
|
40
|
+
"storage"
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@mushinjs/core": "0.2.0-next.20261112165327"
|
|
45
|
+
}
|
|
6
46
|
}
|
package/README.md
DELETED