@noxify/casl-drizzle 0.0.1-beta.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 +21 -0
- package/README.md +107 -0
- package/dist/index.d.mts +214 -0
- package/dist/index.mjs +1 -0
- package/package.json +91 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Guilherme Araújo
|
|
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,107 @@
|
|
|
1
|
+
> work in progress :)
|
|
2
|
+
|
|
3
|
+
# casl-drizzle
|
|
4
|
+
|
|
5
|
+
CASL integration for Drizzle ORM - Add type-safe authorization to your database queries
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install ucastle @casl/ability
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
Define your abilities using Drizzle's query types directly:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import type { QueryInput } from "ucastle"
|
|
19
|
+
import { integer, pgTable, text } from "drizzle-orm/pg-core"
|
|
20
|
+
import { accessibleBy, defineAbility } from "ucastle"
|
|
21
|
+
|
|
22
|
+
// Define your Drizzle schema
|
|
23
|
+
const users = pgTable("users", {
|
|
24
|
+
id: integer().primaryKey(),
|
|
25
|
+
name: text().notNull(),
|
|
26
|
+
email: text().notNull(),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const schema = { users }
|
|
30
|
+
|
|
31
|
+
// Extract query types for your tables
|
|
32
|
+
type UserQuery = QueryInput<typeof schema, "users">
|
|
33
|
+
|
|
34
|
+
// Create abilities with subject-specific autocomplete
|
|
35
|
+
const ability = defineAbility<{ users: UserQuery }>((can, cannot) => {
|
|
36
|
+
can("read", "users", { id: 1 }) // ✅ Autocomplete shows only user fields!
|
|
37
|
+
can("update", "users", { id: 1 })
|
|
38
|
+
cannot("delete", "users")
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// Use with accessibleBy to get database filters
|
|
42
|
+
const filters = accessibleBy(ability, "read")
|
|
43
|
+
const readableUsers = await db.query.users.findMany({ where: filters.users })
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
- 🔒 **Type-safe authorization** - Full TypeScript support with Drizzle types
|
|
49
|
+
- 🎯 **CASL integration** - Leverage CASL's powerful rule system
|
|
50
|
+
- 🗄️ **DB agnostic** - Works with PostgreSQL, MySQL, SQLite, etc.
|
|
51
|
+
- 🔗 **Relation support** - Filter by related table conditions
|
|
52
|
+
- 📦 **Zero overhead** - Direct type composition, no runtime wrappers
|
|
53
|
+
- 💡 **Smart autocomplete** - Subject-specific field suggestions with `defineAbility()`
|
|
54
|
+
|
|
55
|
+
## With Relations
|
|
56
|
+
|
|
57
|
+
For schemas with relations, use `RelationalQueryInput`:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import type { RelationalQueryInput } from "ucastle"
|
|
61
|
+
import { defineRelations } from "drizzle-orm"
|
|
62
|
+
|
|
63
|
+
const posts = pgTable("posts", {
|
|
64
|
+
id: integer().primaryKey(),
|
|
65
|
+
title: text().notNull(),
|
|
66
|
+
authorId: integer().notNull(),
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const relations = defineRelations({ users, posts }, (r) => ({
|
|
70
|
+
users: { posts: r.many(posts) },
|
|
71
|
+
posts: { author: r.one(users, { fields: [posts.authorId], references: [users.id] }) },
|
|
72
|
+
}))
|
|
73
|
+
|
|
74
|
+
type PostQuery = RelationalQueryInput<typeof relations, "posts">
|
|
75
|
+
|
|
76
|
+
const ability = defineAbility<{ posts: PostQuery }>((can) => {
|
|
77
|
+
can("read", "posts", { published: true })
|
|
78
|
+
can("update", "posts", { authorId: 1 })
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Alternative: AbilityBuilder
|
|
83
|
+
|
|
84
|
+
If you prefer the traditional AbilityBuilder pattern:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import type { DefineAbility } from "ucastle"
|
|
88
|
+
import { AbilityBuilder } from "@casl/ability"
|
|
89
|
+
import { createDrizzleAbilityFor } from "ucastle"
|
|
90
|
+
|
|
91
|
+
type AppAbility = DefineAbility<{ users: UserQuery }>
|
|
92
|
+
|
|
93
|
+
const { can, cannot, build } = new AbilityBuilder<AppAbility>(createDrizzleAbilityFor())
|
|
94
|
+
|
|
95
|
+
can("read", "users", { id: 1 })
|
|
96
|
+
cannot("delete", "users")
|
|
97
|
+
|
|
98
|
+
const ability = build()
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Documentation
|
|
102
|
+
|
|
103
|
+
See [SIMPLIFIED_API.md](./SIMPLIFIED_API.md) for detailed examples and patterns.
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { AbilityOptions, AbilityOptionsOf, AbilityTuple, AnyAbility, ForcedSubject, PureAbility, RawRuleFrom, RawRuleOf, hkt } from "@casl/ability";
|
|
2
|
+
import * as _ucast_core0 from "@ucast/core";
|
|
3
|
+
import { DBQueryConfig, SchemaEntry, TablesRelationalConfig } from "drizzle-orm/relations";
|
|
4
|
+
import { KnownKeysOnly } from "drizzle-orm/utils";
|
|
5
|
+
|
|
6
|
+
//#region src/drizzle-query.d.ts
|
|
7
|
+
declare const drizzleQuery: (query: Record<string, unknown>, ...args: unknown[]) => {
|
|
8
|
+
(...args: any[]): any;
|
|
9
|
+
ast: _ucast_core0.Condition;
|
|
10
|
+
};
|
|
11
|
+
type Model<T, TName extends string> = T & ForcedSubject<TName>;
|
|
12
|
+
type Subjects<T extends Partial<Record<string, Record<string, unknown>>>> = keyof T | { [K in keyof T]: Model<T[K], K & string> }[keyof T];
|
|
13
|
+
/**
|
|
14
|
+
* Extracts Drizzle model name from given object and possible list of all subjects
|
|
15
|
+
*/
|
|
16
|
+
type ExtractModelName<TObject, TModelName extends PropertyKey> = TObject extends {
|
|
17
|
+
kind: TModelName;
|
|
18
|
+
} ? TObject["kind"] : TObject extends ForcedSubject<TModelName> ? TObject["__caslSubjectType__"] : TObject extends {
|
|
19
|
+
__typename: TModelName;
|
|
20
|
+
} ? TObject["__typename"] : TModelName;
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/types.d.ts
|
|
23
|
+
interface BaseDrizzleQuery {
|
|
24
|
+
[ɵdrizzleTypes]?: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
declare const ɵdrizzleTypes: unique symbol;
|
|
27
|
+
type DrizzleQueryFactory = Record<string, unknown> & hkt.Container<hkt.GenericFactory> & BaseDrizzleQuery;
|
|
28
|
+
type DrizzleModel = Model<Record<string, unknown>, string>;
|
|
29
|
+
type WhereInput = Record<string, unknown>;
|
|
30
|
+
/**
|
|
31
|
+
* Utility type to recursively remove the RAW property from Drizzle query types.
|
|
32
|
+
* RAW is used internally by Drizzle for SQL functions but shouldn't be exposed in our API.
|
|
33
|
+
*/
|
|
34
|
+
type OmitRaw<T> = T extends object ? { [K in keyof T as K extends "RAW" ? never : K]: T[K] extends object ? T[K] extends any[] ? OmitRaw<T[K][number]>[] : OmitRaw<T[K]> : T[K] } : T;
|
|
35
|
+
/**
|
|
36
|
+
* Query input for tables WITH relations.
|
|
37
|
+
* Extracts the complete where type including relation filters from Drizzle's DBQueryConfig.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* import { defineRelations } from "drizzle-orm"
|
|
42
|
+
* import type { RelationalQueryInput } from "ucastle"
|
|
43
|
+
*
|
|
44
|
+
* const relations = defineRelations({ users, posts }, ...)
|
|
45
|
+
* type UserQuery = RelationalQueryInput<typeof relations, "users">
|
|
46
|
+
*
|
|
47
|
+
* // Now includes: field conditions + relation filters + AND/OR/NOT
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
type RelationalQueryInput<TSchema extends TablesRelationalConfig, TTableName extends keyof TSchema> = OmitRaw<Exclude<KnownKeysOnly<DBQueryConfig<"many", TSchema, TSchema[TTableName]>, DBQueryConfig<"many", TSchema, TSchema[TTableName]>>["where"], undefined>>;
|
|
51
|
+
/**
|
|
52
|
+
* Query input for tables WITHOUT relations.
|
|
53
|
+
* Supports both field conditions and compound operators (AND/OR/NOT) at root level.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* import type { QueryInput } from "ucastle"
|
|
58
|
+
*
|
|
59
|
+
* const schema = { users, posts }
|
|
60
|
+
* type UserQuery = QueryInput<typeof schema, "users">
|
|
61
|
+
*
|
|
62
|
+
* // Field conditions
|
|
63
|
+
* const q1: UserQuery = { id: 1, name: "John" }
|
|
64
|
+
*
|
|
65
|
+
* // With operators
|
|
66
|
+
* const q2: UserQuery = { id: { gte: 1 }, AND: [{ name: "John" }] }
|
|
67
|
+
*
|
|
68
|
+
* // Compound operators
|
|
69
|
+
* const q3: UserQuery = { OR: [{ age: 18 }, { role: "admin" }] }
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
type QueryInput<TSchema extends Record<string, SchemaEntry>, TTableName extends keyof TSchema> = OmitRaw<{ [K in keyof (TSchema[TTableName] extends {
|
|
73
|
+
$inferSelect: infer S;
|
|
74
|
+
} ? S : never)]?: any } & {
|
|
75
|
+
AND?: QueryInput<TSchema, TTableName>[];
|
|
76
|
+
OR?: QueryInput<TSchema, TTableName>[];
|
|
77
|
+
NOT?: QueryInput<TSchema, TTableName>;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* Helper type to create CASL subjects from a mapping of table names to query types.
|
|
81
|
+
* Similar to @casl/prisma's Subjects pattern.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```ts
|
|
85
|
+
* import { PureAbility } from "@casl/ability"
|
|
86
|
+
* import type { RelationalQueryInput, Subjects } from "ucastle"
|
|
87
|
+
*
|
|
88
|
+
* type UserQuery = RelationalQueryInput<typeof relations, "users">
|
|
89
|
+
* type PostQuery = RelationalQueryInput<typeof relations, "posts">
|
|
90
|
+
*
|
|
91
|
+
* type AppAbility = PureAbility<[string, Subjects<{
|
|
92
|
+
* users: UserQuery,
|
|
93
|
+
* posts: PostQuery
|
|
94
|
+
* }>]>
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
type Subjects$1<T> = { [K in keyof T]: Model<T[K], K & string> }[keyof T] | Extract<keyof T, string>;
|
|
98
|
+
/**
|
|
99
|
+
* Custom Ability type with subject-specific conditions.
|
|
100
|
+
* Similar to @casl/prisma's PrismaAbility.
|
|
101
|
+
*
|
|
102
|
+
* This type ensures that conditions are properly typed based on the subject mapping.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* import type { DrizzleAbility } from "ucastle"
|
|
107
|
+
*
|
|
108
|
+
* type SubjectMap = {
|
|
109
|
+
* users: UserQuery
|
|
110
|
+
* posts: PostQuery
|
|
111
|
+
* }
|
|
112
|
+
*
|
|
113
|
+
* type AppAbility = DrizzleAbility<SubjectMap>
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
type DrizzleAbility$1<T> = PureAbility<[string, Subjects$1<T>], T[keyof T]>;
|
|
117
|
+
/**
|
|
118
|
+
* Helper type to create a fully-typed DrizzleAbility from a subject mapping.
|
|
119
|
+
* Provides subject-specific autocomplete in `can()` and `cannot()` methods
|
|
120
|
+
* when used with `defineAbility()`.
|
|
121
|
+
* Works with both `type` and `interface`.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* import { defineAbility, type DefineAbility } from "ucastle"
|
|
126
|
+
*
|
|
127
|
+
* // Works with type
|
|
128
|
+
* type SubjectMap = {
|
|
129
|
+
* users: UserQuery
|
|
130
|
+
* posts: PostQuery
|
|
131
|
+
* }
|
|
132
|
+
*
|
|
133
|
+
* // Also works with interface
|
|
134
|
+
* interface SubjectMap {
|
|
135
|
+
* users: UserQuery
|
|
136
|
+
* posts: PostQuery
|
|
137
|
+
* }
|
|
138
|
+
*
|
|
139
|
+
* // Use with defineAbility for subject-specific autocomplete:
|
|
140
|
+
* const ability = defineAbility<SubjectMap>((can, cannot) => {
|
|
141
|
+
* can("read", "users", { id: 1 }) // ✅ Only user fields!
|
|
142
|
+
* can("read", "posts", { authorId: 1 }) // ✅ Only post fields!
|
|
143
|
+
* })
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
type DefineAbility<T> = DrizzleAbility$1<T>;
|
|
147
|
+
//#endregion
|
|
148
|
+
//#region src/factories/accessible-by.d.ts
|
|
149
|
+
/**
|
|
150
|
+
* @deprecated use accessibleBy directly instead. It will infer the types from passed Ability instance.
|
|
151
|
+
*/
|
|
152
|
+
declare const createAccessibleByFactory: <TResult extends Record<string, unknown>, TDrizzleQuery>() => <TAbility extends PureAbility<any, TDrizzleQuery>>(ability: TAbility, action?: TAbility["rules"][number]["action"]) => TResult;
|
|
153
|
+
declare function accessibleBy<TAbility extends PureAbility<any, any>>(ability: TAbility, action?: TAbility["rules"][number]["action"]): Record<string, WhereInput>;
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/factories/create-ability.d.ts
|
|
156
|
+
declare function createAbilityFactory<TModelName extends string, TDrizzleQuery extends Record<string, any>>(): {
|
|
157
|
+
<T extends PureAbility<any, TDrizzleQuery>>(rules?: RawRuleOf<T>[], options?: AbilityOptionsOf<T>): T;
|
|
158
|
+
<A extends AbilityTuple = [string, TModelName], C extends TDrizzleQuery = TDrizzleQuery>(rules?: RawRuleFrom<A, C>[], options?: AbilityOptions<A, C>): PureAbility<A, C>;
|
|
159
|
+
};
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/query-error.d.ts
|
|
162
|
+
declare class ParsingQueryError extends Error {
|
|
163
|
+
static invalidArgument(operatorName: string, value: unknown, expectValueType: string): ParsingQueryError;
|
|
164
|
+
}
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region src/index.d.ts
|
|
167
|
+
/**
|
|
168
|
+
* Factory function to create a DrizzleAbility instance.
|
|
169
|
+
* Use with AbilityBuilder for type-safe ability definitions.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```ts
|
|
173
|
+
* import { AbilityBuilder } from "@casl/ability"
|
|
174
|
+
* import { createDrizzleAbilityFor, type DefineAbility } from "ucastle"
|
|
175
|
+
*
|
|
176
|
+
* type AppAbility = DefineAbility<{
|
|
177
|
+
* users: UserQuery
|
|
178
|
+
* posts: PostQuery
|
|
179
|
+
* }>
|
|
180
|
+
*
|
|
181
|
+
* const { can, build } = new AbilityBuilder<AppAbility>(createDrizzleAbilityFor())
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
declare function createDrizzleAbilityFor(): new (...args: ConstructorParameters<typeof PureAbility>) => AnyAbility;
|
|
185
|
+
/**
|
|
186
|
+
* Create a type-safe ability with subject-specific conditions.
|
|
187
|
+
* This provides better autocomplete than using AbilityBuilder directly.
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```ts
|
|
191
|
+
* import { defineAbility } from "ucastle"
|
|
192
|
+
*
|
|
193
|
+
* const ability = defineAbility<{
|
|
194
|
+
* users: UserQuery
|
|
195
|
+
* posts: PostQuery
|
|
196
|
+
* }>((can, cannot) => {
|
|
197
|
+
* can("read", "users", { id: 1 }) // ✅ Only user fields
|
|
198
|
+
* can("read", "posts", { authorId: 1 }) // ✅ Only post fields
|
|
199
|
+
* })
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
declare function defineAbility<T>(define: (can: <S extends keyof T & string>(action: string, subject: S, conditions?: T[S]) => void, cannot: <S extends keyof T & string>(action: string, subject: S, conditions?: T[S]) => void) => void): PureAbility<[string, any], T[keyof T]>;
|
|
203
|
+
/**
|
|
204
|
+
* Uses conditional type to support union distribution
|
|
205
|
+
*/
|
|
206
|
+
type ExtendedAbilityTuple<T extends AbilityTuple> = T extends AbilityTuple ? [T[0], "all" | T[1]] : never;
|
|
207
|
+
/**
|
|
208
|
+
* @deprecated use createDrizzleAbilityFor instead
|
|
209
|
+
*/
|
|
210
|
+
declare class DrizzleAbility<A extends AbilityTuple = [string, string], C extends Record<string, unknown> = Record<string, unknown>> extends PureAbility<ExtendedAbilityTuple<A>, C> {
|
|
211
|
+
constructor(rules?: RawRuleFrom<ExtendedAbilityTuple<A>, C>[], options?: AbilityOptions<ExtendedAbilityTuple<A>, C>);
|
|
212
|
+
}
|
|
213
|
+
//#endregion
|
|
214
|
+
export { type BaseDrizzleQuery, type DefineAbility, DrizzleAbility, type DrizzleModel, type DrizzleQueryFactory, type Subjects as DrizzleSubjects, type ExtractModelName, type Model, ParsingQueryError, type QueryInput, type RelationalQueryInput, type Subjects$1 as Subjects, type WhereInput, accessibleBy, createAbilityFactory, createAccessibleByFactory, createDrizzleAbilityFor, defineAbility, drizzleQuery };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AbilityBuilder as e,ForbiddenError as t,PureAbility as n,fieldPatternMatcher as r}from"@casl/ability";import{CompoundCondition as i,FieldCondition as a,NULL_CONDITION as o,ObjectQueryParser as s,buildAnd as c,createTranslatorFactory as l}from"@ucast/core";import{and as u,compare as d,createJsInterpreter as f,eq as p,gt as m,gte as h,lt as ee,lte as te,ne as g,or as _,within as v}from"@ucast/js";import{rulesToQuery as y}from"@casl/ability/extra";const b=(e,t,{get:n})=>n(t,e.field).startsWith(e.value),x=(e,t,{get:n})=>n(t,e.field).toLowerCase().startsWith(e.value.toLowerCase()),ne=(e,t,{get:n})=>n(t,e.field).endsWith(e.value),S=(e,t,{get:n})=>n(t,e.field).toLowerCase().endsWith(e.value.toLowerCase()),C=(e,t,{get:n})=>n(t,e.field).includes(e.value),w=(e,t,{get:n})=>n(t,e.field).toLowerCase().includes(e.value.toLowerCase()),T=(e,t,{get:n})=>{let r=n(t,e.field);return(Array.isArray(r)&&r.length===0)===e.value},E=(e,t,{get:n})=>{let r=n(t,e.field);return Array.isArray(r)&&r.includes(e.value)},D=(e,t,{get:n})=>{let r=n(t,e.field);return Array.isArray(r)&&e.value.some(e=>r.includes(e))},O=(e,t,{get:n})=>{let r=n(t,e.field);return Array.isArray(r)&&e.value.every(e=>r.includes(e))},k=(e,t,{get:n,interpret:r})=>{let i=n(t,e.field);return Array.isArray(i)&&i.length>0&&i.every(t=>r(e.value,t))},A=(e,t,{get:n,interpret:r})=>{let i=n(t,e.field);return Array.isArray(i)&&i.some(t=>r(e.value,t))},j=(e,t,{get:n,interpret:r})=>{let i=n(t,e.field);return typeof i==`object`&&!!i&&r(e.value,i)},M=(e,t,{interpret:n})=>e.value.every(e=>!n(e,t)),N=(e,t,{get:n})=>n(t,e.field)!==void 0;function P(e){return e&&typeof e==`object`?e.valueOf():e}const F=f({equals:p,notEquals:g,in:v,lt:ee,lte:te,gt:m,gte:h,startsWith:b,istartsWith:x,endsWith:ne,iendsWith:S,contains:C,icontains:w,isEmpty:T,has:E,hasSome:D,hasEvery:O,and:u,or:_,AND:u,OR:_,NOT:M,every:k,some:A,is:j,isSet:N},{get:(e,t)=>e[t],compare:(e,t)=>d(P(e),P(t))});var I=class extends Error{static invalidArgument(e,t,n){let r=`${typeof t}(${JSON.stringify(t,null,2)})`;return new this(`"${e}" expects to receive ${n} but instead got "${r}"`)}};const L=e=>typeof e==`object`&&!!e&&(Object.getPrototypeOf(e)===Object.prototype||Object.getPrototypeOf(e)===null),re={type:`field`,validate(e,t){if(Array.isArray(t)||L(t))throw new I(`"${e.name}" does not supports comparison of arrays and objects`)}},R={type:`field`,parse:((e,t,{hasOperators:n,field:r,parse:o})=>{if(L(t)&&!n(t)||Array.isArray(t))throw new I(`"${e.name}" does not supports comparison of arrays and objects`);return L(t)?new i(`NOT`,[o(t,{field:r})]):new a(`notEquals`,r,t)})},z={type:`field`,validate(e,t){if(!Array.isArray(t))throw I.invalidArgument(e.name,t,`an array`)}},B={type:`field`,validate(e,t){let n=typeof t;if(!(n===`string`||n===`number`&&Number.isFinite(t)||t instanceof Date))throw I.invalidArgument(e.name,t,`comparable value`)}},V=new Set([`insensitive`,`default`]),H={type:`field`,validate(e,t){if(!V.has(t))throw I.invalidArgument(e.name,t,`one of ${Array.from(V).join(`, `)}`)},parse:()=>o},U={type:`field`,validate(e,t){if(typeof t!=`string`)throw I.invalidArgument(e.name,t,`string`)},parse(e,t,{query:n,field:r}){return new a(n.mode===`insensitive`?`i${e.name}`:e.name,r,t)}},W={type:`compound`,validate(e,t){if(!t||typeof t!=`object`)throw I.invalidArgument(e.name,t,`an array or object`)},parse(e,t,{parse:n}){let r=(Array.isArray(t)?t:[t]).map(e=>n(e));return new i(e.name,r)}},G={type:`field`,validate(e,t){if(typeof t!=`boolean`)throw I.invalidArgument(e.name,t,`a boolean`)}},K={type:`field`},q={type:`field`,validate(e,t){if(!Array.isArray(t))throw I.invalidArgument(e.name,t,`an array`)}},J={type:`field`,parse(e,t,{field:n,parse:r}){if(!L(t))throw I.invalidArgument(e.name,t,`a query for nested relation`);return new a(e.name,n,r(t))}},Y=(e,t)=>{let n=t.parse?.bind(t);return n?{...t,parse(t,r,a){let o=n(t,r,a);if(o.operator!==t.name)throw Error(`Cannot invert "${e}" operator parser because it returns a complex Condition`);return o.operator=e,new i(`NOT`,[o])}}:{...t,parse(t,n,r){return new i(`NOT`,[new a(e,r.field,n)])}}},X={equals:re,not:R,in:z,notIn:Y(`in`,z),lt:B,lte:B,gt:B,gte:B,$lt:B,$lte:B,$gt:B,$gte:B,$in:z,$nin:Y(`in`,z),mode:H,startsWith:U,endsWith:U,contains:U,isEmpty:G,has:K,hasSome:q,hasEvery:q,NOT:W,AND:W,OR:W,every:J,some:J,none:Y(`some`,J),is:J,isNot:Y(`is`,J),isSet:G},Z=l(new class extends s{constructor(){super(X,{defaultOperatorName:`equals`})}parse(e,t){return t?.field?c(this.parseFieldOperators(t.field,e)):super.parse(e)}}().parse,F);function Q(e){if(typeof e!=`object`||!e)return e;if(Array.isArray(e))return e.map(Q);let t={};for(let[n,r]of Object.entries(e)){if(n===`OR`||n===`AND`){t[n]=Q(r);continue}if(n.startsWith(`$`)){let e=n.substring(1);t[e]=Q(r);continue}typeof r==`object`&&r&&!Array.isArray(r)?t[n]=Q(r):t[n]=r}return t}const ie={get(e,n){let r=y(e._ability,e._action,n,e=>e.inverted?{NOT:e.conditions}:e.conditions);if(r===null){let r=t.from(e._ability).setMessage(`It's not allowed to run "${e._action}" on "${n}"`);throw r.action=e._action,r.subjectType=r.subject=n,r}let i=Object.create(null);if(r.$or&&Array.isArray(r.$or)&&r.$or.length===1){let e=r.$or[0];Object.assign(i,e)}else r.$or&&(i.OR=r.$or);return r.$and&&(i.AND=r.$and),Q(i)}};function ae(e,t=`read`){return new Proxy({_ability:e,_action:t},ie)}function $(){function e(e=[],t={}){return new n(e,{...t,conditionsMatcher:Z,fieldMatcher:r})}return e}function oe(){return $()}function se(t){let n=new e($());return t((e,t,r)=>n.can(e,t,r),(e,t,r)=>n.cannot(e,t,r)),n.build()}var ce=class extends n{constructor(e,t){super(e,{conditionsMatcher:Z,fieldMatcher:r,...t})}};export{ce as DrizzleAbility,I as ParsingQueryError,ae as accessibleBy,oe as createDrizzleAbilityFor,se as defineAbility,Z as drizzleQuery};
|
package/package.json
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@noxify/casl-drizzle",
|
|
3
|
+
"version": "0.0.1-beta.0",
|
|
4
|
+
"description": "Drizzle-ORM adapter for CASL - generate Drizzle where inputs from CASL abilities",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"drizzle",
|
|
7
|
+
"drizzle-orm",
|
|
8
|
+
"casl",
|
|
9
|
+
"ucast"
|
|
10
|
+
],
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/noxify/casl-drizzle"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"authors": [
|
|
17
|
+
{
|
|
18
|
+
"name": "Marcus Reinhardt",
|
|
19
|
+
"email": "webstone@gmail.com"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "Guilherme Araújo",
|
|
23
|
+
"email": "arauujogui@gmail.com"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.mts",
|
|
30
|
+
"import": "./dist/index.mjs",
|
|
31
|
+
"default": "./src/index.ts"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"types": "./dist/index.d.mts",
|
|
35
|
+
"files": [
|
|
36
|
+
"dist"
|
|
37
|
+
],
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@ucast/core": "^1.10.2",
|
|
40
|
+
"@ucast/js": "3.1.0"
|
|
41
|
+
},
|
|
42
|
+
"prettier": "./prettier.config.mjs",
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@casl/ability": "^6.8.0",
|
|
45
|
+
"@changesets/cli": "2.29.8",
|
|
46
|
+
"@eslint/compat": "2.0.2",
|
|
47
|
+
"@eslint/js": "9.39.2",
|
|
48
|
+
"@ianvs/prettier-plugin-sort-imports": "4.7.1",
|
|
49
|
+
"@testcontainers/postgresql": "^11.11.0",
|
|
50
|
+
"@types/js-yaml": "4.0.9",
|
|
51
|
+
"@types/node": "24.10.13",
|
|
52
|
+
"@vitest/coverage-v8": "4.0.18",
|
|
53
|
+
"dedent": "1.7.1",
|
|
54
|
+
"drizzle-kit": "beta",
|
|
55
|
+
"drizzle-orm": "beta",
|
|
56
|
+
"eslint": "9.39.2",
|
|
57
|
+
"eslint-plugin-import": "2.32.0",
|
|
58
|
+
"eslint-plugin-package-json": "0.88.2",
|
|
59
|
+
"json-schema-to-typescript": "15.0.4",
|
|
60
|
+
"jsonc-eslint-parser": "2.4.2",
|
|
61
|
+
"node-pty": "1.1.0",
|
|
62
|
+
"postgres": "3.4.8",
|
|
63
|
+
"prettier": "3.8.1",
|
|
64
|
+
"tsdown": "0.20.3",
|
|
65
|
+
"tsx": "4.21.0",
|
|
66
|
+
"typescript": "^5.9.3",
|
|
67
|
+
"typescript-eslint": "8.55.0",
|
|
68
|
+
"vitest": "4.0.18"
|
|
69
|
+
},
|
|
70
|
+
"peerDependencies": {
|
|
71
|
+
"@casl/ability": "^6.8.0",
|
|
72
|
+
"drizzle-orm": ">=1.0.0-beta.15"
|
|
73
|
+
},
|
|
74
|
+
"engines": {
|
|
75
|
+
"node": ">=22"
|
|
76
|
+
},
|
|
77
|
+
"scripts": {
|
|
78
|
+
"build": "tsdown",
|
|
79
|
+
"ci:publish": "pnpm build && pnpm publish -r --access public --publish-branch main && pnpm changeset tag",
|
|
80
|
+
"ci:version": "pnpm changeset version && pnpm install --no-frozen-lockfile && git add .",
|
|
81
|
+
"cs": "changeset",
|
|
82
|
+
"format": "prettier --check . --ignore-path ./.gitignore --ignore-path ./.prettierignore",
|
|
83
|
+
"format:fix": "prettier --write . --ignore-path ./.gitignore --ignore-path ./.prettierignore",
|
|
84
|
+
"lint": "eslint .",
|
|
85
|
+
"lint:fix": "eslint . --fix",
|
|
86
|
+
"test": "vitest --run",
|
|
87
|
+
"test:coverage": "vitest --coverage",
|
|
88
|
+
"test:watch": "vitest",
|
|
89
|
+
"typecheck": "tsc --noEmit"
|
|
90
|
+
}
|
|
91
|
+
}
|