@omni-api/plugin-orm 0.0.1
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 +49 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +89 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @omni/plugin-orm
|
|
2
|
+
|
|
3
|
+
OmniAPI 数据库事务中间件,**不绑定具体 ORM**。
|
|
4
|
+
|
|
5
|
+
## 核心概念
|
|
6
|
+
- `DataSource`:约定接口(`client` + `withTransaction` + `health`)
|
|
7
|
+
- `attachDataSource(ds)`:把 DataSource 挂到 ctx(全局或 router 级中间件)
|
|
8
|
+
- `transactional()`:把 handler 包进事务
|
|
9
|
+
- `getTx(ctx)`:在 handler / service 中获取当前事务/客户端
|
|
10
|
+
|
|
11
|
+
## 用法
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { PrismaClient } from '@prisma/client';
|
|
15
|
+
import { attachDataSource, transactional, getTx, prismaDataSource } from '@omni/plugin-orm';
|
|
16
|
+
|
|
17
|
+
const ds = prismaDataSource(new PrismaClient());
|
|
18
|
+
|
|
19
|
+
defineProcedure({
|
|
20
|
+
name: 'order.create',
|
|
21
|
+
middleware: [attachDataSource(ds), transactional()],
|
|
22
|
+
handler: async ({ input, ctx }) => {
|
|
23
|
+
const db = getTx<typeof ds.client>(ctx);
|
|
24
|
+
const order = await db.order.create({ data: ... });
|
|
25
|
+
await db.audit.create({ data: { event: 'order_created', orderId: order.id } });
|
|
26
|
+
return order;
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 内置适配器
|
|
32
|
+
- `prismaDataSource(prisma)` — Prisma
|
|
33
|
+
- `drizzleDataSource(db)` — Drizzle
|
|
34
|
+
- `createMemoryDataSource(initial)` — 内存(仅示例 / 单测)
|
|
35
|
+
- 自己实现 `DataSource` 接口适配任何驱动
|
|
36
|
+
|
|
37
|
+
## getTx 设计
|
|
38
|
+
|
|
39
|
+
通过 `AsyncLocalStorage` 自动透传事务,**业务代码不需要传 tx 参数**:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
// service.ts
|
|
43
|
+
function userService_create(ctx, data) {
|
|
44
|
+
const db = getTx(ctx); // 自动拿到事务/非事务客户端
|
|
45
|
+
return db.user.create(...);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 上层是否包了 transactional() 都不影响 service 写法
|
|
49
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Middleware, Context } from '@omni-api/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 数据源约定:与具体 ORM 解耦的最小接口。
|
|
5
|
+
*
|
|
6
|
+
* 任何 ORM/驱动都可以实现这个接口被 OmniAPI 使用:
|
|
7
|
+
* - Prisma:`{ withTransaction: async (fn) => prisma.$transaction(fn), client: prisma }`
|
|
8
|
+
* - Drizzle:`{ withTransaction: async (fn) => db.transaction(fn), client: db }`
|
|
9
|
+
* - 原生 pg:`{ withTransaction: async (fn) => pool.connect()...., client: pool }`
|
|
10
|
+
*
|
|
11
|
+
* @template TClient ORM 的根客户端类型(例如 PrismaClient / DrizzleDB)
|
|
12
|
+
* @template TTx 事务上下文类型(通常是 client 在 transaction 中的同形版本)
|
|
13
|
+
*/
|
|
14
|
+
interface DataSource<TClient = unknown, TTx = TClient> {
|
|
15
|
+
/** 根客户端 —— 不需要事务时直接用这个 */
|
|
16
|
+
client: TClient;
|
|
17
|
+
/**
|
|
18
|
+
* 在事务中执行 fn;fn 抛错则回滚。
|
|
19
|
+
* 实现方需保证:fn 看到的 tx 对象是事务范围内的句柄。
|
|
20
|
+
*/
|
|
21
|
+
withTransaction<R>(fn: (tx: TTx) => Promise<R>): Promise<R>;
|
|
22
|
+
/** 健康检查(可选实现) */
|
|
23
|
+
health?(): Promise<boolean>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 在中间件 / handler 中获取当前事务对象。
|
|
28
|
+
*
|
|
29
|
+
* - 如果在 transactional() 中间件内:返回事务句柄
|
|
30
|
+
* - 否则:返回 dataSource.client(非事务模式)
|
|
31
|
+
*
|
|
32
|
+
* 这样业务代码可以无脑写:
|
|
33
|
+
* ```ts
|
|
34
|
+
* const db = getTx<DBClient>(ctx);
|
|
35
|
+
* await db.user.create(...);
|
|
36
|
+
* ```
|
|
37
|
+
* 不论外层是否包了事务,都不需要改。
|
|
38
|
+
*/
|
|
39
|
+
declare function getTx<TTx = unknown>(ctx: Context): TTx;
|
|
40
|
+
/**
|
|
41
|
+
* 把 DataSource 挂到 ctx —— 通常作为 App 全局中间件。
|
|
42
|
+
*
|
|
43
|
+
* 这一步不开启事务,仅让 getTx() 在非事务路径下也能拿到 client。
|
|
44
|
+
*/
|
|
45
|
+
declare function attachDataSource(ds: DataSource): Middleware;
|
|
46
|
+
/**
|
|
47
|
+
* 事务中间件:把 handler(含其调用栈中所有 getTx)跑在一个数据库事务里。
|
|
48
|
+
*
|
|
49
|
+
* 用法:
|
|
50
|
+
* ```ts
|
|
51
|
+
* defineProcedure({
|
|
52
|
+
* name: 'order.create',
|
|
53
|
+
* middleware: [auth(), transactional()],
|
|
54
|
+
* handler: async ({ input, ctx }) => {
|
|
55
|
+
* const db = getTx<DBClient>(ctx);
|
|
56
|
+
* await db.order.create({ data: ... });
|
|
57
|
+
* await db.audit.log({ ... }); // 同一个事务
|
|
58
|
+
* },
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
declare function transactional(): Middleware;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 内存版 DataSource —— 仅供示例 / 单测使用。
|
|
66
|
+
*
|
|
67
|
+
* 提供一个最小可工作的 DataSource:
|
|
68
|
+
* - client 是一个普通对象(业务可任意往里塞数据/方法)
|
|
69
|
+
* - withTransaction 用「快照 + 回滚」语义模拟事务
|
|
70
|
+
*
|
|
71
|
+
* 真实场景请用 Prisma/Drizzle/原生驱动的适配器。
|
|
72
|
+
*/
|
|
73
|
+
declare function createMemoryDataSource<TClient extends object = Record<string, unknown>>(initial: TClient): DataSource<TClient, TClient>;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Prisma 适配器示例(不引入 prisma 依赖;通过结构类型适配)。
|
|
77
|
+
*/
|
|
78
|
+
interface PrismaLike {
|
|
79
|
+
$transaction<R>(fn: (tx: unknown) => Promise<R>): Promise<R>;
|
|
80
|
+
}
|
|
81
|
+
declare function prismaDataSource<TClient extends PrismaLike>(client: TClient): DataSource<TClient, TClient>;
|
|
82
|
+
/**
|
|
83
|
+
* Drizzle 适配器示例。
|
|
84
|
+
*/
|
|
85
|
+
interface DrizzleLike {
|
|
86
|
+
transaction<R>(fn: (tx: unknown) => Promise<R>): Promise<R>;
|
|
87
|
+
}
|
|
88
|
+
declare function drizzleDataSource<TClient extends DrizzleLike>(client: TClient): DataSource<TClient, TClient>;
|
|
89
|
+
|
|
90
|
+
export { type DataSource, type DrizzleLike, type PrismaLike, attachDataSource, createMemoryDataSource, drizzleDataSource, getTx, prismaDataSource, transactional };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
|
+
|
|
3
|
+
// src/transactional.ts
|
|
4
|
+
var txStore = new AsyncLocalStorage();
|
|
5
|
+
function getTx(ctx) {
|
|
6
|
+
const inTx = txStore.getStore();
|
|
7
|
+
if (inTx !== void 0) return inTx;
|
|
8
|
+
const ds = ctx.state.__dataSource;
|
|
9
|
+
if (!ds) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
"getTx: no DataSource attached. Did you forget to register dataSourcePlugin?"
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
return ds.client;
|
|
15
|
+
}
|
|
16
|
+
function attachDataSource(ds) {
|
|
17
|
+
return async (ctx, next) => {
|
|
18
|
+
ctx.state.__dataSource = ds;
|
|
19
|
+
return next();
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function transactional() {
|
|
23
|
+
return async (ctx, next) => {
|
|
24
|
+
const ds = ctx.state.__dataSource;
|
|
25
|
+
if (!ds) {
|
|
26
|
+
throw new Error("transactional: DataSource not attached. Use attachDataSource() first.");
|
|
27
|
+
}
|
|
28
|
+
return ds.withTransaction(async (tx) => {
|
|
29
|
+
return txStore.run(tx, () => next());
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/memory-datasource.ts
|
|
35
|
+
function createMemoryDataSource(initial) {
|
|
36
|
+
const client = initial;
|
|
37
|
+
return {
|
|
38
|
+
client,
|
|
39
|
+
async withTransaction(fn) {
|
|
40
|
+
const snapshot = structuredClone(client);
|
|
41
|
+
try {
|
|
42
|
+
return await fn(client);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
for (const k of Object.keys(client)) delete client[k];
|
|
45
|
+
Object.assign(client, snapshot);
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
async health() {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/adapters.ts
|
|
56
|
+
function prismaDataSource(client) {
|
|
57
|
+
return {
|
|
58
|
+
client,
|
|
59
|
+
withTransaction(fn) {
|
|
60
|
+
return client.$transaction((tx) => fn(tx));
|
|
61
|
+
},
|
|
62
|
+
async health() {
|
|
63
|
+
try {
|
|
64
|
+
const c = client;
|
|
65
|
+
if (typeof c.$queryRaw === "function") {
|
|
66
|
+
await c.$queryRaw(["SELECT 1"]);
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function drizzleDataSource(client) {
|
|
76
|
+
return {
|
|
77
|
+
client,
|
|
78
|
+
withTransaction(fn) {
|
|
79
|
+
return client.transaction((tx) => fn(tx));
|
|
80
|
+
},
|
|
81
|
+
async health() {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { attachDataSource, createMemoryDataSource, drizzleDataSource, getTx, prismaDataSource, transactional };
|
|
88
|
+
//# sourceMappingURL=index.js.map
|
|
89
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/transactional.ts","../src/memory-datasource.ts","../src/adapters.ts"],"names":[],"mappings":";;;AAQA,IAAM,OAAA,GAAU,IAAI,iBAAA,EAA2B;AAexC,SAAS,MAAqB,GAAA,EAAmB;AACtD,EAAA,MAAM,IAAA,GAAO,QAAQ,QAAA,EAAS;AAC9B,EAAA,IAAI,IAAA,KAAS,QAAW,OAAO,IAAA;AAG/B,EAAA,MAAM,EAAA,GAAM,IAAI,KAAA,CAAwC,YAAA;AACxD,EAAA,IAAI,CAAC,EAAA,EAAI;AACP,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AACA,EAAA,OAAO,EAAA,CAAG,MAAA;AACZ;AAOO,SAAS,iBAAiB,EAAA,EAA4B;AAC3D,EAAA,OAAO,OAAO,KAAK,IAAA,KAAS;AAC1B,IAAC,GAAA,CAAI,MAAwC,YAAA,GAAe,EAAA;AAC5D,IAAA,OAAO,IAAA,EAAK;AAAA,EACd,CAAA;AACF;AAkBO,SAAS,aAAA,GAA4B;AAC1C,EAAA,OAAO,OAAO,KAAK,IAAA,KAAS;AAC1B,IAAA,MAAM,EAAA,GAAM,IAAI,KAAA,CAAwC,YAAA;AACxD,IAAA,IAAI,CAAC,EAAA,EAAI;AACP,MAAA,MAAM,IAAI,MAAM,uEAAuE,CAAA;AAAA,IACzF;AACA,IAAA,OAAO,EAAA,CAAG,eAAA,CAAgB,OAAO,EAAA,KAAO;AACtC,MAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,EAAA,EAAI,MAAM,MAAM,CAAA;AAAA,IACrC,CAAC,CAAA;AAAA,EACH,CAAA;AACF;;;AChEO,SAAS,uBACd,OAAA,EAC8B;AAC9B,EAAA,MAAM,MAAA,GAAS,OAAA;AAEf,EAAA,OAAO;AAAA,IACL,MAAA;AAAA,IACA,MAAM,gBAAgB,EAAA,EAAI;AAExB,MAAA,MAAM,QAAA,GAAW,gBAAgB,MAAM,CAAA;AACvC,MAAA,IAAI;AACF,QAAA,OAAO,MAAM,GAAG,MAAM,CAAA;AAAA,MACxB,SAAS,GAAA,EAAK;AAEZ,QAAA,KAAA,MAAW,KAAK,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA,EAAG,OAAQ,OAAmC,CAAC,CAAA;AACjF,QAAA,MAAA,CAAO,MAAA,CAAO,QAAQ,QAAQ,CAAA;AAC9B,QAAA,MAAM,GAAA;AAAA,MACR;AAAA,IACF,CAAA;AAAA,IACA,MAAM,MAAA,GAAS;AACb,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,GACF;AACF;;;ACzBO,SAAS,iBACd,MAAA,EAC8B;AAC9B,EAAA,OAAO;AAAA,IACL,MAAA;AAAA,IACA,gBAAmB,EAAA,EAA6C;AAE9D,MAAA,OAAO,OAAO,YAAA,CAAa,CAAC,EAAA,KAAO,EAAA,CAAG,EAAa,CAAC,CAAA;AAAA,IACtD,CAAA;AAAA,IACA,MAAM,MAAA,GAAS;AACb,MAAA,IAAI;AACF,QAAA,MAAM,CAAA,GAAI,MAAA;AACV,QAAA,IAAI,OAAO,CAAA,CAAE,SAAA,KAAc,UAAA,EAAY;AACrC,UAAA,MAAM,CAAA,CAAE,SAAA,CAAU,CAAC,UAAU,CAAC,CAAA;AAAA,QAChC;AACA,QAAA,OAAO,IAAA;AAAA,MACT,CAAA,CAAA,MAAQ;AACN,QAAA,OAAO,KAAA;AAAA,MACT;AAAA,IACF;AAAA,GACF;AACF;AASO,SAAS,kBACd,MAAA,EAC8B;AAC9B,EAAA,OAAO;AAAA,IACL,MAAA;AAAA,IACA,gBAAmB,EAAA,EAA6C;AAC9D,MAAA,OAAO,OAAO,WAAA,CAAY,CAAC,EAAA,KAAO,EAAA,CAAG,EAAa,CAAC,CAAA;AAAA,IACrD,CAAA;AAAA,IACA,MAAM,MAAA,GAAS;AACb,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,GACF;AACF","file":"index.js","sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport { type Context, type Middleware } from '@omni-api/core';\nimport type { DataSource } from './datasource.js';\n\n/**\n * 事务上下文存储。用 AsyncLocalStorage 让 getTx() 能在嵌套调用中拿到正确的事务对象,\n * 而不需要业务一路把 tx 传下去。\n */\nconst txStore = new AsyncLocalStorage<unknown>();\n\n/**\n * 在中间件 / handler 中获取当前事务对象。\n *\n * - 如果在 transactional() 中间件内:返回事务句柄\n * - 否则:返回 dataSource.client(非事务模式)\n *\n * 这样业务代码可以无脑写:\n * ```ts\n * const db = getTx<DBClient>(ctx);\n * await db.user.create(...);\n * ```\n * 不论外层是否包了事务,都不需要改。\n */\nexport function getTx<TTx = unknown>(ctx: Context): TTx {\n const inTx = txStore.getStore();\n if (inTx !== undefined) return inTx as TTx;\n\n // 不在事务中:用 ctx.state 里的 client(由 transactional 中间件外部装配)\n const ds = (ctx.state as { __dataSource?: DataSource }).__dataSource;\n if (!ds) {\n throw new Error(\n 'getTx: no DataSource attached. Did you forget to register dataSourcePlugin?',\n );\n }\n return ds.client as TTx;\n}\n\n/**\n * 把 DataSource 挂到 ctx —— 通常作为 App 全局中间件。\n *\n * 这一步不开启事务,仅让 getTx() 在非事务路径下也能拿到 client。\n */\nexport function attachDataSource(ds: DataSource): Middleware {\n return async (ctx, next) => {\n (ctx.state as { __dataSource?: DataSource }).__dataSource = ds;\n return next();\n };\n}\n\n/**\n * 事务中间件:把 handler(含其调用栈中所有 getTx)跑在一个数据库事务里。\n *\n * 用法:\n * ```ts\n * defineProcedure({\n * name: 'order.create',\n * middleware: [auth(), transactional()],\n * handler: async ({ input, ctx }) => {\n * const db = getTx<DBClient>(ctx);\n * await db.order.create({ data: ... });\n * await db.audit.log({ ... }); // 同一个事务\n * },\n * });\n * ```\n */\nexport function transactional(): Middleware {\n return async (ctx, next) => {\n const ds = (ctx.state as { __dataSource?: DataSource }).__dataSource;\n if (!ds) {\n throw new Error('transactional: DataSource not attached. Use attachDataSource() first.');\n }\n return ds.withTransaction(async (tx) => {\n return txStore.run(tx, () => next());\n });\n };\n}\n","import type { DataSource } from './datasource.js';\n\n/**\n * 内存版 DataSource —— 仅供示例 / 单测使用。\n *\n * 提供一个最小可工作的 DataSource:\n * - client 是一个普通对象(业务可任意往里塞数据/方法)\n * - withTransaction 用「快照 + 回滚」语义模拟事务\n *\n * 真实场景请用 Prisma/Drizzle/原生驱动的适配器。\n */\nexport function createMemoryDataSource<TClient extends object = Record<string, unknown>>(\n initial: TClient,\n): DataSource<TClient, TClient> {\n const client = initial;\n\n return {\n client,\n async withTransaction(fn) {\n // 简单快照(仅一层,深拷贝用 structuredClone)\n const snapshot = structuredClone(client);\n try {\n return await fn(client);\n } catch (err) {\n // 回滚:把 client 恢复成快照\n for (const k of Object.keys(client)) delete (client as Record<string, unknown>)[k];\n Object.assign(client, snapshot);\n throw err;\n }\n },\n async health() {\n return true;\n },\n };\n}\n","import type { DataSource } from './datasource.js';\n\n/**\n * Prisma 适配器示例(不引入 prisma 依赖;通过结构类型适配)。\n */\nexport interface PrismaLike {\n $transaction<R>(fn: (tx: unknown) => Promise<R>): Promise<R>;\n}\n\nexport function prismaDataSource<TClient extends PrismaLike>(\n client: TClient,\n): DataSource<TClient, TClient> {\n return {\n client,\n withTransaction<R>(fn: (tx: TClient) => Promise<R>): Promise<R> {\n // tx 在 prisma 内是同形客户端,做 cast 即可\n return client.$transaction((tx) => fn(tx as TClient));\n },\n async health() {\n try {\n const c = client as { $queryRaw?: (...args: unknown[]) => Promise<unknown> };\n if (typeof c.$queryRaw === 'function') {\n await c.$queryRaw(['SELECT 1']);\n }\n return true;\n } catch {\n return false;\n }\n },\n };\n}\n\n/**\n * Drizzle 适配器示例。\n */\nexport interface DrizzleLike {\n transaction<R>(fn: (tx: unknown) => Promise<R>): Promise<R>;\n}\n\nexport function drizzleDataSource<TClient extends DrizzleLike>(\n client: TClient,\n): DataSource<TClient, TClient> {\n return {\n client,\n withTransaction<R>(fn: (tx: TClient) => Promise<R>): Promise<R> {\n return client.transaction((tx) => fn(tx as TClient));\n },\n async health() {\n return true;\n },\n };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@omni-api/plugin-orm",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Database transaction middleware for OmniAPI; ORM-agnostic via DataSource convention.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": ["dist", "README.md"],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"clean": "rm -rf dist .turbo coverage"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@omni-api/core": "workspace:*"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"tsup": "^8.3.0",
|
|
28
|
+
"typescript": "^5.7.0",
|
|
29
|
+
"vitest": "^2.1.0",
|
|
30
|
+
"zod": "^3.23.8",
|
|
31
|
+
"@types/node": "^22.10.0"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
}
|
|
36
|
+
}
|