jurist 0.0.8 → 0.0.10

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 CHANGED
@@ -14,32 +14,32 @@ With Jurist, permissions are expressed in a tree structure.
14
14
 
15
15
  ```typescript
16
16
  // my-authorization.ts
17
- import { Laws, optional, required } from "jurist";
17
+ import { Laws, optional, required } from "jurist"
18
18
 
19
19
  const authorization = new Laws({
20
- roles: ["suspended", "free", "premium"],
21
- permissions: required({
22
- create: optional({
23
- document: required({
24
- "<=5": optional({ "<=5000": null }),
25
- shareDocuments: null,
26
- }),
27
- }),
28
- }),
29
- rolePermissions: {
30
- suspended: new Set([]),
31
- free: new Set([
32
- /* a granted permission grants all preceding permissions, e.g.,
20
+ roles: ["suspended", "free", "premium"],
21
+ permissions: required({
22
+ create: optional({
23
+ document: required({
24
+ "<=5": optional({ "<=5000": null }),
25
+ shareDocuments: null,
26
+ }),
27
+ }),
28
+ }),
29
+ rolePermissions: {
30
+ suspended: new Set([]),
31
+ free: new Set([
32
+ /* a granted permission grants all preceding permissions, e.g.,
33
33
  "create"
34
34
  "create_documents" */
35
- "create_documents_<=5",
36
- ]),
37
- premium: new Set([
38
- "create_documents_<=5_<=5000",
39
- "create_documents_shareDocuments",
40
- ]),
41
- },
42
- });
35
+ "create_documents_<=5",
36
+ ]),
37
+ premium: new Set([
38
+ "create_documents_<=5_<=5000",
39
+ "create_documents_shareDocuments",
40
+ ]),
41
+ },
42
+ })
43
43
  ```
44
44
 
45
45
  The idea here is that some permissions must logically follow from other permissions. In other words, the `create_documents_<=5` permission is a prerequisite for the `create_documents_<=5_<=5000` permission, because if you are allowed to create 5,000 documents, you are logically allowed to create 5 documents in the first place.
@@ -48,24 +48,24 @@ This makes authorization checks easier, safer, and more explicit.
48
48
 
49
49
  ```typescript
50
50
  // routes.ts
51
- import { Roles } from "jurist";
51
+ import { Roles } from "jurist"
52
52
 
53
- import { authorization } from "./my-authorization";
54
- import { app } from "./my-hono-like-app";
53
+ import { authorization } from "./my-authorization"
54
+ import { app } from "./my-hono-like-app"
55
55
 
56
- type Role = Roles<typeof authorization>; // "free" | "premium" | "suspended"
56
+ type Role = Roles<typeof authorization> // "free" | "premium" | "suspended"
57
57
 
58
58
  type User = {
59
- id: string;
60
- role: Role;
61
- };
59
+ id: string
60
+ role: Role
61
+ }
62
62
 
63
63
  app.post("/api/v1/resources", async (c) => {
64
- const { user /* { id: "123-456", role: "free" } */ } = c.get("user");
65
- /* ✅ true */ authorization.check(user.role, "create");
66
- /* ✅ true */ authorization.check(user.role, "create_documents_<=5");
67
- /* ❌ false */ authorization.check(user.role, "create_documents_<=5_<=5000");
68
- });
64
+ const { user /* { id: "123-456", role: "free" } */ } = c.get("user")
65
+ /* ✅ true */ authorization.check(user.role, "create")
66
+ /* ✅ true */ authorization.check(user.role, "create_documents_<=5")
67
+ /* ❌ false */ authorization.check(user.role, "create_documents_<=5_<=5000")
68
+ })
69
69
  ```
70
70
 
71
71
  Here, we can see that a free user has permission to create a resource, and has permission to own up to 5 documents, but they cannot own up to 5,000 documents like a premium user.
package/dist/jurist.js CHANGED
@@ -1,5 +1,4 @@
1
- export * from "treetrunks"
2
-
1
+ export * from "treetrunks";
3
2
  //#region src/jurist.ts
4
3
  var Laws = class {
5
4
  roles;
@@ -64,7 +63,7 @@ function fromEntries(entries) {
64
63
  function toEntries(obj) {
65
64
  return Object.entries(obj);
66
65
  }
67
-
68
66
  //#endregion
69
67
  export { Escalator, Laws };
68
+
70
69
  //# sourceMappingURL=jurist.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"jurist.js","names":[],"sources":["../src/jurist.ts"],"sourcesContent":["import type { Join, Tree, TreePathName } from \"treetrunks\"\n\nexport * from \"treetrunks\"\n\nexport type Roles<L extends Laws<any, any, any, any>> =\n\tL extends Laws<infer Role, any, any, any> ? Role : never\n\nexport type Permissions<L extends Laws<any, any, any, any>> =\n\tL extends Laws<any, any, infer Permission, any> ? Permission : never\n\nexport type PermissionData<L extends Laws<any, any, any, any>, D> = Entries<\n\tPermissions<L>,\n\tD\n>\n\nexport class Laws<\n\tRole extends string,\n\tPermissionTree extends Tree,\n\tPermission extends Join<TreePathName<PermissionTree>, `_`>,\n\tRolePermissions extends { [r in Role]: ReadonlySet<Permission> },\n> {\n\tpublic readonly roles: Role[]\n\tpublic readonly permissionTree: PermissionTree\n\tpublic readonly rolePermissions: RolePermissions\n\tprotected readonly decompressedRolePermissions: {\n\t\t[r in Role]: ReadonlySet<string>\n\t}\n\n\tpublic constructor(options: {\n\t\troles: Role[]\n\t\tpermissions: PermissionTree\n\t\trolePermissions: RolePermissions\n\t}) {\n\t\tconst { roles, permissions, rolePermissions } = options\n\t\tthis.roles = roles\n\t\tthis.permissionTree = permissions\n\t\tthis.rolePermissions = rolePermissions\n\t\tthis.decompressedRolePermissions = fromEntries(\n\t\t\ttoEntries<RolePermissions>(rolePermissions).map(\n\t\t\t\t([role, permissionsOfRole]) => [\n\t\t\t\t\trole,\n\t\t\t\t\tdecompressRolePermissions(permissionsOfRole),\n\t\t\t\t],\n\t\t\t),\n\t\t)\n\t}\n\n\tpublic check(role: Role, permission: Permission): boolean {\n\t\treturn this.decompressedRolePermissions[role].has(permission)\n\t}\n}\n\nfunction decompressRolePermissions(\n\tpermissionSet: ReadonlySet<string>,\n): ReadonlySet<string> {\n\tconst decompressed = new Set<string>(permissionSet)\n\tfor (const permission of permissionSet) {\n\t\tconst preconditions = permission.split(`_`)\n\t\tfor (let i = 0; i < preconditions.length - 1; i++) {\n\t\t\tconst subPermission = preconditions.slice(0, i + 1).join(`_`)\n\t\t\tdecompressed.add(subPermission)\n\t\t}\n\t}\n\treturn decompressed\n}\n\nexport type EscalatorStyle = `firstFound` | `lastFound` | `untilMiss`\nexport class Escalator<\n\tS extends EscalatorStyle,\n\tL extends Laws<any, any, any, any>,\n\tP extends PermissionData<L, any>,\n\tD extends P extends PermissionData<L, infer d> ? d : never,\n\tF,\n> {\n\tpublic readonly style: S\n\tpublic readonly laws: L\n\tpublic readonly permissionData: P\n\tpublic readonly fallback: F\n\tpublic constructor(\n\t\toptions: Readonly<{\n\t\t\tstyle: S\n\t\t\tlaws: L\n\t\t\tpermissionData: P\n\t\t\tfallback: F\n\t\t}>,\n\t) {\n\t\tconst { style, laws, permissionData, fallback } = options\n\t\tthis.style = style\n\t\tthis.laws = laws\n\t\tthis.permissionData = permissionData\n\t\tthis.fallback = fallback\n\t}\n\n\tpublic get(role: Roles<L>): D | F {\n\t\tlet result: D | F = this.fallback\n\t\tfor (const [permission, data] of this.permissionData) {\n\t\t\tconst hasPermission = this.laws.check(role, permission)\n\t\t\tswitch (this.style) {\n\t\t\t\tcase `firstFound`:\n\t\t\t\t\tif (hasPermission) return data\n\t\t\t\t\tbreak\n\t\t\t\tcase `lastFound`:\n\t\t\t\t\tif (hasPermission) result = data\n\t\t\t\t\tbreak\n\t\t\t\tcase `untilMiss`:\n\t\t\t\t\tif (hasPermission) result = data\n\t\t\t\t\telse return result\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n}\n\ntype Flat<R extends { [K in PropertyKey]: any }> = {\n\t[K in keyof R]: R[K]\n}\n\nexport type Count<N extends number, A extends any[] = []> = [\n\t...A,\n\tany,\n][`length`] extends N\n\t? A[`length`]\n\t: A[`length`] | Count<N, [...A, any]>\n\ntype Entries<K extends PropertyKey = PropertyKey, V = any> = [K, V][]\n\ntype KeyOfEntries<E extends Entries> = E extends [infer K, any][] ? K : never\n\ntype ValueOfEntry<E extends Entries, K extends KeyOfEntries<E>> = {\n\t[P in Count<E[`length`]>]: E[P] extends [K, infer V] ? V : never\n}[Count<E[`length`]>]\n\ntype FromEntries<E extends Entries> = Flat<{\n\t[K in KeyOfEntries<E>]: ValueOfEntry<E, K>\n}>\n\nfunction fromEntries<E extends Entries>(entries: E): FromEntries<E> {\n\treturn Object.fromEntries(entries) as FromEntries<E>\n}\n\nfunction toEntries<T extends object>(obj: T): Entries<keyof T, T[keyof T]> {\n\treturn Object.entries(obj) as Entries<keyof T, T[keyof T]>\n}\n"],"mappings":";;;AAeA,IAAa,OAAb,MAKE;CACD,AAAgB;CAChB,AAAgB;CAChB,AAAgB;CAChB,AAAmB;CAInB,AAAO,YAAY,SAIhB;EACF,MAAM,EAAE,OAAO,aAAa,oBAAoB;AAChD,OAAK,QAAQ;AACb,OAAK,iBAAiB;AACtB,OAAK,kBAAkB;AACvB,OAAK,8BAA8B,YAClC,UAA2B,gBAAgB,CAAC,KAC1C,CAAC,MAAM,uBAAuB,CAC9B,MACA,0BAA0B,kBAAkB,CAC5C,CACD,CACD;;CAGF,AAAO,MAAM,MAAY,YAAiC;AACzD,SAAO,KAAK,4BAA4B,MAAM,IAAI,WAAW;;;AAI/D,SAAS,0BACR,eACsB;CACtB,MAAM,eAAe,IAAI,IAAY,cAAc;AACnD,MAAK,MAAM,cAAc,eAAe;EACvC,MAAM,gBAAgB,WAAW,MAAM,IAAI;AAC3C,OAAK,IAAI,IAAI,GAAG,IAAI,cAAc,SAAS,GAAG,KAAK;GAClD,MAAM,gBAAgB,cAAc,MAAM,GAAG,IAAI,EAAE,CAAC,KAAK,IAAI;AAC7D,gBAAa,IAAI,cAAc;;;AAGjC,QAAO;;AAIR,IAAa,YAAb,MAME;CACD,AAAgB;CAChB,AAAgB;CAChB,AAAgB;CAChB,AAAgB;CAChB,AAAO,YACN,SAMC;EACD,MAAM,EAAE,OAAO,MAAM,gBAAgB,aAAa;AAClD,OAAK,QAAQ;AACb,OAAK,OAAO;AACZ,OAAK,iBAAiB;AACtB,OAAK,WAAW;;CAGjB,AAAO,IAAI,MAAuB;EACjC,IAAI,SAAgB,KAAK;AACzB,OAAK,MAAM,CAAC,YAAY,SAAS,KAAK,gBAAgB;GACrD,MAAM,gBAAgB,KAAK,KAAK,MAAM,MAAM,WAAW;AACvD,WAAQ,KAAK,OAAb;IACC,KAAK;AACJ,SAAI,cAAe,QAAO;AAC1B;IACD,KAAK;AACJ,SAAI,cAAe,UAAS;AAC5B;IACD,KAAK,YACJ,KAAI,cAAe,UAAS;QACvB,QAAO;;;AAGf,SAAO;;;AA2BT,SAAS,YAA+B,SAA4B;AACnE,QAAO,OAAO,YAAY,QAAQ;;AAGnC,SAAS,UAA4B,KAAsC;AAC1E,QAAO,OAAO,QAAQ,IAAI"}
1
+ {"version":3,"file":"jurist.js","names":[],"sources":["../src/jurist.ts"],"sourcesContent":["import type { Join, Tree, TreePathName } from \"treetrunks\"\n\nexport * from \"treetrunks\"\n\nexport type Roles<L extends Laws<any, any, any, any>> =\n\tL extends Laws<infer Role, any, any, any> ? Role : never\n\nexport type Permissions<L extends Laws<any, any, any, any>> =\n\tL extends Laws<any, any, infer Permission, any> ? Permission : never\n\nexport type PermissionData<L extends Laws<any, any, any, any>, D> = Entries<\n\tPermissions<L>,\n\tD\n>\n\nexport class Laws<\n\tRole extends string,\n\tPermissionTree extends Tree,\n\tPermission extends Join<TreePathName<PermissionTree>, `_`>,\n\tRolePermissions extends { [r in Role]: ReadonlySet<Permission> },\n> {\n\tpublic readonly roles: Role[]\n\tpublic readonly permissionTree: PermissionTree\n\tpublic readonly rolePermissions: RolePermissions\n\tprotected readonly decompressedRolePermissions: {\n\t\t[r in Role]: ReadonlySet<string>\n\t}\n\n\tpublic constructor(options: {\n\t\troles: Role[]\n\t\tpermissions: PermissionTree\n\t\trolePermissions: RolePermissions\n\t}) {\n\t\tconst { roles, permissions, rolePermissions } = options\n\t\tthis.roles = roles\n\t\tthis.permissionTree = permissions\n\t\tthis.rolePermissions = rolePermissions\n\t\tthis.decompressedRolePermissions = fromEntries(\n\t\t\ttoEntries<RolePermissions>(rolePermissions).map(\n\t\t\t\t([role, permissionsOfRole]) => [\n\t\t\t\t\trole,\n\t\t\t\t\tdecompressRolePermissions(permissionsOfRole),\n\t\t\t\t],\n\t\t\t),\n\t\t)\n\t}\n\n\tpublic check(role: Role, permission: Permission): boolean {\n\t\treturn this.decompressedRolePermissions[role].has(permission)\n\t}\n}\n\nfunction decompressRolePermissions(\n\tpermissionSet: ReadonlySet<string>,\n): ReadonlySet<string> {\n\tconst decompressed = new Set<string>(permissionSet)\n\tfor (const permission of permissionSet) {\n\t\tconst preconditions = permission.split(`_`)\n\t\tfor (let i = 0; i < preconditions.length - 1; i++) {\n\t\t\tconst subPermission = preconditions.slice(0, i + 1).join(`_`)\n\t\t\tdecompressed.add(subPermission)\n\t\t}\n\t}\n\treturn decompressed\n}\n\nexport type EscalatorStyle = `firstFound` | `lastFound` | `untilMiss`\nexport class Escalator<\n\tS extends EscalatorStyle,\n\tL extends Laws<any, any, any, any>,\n\tP extends PermissionData<L, any>,\n\tD extends P extends PermissionData<L, infer d> ? d : never,\n\tF,\n> {\n\tpublic readonly style: S\n\tpublic readonly laws: L\n\tpublic readonly permissionData: P\n\tpublic readonly fallback: F\n\tpublic constructor(\n\t\toptions: Readonly<{\n\t\t\tstyle: S\n\t\t\tlaws: L\n\t\t\tpermissionData: P\n\t\t\tfallback: F\n\t\t}>,\n\t) {\n\t\tconst { style, laws, permissionData, fallback } = options\n\t\tthis.style = style\n\t\tthis.laws = laws\n\t\tthis.permissionData = permissionData\n\t\tthis.fallback = fallback\n\t}\n\n\tpublic get(role: Roles<L>): D | F {\n\t\tlet result: D | F = this.fallback\n\t\tfor (const [permission, data] of this.permissionData) {\n\t\t\tconst hasPermission = this.laws.check(role, permission)\n\t\t\tswitch (this.style) {\n\t\t\t\tcase `firstFound`:\n\t\t\t\t\tif (hasPermission) return data\n\t\t\t\t\tbreak\n\t\t\t\tcase `lastFound`:\n\t\t\t\t\tif (hasPermission) result = data\n\t\t\t\t\tbreak\n\t\t\t\tcase `untilMiss`:\n\t\t\t\t\tif (hasPermission) result = data\n\t\t\t\t\telse return result\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n}\n\ntype Flat<R extends { [K in PropertyKey]: any }> = {\n\t[K in keyof R]: R[K]\n}\n\nexport type Count<N extends number, A extends any[] = []> = [\n\t...A,\n\tany,\n][`length`] extends N\n\t? A[`length`]\n\t: A[`length`] | Count<N, [...A, any]>\n\ntype Entries<K extends PropertyKey = PropertyKey, V = any> = [K, V][]\n\ntype KeyOfEntries<E extends Entries> = E extends [infer K, any][] ? K : never\n\ntype ValueOfEntry<E extends Entries, K extends KeyOfEntries<E>> = {\n\t[P in Count<E[`length`]>]: E[P] extends [K, infer V] ? V : never\n}[Count<E[`length`]>]\n\ntype FromEntries<E extends Entries> = Flat<{\n\t[K in KeyOfEntries<E>]: ValueOfEntry<E, K>\n}>\n\nfunction fromEntries<E extends Entries>(entries: E): FromEntries<E> {\n\treturn Object.fromEntries(entries) as FromEntries<E>\n}\n\nfunction toEntries<T extends object>(obj: T): Entries<keyof T, T[keyof T]> {\n\treturn Object.entries(obj) as Entries<keyof T, T[keyof T]>\n}\n"],"mappings":";;AAeA,IAAa,OAAb,MAKE;CACD;CACA;CACA;CACA;CAIA,YAAmB,SAIhB;EACF,MAAM,EAAE,OAAO,aAAa,oBAAoB;EAChD,KAAK,QAAQ;EACb,KAAK,iBAAiB;EACtB,KAAK,kBAAkB;EACvB,KAAK,8BAA8B,YAClC,UAA2B,eAAe,CAAC,CAAC,KAC1C,CAAC,MAAM,uBAAuB,CAC9B,MACA,0BAA0B,iBAAiB,CAC5C,CACD,CACD;CACD;CAEA,MAAa,MAAY,YAAiC;EACzD,OAAO,KAAK,4BAA4B,KAAK,CAAC,IAAI,UAAU;CAC7D;AACD;AAEA,SAAS,0BACR,eACsB;CACtB,MAAM,eAAe,IAAI,IAAY,aAAa;CAClD,KAAK,MAAM,cAAc,eAAe;EACvC,MAAM,gBAAgB,WAAW,MAAM,GAAG;EAC1C,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,SAAS,GAAG,KAAK;GAClD,MAAM,gBAAgB,cAAc,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG;GAC5D,aAAa,IAAI,aAAa;EAC/B;CACD;CACA,OAAO;AACR;AAGA,IAAa,YAAb,MAME;CACD;CACA;CACA;CACA;CACA,YACC,SAMC;EACD,MAAM,EAAE,OAAO,MAAM,gBAAgB,aAAa;EAClD,KAAK,QAAQ;EACb,KAAK,OAAO;EACZ,KAAK,iBAAiB;EACtB,KAAK,WAAW;CACjB;CAEA,IAAW,MAAuB;EACjC,IAAI,SAAgB,KAAK;EACzB,KAAK,MAAM,CAAC,YAAY,SAAS,KAAK,gBAAgB;GACrD,MAAM,gBAAgB,KAAK,KAAK,MAAM,MAAM,UAAU;GACtD,QAAQ,KAAK,OAAb;IACC,KAAK;KACJ,IAAI,eAAe,OAAO;KAC1B;IACD,KAAK;KACJ,IAAI,eAAe,SAAS;KAC5B;IACD,KAAK,aACJ,IAAI,eAAe,SAAS;SACvB,OAAO;GACd;EACD;EACA,OAAO;CACR;AACD;AAyBA,SAAS,YAA+B,SAA4B;CACnE,OAAO,OAAO,YAAY,OAAO;AAClC;AAEA,SAAS,UAA4B,KAAsC;CAC1E,OAAO,OAAO,QAAQ,GAAG;AAC1B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jurist",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "license": "MIT",
5
5
  "author": {
6
6
  "name": "Jeremy Banka",
@@ -21,26 +21,25 @@
21
21
  ],
22
22
  "main": "dist/jurist.js",
23
23
  "dependencies": {
24
- "treetrunks": "0.1.6"
24
+ "treetrunks": "0.1.8"
25
25
  },
26
26
  "devDependencies": {
27
- "@typescript/native-preview": "7.0.0-dev.20260303.1",
28
- "concurrently": "9.2.1",
29
- "eslint": "10.0.2",
30
- "rimraf": "6.1.3",
31
- "tsdown": "0.20.3",
32
- "vite": "7.3.1",
33
- "vitest": "4.0.18"
27
+ "@typescript/native-preview": "7.0.0-dev.20260611.2",
28
+ "concurrently": "10.0.3",
29
+ "eslint": "10.4.1",
30
+ "tsdown": "0.22.2",
31
+ "vite": "8.0.16",
32
+ "vitest": "4.1.8"
34
33
  },
35
34
  "scripts": {
36
35
  "build": "tsdown",
37
- "lint:biome": "biome check -- .",
38
36
  "lint:eslint": "eslint -- .",
37
+ "lint:oxlint": "oxlint --type-aware -c ../../.oxlintrc.jsonc .",
39
38
  "lint:types": "tsgo --noEmit",
40
39
  "watch:types": "tsgo --watch --noEmit",
41
40
  "lint": "concurrently \"bun:lint:*\"",
42
41
  "test": "vitest",
43
42
  "test:once": "vitest run",
44
- "postversion": "biome format --write package.json"
43
+ "postversion": "dprint fmt package.json"
45
44
  }
46
45
  }