@visiblebase/plugin-usage 0.1.4

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/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # @visiblebase/plugin-usage
2
+
3
+ VisibleBase 官方 usage 记录插件。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pnpm add @visiblebase/plugin-usage
9
+ ```
10
+
11
+ ## 使用
12
+
13
+ ```ts
14
+ import { Base } from "@visiblebase/base";
15
+ import { usagePlugin } from "@visiblebase/plugin-usage";
16
+
17
+ const base = new Base({
18
+ plugins: [
19
+ usagePlugin({
20
+ record_errors: true,
21
+ }),
22
+ ],
23
+ });
24
+ ```
25
+
26
+ 插件会自动创建 `visiblebase_usage_events` 表,并通过 service hook 记录调用事件。
27
+
28
+ ## 路由
29
+
30
+ - `GET /api/admin/plugins/usage/events`
31
+ - `GET /api/admin/plugins/usage/summary`
32
+ - `GET /v1/plugins/usage/me`
33
+
34
+ `summary` 会按 product、service 和状态汇总调用次数。
package/bin/index.d.ts ADDED
@@ -0,0 +1,173 @@
1
+ /**
2
+ * VisibleBase 官方 Usage 插件。
3
+ *
4
+ * 通过全局 hook 记录 service 调用事件。
5
+ * 兼容 Node.js 和 Cloudflare Workers(使用 Web Crypto API)。
6
+ */
7
+ import type { BasePlugin } from "@visiblebase/core";
8
+ export interface UsagePluginOptions {
9
+ /** 是否记录失败调用 */
10
+ record_errors?: boolean;
11
+ }
12
+ /** Usage 事件表 */
13
+ export declare const usageEvents: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
14
+ name: "visiblebase_usage_events";
15
+ schema: undefined;
16
+ columns: {
17
+ event_id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
18
+ name: "event_id";
19
+ tableName: "visiblebase_usage_events";
20
+ dataType: "string";
21
+ columnType: "SQLiteText";
22
+ data: string;
23
+ driverParam: string;
24
+ notNull: true;
25
+ hasDefault: false;
26
+ isPrimaryKey: true;
27
+ isAutoincrement: false;
28
+ hasRuntimeDefault: false;
29
+ enumValues: [string, ...string[]];
30
+ baseColumn: never;
31
+ identity: undefined;
32
+ generated: undefined;
33
+ }, {}, {
34
+ length: number | undefined;
35
+ }>;
36
+ product_id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
37
+ name: "product_id";
38
+ tableName: "visiblebase_usage_events";
39
+ dataType: "string";
40
+ columnType: "SQLiteText";
41
+ data: string;
42
+ driverParam: string;
43
+ notNull: true;
44
+ hasDefault: false;
45
+ isPrimaryKey: false;
46
+ isAutoincrement: false;
47
+ hasRuntimeDefault: false;
48
+ enumValues: [string, ...string[]];
49
+ baseColumn: never;
50
+ identity: undefined;
51
+ generated: undefined;
52
+ }, {}, {
53
+ length: number | undefined;
54
+ }>;
55
+ user_id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
56
+ name: "user_id";
57
+ tableName: "visiblebase_usage_events";
58
+ dataType: "string";
59
+ columnType: "SQLiteText";
60
+ data: string;
61
+ driverParam: string;
62
+ notNull: true;
63
+ hasDefault: false;
64
+ isPrimaryKey: false;
65
+ isAutoincrement: false;
66
+ hasRuntimeDefault: false;
67
+ enumValues: [string, ...string[]];
68
+ baseColumn: never;
69
+ identity: undefined;
70
+ generated: undefined;
71
+ }, {}, {
72
+ length: number | undefined;
73
+ }>;
74
+ service: import("drizzle-orm/sqlite-core").SQLiteColumn<{
75
+ name: "service";
76
+ tableName: "visiblebase_usage_events";
77
+ dataType: "string";
78
+ columnType: "SQLiteText";
79
+ data: string;
80
+ driverParam: string;
81
+ notNull: true;
82
+ hasDefault: false;
83
+ isPrimaryKey: false;
84
+ isAutoincrement: false;
85
+ hasRuntimeDefault: false;
86
+ enumValues: [string, ...string[]];
87
+ baseColumn: never;
88
+ identity: undefined;
89
+ generated: undefined;
90
+ }, {}, {
91
+ length: number | undefined;
92
+ }>;
93
+ model_id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
94
+ name: "model_id";
95
+ tableName: "visiblebase_usage_events";
96
+ dataType: "string";
97
+ columnType: "SQLiteText";
98
+ data: string;
99
+ driverParam: string;
100
+ notNull: true;
101
+ hasDefault: false;
102
+ isPrimaryKey: false;
103
+ isAutoincrement: false;
104
+ hasRuntimeDefault: false;
105
+ enumValues: [string, ...string[]];
106
+ baseColumn: never;
107
+ identity: undefined;
108
+ generated: undefined;
109
+ }, {}, {
110
+ length: number | undefined;
111
+ }>;
112
+ status: import("drizzle-orm/sqlite-core").SQLiteColumn<{
113
+ name: "status";
114
+ tableName: "visiblebase_usage_events";
115
+ dataType: "string";
116
+ columnType: "SQLiteText";
117
+ data: string;
118
+ driverParam: string;
119
+ notNull: true;
120
+ hasDefault: false;
121
+ isPrimaryKey: false;
122
+ isAutoincrement: false;
123
+ hasRuntimeDefault: false;
124
+ enumValues: [string, ...string[]];
125
+ baseColumn: never;
126
+ identity: undefined;
127
+ generated: undefined;
128
+ }, {}, {
129
+ length: number | undefined;
130
+ }>;
131
+ metadata_json: import("drizzle-orm/sqlite-core").SQLiteColumn<{
132
+ name: "metadata_json";
133
+ tableName: "visiblebase_usage_events";
134
+ dataType: "string";
135
+ columnType: "SQLiteText";
136
+ data: string;
137
+ driverParam: string;
138
+ notNull: true;
139
+ hasDefault: false;
140
+ isPrimaryKey: false;
141
+ isAutoincrement: false;
142
+ hasRuntimeDefault: false;
143
+ enumValues: [string, ...string[]];
144
+ baseColumn: never;
145
+ identity: undefined;
146
+ generated: undefined;
147
+ }, {}, {
148
+ length: number | undefined;
149
+ }>;
150
+ created_at: import("drizzle-orm/sqlite-core").SQLiteColumn<{
151
+ name: "created_at";
152
+ tableName: "visiblebase_usage_events";
153
+ dataType: "string";
154
+ columnType: "SQLiteText";
155
+ data: string;
156
+ driverParam: string;
157
+ notNull: true;
158
+ hasDefault: false;
159
+ isPrimaryKey: false;
160
+ isAutoincrement: false;
161
+ hasRuntimeDefault: false;
162
+ enumValues: [string, ...string[]];
163
+ baseColumn: never;
164
+ identity: undefined;
165
+ generated: undefined;
166
+ }, {}, {
167
+ length: number | undefined;
168
+ }>;
169
+ };
170
+ dialect: "sqlite";
171
+ }>;
172
+ export declare function usagePlugin(options?: UsagePluginOptions): BasePlugin;
173
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAW,MAAM,mBAAmB,CAAC;AAE7D,MAAM,WAAW,kBAAkB;IACjC,eAAe;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,gBAAgB;AAChB,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAStB,CAAC;AAEH,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,UAAU,CAwDxE"}
package/bin/index.js ADDED
@@ -0,0 +1,115 @@
1
+ /**
2
+ * VisibleBase 官方 Usage 插件。
3
+ *
4
+ * 通过全局 hook 记录 service 调用事件。
5
+ * 兼容 Node.js 和 Cloudflare Workers(使用 Web Crypto API)。
6
+ */
7
+ import { sqliteTable, text } from "drizzle-orm/sqlite-core";
8
+ /** Usage 事件表 */
9
+ export const usageEvents = sqliteTable("visiblebase_usage_events", {
10
+ event_id: text("event_id").primaryKey(),
11
+ product_id: text("product_id").notNull(),
12
+ user_id: text("user_id").notNull(),
13
+ service: text("service").notNull(),
14
+ model_id: text("model_id").notNull(),
15
+ status: text("status").notNull(),
16
+ metadata_json: text("metadata_json").notNull(),
17
+ created_at: text("created_at").notNull(),
18
+ });
19
+ export function usagePlugin(options = {}) {
20
+ return {
21
+ id: "usage",
22
+ name: "Usage",
23
+ version: "0.1.0",
24
+ schema: { events: usageEvents },
25
+ install(ctx) {
26
+ const events = ctx.table("events");
27
+ ctx.hook.after(async (serviceCtx) => {
28
+ if (!shouldRecordUsage(serviceCtx))
29
+ return;
30
+ await events.insert(createUsageEvent(serviceCtx, "success"));
31
+ });
32
+ if (options.record_errors) {
33
+ ctx.hook.onError(async (serviceCtx) => {
34
+ if (!shouldRecordUsage(serviceCtx))
35
+ return;
36
+ await events.insert(createUsageEvent(serviceCtx, "error"));
37
+ });
38
+ }
39
+ ctx.route({
40
+ method: "GET",
41
+ path: "/events",
42
+ auth: "admin",
43
+ async handler(requestCtx) {
44
+ return requestCtx.jsonResponse({ items: await events.select() });
45
+ },
46
+ });
47
+ ctx.route({
48
+ method: "GET",
49
+ path: "/summary",
50
+ auth: "admin",
51
+ async handler(requestCtx) {
52
+ return requestCtx.jsonResponse({
53
+ items: summarizeUsage(await events.select()),
54
+ });
55
+ },
56
+ });
57
+ ctx.route({
58
+ method: "GET",
59
+ path: "/me",
60
+ auth: "user",
61
+ async handler(requestCtx) {
62
+ return requestCtx.jsonResponse({
63
+ items: await events.select({
64
+ user_id: requestCtx.user?.user_id ?? "",
65
+ product_id: requestCtx.product?.product_id ?? "",
66
+ }),
67
+ });
68
+ },
69
+ });
70
+ },
71
+ };
72
+ }
73
+ /**
74
+ * 只记录真实用户侧调用。
75
+ *
76
+ * 管理端操作没有 user/product 上下文,usage 插件自己的查询也不应反过来
77
+ * 产生 usage 事件,否则统计接口会污染自身结果。
78
+ */
79
+ function shouldRecordUsage(ctx) {
80
+ return Boolean(ctx.user?.user_id && ctx.product?.product_id && ctx.service?.id !== "usage");
81
+ }
82
+ function createUsageEvent(ctx, status) {
83
+ return {
84
+ event_id: `usage_${randomId()}`,
85
+ product_id: ctx.product?.product_id ?? "",
86
+ user_id: ctx.user?.user_id ?? "",
87
+ service: ctx.service?.id ?? "",
88
+ model_id: ctx.variant?.id ?? "",
89
+ status,
90
+ metadata_json: JSON.stringify({
91
+ variant: ctx.variant?.id,
92
+ started_at: ctx.started_at?.toISOString(),
93
+ ended_at: ctx.ended_at?.toISOString(),
94
+ error: ctx.error?.message,
95
+ }),
96
+ created_at: new Date().toISOString(),
97
+ };
98
+ }
99
+ function summarizeUsage(rows) {
100
+ const byKey = new Map();
101
+ for (const row of rows) {
102
+ const key = `${row.product_id}\u0000${row.service}\u0000${row.status}`;
103
+ const current = byKey.get(key) ?? { product_id: row.product_id, service: row.service, status: row.status, count: 0 };
104
+ current.count += 1;
105
+ byKey.set(key, current);
106
+ }
107
+ return [...byKey.values()].sort((a, b) => `${a.product_id}:${a.service}:${a.status}`.localeCompare(`${b.product_id}:${b.service}:${b.status}`));
108
+ }
109
+ /** 生成随机 ID(兼容 Node 和 Workers) */
110
+ function randomId() {
111
+ const buf = new Uint8Array(12);
112
+ crypto.getRandomValues(buf);
113
+ return btoa(String.fromCharCode(...buf)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
114
+ }
115
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAQ5D,gBAAgB;AAChB,MAAM,CAAC,MAAM,WAAW,GAAG,WAAW,CAAC,0BAA0B,EAAE;IACjE,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,UAAU,EAAE;IACvC,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;IACxC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;IAClC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE;IAClC,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE;IACpC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE;IAChC,aAAa,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC,OAAO,EAAE;IAC9C,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,OAAO,EAAE;CACzC,CAAC,CAAC;AAEH,MAAM,UAAU,WAAW,CAAC,UAA8B,EAAE;IAC1D,OAAO;QACL,EAAE,EAAE,OAAO;QACX,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,OAAO;QAChB,MAAM,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE;QAC/B,OAAO,CAAC,GAAG;YACT,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAgB,QAAQ,CAAC,CAAC;YAElD,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE;gBAClC,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC;oBAAE,OAAO;gBAC3C,MAAM,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC;YAC/D,CAAC,CAAC,CAAC;YAEH,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;gBAC1B,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE;oBACpC,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC;wBAAE,OAAO;oBAC3C,MAAM,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;gBAC7D,CAAC,CAAC,CAAC;YACL,CAAC;YAED,GAAG,CAAC,KAAK,CAAC;gBACR,MAAM,EAAE,KAAK;gBACb,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE,OAAO;gBACb,KAAK,CAAC,OAAO,CAAC,UAAU;oBACtB,OAAO,UAAU,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;gBACnE,CAAC;aACF,CAAC,CAAC;YAEH,GAAG,CAAC,KAAK,CAAC;gBACR,MAAM,EAAE,KAAK;gBACb,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,OAAO;gBACb,KAAK,CAAC,OAAO,CAAC,UAAU;oBACtB,OAAO,UAAU,CAAC,YAAY,CAAC;wBAC7B,KAAK,EAAE,cAAc,CAAC,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;qBAC7C,CAAC,CAAC;gBACL,CAAC;aACF,CAAC,CAAC;YAEH,GAAG,CAAC,KAAK,CAAC;gBACR,MAAM,EAAE,KAAK;gBACb,IAAI,EAAE,KAAK;gBACX,IAAI,EAAE,MAAM;gBACZ,KAAK,CAAC,OAAO,CAAC,UAAU;oBACtB,OAAO,UAAU,CAAC,YAAY,CAAC;wBAC7B,KAAK,EAAE,MAAM,MAAM,CAAC,MAAM,CAAC;4BACzB,OAAO,EAAE,UAAU,CAAC,IAAI,EAAE,OAAO,IAAI,EAAE;4BACvC,UAAU,EAAE,UAAU,CAAC,OAAO,EAAE,UAAU,IAAI,EAAE;yBACjD,CAAC;qBACH,CAAC,CAAC;gBACL,CAAC;aACF,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAS,iBAAiB,CAAC,GAAY;IACrC,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,IAAI,GAAG,CAAC,OAAO,EAAE,UAAU,IAAI,GAAG,CAAC,OAAO,EAAE,EAAE,KAAK,OAAO,CAAC,CAAC;AAC9F,CAAC;AAQD,SAAS,gBAAgB,CAAC,GAAY,EAAE,MAA2B;IACjE,OAAO;QACL,QAAQ,EAAE,SAAS,QAAQ,EAAE,EAAE;QAC/B,UAAU,EAAE,GAAG,CAAC,OAAO,EAAE,UAAU,IAAI,EAAE;QACzC,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,IAAI,EAAE;QAChC,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE;QAC9B,QAAQ,EAAE,GAAG,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE;QAC/B,MAAM;QACN,aAAa,EAAE,IAAI,CAAC,SAAS,CAAC;YAC5B,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,EAAE;YACxB,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,WAAW,EAAE;YACzC,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE;YACrC,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,OAAO;SAC1B,CAAC;QACF,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACrC,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,IAAqB;IAC3C,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkF,CAAC;IACxG,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC,UAAU,SAAS,GAAG,CAAC,OAAO,SAAS,GAAG,CAAC,MAAM,EAAE,CAAC;QACvE,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QACrH,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC;QACnB,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAC1B,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACvC,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,CACrG,CAAC;AACJ,CAAC;AAED,iCAAiC;AACjC,SAAS,QAAQ;IACf,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IAC/B,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC;IAC5B,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACtG,CAAC"}
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@visiblebase/plugin-usage",
3
+ "version": "0.1.4",
4
+ "description": "VisibleBase usage plugin for recording service calls.",
5
+ "type": "module",
6
+ "main": "./bin/index.js",
7
+ "module": "./bin/index.js",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./bin/index.d.ts",
11
+ "import": "./bin/index.js"
12
+ }
13
+ },
14
+ "types": "./bin/index.d.ts",
15
+ "files": [
16
+ "bin"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/wangenius/visiblebase.git",
24
+ "directory": "packages/plugin-usage"
25
+ },
26
+ "homepage": "https://github.com/wangenius/visiblebase#readme",
27
+ "bugs": {
28
+ "url": "https://github.com/wangenius/visiblebase/issues"
29
+ },
30
+ "keywords": [
31
+ "visiblebase",
32
+ "usage",
33
+ "plugin",
34
+ "serverbase"
35
+ ],
36
+ "engines": {
37
+ "node": ">=22.13.0"
38
+ },
39
+ "license": "MIT",
40
+ "devDependencies": {
41
+ "@types/node": "^22.0.0",
42
+ "typescript": "^5.7.0",
43
+ "@visiblebase/node": "0.1.2"
44
+ },
45
+ "dependencies": {
46
+ "drizzle-orm": "^0.45.2",
47
+ "@visiblebase/core": "0.2.2"
48
+ },
49
+ "scripts": {
50
+ "build": "tsc",
51
+ "dev": "tsc --watch",
52
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
53
+ "test": "tsc && tsc -p tsconfig.test.json && node --test test/*.test.mjs"
54
+ }
55
+ }