field-guard 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Masaaki Ota
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.md ADDED
@@ -0,0 +1,197 @@
1
+ # field-guard
2
+
3
+ A lightweight, fully type-safe, field-level access control library for TypeScript.
4
+
5
+ Define **who** can see **which fields** of your data — with zero runtime dependencies.
6
+
7
+ ## Features
8
+
9
+ - 🔒 **Field-level access control** — Grant or deny access per field, per access level
10
+ - 🏗️ **Builder pattern API** — Chainable `.withDerive()` and `.withCheck()` for composable guards
11
+ - 🔀 **Merge strategies** — Combine multiple verdicts via `union` or `intersection`
12
+ - 🧩 **Composable** — Combine multiple guards into a single object with `combineGuards`
13
+ - 🦺 **Fully type-safe** — All fields, levels, and results are inferred from your definitions
14
+
15
+ ## Installation
16
+
17
+ ```
18
+ npm install field-guard
19
+ ```
20
+
21
+ ```
22
+ import { defineGuard, combineGuards, mergeFieldVerdicts } from "field-guard";
23
+ ```
24
+
25
+ ## Core Concepts
26
+
27
+ | Concept | Description |
28
+ | ------------- | --------------------------------------------------------------------------- |
29
+ | **Field** | A string literal representing a property name (e.g. `"id"`, `"email"`) |
30
+ | **Level** | An access level label (e.g. `"owner"`, `"public"`, `"admin"`) |
31
+ | **Policy** | A mapping from levels to field permissions (`true`, `false`, or per-field) |
32
+ | **Verdict** | The resolved result: a list of allowed fields with helper methods |
33
+ | **Context** | Arbitrary user/session data passed into guards at evaluation time |
34
+
35
+ ## Usage
36
+
37
+ ### 1. Define a Guard
38
+
39
+ ```ts
40
+ import { defineGuard } from "field-guard";
41
+
42
+ type Ctx = { userId: string; role: "admin" | "user" };
43
+ type User = { id: string; email: string; name: string };
44
+
45
+ const userGuard = defineGuard<"owner" | "other", Ctx>()({
46
+ fields: ["id", "email", "name"],
47
+ policy: {
48
+ owner: true, // all fields allowed
49
+ other: { id: true, name: true }, // whitelist mode — only id and name
50
+ },
51
+ });
52
+ ```
53
+
54
+ #### Policy Modes
55
+
56
+ - **`true`** — Allow all fields for this level
57
+ - **`false`** — Deny all fields for this level
58
+ - **Whitelist** `{ id: true, name: true }` — Only explicitly listed fields are allowed
59
+ - **Blacklist** `{ secretField: false }` — All fields allowed *except* those set to `false`
60
+
61
+ > The mode is auto-detected: if any value is `true`, it's whitelist mode; if all values are `false`, it's blacklist mode.
62
+
63
+ ### 2. Add a Target Check
64
+
65
+ Use `.withCheck<T>()` to resolve the access level based on the context and a target object:
66
+
67
+ ```ts
68
+ const userGuard = defineGuard<"owner" | "other", Ctx>()({
69
+ fields: ["id", "email", "name"],
70
+ policy: {
71
+ owner: true,
72
+ other: { id: true, name: true },
73
+ },
74
+ }).withCheck<User>()(({ ctx, target, verdictMap }) => {
75
+ const level = ctx.userId === target.id ? "owner" : "other";
76
+ return verdictMap[level];
77
+ });
78
+ ```
79
+
80
+ ### 3. Evaluate the Guard
81
+
82
+ ```ts
83
+ const guard = userGuard.for({ userId: "1", role: "user" });
84
+
85
+ const verdict = guard.check({ id: "1", email: "me@example.com", name: "Me" });
86
+ verdict.allowedFields; // ["id", "email", "name"]
87
+
88
+ const verdict2 = guard.check({ id: "2", email: "other@example.com", name: "Other" });
89
+ verdict2.allowedFields; // ["id", "name"]
90
+ ```
91
+
92
+ ### 4. Use Verdict Helpers
93
+
94
+ Each `FieldVerdict` comes with two convenience methods:
95
+
96
+ ```ts
97
+ verdict.coversAll(["id", "name"]); // true — all requested fields are allowed
98
+ verdict.coversSome(["email"]); // true — at least one requested field is allowed
99
+ ```
100
+
101
+ ### 5. Derive Extra Properties
102
+
103
+ Use `.withDerive()` to compute additional properties from the context:
104
+
105
+ ```ts
106
+ const guard = defineGuard<"public", Ctx>()({
107
+ fields: ["id", "email"],
108
+ policy: { public: true },
109
+ }).withDerive(({ ctx }) => ({
110
+ isAdmin: ctx.role === "admin",
111
+ }));
112
+
113
+ const g = guard.for({ userId: "1", role: "admin" });
114
+ g.isAdmin; // true
115
+ ```
116
+
117
+ ### 6. Combine Multiple Guards
118
+
119
+ Use `combineGuards` to bundle guards for different resources and bind them all at once:
120
+
121
+ ```ts
122
+ import { combineGuards } from "field-guard";
123
+
124
+ const guards = combineGuards<Ctx>()({
125
+ users: userGuard,
126
+ posts: postGuard,
127
+ });
128
+
129
+ const g = guards.for({ userId: "1", role: "user" });
130
+
131
+ g.users.check({ id: "1", email: "a@b.com", name: "A" });
132
+ g.posts.check({ id: "p1", content: "hello", authorId: "1" });
133
+ ```
134
+
135
+ ### 7. Merge Verdicts
136
+
137
+ Merge multiple verdicts with `union` (any-of) or `intersection` (all-of) strategy:
138
+
139
+ ```ts
140
+ import { mergeFieldVerdicts } from "field-guard";
141
+
142
+ // Union: field is allowed if ANY verdict allows it
143
+ mergeFieldVerdicts("union", [verdictA, verdictB], fields);
144
+
145
+ // Intersection: field is allowed only if ALL verdicts allow it
146
+ mergeFieldVerdicts("intersection", [verdictA, verdictB], fields);
147
+ ```
148
+
149
+ This is also available as `mergeVerdicts` on every guard instance:
150
+
151
+ ```ts
152
+ const guard = defineGuard<"owner" | "admin", Ctx>()({ /* ... */ });
153
+
154
+ const verdict = guard.mergeVerdicts("union", { owner: true, admin: false });
155
+ ```
156
+
157
+ ## API Reference
158
+
159
+ ### `defineGuard<Levels, Context>()`
160
+
161
+ Returns a factory function that accepts `{ fields, policy }` and returns a guard chain.
162
+
163
+ ### Guard Chain Methods
164
+
165
+ | Method | Description |
166
+ | ------------------------- | ------------------------------------------------------------------ |
167
+ | `.withDerive(fn)` | Add derived properties computed from context |
168
+ | `.withCheck<Target>()(fn)` | Add a `check(target)` method that resolves a verdict per target |
169
+ | `.for(ctx)` | Bind context and return the resolved guard object |
170
+
171
+ ### Guard Base Properties
172
+
173
+ | Property | Description |
174
+ | --------------- | -------------------------------------------------------- |
175
+ | `fields` | The full list of field names |
176
+ | `verdictMap` | Pre-computed `FieldVerdictMap` for each level |
177
+ | `mergeVerdicts` | Helper to merge verdicts by level flags |
178
+
179
+ ### `combineGuards<Context>()(guards)`
180
+
181
+ Combines multiple guards into a single object with a shared `.for(ctx)` method.
182
+
183
+ ### `mergeFieldVerdicts(mode, verdicts, fields)`
184
+
185
+ Merges an array of `FieldVerdict` objects using `"union"` or `"intersection"` strategy.
186
+
187
+ ### `FieldVerdict<F>`
188
+
189
+ | Property | Type | Description |
190
+ | ---------------- | ----------------------- | ---------------------------------------- |
191
+ | `allowedFields` | `F[]` | List of allowed field names |
192
+ | `coversAll(fs)` | `(fields: F[]) => boolean` | `true` if all given fields are allowed |
193
+ | `coversSome(fs)` | `(fields: F[]) => boolean` | `true` if any given field is allowed |
194
+
195
+ ## License
196
+
197
+ MIT
@@ -0,0 +1,14 @@
1
+ type GuardWithFor<A> = {
2
+ for: (actor: A) => Record<string, unknown>;
3
+ };
4
+ type Params<A> = Record<string, GuardWithFor<A>>;
5
+ type BoundGuards<A, P extends Params<A>> = {
6
+ [K in keyof P]: P[K] extends {
7
+ for: (actor: A) => infer R;
8
+ } ? R : never;
9
+ };
10
+ export declare function combineGuards<A>(): <P extends Params<A>>(params: P) => {
11
+ for: (actor: A) => BoundGuards<A, P>;
12
+ };
13
+ export {};
14
+ //# sourceMappingURL=combineGuards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combineGuards.d.ts","sourceRoot":"","sources":["../src/combineGuards.ts"],"names":[],"mappings":"AAAA,KAAK,YAAY,CAAC,CAAC,IAAI;IAAE,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC;AACtE,KAAK,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;AACjD,KAAK,WAAW,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,CAAC,CAAC,IAAI;KACxC,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS;QAAE,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,CAAC,CAAA;KAAE,GAAG,CAAC,GAAG,KAAK;CACxE,CAAC;AAEF,wBAAgB,aAAa,CAAC,CAAC,MACrB,CAAC,SAAS,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC;iBAErB,CAAC,KAAG,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;EAUvC"}
@@ -0,0 +1,13 @@
1
+ export function combineGuards() {
2
+ return (params) => {
3
+ return {
4
+ for: (actor) => {
5
+ return Object.fromEntries(Object.entries(params).map(([key, guard]) => [
6
+ key,
7
+ guard.for(actor),
8
+ ]));
9
+ },
10
+ };
11
+ };
12
+ }
13
+ //# sourceMappingURL=combineGuards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combineGuards.js","sourceRoot":"","sources":["../src/combineGuards.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,aAAa;IAC3B,OAAO,CAAsB,MAAS,EAAE,EAAE;QACxC,OAAO;YACL,GAAG,EAAE,CAAC,KAAQ,EAAqB,EAAE;gBACnC,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;oBAC3C,GAAG;oBACH,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;iBACjB,CAAC,CACkB,CAAC;YACzB,CAAC;SACF,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,32 @@
1
+ import { type MergeFieldVerdictsMode } from "./mergeFieldVerdicts.js";
2
+ import { type FieldPolicy, type FieldVerdict, type FieldVerdictMap } from "./types.js";
3
+ type BaseParams<C, L extends string, F extends string> = {
4
+ ctx: C;
5
+ fields: F[];
6
+ verdictMap: FieldVerdictMap<L, F>;
7
+ mergeVerdicts: (mode: MergeFieldVerdictsMode, flags: Partial<Record<L, boolean>>) => FieldVerdict<F>;
8
+ };
9
+ export type DeriveParams<C, L extends string, F extends string> = BaseParams<C, L, F>;
10
+ export type ResolveParams<C, T, L extends string, F extends string> = BaseParams<C, L, F> & {
11
+ target: T;
12
+ };
13
+ export type GuardBase<L extends string, F extends string> = {
14
+ fields: F[];
15
+ verdictMap: FieldVerdictMap<L, F>;
16
+ mergeVerdicts: (mode: MergeFieldVerdictsMode, flags: Partial<Record<L, boolean>>) => FieldVerdict<F>;
17
+ };
18
+ export type GuardChain<C, L extends string, F extends string, R extends Record<string, unknown>, HasDerive extends boolean = false, HasResolve extends boolean = false> = GuardBase<L, F> & {
19
+ for(ctx: C): R;
20
+ } & (HasDerive extends false ? {
21
+ withDerive<D extends Record<string, unknown>>(fn: (p: DeriveParams<C, L, F>) => D): GuardChain<C, L, F, R & D, true, HasResolve>;
22
+ } : {}) & (HasResolve extends false ? {
23
+ withCheck<T>(): <M>(fn: (p: ResolveParams<C, T, L, F>) => M) => GuardChain<C, L, F, R & {
24
+ check: (target: T) => M;
25
+ }, HasDerive, true>;
26
+ } : {});
27
+ export declare function defineGuard<L extends string, C>(): <F extends string>(params: {
28
+ fields: F[];
29
+ policy: FieldPolicy<L, F>;
30
+ }) => GuardChain<C, L, F, Record<string, never>, false, false>;
31
+ export {};
32
+ //# sourceMappingURL=defineGuard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defineGuard.d.ts","sourceRoot":"","sources":["../src/defineGuard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import { mergeFieldVerdicts } from "./mergeFieldVerdicts.js";
2
+ import { createVerdict } from "./types.js";
3
+ function buildVerdictMap(policy, fields) {
4
+ return Object.fromEntries(Object.entries(policy).map(([_level, _mask]) => {
5
+ const level = _level;
6
+ const mask = _mask;
7
+ if (typeof mask === "boolean") {
8
+ return [level, createVerdict(mask ? fields : [])];
9
+ }
10
+ const isWhiteListMode = Object.values(mask).length === 0 || Object.values(mask).some((v) => v === true);
11
+ const allowedFields = isWhiteListMode
12
+ ? fields.filter((f) => mask[f] === true)
13
+ : fields.filter((f) => mask[f] !== false);
14
+ return [level, createVerdict(allowedFields)];
15
+ }));
16
+ }
17
+ function createChain(fields, verdictMap, mergeVerdicts, deriveFn, resolveFn) {
18
+ return {
19
+ fields,
20
+ verdictMap,
21
+ mergeVerdicts,
22
+ withDerive(fn) {
23
+ const prevDeriveFn = deriveFn;
24
+ const nextDeriveFn = (p) => ({
25
+ ...(prevDeriveFn ? prevDeriveFn(p) : {}),
26
+ ...fn(p),
27
+ });
28
+ return createChain(fields, verdictMap, mergeVerdicts, nextDeriveFn, resolveFn);
29
+ },
30
+ withCheck() {
31
+ return (fn) => {
32
+ return createChain(fields, verdictMap, mergeVerdicts, deriveFn, fn);
33
+ };
34
+ },
35
+ for(ctx) {
36
+ const baseParams = { ctx, fields, verdictMap, mergeVerdicts };
37
+ const derived = deriveFn ? deriveFn(baseParams) : {};
38
+ const result = resolveFn
39
+ ? { ...derived, check: (target) => resolveFn({ ...baseParams, target }) }
40
+ : { ...derived };
41
+ return result;
42
+ },
43
+ };
44
+ }
45
+ export function defineGuard() {
46
+ return (params) => {
47
+ const { fields, policy } = params;
48
+ const verdictMap = buildVerdictMap(policy, fields);
49
+ const _mergeVerdicts = (mode, flags) => {
50
+ const levels = Object.keys(flags).filter((l) => flags[l]);
51
+ const verdicts = levels.map((l) => verdictMap[l]);
52
+ return mergeFieldVerdicts(mode, verdicts, fields);
53
+ };
54
+ return createChain(fields, verdictMap, _mergeVerdicts, undefined, undefined);
55
+ };
56
+ }
57
+ //# sourceMappingURL=defineGuard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defineGuard.js","sourceRoot":"","sources":["../src/defineGuard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export * from "./combineGuards.js";
2
+ export * from "./defineGuard.js";
3
+ export * from "./mergeFieldVerdicts.js";
4
+ export * from "./types.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export * from "./combineGuards.js";
2
+ export * from "./defineGuard.js";
3
+ export * from "./mergeFieldVerdicts.js";
4
+ export * from "./types.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { FieldVerdict } from "./types.js";
2
+ export type MergeFieldVerdictsMode = "union" | "intersection";
3
+ export declare function mergeFieldVerdicts<F extends string>(mode: MergeFieldVerdictsMode, verdicts: FieldVerdict<F>[], fields: F[]): FieldVerdict<F>;
4
+ //# sourceMappingURL=mergeFieldVerdicts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mergeFieldVerdicts.d.ts","sourceRoot":"","sources":["../src/mergeFieldVerdicts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,YAAY,EAAE,MAAM,YAAY,CAAC;AAEzD,MAAM,MAAM,sBAAsB,GAAG,OAAO,GAAG,cAAc,CAAC;AAE9D,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,EACjD,IAAI,EAAE,sBAAsB,EAC5B,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,EAC3B,MAAM,EAAE,CAAC,EAAE,GACV,YAAY,CAAC,CAAC,CAAC,CAYjB"}
@@ -0,0 +1,12 @@
1
+ import { createVerdict } from "./types.js";
2
+ export function mergeFieldVerdicts(mode, verdicts, fields) {
3
+ if (verdicts.length === 0) {
4
+ return createVerdict([]);
5
+ }
6
+ const allowedSets = verdicts.map((v) => new Set(v.allowedFields));
7
+ const allowedFields = mode === "union"
8
+ ? fields.filter((f) => allowedSets.some((set) => set.has(f)))
9
+ : fields.filter((f) => allowedSets.every((set) => set.has(f)));
10
+ return createVerdict(allowedFields);
11
+ }
12
+ //# sourceMappingURL=mergeFieldVerdicts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mergeFieldVerdicts.js","sourceRoot":"","sources":["../src/mergeFieldVerdicts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAgB,MAAM,YAAY,CAAC;AAIzD,MAAM,UAAU,kBAAkB,CAChC,IAA4B,EAC5B,QAA2B,EAC3B,MAAW;IAEX,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,aAAa,CAAI,EAAE,CAAC,CAAC;IAC9B,CAAC;IAED,MAAM,WAAW,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC;IAElE,MAAM,aAAa,GAAG,IAAI,KAAK,OAAO;QACpC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7D,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAEjE,OAAO,aAAa,CAAC,aAAa,CAAC,CAAC;AACtC,CAAC"}
@@ -0,0 +1,10 @@
1
+ export type FieldRule<F extends string> = Record<F, boolean>;
2
+ export type FieldPolicy<L extends string, F extends string> = Record<L, boolean | Partial<FieldRule<F>>>;
3
+ export type FieldVerdict<F extends string> = {
4
+ allowedFields: F[];
5
+ coversAll: (fields: F[]) => boolean;
6
+ coversSome: (fields: F[]) => boolean;
7
+ };
8
+ export declare function createVerdict<F extends string>(allowedFields: F[]): FieldVerdict<F>;
9
+ export type FieldVerdictMap<L extends string, F extends string> = Record<L, FieldVerdict<F>>;
10
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,MAAM,IAAI,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AAG7D,MAAM,MAAM,WAAW,CACrB,CAAC,SAAS,MAAM,EAChB,CAAC,SAAS,MAAM,IACd,MAAM,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAM/C,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,MAAM,IAAI;IAC3C,aAAa,EAAE,CAAC,EAAE,CAAC;IACnB,SAAS,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC;IACpC,UAAU,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC;CACtC,CAAC;AAEF,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,aAAa,EAAE,CAAC,EAAE,GAAG,YAAY,CAAC,CAAC,CAAC,CAOnF;AAED,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,SAAS,MAAM,IAAI,MAAM,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,14 @@
1
+ const fieldRule = { f1: true, f2: false };
2
+ const fieldPolicy = {
3
+ public: { f1: true },
4
+ private: { f1: true, f2: false },
5
+ };
6
+ export function createVerdict(allowedFields) {
7
+ const set = new Set(allowedFields);
8
+ return {
9
+ allowedFields,
10
+ coversAll: (fields) => fields.every((f) => set.has(f)),
11
+ coversSome: (fields) => fields.some((f) => set.has(f)),
12
+ };
13
+ }
14
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AACA,MAAM,SAAS,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAA2B,CAAC;AAMnE,MAAM,WAAW,GAAG;IAClB,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE;IACpB,OAAO,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE;CACD,CAAC;AAQlC,MAAM,UAAU,aAAa,CAAmB,aAAkB;IAChE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAI,aAAa,CAAC,CAAC;IACtC,OAAO;QACL,aAAa;QACb,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACtD,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KACvD,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "field-guard",
3
+ "version": "0.1.0",
4
+ "description": "A lightweight, fully type-safe, field-level access control library for TypeScript",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "default": "./dist/index.js"
10
+ }
11
+ },
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "files": [
15
+ "dist",
16
+ "LICENSE",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "test": "bun run vitest run",
22
+ "prepublishOnly": "bun run build"
23
+ },
24
+ "keywords": [
25
+ "field",
26
+ "guard",
27
+ "access-control",
28
+ "permission",
29
+ "authorization",
30
+ "field-level",
31
+ "typescript",
32
+ "type-safe"
33
+ ],
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/mohhh-ok/field-guard.git"
38
+ },
39
+ "sideEffects": false,
40
+ "devDependencies": {
41
+ "typescript": "^5.8.3",
42
+ "vitest": "^4.0.18"
43
+ }
44
+ }