@absolutejs/sync-pack-utils 0.1.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/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # @absolutejs/sync-pack-utils
2
+
3
+ Shared helpers for [`@absolutejs/sync`](https://github.com/absolutejs/sync)
4
+ packs. Each helper closes one repeated pattern that showed up across the six
5
+ official packs (presence, comments, digest, notifications, favorites,
6
+ counters). The goal: make new packs — first- or third-party — cheaper to
7
+ write and consistent with what's already shipped.
8
+
9
+ ```bash
10
+ bun add @absolutejs/sync-pack-utils
11
+ ```
12
+
13
+ ## API
14
+
15
+ ### Actor resolution
16
+
17
+ ```ts
18
+ import {
19
+ defaultGetActorId,
20
+ resolveActor,
21
+ } from '@absolutejs/sync-pack-utils';
22
+
23
+ // (ctx) => ctx.userId — the conventional default.
24
+ const getActorId = config.getActorId ?? defaultGetActorId<MyCtx>();
25
+
26
+ // Inside a mutation handler: throws UnauthorizedError if no actor id.
27
+ // The context string ends up in the error message so logs name the op.
28
+ const actorId = resolveActor(getActorId, ctx, 'mypack:create');
29
+ ```
30
+
31
+ ### Permission builders
32
+
33
+ ```ts
34
+ import {
35
+ requireRowOwner,
36
+ requireOwnerOrModerator,
37
+ } from '@absolutejs/sync-pack-utils';
38
+
39
+ permissions: {
40
+ [table]: {
41
+ read: ...,
42
+ // Owner-only writes — uses row.actorId by default; override the
43
+ // field name for packs that use authorId, userId, etc.
44
+ insert: requireRowOwner(getActorId, 'actorId'),
45
+ update: requireRowOwner(getActorId, 'actorId'),
46
+ // Owner OR moderator delete. Falls back to store.getById when
47
+ // actions.delete supplied only the row key (the common case).
48
+ delete: requireOwnerOrModerator({
49
+ actorIdField: 'actorId',
50
+ canModerate, // optional
51
+ getActorId,
52
+ store, // { getById }
53
+ }),
54
+ },
55
+ },
56
+ ```
57
+
58
+ `requireRowOwner` and `requireOwnerOrModerator` are higher-order — call
59
+ them once at pack-build time to receive the per-row predicate the engine
60
+ expects. Both reject when `getActorId(ctx)` is `undefined` (anonymous
61
+ callers can't own a row).
62
+
63
+ ### In-memory store
64
+
65
+ ```ts
66
+ import {
67
+ createInMemoryStore,
68
+ type InMemoryStore,
69
+ } from '@absolutejs/sync-pack-utils';
70
+
71
+ type FavoriteRow = { id: string; actorId: string; /* ... */ };
72
+
73
+ const store: InMemoryStore<FavoriteRow> =
74
+ config.store ?? createInMemoryStore<FavoriteRow>();
75
+
76
+ engine.registerPack(defineSyncPack({
77
+ // ...
78
+ readers: { favorites: store.reader },
79
+ writers: { favorites: store.writer },
80
+ // store.getById is available for permission + mutation handler use.
81
+ }));
82
+ ```
83
+
84
+ `createInMemoryStore<Row>()` returns:
85
+
86
+ - `reader: TableReader<CollectionContext>` — `all()` returns `[...rows.values()]`
87
+ - `writer: TableWriter<Row>` — upsert-by-id; `delete` removes by `row.id`
88
+ - `getById(id)` — point lookup
89
+ - `rows: Map<string, Row>` — direct access (treat as read-only)
90
+
91
+ Every official pack uses this as its default backing store and exposes a
92
+ `store?: ...` config field so consumers can swap in a Postgres / Drizzle /
93
+ Redis implementation when they need persistence.
94
+
95
+ ## What's intentionally not here
96
+
97
+ - **`prefixed(prefix, ...)`**: just use template literals.
98
+ ``${prefix}<my-collection>`` is fine.
99
+ - **`defineActorPack(...)`**: a Convex-style abstraction that wraps
100
+ `defineSyncPack` with permission defaults. Decided against — it'd hide
101
+ the engine surface, which is the thing pack authors learn first.
102
+ - **Validators / Zod adapters**: out of scope; sync's `field` helper +
103
+ schema is the validation layer.
104
+
105
+ ## Pairing with `@absolutejs/sync/testing`
106
+
107
+ Pack tests typically pair with `@absolutejs/sync/testing` (added in sync
108
+ 1.9.2):
109
+
110
+ ```ts
111
+ import { expectRejection } from '@absolutejs/sync/testing';
112
+ import { resolveActor } from '@absolutejs/sync-pack-utils';
113
+
114
+ const error = await expectRejection(() => {
115
+ resolveActor(getActorId, {}, 'mypack:do');
116
+ return Promise.resolve();
117
+ });
118
+ expect(error).toBeInstanceOf(UnauthorizedError);
119
+ ```
120
+
121
+ ## Versioning
122
+
123
+ This package stays 0.x indefinitely while the ecosystem settles. When the
124
+ six official packs are stable, the helpers will follow.
@@ -0,0 +1,97 @@
1
+ /**
2
+ * `@absolutejs/sync-pack-utils` — shared helpers extracted from the six
3
+ * official `@absolutejs/sync` packs.
4
+ *
5
+ * Each helper closes one repeated pattern. The goal is to make new packs
6
+ * (first- and third-party) cheaper to write and stop drift from the
7
+ * standard shapes already shipped by presence / comments / digest /
8
+ * notifications / favorites / counters.
9
+ */
10
+ import { type CollectionContext, type TableReader, type TableWriter } from '@absolutejs/sync/engine';
11
+ /**
12
+ * The conventional default — `(ctx) => ctx.userId`. Use as the fallback when
13
+ * a pack's config doesn't supply its own `getActorId`.
14
+ *
15
+ * @example
16
+ * const getActorId = config.getActorId ?? defaultGetActorId<MyCtx>();
17
+ */
18
+ export declare const defaultGetActorId: <Ctx = CollectionContext>() => ((ctx: Ctx) => string | undefined);
19
+ /**
20
+ * Resolve the caller's actor id from a ctx, or throw {@link UnauthorizedError}
21
+ * with a descriptive message. Use inside mutation handlers that require an
22
+ * actor; the message embeds your `context` string so error reports name the
23
+ * pack and operation.
24
+ *
25
+ * @example
26
+ * const actorId = resolveActor(getActorId, ctx, 'comments:create');
27
+ */
28
+ export declare const resolveActor: <Ctx = CollectionContext>(getActorId: (ctx: Ctx) => string | undefined, ctx: unknown, context: string) => string;
29
+ /**
30
+ * Permission validator that requires `row[actorIdField] === getActorId(ctx)`.
31
+ * Use for `insert` and `update` on rows the actor owns; the handler stamps
32
+ * the actor on the row, so this is the canonical row-ownership check.
33
+ *
34
+ * @example
35
+ * permissions: {
36
+ * notifications: {
37
+ * insert: requireRowOwner(getActorId, 'actorId'),
38
+ * update: requireRowOwner(getActorId, 'actorId'),
39
+ * },
40
+ * },
41
+ */
42
+ export declare const requireRowOwner: <Ctx = CollectionContext>(getActorId: (ctx: Ctx) => string | undefined, actorIdField?: string) => (ctx: unknown, row: unknown) => boolean;
43
+ /**
44
+ * Permission validator for delete (or any op) that accepts the row OR
45
+ * just its id. Falls back to `store.getById(row.id)` when the row's actor
46
+ * field isn't on the supplied subject — sync's engine passes only the
47
+ * key to `actions.delete({ id })`, so this pattern is needed for every
48
+ * owner-gated delete.
49
+ *
50
+ * Returns true when the row's owner matches the caller OR `canModerate(ctx)`
51
+ * is `true`.
52
+ *
53
+ * @example
54
+ * permissions: {
55
+ * notifications: {
56
+ * delete: requireOwnerOrModerator({
57
+ * getActorId,
58
+ * canModerate,
59
+ * store, // { getById }
60
+ * actorIdField: 'actorId',
61
+ * }),
62
+ * },
63
+ * },
64
+ */
65
+ export declare const requireOwnerOrModerator: <Ctx = CollectionContext>(options: {
66
+ getActorId: (ctx: Ctx) => string | undefined;
67
+ canModerate?: (ctx: Ctx) => boolean;
68
+ store: {
69
+ getById: (id: string) => unknown;
70
+ };
71
+ actorIdField?: string;
72
+ }) => (ctx: unknown, row: unknown) => boolean;
73
+ /**
74
+ * Standard in-memory store for a pack table. Every row needs an `id: string`
75
+ * (the table's primary key); the writer is upsert-by-id. Used by every
76
+ * official pack as the default backing store.
77
+ */
78
+ export type InMemoryStore<Row extends {
79
+ id: string;
80
+ }> = {
81
+ reader: TableReader<CollectionContext>;
82
+ writer: TableWriter<Row, CollectionContext, unknown>;
83
+ /** Lookup by id — convenient for permission and mutation handlers. */
84
+ getById: (id: string) => Row | undefined;
85
+ /** Direct read of the underlying Map for advanced use; treat as read-only. */
86
+ rows: Map<string, Row>;
87
+ };
88
+ /**
89
+ * Build a fresh, isolated in-memory store. Each call gets its own Map, so
90
+ * two pack instances on the same engine don't share state.
91
+ *
92
+ * @example
93
+ * const store = config.store ?? createInMemoryStore<NotificationRow>();
94
+ */
95
+ export declare const createInMemoryStore: <Row extends {
96
+ id: string;
97
+ }>() => InMemoryStore<Row>;
package/dist/index.js ADDED
@@ -0,0 +1,79 @@
1
+ // @bun
2
+ // src/index.ts
3
+ import {
4
+ UnauthorizedError
5
+ } from "@absolutejs/sync/engine";
6
+ var defaultGetActorId = () => (ctx) => ctx.userId;
7
+ var resolveActor = (getActorId, ctx, context) => {
8
+ const actorId = getActorId(ctx);
9
+ if (actorId === undefined || actorId === "") {
10
+ throw new UnauthorizedError(`${context} (no actor id)`);
11
+ }
12
+ return actorId;
13
+ };
14
+ var requireRowOwner = (getActorId, actorIdField = "actorId") => (ctx, row) => {
15
+ const callerId = getActorId(ctx);
16
+ if (callerId === undefined)
17
+ return false;
18
+ const rowActor = row[actorIdField];
19
+ return rowActor === callerId;
20
+ };
21
+ var requireOwnerOrModerator = (options) => (ctx, row) => {
22
+ const {
23
+ getActorId,
24
+ canModerate,
25
+ store,
26
+ actorIdField = "actorId"
27
+ } = options;
28
+ const callerId = getActorId(ctx);
29
+ if (callerId === undefined)
30
+ return false;
31
+ const supplied = row;
32
+ const ownerId = supplied[actorIdField] !== undefined ? supplied[actorIdField] : (() => {
33
+ const id = supplied.id;
34
+ if (typeof id !== "string")
35
+ return;
36
+ const stored = store.getById(id);
37
+ return stored?.[actorIdField];
38
+ })();
39
+ if (ownerId === undefined)
40
+ return false;
41
+ if (ownerId === callerId)
42
+ return true;
43
+ return canModerate !== undefined ? canModerate(ctx) : false;
44
+ };
45
+ var createInMemoryStore = () => {
46
+ const rows = new Map;
47
+ return {
48
+ getById: (id) => rows.get(id),
49
+ reader: {
50
+ all: () => [...rows.values()]
51
+ },
52
+ rows,
53
+ writer: {
54
+ delete: (row) => {
55
+ rows.delete(row.id);
56
+ },
57
+ insert: (data) => {
58
+ rows.set(data.id, data);
59
+ return data;
60
+ },
61
+ update: (data) => {
62
+ const prior = rows.get(data.id);
63
+ const merged = { ...prior ?? {}, ...data };
64
+ rows.set(data.id, merged);
65
+ return merged;
66
+ }
67
+ }
68
+ };
69
+ };
70
+ export {
71
+ resolveActor,
72
+ requireRowOwner,
73
+ requireOwnerOrModerator,
74
+ defaultGetActorId,
75
+ createInMemoryStore
76
+ };
77
+
78
+ //# debugId=EA54D19BA095911A64756E2164756E21
79
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * `@absolutejs/sync-pack-utils` — shared helpers extracted from the six\n * official `@absolutejs/sync` packs.\n *\n * Each helper closes one repeated pattern. The goal is to make new packs\n * (first- and third-party) cheaper to write and stop drift from the\n * standard shapes already shipped by presence / comments / digest /\n * notifications / favorites / counters.\n */\n\nimport {\n\tUnauthorizedError,\n\ttype CollectionContext,\n\ttype TableReader,\n\ttype TableWriter\n} from '@absolutejs/sync/engine';\n\n// ─── Actor resolution ────────────────────────────────────────────────────────\n\n/**\n * The conventional default — `(ctx) => ctx.userId`. Use as the fallback when\n * a pack's config doesn't supply its own `getActorId`.\n *\n * @example\n * const getActorId = config.getActorId ?? defaultGetActorId<MyCtx>();\n */\nexport const defaultGetActorId = <\n\tCtx = CollectionContext\n>(): ((ctx: Ctx) => string | undefined) =>\n\t(ctx) => (ctx as { userId?: string }).userId;\n\n/**\n * Resolve the caller's actor id from a ctx, or throw {@link UnauthorizedError}\n * with a descriptive message. Use inside mutation handlers that require an\n * actor; the message embeds your `context` string so error reports name the\n * pack and operation.\n *\n * @example\n * const actorId = resolveActor(getActorId, ctx, 'comments:create');\n */\nexport const resolveActor = <Ctx = CollectionContext>(\n\tgetActorId: (ctx: Ctx) => string | undefined,\n\tctx: unknown,\n\tcontext: string\n): string => {\n\tconst actorId = getActorId(ctx as Ctx);\n\tif (actorId === undefined || actorId === '') {\n\t\tthrow new UnauthorizedError(`${context} (no actor id)`);\n\t}\n\treturn actorId;\n};\n\n// ─── Permission builders ─────────────────────────────────────────────────────\n\n/**\n * Permission validator that requires `row[actorIdField] === getActorId(ctx)`.\n * Use for `insert` and `update` on rows the actor owns; the handler stamps\n * the actor on the row, so this is the canonical row-ownership check.\n *\n * @example\n * permissions: {\n * notifications: {\n * insert: requireRowOwner(getActorId, 'actorId'),\n * update: requireRowOwner(getActorId, 'actorId'),\n * },\n * },\n */\nexport const requireRowOwner =\n\t<Ctx = CollectionContext>(\n\t\tgetActorId: (ctx: Ctx) => string | undefined,\n\t\tactorIdField: string = 'actorId'\n\t) =>\n\t(ctx: unknown, row: unknown): boolean => {\n\t\tconst callerId = getActorId(ctx as Ctx);\n\t\tif (callerId === undefined) return false;\n\t\tconst rowActor = (row as Record<string, unknown>)[actorIdField];\n\t\treturn rowActor === callerId;\n\t};\n\n/**\n * Permission validator for delete (or any op) that accepts the row OR\n * just its id. Falls back to `store.getById(row.id)` when the row's actor\n * field isn't on the supplied subject — sync's engine passes only the\n * key to `actions.delete({ id })`, so this pattern is needed for every\n * owner-gated delete.\n *\n * Returns true when the row's owner matches the caller OR `canModerate(ctx)`\n * is `true`.\n *\n * @example\n * permissions: {\n * notifications: {\n * delete: requireOwnerOrModerator({\n * getActorId,\n * canModerate,\n * store, // { getById }\n * actorIdField: 'actorId',\n * }),\n * },\n * },\n */\nexport const requireOwnerOrModerator =\n\t<Ctx = CollectionContext>(options: {\n\t\tgetActorId: (ctx: Ctx) => string | undefined;\n\t\tcanModerate?: (ctx: Ctx) => boolean;\n\t\tstore: { getById: (id: string) => unknown };\n\t\tactorIdField?: string;\n\t}) =>\n\t(ctx: unknown, row: unknown): boolean => {\n\t\tconst {\n\t\t\tgetActorId,\n\t\t\tcanModerate,\n\t\t\tstore,\n\t\t\tactorIdField = 'actorId'\n\t\t} = options;\n\t\tconst callerId = getActorId(ctx as Ctx);\n\t\tif (callerId === undefined) return false;\n\t\tconst supplied = row as Record<string, unknown>;\n\t\tconst ownerId =\n\t\t\tsupplied[actorIdField] !== undefined\n\t\t\t\t? supplied[actorIdField]\n\t\t\t\t: (() => {\n\t\t\t\t\t\tconst id = supplied.id;\n\t\t\t\t\t\tif (typeof id !== 'string') return undefined;\n\t\t\t\t\t\tconst stored = store.getById(id) as\n\t\t\t\t\t\t\t| Record<string, unknown>\n\t\t\t\t\t\t\t| undefined;\n\t\t\t\t\t\treturn stored?.[actorIdField];\n\t\t\t\t\t})();\n\t\tif (ownerId === undefined) return false;\n\t\tif (ownerId === callerId) return true;\n\t\treturn canModerate !== undefined ? canModerate(ctx as Ctx) : false;\n\t};\n\n// ─── In-memory store ────────────────────────────────────────────────────────\n\n/**\n * Standard in-memory store for a pack table. Every row needs an `id: string`\n * (the table's primary key); the writer is upsert-by-id. Used by every\n * official pack as the default backing store.\n */\nexport type InMemoryStore<Row extends { id: string }> = {\n\treader: TableReader<CollectionContext>;\n\twriter: TableWriter<Row, CollectionContext, unknown>;\n\t/** Lookup by id — convenient for permission and mutation handlers. */\n\tgetById: (id: string) => Row | undefined;\n\t/** Direct read of the underlying Map for advanced use; treat as read-only. */\n\trows: Map<string, Row>;\n};\n\n/**\n * Build a fresh, isolated in-memory store. Each call gets its own Map, so\n * two pack instances on the same engine don't share state.\n *\n * @example\n * const store = config.store ?? createInMemoryStore<NotificationRow>();\n */\nexport const createInMemoryStore = <\n\tRow extends { id: string }\n>(): InMemoryStore<Row> => {\n\tconst rows = new Map<string, Row>();\n\treturn {\n\t\tgetById: (id) => rows.get(id),\n\t\treader: {\n\t\t\tall: () => [...rows.values()]\n\t\t},\n\t\trows,\n\t\twriter: {\n\t\t\tdelete: (row) => {\n\t\t\t\trows.delete((row as { id: string }).id);\n\t\t\t},\n\t\t\tinsert: (data) => {\n\t\t\t\trows.set(data.id, data);\n\t\t\t\treturn data;\n\t\t\t},\n\t\t\tupdate: (data) => {\n\t\t\t\tconst prior = rows.get(data.id);\n\t\t\t\tconst merged = { ...(prior ?? {}), ...data } as Row;\n\t\t\t\trows.set(data.id, merged);\n\t\t\t\treturn merged;\n\t\t\t}\n\t\t}\n\t};\n};\n"
6
+ ],
7
+ "mappings": ";;AAUA;AAAA;AAAA;AAgBO,IAAM,oBAAoB,MAGhC,CAAC,QAAS,IAA4B;AAWhC,IAAM,eAAe,CAC3B,YACA,KACA,YACY;AAAA,EACZ,MAAM,UAAU,WAAW,GAAU;AAAA,EACrC,IAAI,YAAY,aAAa,YAAY,IAAI;AAAA,IAC5C,MAAM,IAAI,kBAAkB,GAAG,uBAAuB;AAAA,EACvD;AAAA,EACA,OAAO;AAAA;AAkBD,IAAM,kBACZ,CACC,YACA,eAAuB,cAExB,CAAC,KAAc,QAA0B;AAAA,EACxC,MAAM,WAAW,WAAW,GAAU;AAAA,EACtC,IAAI,aAAa;AAAA,IAAW,OAAO;AAAA,EACnC,MAAM,WAAY,IAAgC;AAAA,EAClD,OAAO,aAAa;AAAA;AAyBf,IAAM,0BACZ,CAA0B,YAM1B,CAAC,KAAc,QAA0B;AAAA,EACxC;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe;AAAA,MACZ;AAAA,EACJ,MAAM,WAAW,WAAW,GAAU;AAAA,EACtC,IAAI,aAAa;AAAA,IAAW,OAAO;AAAA,EACnC,MAAM,WAAW;AAAA,EACjB,MAAM,UACL,SAAS,kBAAkB,YACxB,SAAS,iBACR,MAAM;AAAA,IACP,MAAM,KAAK,SAAS;AAAA,IACpB,IAAI,OAAO,OAAO;AAAA,MAAU;AAAA,IAC5B,MAAM,SAAS,MAAM,QAAQ,EAAE;AAAA,IAG/B,OAAO,SAAS;AAAA,KACd;AAAA,EACN,IAAI,YAAY;AAAA,IAAW,OAAO;AAAA,EAClC,IAAI,YAAY;AAAA,IAAU,OAAO;AAAA,EACjC,OAAO,gBAAgB,YAAY,YAAY,GAAU,IAAI;AAAA;AA0BxD,IAAM,sBAAsB,MAER;AAAA,EAC1B,MAAM,OAAO,IAAI;AAAA,EACjB,OAAO;AAAA,IACN,SAAS,CAAC,OAAO,KAAK,IAAI,EAAE;AAAA,IAC5B,QAAQ;AAAA,MACP,KAAK,MAAM,CAAC,GAAG,KAAK,OAAO,CAAC;AAAA,IAC7B;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,MACP,QAAQ,CAAC,QAAQ;AAAA,QAChB,KAAK,OAAQ,IAAuB,EAAE;AAAA;AAAA,MAEvC,QAAQ,CAAC,SAAS;AAAA,QACjB,KAAK,IAAI,KAAK,IAAI,IAAI;AAAA,QACtB,OAAO;AAAA;AAAA,MAER,QAAQ,CAAC,SAAS;AAAA,QACjB,MAAM,QAAQ,KAAK,IAAI,KAAK,EAAE;AAAA,QAC9B,MAAM,SAAS,KAAM,SAAS,CAAC,MAAO,KAAK;AAAA,QAC3C,KAAK,IAAI,KAAK,IAAI,MAAM;AAAA,QACxB,OAAO;AAAA;AAAA,IAET;AAAA,EACD;AAAA;",
8
+ "debugId": "EA54D19BA095911A64756E2164756E21",
9
+ "names": []
10
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@absolutejs/sync-pack-utils",
3
+ "version": "0.1.0",
4
+ "description": "Shared helpers for @absolutejs/sync packs — resolveActor, requireRowOwner/Moderator, createInMemoryStore, and other patterns extracted from the official packs",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/absolutejs/sync-packs.git",
8
+ "directory": "utils"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "module": "./dist/index.js",
12
+ "types": "./dist/index.d.ts",
13
+ "type": "module",
14
+ "license": "CC BY-NC 4.0",
15
+ "author": "Alex Kahn",
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "keywords": [
20
+ "absolutejs",
21
+ "sync",
22
+ "sync-pack",
23
+ "utils",
24
+ "helpers"
25
+ ],
26
+ "scripts": {
27
+ "build": "rm -rf dist && bun build src/index.ts --outdir dist --sourcemap --target=bun --external @absolutejs/sync && tsc --project tsconfig.build.json",
28
+ "test": "bun test",
29
+ "typecheck": "tsc --noEmit",
30
+ "format": "prettier --write \"./**/*.{ts,json,md}\"",
31
+ "release": "bun run format && bun run typecheck && bun run test && bun run build && bun publish"
32
+ },
33
+ "peerDependencies": {
34
+ "@absolutejs/sync": ">= 1.9.2"
35
+ },
36
+ "devDependencies": {
37
+ "@absolutejs/sync": "^1.9.2",
38
+ "@types/bun": "1.3.14",
39
+ "prettier": "3.5.3",
40
+ "typescript": "5.8.3"
41
+ },
42
+ "exports": {
43
+ ".": {
44
+ "types": "./dist/index.d.ts",
45
+ "import": "./dist/index.js",
46
+ "default": "./dist/index.js"
47
+ }
48
+ },
49
+ "files": ["dist", "README.md"]
50
+ }