@m1212e/rumble 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 ADDED
@@ -0,0 +1,15 @@
1
+ # rumble
2
+
3
+ To install dependencies:
4
+
5
+ ```bash
6
+ bun install
7
+ ```
8
+
9
+ To run:
10
+
11
+ ```bash
12
+ bun run index.ts
13
+ ```
14
+
15
+ This project was created using `bun init` in bun v1.1.24. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
package/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';var P=require('@pothos/core'),O=require('@pothos/plugin-drizzle'),graphqlYoga=require('graphql-yoga'),drizzleOrm=require('drizzle-orm');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var P__default=/*#__PURE__*/_interopDefault(P);var O__default=/*#__PURE__*/_interopDefault(O);function h(e){return typeof e!="function"}function A(e){return typeof e=="function"&&e.constructor.name!=="AsyncFunction"}var C=({db:e,actions:s=["create","read","update","delete"]})=>{let u={},c={},p=n=>({allow:r=>{let i=c[n];i||(i={},c[n]=i);let a=Array.isArray(r)?r:[r];for(let d of a){let o=i[d];o||(o=[],i[d]=o);}return {when:d=>{for(let o of a)i[o].push(d);}}}});for(let n of Object.keys(e.query))u[n]=p(n);return {...u,registeredConditions:c,buildWithUserContext:n=>{let r={},i=a=>({filter:d=>{let o=c[a];if(!o)throw "TODO (No allowed entry found for this condition) #1";let f=o[d];if(!f)throw "TODO (No allowed entry found for this condition) #2";let b=f.filter(h),B=f.filter(A).map(t=>t(n)),D=[...b,...B],m;for(let t of D)t.limit&&(m===void 0||t.limit>m)&&(m=t.limit);let l;for(let t of D)t.columns&&(l===void 0?l=t.columns:l={...l,...t.columns});let x=D.filter(t=>t.where).map(t=>t.where);return {where:x.length>0?drizzleOrm.or(...x):void 0,columns:l,limit:m}}});for(let a of Object.keys(e.query))r[a]=i(a);return r}}};var U=async({db:e,nativeServerOptions:s,context:u})=>{let c=C({db:e}),p=async r=>{let i=u?await u(r):{};return {...i,abilities:c.buildWithUserContext(i)}},n=new P__default.default({plugins:[O__default.default],drizzle:{client:e},defaultFieldNullability:!1,defaultInputFieldRequiredness:!0});return n.queryType({}),n.mutationType({}),{abilityBuilder:c,schemaBuilder:n,yoga:()=>graphqlYoga.createYoga({...s,schema:n.toSchema(),context:p})}};var y=class extends Error{constructor(s){super(s),this.name="RumbleError";}};var g=e=>{if(!e)throw new y("Value not found but required (findFirst)");return e},z=e=>{let s=e.at(0);if(!s)throw new y("Value not found but required (firstEntry)");return s};exports.RumbleError=y;exports.assertFindFirstExists=g;exports.assertFirstEntryExists=z;exports.rumble=U;//# sourceMappingURL=index.cjs.map
2
+ //# sourceMappingURL=index.cjs.map
package/index.cjs.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../lib/abilities/builder.ts","../lib/gql/builder.ts","../lib/helpers/rumbleError.ts","../lib/helpers/helper.ts"],"names":["isSimpleCondition","condition","isSyncFunctionCondition","createAbilityBuilder","db","actions","builder","registeredConditions","createEntityObject","entityKey","action","conditionsPerEntity","conditionsPerEntityAndAction","userContext","simpleConditions","syncFunctionConditions","allConditionObjects","highestLimit","conditionObject","combinedAllowedColumns","accumulatedWhereConditions","o","or","rumble","nativeServerOptions","makeUserContext","abilityBuilder","makeContext","req","nativeBuilder","SchemaBuilder","DrizzlePlugin","createYoga","RumbleError","message","assertFindFirstExists","value","assertFirstEntryExists","v"],"mappings":"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","file":"index.cjs","sourcesContent":["import { or } from \"drizzle-orm\";\nimport type {\n\tGenericDrizzleDbTypeConstraints,\n\tQueryConditionObject,\n} from \"../types/genericDrizzleDbType\";\n\nexport type AbilityBuilder = ReturnType<typeof createAbilityBuilder>;\n\ntype Condition<DBParameters, UserContext> =\n\t| SimpleCondition<DBParameters>\n\t| SyncFunctionCondition<DBParameters, UserContext>;\n// | AsyncFunctionCondition<DBParameters, UserContext>;\n\ntype SimpleCondition<DBParameters> = DBParameters;\ntype SyncFunctionCondition<DBParameters, UserContext> = (\n\tcontext: UserContext,\n) => DBParameters;\n// type AsyncFunctionCondition<DBParameters, UserContext> = (\n// \tcontext: UserContext,\n// ) => Promise<DBParameters>;\n\n// type guards for the condition types\nfunction isSimpleCondition<DBParameters, UserContext>(\n\tcondition: Condition<DBParameters, UserContext>,\n): condition is SimpleCondition<DBParameters> {\n\treturn typeof condition !== \"function\";\n}\n\nfunction isSyncFunctionCondition<DBParameters, UserContext>(\n\tcondition: Condition<DBParameters, UserContext>,\n): condition is SyncFunctionCondition<DBParameters, UserContext> {\n\treturn (\n\t\ttypeof condition === \"function\" &&\n\t\tcondition.constructor.name !== \"AsyncFunction\"\n\t);\n}\n\n// function isAsyncFunctionCondition<DBParameters, UserContext>(\n// \tcondition: Condition<DBParameters, UserContext>,\n// ): condition is AsyncFunctionCondition<DBParameters, UserContext> {\n// \treturn (\n// \t\ttypeof condition === \"function\" &&\n// \t\tcondition.constructor.name === \"AsyncFunction\"\n// \t);\n// }\n\nexport const createAbilityBuilder = <\n\tUserContext extends Record<string, any>,\n\tDB extends GenericDrizzleDbTypeConstraints,\n\tAction extends string = \"create\" | \"read\" | \"update\" | \"delete\",\n>({\n\tdb,\n\tactions = [\"create\", \"read\", \"update\", \"delete\"] as Action[],\n}: {\n\tdb: DB;\n\tactions?: Action[];\n}) => {\n\ttype DBEntityKey = keyof DB[\"query\"];\n\n\tconst builder: {\n\t\t[key in DBEntityKey]: ReturnType<typeof createEntityObject>;\n\t} = {} as any;\n\n\tconst registeredConditions: {\n\t\t[key in DBEntityKey]: {\n\t\t\t[key in Action[number]]: (\n\t\t\t\t| QueryConditionObject\n\t\t\t\t| ((context: UserContext) => QueryConditionObject)\n\t\t\t)[];\n\t\t\t// | ((context: UserContext) => Promise<QueryConditionObject>)\n\t\t};\n\t} = {} as any;\n\n\tconst createEntityObject = (entityKey: DBEntityKey) => ({\n\t\tallow: (action: Action | Action[]) => {\n\t\t\ttype DBParameters = Parameters<DB[\"query\"][DBEntityKey][\"findMany\"]>[0];\n\n\t\t\tlet conditionsPerEntity = registeredConditions[entityKey];\n\t\t\tif (!conditionsPerEntity) {\n\t\t\t\tconditionsPerEntity = {} as any;\n\t\t\t\tregisteredConditions[entityKey] = conditionsPerEntity;\n\t\t\t}\n\n\t\t\tconst actions = Array.isArray(action) ? action : [action];\n\t\t\tfor (const action of actions) {\n\t\t\t\tlet conditionsPerEntityAndAction = conditionsPerEntity[action];\n\t\t\t\tif (!conditionsPerEntityAndAction) {\n\t\t\t\t\tconditionsPerEntityAndAction = [];\n\t\t\t\t\tconditionsPerEntity[action] = conditionsPerEntityAndAction;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\twhen: (condition: Condition<DBParameters, UserContext>) => {\n\t\t\t\t\tfor (const action of actions) {\n\t\t\t\t\t\tconst conditionsPerEntityAndAction = conditionsPerEntity[action];\n\t\t\t\t\t\tconditionsPerEntityAndAction.push(condition);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t});\n\n\tfor (const entityKey of Object.keys(db.query) as DBEntityKey[]) {\n\t\tbuilder[entityKey] = createEntityObject(entityKey);\n\t}\n\treturn {\n\t\t...builder,\n\t\tregisteredConditions,\n\t\tbuildWithUserContext: (userContext: UserContext) => {\n\t\t\tconst builder: {\n\t\t\t\t[key in DBEntityKey]: ReturnType<typeof createEntityObject>;\n\t\t\t} = {} as any;\n\n\t\t\tconst createEntityObject = (entityKey: DBEntityKey) => ({\n\t\t\t\tfilter: (action: Action) => {\n\t\t\t\t\tconst conditionsPerEntity = registeredConditions[entityKey];\n\t\t\t\t\tif (!conditionsPerEntity) {\n\t\t\t\t\t\tthrow \"TODO (No allowed entry found for this condition) #1\";\n\t\t\t\t\t}\n\n\t\t\t\t\tconst conditionsPerEntityAndAction = conditionsPerEntity[action];\n\t\t\t\t\tif (!conditionsPerEntityAndAction) {\n\t\t\t\t\t\tthrow \"TODO (No allowed entry found for this condition) #2\";\n\t\t\t\t\t}\n\n\t\t\t\t\tconst simpleConditions =\n\t\t\t\t\t\tconditionsPerEntityAndAction.filter(isSimpleCondition);\n\n\t\t\t\t\tconst syncFunctionConditions = conditionsPerEntityAndAction\n\t\t\t\t\t\t.filter(isSyncFunctionCondition)\n\t\t\t\t\t\t.map((condition) => condition(userContext));\n\n\t\t\t\t\t// const asyncFunctionConditions = await Promise.all(\n\t\t\t\t\t// \tconditionsPerEntityAndAction\n\t\t\t\t\t// \t\t.filter(isAsyncFunctionCondition)\n\t\t\t\t\t// \t\t.map((condition) => condition(userContext)),\n\t\t\t\t\t// );\n\n\t\t\t\t\tconst allConditionObjects = [\n\t\t\t\t\t\t...simpleConditions,\n\t\t\t\t\t\t...syncFunctionConditions,\n\t\t\t\t\t\t// ...asyncFunctionConditions,\n\t\t\t\t\t];\n\n\t\t\t\t\tlet highestLimit = undefined;\n\t\t\t\t\tfor (const conditionObject of allConditionObjects) {\n\t\t\t\t\t\tif (conditionObject.limit) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\thighestLimit === undefined ||\n\t\t\t\t\t\t\t\tconditionObject.limit > highestLimit\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\thighestLimit = conditionObject.limit;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tlet combinedAllowedColumns: Record<string, any> | undefined =\n\t\t\t\t\t\tundefined;\n\t\t\t\t\tfor (const conditionObject of allConditionObjects) {\n\t\t\t\t\t\tif (conditionObject.columns) {\n\t\t\t\t\t\t\tif (combinedAllowedColumns === undefined) {\n\t\t\t\t\t\t\t\tcombinedAllowedColumns = conditionObject.columns;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcombinedAllowedColumns = {\n\t\t\t\t\t\t\t\t\t...combinedAllowedColumns,\n\t\t\t\t\t\t\t\t\t...conditionObject.columns,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst accumulatedWhereConditions = allConditionObjects\n\t\t\t\t\t\t.filter((o) => o.where)\n\t\t\t\t\t\t.map((o) => o.where);\n\n\t\t\t\t\tconst combinedWhere =\n\t\t\t\t\t\taccumulatedWhereConditions.length > 0\n\t\t\t\t\t\t\t? or(...accumulatedWhereConditions)\n\t\t\t\t\t\t\t: undefined;\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\twhere: combinedWhere,\n\t\t\t\t\t\tcolumns: combinedAllowedColumns,\n\t\t\t\t\t\tlimit: highestLimit,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tfor (const entityKey of Object.keys(db.query) as DBEntityKey[]) {\n\t\t\t\tbuilder[entityKey] = createEntityObject(entityKey);\n\t\t\t}\n\n\t\t\treturn builder;\n\t\t},\n\t};\n};\n","import SchemaBuilder from \"@pothos/core\";\nimport DrizzlePlugin from \"@pothos/plugin-drizzle\";\nimport { type YogaServerOptions, createYoga } from \"graphql-yoga\";\nimport { createAbilityBuilder } from \"../abilities/builder\";\nimport type { GenericDrizzleDbTypeConstraints } from \"../types/genericDrizzleDbType\";\n\nexport const rumble = async <\n\tUserContext extends Record<string, any>,\n\tDB extends GenericDrizzleDbTypeConstraints,\n\tRequestEvent extends Record<string, any>,\n>({\n\tdb,\n\tnativeServerOptions,\n\tcontext: makeUserContext,\n}: {\n\t/**\n\t * Your drizzle database instance\n\t */\n\tdb: DB;\n\t/**\n\t * Optional options for the native GraphQL Yoga server\n\t */\n\tnativeServerOptions?:\n\t\t| Omit<YogaServerOptions<RequestEvent, any>, \"schema\" | \"context\">\n\t\t| undefined;\n\t/**\n\t * A function for providing context for each request based on the incoming HTTP Request.\n\t * The type of the parameter equals the HTTPRequest type of your chosen server.\n\t */\n\tcontext?:\n\t\t| ((event: RequestEvent) => Promise<UserContext> | UserContext)\n\t\t| undefined;\n}) => {\n\tconst abilityBuilder = createAbilityBuilder<UserContext, DB>({\n\t\tdb,\n\t});\n\n\tconst makeContext = async (req: RequestEvent) => {\n\t\tconst userContext = makeUserContext\n\t\t\t? await makeUserContext(req)\n\t\t\t: ({} as UserContext);\n\t\treturn {\n\t\t\t...userContext,\n\t\t\tabilities: abilityBuilder.buildWithUserContext(userContext),\n\t\t};\n\t};\n\n\tconst nativeBuilder = new SchemaBuilder<{\n\t\tContext: Awaited<ReturnType<typeof makeContext>>;\n\t\t// Scalars: Scalars<Prisma.Decimal, Prisma.InputJsonValue | null, Prisma.InputJsonValue> & {\n\t\t// \tFile: {\n\t\t// \t\tInput: File;\n\t\t// \t\tOutput: never;\n\t\t// \t};\n\t\t// \tJSONObject: {\n\t\t// \t\tInput: any;\n\t\t// \t\tOutput: any;\n\t\t// \t};\n\t\t// };\n\t\tDrizzleSchema: DB[\"_\"][\"fullSchema\"];\n\t\tDefaultFieldNullability: false;\n\t\tDefaultArgumentNullability: false;\n\t\tDefaultInputFieldRequiredness: true;\n\t}>({\n\t\tplugins: [DrizzlePlugin],\n\t\tdrizzle: {\n\t\t\tclient: db,\n\t\t},\n\t\tdefaultFieldNullability: false,\n\t\tdefaultInputFieldRequiredness: true,\n\t});\n\n\tnativeBuilder.queryType({});\n\tnativeBuilder.mutationType({});\n\n\treturn {\n\t\t/**\n * The ability builder. Use it to declare whats allowed for each entity in your DB.\n * \n * @example\n * \n * ```ts\n * // users can edit themselves\n abilityBuilder.users\n .allow([\"read\", \"update\", \"delete\"])\n .when(({ userId }) => ({ where: eq(schema.users.id, userId) }));\n \n // everyone can read posts\n abilityBuilder.posts.allow(\"read\");\n * \n * ```\n */\n\t\tabilityBuilder,\n\t\t/**\n\t\t * The pothos schema builder. See https://pothos-graphql.dev/docs/plugins/drizzle\n\t\t */\n\t\tschemaBuilder: nativeBuilder,\n\t\t/**\n * The native yoga instance. Can be used to run an actual HTTP server.\n * \n * @example\n * \n * ```ts\n import { createServer } from \"node:http\";\n * const server = createServer(yoga());\n server.listen(3000, () => {\n console.log(\"Visit http://localhost:3000/graphql\");\n });\n * ```\n */\n\t\tyoga: () =>\n\t\t\tcreateYoga<RequestEvent>({\n\t\t\t\t...nativeServerOptions,\n\t\t\t\tschema: nativeBuilder.toSchema(),\n\t\t\t\tcontext: makeContext,\n\t\t\t}),\n\t};\n};\n","export class RumbleError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"RumbleError\";\n\t}\n}\n","import { RumbleError } from \"./rumbleError\";\n\n/**\n * \n * Helper function to map a drizzle findFirst query result,\n * which may be optional, to a correct drizzle type.\n * \n * @throws RumbleError\n * \n * @example\n * \n * ```ts\n * schemaBuilder.queryFields((t) => {\n return {\n findFirstUser: t.drizzleField({\n type: UserRef,\n resolve: (query, root, args, ctx, info) => {\n return (\n db.query.users\n .findFirst({\n ...query,\n where: ctx.abilities.users.filter(\"read\").where,\n })\n // note that we need to manually raise an error if the value is not found\n .then(assertFindFirstExists)\n );\n },\n }),\n };\n });\n * ```\n */\nexport const assertFindFirstExists = <T>(value: T | undefined): T => {\n\tif (!value) throw new RumbleError(\"Value not found but required (findFirst)\");\n\treturn value;\n};\n\n/**\n * \n * Helper function to map a drizzle findFirst query result,\n * which may be optional, to a correct drizzle type.\n * \n * @throws RumbleError\n * \n * @example\n * \n * ```ts\n schemaBuilder.mutationFields((t) => {\n return {\n updateUsername: t.drizzleField({\n type: UserRef,\n args: {\n userId: t.arg.int({ required: true }),\n newName: t.arg.string({ required: true }),\n },\n resolve: (query, root, args, ctx, info) => {\n return db\n .update(schema.users)\n .set({\n name: args.newName,\n })\n .where(\n and(\n eq(schema.users.id, args.userId),\n ctx.abilities.users.filter(\"update\").where\n )\n )\n .returning({ id: schema.users.id, name: schema.users.name })\n // note that we need to manually raise an error if the value is not found\n .then(assertFirstEntryExists);\n },\n }),\n };\n });\n * ```\n */\nexport const assertFirstEntryExists = <T>(value: T[]): T => {\n\tconst v = value.at(0);\n\tif (!v) throw new RumbleError(\"Value not found but required (firstEntry)\");\n\treturn v;\n};\n"]}
package/index.d.cts ADDED
@@ -0,0 +1,176 @@
1
+ import * as graphql_yoga from 'graphql-yoga';
2
+ import { YogaServerOptions } from 'graphql-yoga';
3
+ import * as drizzle_orm from 'drizzle-orm';
4
+ import { DrizzleClient } from '@pothos/plugin-drizzle';
5
+
6
+ type QueryConditionObject = {
7
+ where: any;
8
+ columns: any;
9
+ limit: any;
10
+ };
11
+ type GenericDrizzleDbTypeConstraints = {
12
+ query: {
13
+ [key: string]: {
14
+ findMany: (P: QueryConditionObject) => any;
15
+ };
16
+ };
17
+ } & DrizzleClient;
18
+
19
+ declare const rumble: <UserContext extends Record<string, any>, DB extends GenericDrizzleDbTypeConstraints, RequestEvent extends Record<string, any>>({ db, nativeServerOptions, context: makeUserContext, }: {
20
+ /**
21
+ * Your drizzle database instance
22
+ */
23
+ db: DB;
24
+ /**
25
+ * Optional options for the native GraphQL Yoga server
26
+ */
27
+ nativeServerOptions?: Omit<YogaServerOptions<RequestEvent, any>, "schema" | "context"> | undefined;
28
+ /**
29
+ * A function for providing context for each request based on the incoming HTTP Request.
30
+ * The type of the parameter equals the HTTPRequest type of your chosen server.
31
+ */
32
+ context?: ((event: RequestEvent) => Promise<UserContext> | UserContext) | undefined;
33
+ }) => Promise<{
34
+ /**
35
+ * The ability builder. Use it to declare whats allowed for each entity in your DB.
36
+ *
37
+ * @example
38
+ *
39
+ * ```ts
40
+ * // users can edit themselves
41
+ abilityBuilder.users
42
+ .allow(["read", "update", "delete"])
43
+ .when(({ userId }) => ({ where: eq(schema.users.id, userId) }));
44
+
45
+ // everyone can read posts
46
+ abilityBuilder.posts.allow("read");
47
+ *
48
+ * ```
49
+ */
50
+ abilityBuilder: (keyof DB["query"] extends infer T extends keyof DB["query"] ? { [key in T]: {
51
+ allow: (action: "create" | "read" | "update" | "delete" | ("create" | "read" | "update" | "delete")[]) => {
52
+ when: (condition: Parameters<DB["query"][keyof DB["query"]]["findMany"]>[0] | ((context: UserContext) => Parameters<DB["query"][keyof DB["query"]]["findMany"]>[0])) => void;
53
+ };
54
+ }; } : never) & {
55
+ registeredConditions: keyof DB["query"] extends infer T_1 extends keyof DB["query"] ? { [key_1 in T_1]: {
56
+ [x: string]: (QueryConditionObject | ((context: UserContext) => QueryConditionObject))[];
57
+ }; } : never;
58
+ buildWithUserContext: (userContext: UserContext) => keyof DB["query"] extends infer T_2 extends keyof DB["query"] ? { [key_2 in T_2]: {
59
+ filter: (action: "create" | "read" | "update" | "delete") => {
60
+ where: drizzle_orm.SQL<unknown> | undefined;
61
+ columns: Record<string, any> | undefined;
62
+ limit: any;
63
+ };
64
+ }; } : never;
65
+ };
66
+ /**
67
+ * The pothos schema builder. See https://pothos-graphql.dev/docs/plugins/drizzle
68
+ */
69
+ schemaBuilder: PothosSchemaTypes.SchemaBuilder<PothosSchemaTypes.ExtendDefaultTypes<{
70
+ Context: Awaited<ReturnType<(req: RequestEvent) => Promise<UserContext & {
71
+ abilities: keyof DB["query"] extends infer T_2 extends keyof DB["query"] ? { [key_2 in T_2]: {
72
+ filter: (action: "create" | "read" | "update" | "delete") => {
73
+ where: drizzle_orm.SQL<unknown> | undefined;
74
+ columns: Record<string, any> | undefined;
75
+ limit: any;
76
+ };
77
+ }; } : never;
78
+ }>>>;
79
+ DrizzleSchema: DB["_"]["fullSchema"];
80
+ DefaultFieldNullability: false;
81
+ DefaultArgumentNullability: false;
82
+ DefaultInputFieldRequiredness: true;
83
+ }>>;
84
+ /**
85
+ * The native yoga instance. Can be used to run an actual HTTP server.
86
+ *
87
+ * @example
88
+ *
89
+ * ```ts
90
+ import { createServer } from "node:http";
91
+ * const server = createServer(yoga());
92
+ server.listen(3000, () => {
93
+ console.log("Visit http://localhost:3000/graphql");
94
+ });
95
+ * ```
96
+ */
97
+ yoga: () => graphql_yoga.YogaServerInstance<RequestEvent, {}>;
98
+ }>;
99
+
100
+ /**
101
+ *
102
+ * Helper function to map a drizzle findFirst query result,
103
+ * which may be optional, to a correct drizzle type.
104
+ *
105
+ * @throws RumbleError
106
+ *
107
+ * @example
108
+ *
109
+ * ```ts
110
+ * schemaBuilder.queryFields((t) => {
111
+ return {
112
+ findFirstUser: t.drizzleField({
113
+ type: UserRef,
114
+ resolve: (query, root, args, ctx, info) => {
115
+ return (
116
+ db.query.users
117
+ .findFirst({
118
+ ...query,
119
+ where: ctx.abilities.users.filter("read").where,
120
+ })
121
+ // note that we need to manually raise an error if the value is not found
122
+ .then(assertFindFirstExists)
123
+ );
124
+ },
125
+ }),
126
+ };
127
+ });
128
+ * ```
129
+ */
130
+ declare const assertFindFirstExists: <T>(value: T | undefined) => T;
131
+ /**
132
+ *
133
+ * Helper function to map a drizzle findFirst query result,
134
+ * which may be optional, to a correct drizzle type.
135
+ *
136
+ * @throws RumbleError
137
+ *
138
+ * @example
139
+ *
140
+ * ```ts
141
+ schemaBuilder.mutationFields((t) => {
142
+ return {
143
+ updateUsername: t.drizzleField({
144
+ type: UserRef,
145
+ args: {
146
+ userId: t.arg.int({ required: true }),
147
+ newName: t.arg.string({ required: true }),
148
+ },
149
+ resolve: (query, root, args, ctx, info) => {
150
+ return db
151
+ .update(schema.users)
152
+ .set({
153
+ name: args.newName,
154
+ })
155
+ .where(
156
+ and(
157
+ eq(schema.users.id, args.userId),
158
+ ctx.abilities.users.filter("update").where
159
+ )
160
+ )
161
+ .returning({ id: schema.users.id, name: schema.users.name })
162
+ // note that we need to manually raise an error if the value is not found
163
+ .then(assertFirstEntryExists);
164
+ },
165
+ }),
166
+ };
167
+ });
168
+ * ```
169
+ */
170
+ declare const assertFirstEntryExists: <T>(value: T[]) => T;
171
+
172
+ declare class RumbleError extends Error {
173
+ constructor(message: string);
174
+ }
175
+
176
+ export { RumbleError, assertFindFirstExists, assertFirstEntryExists, rumble };
package/index.d.ts ADDED
@@ -0,0 +1,176 @@
1
+ import * as graphql_yoga from 'graphql-yoga';
2
+ import { YogaServerOptions } from 'graphql-yoga';
3
+ import * as drizzle_orm from 'drizzle-orm';
4
+ import { DrizzleClient } from '@pothos/plugin-drizzle';
5
+
6
+ type QueryConditionObject = {
7
+ where: any;
8
+ columns: any;
9
+ limit: any;
10
+ };
11
+ type GenericDrizzleDbTypeConstraints = {
12
+ query: {
13
+ [key: string]: {
14
+ findMany: (P: QueryConditionObject) => any;
15
+ };
16
+ };
17
+ } & DrizzleClient;
18
+
19
+ declare const rumble: <UserContext extends Record<string, any>, DB extends GenericDrizzleDbTypeConstraints, RequestEvent extends Record<string, any>>({ db, nativeServerOptions, context: makeUserContext, }: {
20
+ /**
21
+ * Your drizzle database instance
22
+ */
23
+ db: DB;
24
+ /**
25
+ * Optional options for the native GraphQL Yoga server
26
+ */
27
+ nativeServerOptions?: Omit<YogaServerOptions<RequestEvent, any>, "schema" | "context"> | undefined;
28
+ /**
29
+ * A function for providing context for each request based on the incoming HTTP Request.
30
+ * The type of the parameter equals the HTTPRequest type of your chosen server.
31
+ */
32
+ context?: ((event: RequestEvent) => Promise<UserContext> | UserContext) | undefined;
33
+ }) => Promise<{
34
+ /**
35
+ * The ability builder. Use it to declare whats allowed for each entity in your DB.
36
+ *
37
+ * @example
38
+ *
39
+ * ```ts
40
+ * // users can edit themselves
41
+ abilityBuilder.users
42
+ .allow(["read", "update", "delete"])
43
+ .when(({ userId }) => ({ where: eq(schema.users.id, userId) }));
44
+
45
+ // everyone can read posts
46
+ abilityBuilder.posts.allow("read");
47
+ *
48
+ * ```
49
+ */
50
+ abilityBuilder: (keyof DB["query"] extends infer T extends keyof DB["query"] ? { [key in T]: {
51
+ allow: (action: "create" | "read" | "update" | "delete" | ("create" | "read" | "update" | "delete")[]) => {
52
+ when: (condition: Parameters<DB["query"][keyof DB["query"]]["findMany"]>[0] | ((context: UserContext) => Parameters<DB["query"][keyof DB["query"]]["findMany"]>[0])) => void;
53
+ };
54
+ }; } : never) & {
55
+ registeredConditions: keyof DB["query"] extends infer T_1 extends keyof DB["query"] ? { [key_1 in T_1]: {
56
+ [x: string]: (QueryConditionObject | ((context: UserContext) => QueryConditionObject))[];
57
+ }; } : never;
58
+ buildWithUserContext: (userContext: UserContext) => keyof DB["query"] extends infer T_2 extends keyof DB["query"] ? { [key_2 in T_2]: {
59
+ filter: (action: "create" | "read" | "update" | "delete") => {
60
+ where: drizzle_orm.SQL<unknown> | undefined;
61
+ columns: Record<string, any> | undefined;
62
+ limit: any;
63
+ };
64
+ }; } : never;
65
+ };
66
+ /**
67
+ * The pothos schema builder. See https://pothos-graphql.dev/docs/plugins/drizzle
68
+ */
69
+ schemaBuilder: PothosSchemaTypes.SchemaBuilder<PothosSchemaTypes.ExtendDefaultTypes<{
70
+ Context: Awaited<ReturnType<(req: RequestEvent) => Promise<UserContext & {
71
+ abilities: keyof DB["query"] extends infer T_2 extends keyof DB["query"] ? { [key_2 in T_2]: {
72
+ filter: (action: "create" | "read" | "update" | "delete") => {
73
+ where: drizzle_orm.SQL<unknown> | undefined;
74
+ columns: Record<string, any> | undefined;
75
+ limit: any;
76
+ };
77
+ }; } : never;
78
+ }>>>;
79
+ DrizzleSchema: DB["_"]["fullSchema"];
80
+ DefaultFieldNullability: false;
81
+ DefaultArgumentNullability: false;
82
+ DefaultInputFieldRequiredness: true;
83
+ }>>;
84
+ /**
85
+ * The native yoga instance. Can be used to run an actual HTTP server.
86
+ *
87
+ * @example
88
+ *
89
+ * ```ts
90
+ import { createServer } from "node:http";
91
+ * const server = createServer(yoga());
92
+ server.listen(3000, () => {
93
+ console.log("Visit http://localhost:3000/graphql");
94
+ });
95
+ * ```
96
+ */
97
+ yoga: () => graphql_yoga.YogaServerInstance<RequestEvent, {}>;
98
+ }>;
99
+
100
+ /**
101
+ *
102
+ * Helper function to map a drizzle findFirst query result,
103
+ * which may be optional, to a correct drizzle type.
104
+ *
105
+ * @throws RumbleError
106
+ *
107
+ * @example
108
+ *
109
+ * ```ts
110
+ * schemaBuilder.queryFields((t) => {
111
+ return {
112
+ findFirstUser: t.drizzleField({
113
+ type: UserRef,
114
+ resolve: (query, root, args, ctx, info) => {
115
+ return (
116
+ db.query.users
117
+ .findFirst({
118
+ ...query,
119
+ where: ctx.abilities.users.filter("read").where,
120
+ })
121
+ // note that we need to manually raise an error if the value is not found
122
+ .then(assertFindFirstExists)
123
+ );
124
+ },
125
+ }),
126
+ };
127
+ });
128
+ * ```
129
+ */
130
+ declare const assertFindFirstExists: <T>(value: T | undefined) => T;
131
+ /**
132
+ *
133
+ * Helper function to map a drizzle findFirst query result,
134
+ * which may be optional, to a correct drizzle type.
135
+ *
136
+ * @throws RumbleError
137
+ *
138
+ * @example
139
+ *
140
+ * ```ts
141
+ schemaBuilder.mutationFields((t) => {
142
+ return {
143
+ updateUsername: t.drizzleField({
144
+ type: UserRef,
145
+ args: {
146
+ userId: t.arg.int({ required: true }),
147
+ newName: t.arg.string({ required: true }),
148
+ },
149
+ resolve: (query, root, args, ctx, info) => {
150
+ return db
151
+ .update(schema.users)
152
+ .set({
153
+ name: args.newName,
154
+ })
155
+ .where(
156
+ and(
157
+ eq(schema.users.id, args.userId),
158
+ ctx.abilities.users.filter("update").where
159
+ )
160
+ )
161
+ .returning({ id: schema.users.id, name: schema.users.name })
162
+ // note that we need to manually raise an error if the value is not found
163
+ .then(assertFirstEntryExists);
164
+ },
165
+ }),
166
+ };
167
+ });
168
+ * ```
169
+ */
170
+ declare const assertFirstEntryExists: <T>(value: T[]) => T;
171
+
172
+ declare class RumbleError extends Error {
173
+ constructor(message: string);
174
+ }
175
+
176
+ export { RumbleError, assertFindFirstExists, assertFirstEntryExists, rumble };
package/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import P from'@pothos/core';import O from'@pothos/plugin-drizzle';import {createYoga}from'graphql-yoga';import {or}from'drizzle-orm';function h(e){return typeof e!="function"}function A(e){return typeof e=="function"&&e.constructor.name!=="AsyncFunction"}var C=({db:e,actions:s=["create","read","update","delete"]})=>{let u={},c={},p=n=>({allow:r=>{let i=c[n];i||(i={},c[n]=i);let a=Array.isArray(r)?r:[r];for(let d of a){let o=i[d];o||(o=[],i[d]=o);}return {when:d=>{for(let o of a)i[o].push(d);}}}});for(let n of Object.keys(e.query))u[n]=p(n);return {...u,registeredConditions:c,buildWithUserContext:n=>{let r={},i=a=>({filter:d=>{let o=c[a];if(!o)throw "TODO (No allowed entry found for this condition) #1";let f=o[d];if(!f)throw "TODO (No allowed entry found for this condition) #2";let b=f.filter(h),B=f.filter(A).map(t=>t(n)),D=[...b,...B],m;for(let t of D)t.limit&&(m===void 0||t.limit>m)&&(m=t.limit);let l;for(let t of D)t.columns&&(l===void 0?l=t.columns:l={...l,...t.columns});let x=D.filter(t=>t.where).map(t=>t.where);return {where:x.length>0?or(...x):void 0,columns:l,limit:m}}});for(let a of Object.keys(e.query))r[a]=i(a);return r}}};var U=async({db:e,nativeServerOptions:s,context:u})=>{let c=C({db:e}),p=async r=>{let i=u?await u(r):{};return {...i,abilities:c.buildWithUserContext(i)}},n=new P({plugins:[O],drizzle:{client:e},defaultFieldNullability:!1,defaultInputFieldRequiredness:!0});return n.queryType({}),n.mutationType({}),{abilityBuilder:c,schemaBuilder:n,yoga:()=>createYoga({...s,schema:n.toSchema(),context:p})}};var y=class extends Error{constructor(s){super(s),this.name="RumbleError";}};var g=e=>{if(!e)throw new y("Value not found but required (findFirst)");return e},z=e=>{let s=e.at(0);if(!s)throw new y("Value not found but required (firstEntry)");return s};export{y as RumbleError,g as assertFindFirstExists,z as assertFirstEntryExists,U as rumble};//# sourceMappingURL=index.js.map
2
+ //# sourceMappingURL=index.js.map
package/index.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../lib/abilities/builder.ts","../lib/gql/builder.ts","../lib/helpers/rumbleError.ts","../lib/helpers/helper.ts"],"names":["isSimpleCondition","condition","isSyncFunctionCondition","createAbilityBuilder","db","actions","builder","registeredConditions","createEntityObject","entityKey","action","conditionsPerEntity","conditionsPerEntityAndAction","userContext","simpleConditions","syncFunctionConditions","allConditionObjects","highestLimit","conditionObject","combinedAllowedColumns","accumulatedWhereConditions","o","or","rumble","nativeServerOptions","makeUserContext","abilityBuilder","makeContext","req","nativeBuilder","SchemaBuilder","DrizzlePlugin","createYoga","RumbleError","message","assertFindFirstExists","value","assertFirstEntryExists","v"],"mappings":"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","file":"index.js","sourcesContent":["import { or } from \"drizzle-orm\";\nimport type {\n\tGenericDrizzleDbTypeConstraints,\n\tQueryConditionObject,\n} from \"../types/genericDrizzleDbType\";\n\nexport type AbilityBuilder = ReturnType<typeof createAbilityBuilder>;\n\ntype Condition<DBParameters, UserContext> =\n\t| SimpleCondition<DBParameters>\n\t| SyncFunctionCondition<DBParameters, UserContext>;\n// | AsyncFunctionCondition<DBParameters, UserContext>;\n\ntype SimpleCondition<DBParameters> = DBParameters;\ntype SyncFunctionCondition<DBParameters, UserContext> = (\n\tcontext: UserContext,\n) => DBParameters;\n// type AsyncFunctionCondition<DBParameters, UserContext> = (\n// \tcontext: UserContext,\n// ) => Promise<DBParameters>;\n\n// type guards for the condition types\nfunction isSimpleCondition<DBParameters, UserContext>(\n\tcondition: Condition<DBParameters, UserContext>,\n): condition is SimpleCondition<DBParameters> {\n\treturn typeof condition !== \"function\";\n}\n\nfunction isSyncFunctionCondition<DBParameters, UserContext>(\n\tcondition: Condition<DBParameters, UserContext>,\n): condition is SyncFunctionCondition<DBParameters, UserContext> {\n\treturn (\n\t\ttypeof condition === \"function\" &&\n\t\tcondition.constructor.name !== \"AsyncFunction\"\n\t);\n}\n\n// function isAsyncFunctionCondition<DBParameters, UserContext>(\n// \tcondition: Condition<DBParameters, UserContext>,\n// ): condition is AsyncFunctionCondition<DBParameters, UserContext> {\n// \treturn (\n// \t\ttypeof condition === \"function\" &&\n// \t\tcondition.constructor.name === \"AsyncFunction\"\n// \t);\n// }\n\nexport const createAbilityBuilder = <\n\tUserContext extends Record<string, any>,\n\tDB extends GenericDrizzleDbTypeConstraints,\n\tAction extends string = \"create\" | \"read\" | \"update\" | \"delete\",\n>({\n\tdb,\n\tactions = [\"create\", \"read\", \"update\", \"delete\"] as Action[],\n}: {\n\tdb: DB;\n\tactions?: Action[];\n}) => {\n\ttype DBEntityKey = keyof DB[\"query\"];\n\n\tconst builder: {\n\t\t[key in DBEntityKey]: ReturnType<typeof createEntityObject>;\n\t} = {} as any;\n\n\tconst registeredConditions: {\n\t\t[key in DBEntityKey]: {\n\t\t\t[key in Action[number]]: (\n\t\t\t\t| QueryConditionObject\n\t\t\t\t| ((context: UserContext) => QueryConditionObject)\n\t\t\t)[];\n\t\t\t// | ((context: UserContext) => Promise<QueryConditionObject>)\n\t\t};\n\t} = {} as any;\n\n\tconst createEntityObject = (entityKey: DBEntityKey) => ({\n\t\tallow: (action: Action | Action[]) => {\n\t\t\ttype DBParameters = Parameters<DB[\"query\"][DBEntityKey][\"findMany\"]>[0];\n\n\t\t\tlet conditionsPerEntity = registeredConditions[entityKey];\n\t\t\tif (!conditionsPerEntity) {\n\t\t\t\tconditionsPerEntity = {} as any;\n\t\t\t\tregisteredConditions[entityKey] = conditionsPerEntity;\n\t\t\t}\n\n\t\t\tconst actions = Array.isArray(action) ? action : [action];\n\t\t\tfor (const action of actions) {\n\t\t\t\tlet conditionsPerEntityAndAction = conditionsPerEntity[action];\n\t\t\t\tif (!conditionsPerEntityAndAction) {\n\t\t\t\t\tconditionsPerEntityAndAction = [];\n\t\t\t\t\tconditionsPerEntity[action] = conditionsPerEntityAndAction;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\twhen: (condition: Condition<DBParameters, UserContext>) => {\n\t\t\t\t\tfor (const action of actions) {\n\t\t\t\t\t\tconst conditionsPerEntityAndAction = conditionsPerEntity[action];\n\t\t\t\t\t\tconditionsPerEntityAndAction.push(condition);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t};\n\t\t},\n\t});\n\n\tfor (const entityKey of Object.keys(db.query) as DBEntityKey[]) {\n\t\tbuilder[entityKey] = createEntityObject(entityKey);\n\t}\n\treturn {\n\t\t...builder,\n\t\tregisteredConditions,\n\t\tbuildWithUserContext: (userContext: UserContext) => {\n\t\t\tconst builder: {\n\t\t\t\t[key in DBEntityKey]: ReturnType<typeof createEntityObject>;\n\t\t\t} = {} as any;\n\n\t\t\tconst createEntityObject = (entityKey: DBEntityKey) => ({\n\t\t\t\tfilter: (action: Action) => {\n\t\t\t\t\tconst conditionsPerEntity = registeredConditions[entityKey];\n\t\t\t\t\tif (!conditionsPerEntity) {\n\t\t\t\t\t\tthrow \"TODO (No allowed entry found for this condition) #1\";\n\t\t\t\t\t}\n\n\t\t\t\t\tconst conditionsPerEntityAndAction = conditionsPerEntity[action];\n\t\t\t\t\tif (!conditionsPerEntityAndAction) {\n\t\t\t\t\t\tthrow \"TODO (No allowed entry found for this condition) #2\";\n\t\t\t\t\t}\n\n\t\t\t\t\tconst simpleConditions =\n\t\t\t\t\t\tconditionsPerEntityAndAction.filter(isSimpleCondition);\n\n\t\t\t\t\tconst syncFunctionConditions = conditionsPerEntityAndAction\n\t\t\t\t\t\t.filter(isSyncFunctionCondition)\n\t\t\t\t\t\t.map((condition) => condition(userContext));\n\n\t\t\t\t\t// const asyncFunctionConditions = await Promise.all(\n\t\t\t\t\t// \tconditionsPerEntityAndAction\n\t\t\t\t\t// \t\t.filter(isAsyncFunctionCondition)\n\t\t\t\t\t// \t\t.map((condition) => condition(userContext)),\n\t\t\t\t\t// );\n\n\t\t\t\t\tconst allConditionObjects = [\n\t\t\t\t\t\t...simpleConditions,\n\t\t\t\t\t\t...syncFunctionConditions,\n\t\t\t\t\t\t// ...asyncFunctionConditions,\n\t\t\t\t\t];\n\n\t\t\t\t\tlet highestLimit = undefined;\n\t\t\t\t\tfor (const conditionObject of allConditionObjects) {\n\t\t\t\t\t\tif (conditionObject.limit) {\n\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\thighestLimit === undefined ||\n\t\t\t\t\t\t\t\tconditionObject.limit > highestLimit\n\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\thighestLimit = conditionObject.limit;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tlet combinedAllowedColumns: Record<string, any> | undefined =\n\t\t\t\t\t\tundefined;\n\t\t\t\t\tfor (const conditionObject of allConditionObjects) {\n\t\t\t\t\t\tif (conditionObject.columns) {\n\t\t\t\t\t\t\tif (combinedAllowedColumns === undefined) {\n\t\t\t\t\t\t\t\tcombinedAllowedColumns = conditionObject.columns;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcombinedAllowedColumns = {\n\t\t\t\t\t\t\t\t\t...combinedAllowedColumns,\n\t\t\t\t\t\t\t\t\t...conditionObject.columns,\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst accumulatedWhereConditions = allConditionObjects\n\t\t\t\t\t\t.filter((o) => o.where)\n\t\t\t\t\t\t.map((o) => o.where);\n\n\t\t\t\t\tconst combinedWhere =\n\t\t\t\t\t\taccumulatedWhereConditions.length > 0\n\t\t\t\t\t\t\t? or(...accumulatedWhereConditions)\n\t\t\t\t\t\t\t: undefined;\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\twhere: combinedWhere,\n\t\t\t\t\t\tcolumns: combinedAllowedColumns,\n\t\t\t\t\t\tlimit: highestLimit,\n\t\t\t\t\t};\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tfor (const entityKey of Object.keys(db.query) as DBEntityKey[]) {\n\t\t\t\tbuilder[entityKey] = createEntityObject(entityKey);\n\t\t\t}\n\n\t\t\treturn builder;\n\t\t},\n\t};\n};\n","import SchemaBuilder from \"@pothos/core\";\nimport DrizzlePlugin from \"@pothos/plugin-drizzle\";\nimport { type YogaServerOptions, createYoga } from \"graphql-yoga\";\nimport { createAbilityBuilder } from \"../abilities/builder\";\nimport type { GenericDrizzleDbTypeConstraints } from \"../types/genericDrizzleDbType\";\n\nexport const rumble = async <\n\tUserContext extends Record<string, any>,\n\tDB extends GenericDrizzleDbTypeConstraints,\n\tRequestEvent extends Record<string, any>,\n>({\n\tdb,\n\tnativeServerOptions,\n\tcontext: makeUserContext,\n}: {\n\t/**\n\t * Your drizzle database instance\n\t */\n\tdb: DB;\n\t/**\n\t * Optional options for the native GraphQL Yoga server\n\t */\n\tnativeServerOptions?:\n\t\t| Omit<YogaServerOptions<RequestEvent, any>, \"schema\" | \"context\">\n\t\t| undefined;\n\t/**\n\t * A function for providing context for each request based on the incoming HTTP Request.\n\t * The type of the parameter equals the HTTPRequest type of your chosen server.\n\t */\n\tcontext?:\n\t\t| ((event: RequestEvent) => Promise<UserContext> | UserContext)\n\t\t| undefined;\n}) => {\n\tconst abilityBuilder = createAbilityBuilder<UserContext, DB>({\n\t\tdb,\n\t});\n\n\tconst makeContext = async (req: RequestEvent) => {\n\t\tconst userContext = makeUserContext\n\t\t\t? await makeUserContext(req)\n\t\t\t: ({} as UserContext);\n\t\treturn {\n\t\t\t...userContext,\n\t\t\tabilities: abilityBuilder.buildWithUserContext(userContext),\n\t\t};\n\t};\n\n\tconst nativeBuilder = new SchemaBuilder<{\n\t\tContext: Awaited<ReturnType<typeof makeContext>>;\n\t\t// Scalars: Scalars<Prisma.Decimal, Prisma.InputJsonValue | null, Prisma.InputJsonValue> & {\n\t\t// \tFile: {\n\t\t// \t\tInput: File;\n\t\t// \t\tOutput: never;\n\t\t// \t};\n\t\t// \tJSONObject: {\n\t\t// \t\tInput: any;\n\t\t// \t\tOutput: any;\n\t\t// \t};\n\t\t// };\n\t\tDrizzleSchema: DB[\"_\"][\"fullSchema\"];\n\t\tDefaultFieldNullability: false;\n\t\tDefaultArgumentNullability: false;\n\t\tDefaultInputFieldRequiredness: true;\n\t}>({\n\t\tplugins: [DrizzlePlugin],\n\t\tdrizzle: {\n\t\t\tclient: db,\n\t\t},\n\t\tdefaultFieldNullability: false,\n\t\tdefaultInputFieldRequiredness: true,\n\t});\n\n\tnativeBuilder.queryType({});\n\tnativeBuilder.mutationType({});\n\n\treturn {\n\t\t/**\n * The ability builder. Use it to declare whats allowed for each entity in your DB.\n * \n * @example\n * \n * ```ts\n * // users can edit themselves\n abilityBuilder.users\n .allow([\"read\", \"update\", \"delete\"])\n .when(({ userId }) => ({ where: eq(schema.users.id, userId) }));\n \n // everyone can read posts\n abilityBuilder.posts.allow(\"read\");\n * \n * ```\n */\n\t\tabilityBuilder,\n\t\t/**\n\t\t * The pothos schema builder. See https://pothos-graphql.dev/docs/plugins/drizzle\n\t\t */\n\t\tschemaBuilder: nativeBuilder,\n\t\t/**\n * The native yoga instance. Can be used to run an actual HTTP server.\n * \n * @example\n * \n * ```ts\n import { createServer } from \"node:http\";\n * const server = createServer(yoga());\n server.listen(3000, () => {\n console.log(\"Visit http://localhost:3000/graphql\");\n });\n * ```\n */\n\t\tyoga: () =>\n\t\t\tcreateYoga<RequestEvent>({\n\t\t\t\t...nativeServerOptions,\n\t\t\t\tschema: nativeBuilder.toSchema(),\n\t\t\t\tcontext: makeContext,\n\t\t\t}),\n\t};\n};\n","export class RumbleError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"RumbleError\";\n\t}\n}\n","import { RumbleError } from \"./rumbleError\";\n\n/**\n * \n * Helper function to map a drizzle findFirst query result,\n * which may be optional, to a correct drizzle type.\n * \n * @throws RumbleError\n * \n * @example\n * \n * ```ts\n * schemaBuilder.queryFields((t) => {\n return {\n findFirstUser: t.drizzleField({\n type: UserRef,\n resolve: (query, root, args, ctx, info) => {\n return (\n db.query.users\n .findFirst({\n ...query,\n where: ctx.abilities.users.filter(\"read\").where,\n })\n // note that we need to manually raise an error if the value is not found\n .then(assertFindFirstExists)\n );\n },\n }),\n };\n });\n * ```\n */\nexport const assertFindFirstExists = <T>(value: T | undefined): T => {\n\tif (!value) throw new RumbleError(\"Value not found but required (findFirst)\");\n\treturn value;\n};\n\n/**\n * \n * Helper function to map a drizzle findFirst query result,\n * which may be optional, to a correct drizzle type.\n * \n * @throws RumbleError\n * \n * @example\n * \n * ```ts\n schemaBuilder.mutationFields((t) => {\n return {\n updateUsername: t.drizzleField({\n type: UserRef,\n args: {\n userId: t.arg.int({ required: true }),\n newName: t.arg.string({ required: true }),\n },\n resolve: (query, root, args, ctx, info) => {\n return db\n .update(schema.users)\n .set({\n name: args.newName,\n })\n .where(\n and(\n eq(schema.users.id, args.userId),\n ctx.abilities.users.filter(\"update\").where\n )\n )\n .returning({ id: schema.users.id, name: schema.users.name })\n // note that we need to manually raise an error if the value is not found\n .then(assertFirstEntryExists);\n },\n }),\n };\n });\n * ```\n */\nexport const assertFirstEntryExists = <T>(value: T[]): T => {\n\tconst v = value.at(0);\n\tif (!v) throw new RumbleError(\"Value not found but required (firstEntry)\");\n\treturn v;\n};\n"]}
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@m1212e/rumble",
3
+ "module": "index.ts",
4
+ "type": "module",
5
+ "peerDependencies": {
6
+ "typescript": "^5.7.2",
7
+ "drizzle-orm": "^0.38.3"
8
+ },
9
+ "dependencies": {
10
+ "@pothos/plugin-drizzle": "^0.5.3",
11
+ "graphql-yoga": "^5.10.8",
12
+ "@pothos/core": "^4.3.0"
13
+ },
14
+ "version": "0.0.1",
15
+ "exports": {
16
+ "./package.json": "./package.json",
17
+ ".": {
18
+ "require": "./index.cjs",
19
+ "import": "./index.js",
20
+ "node": "./index.cjs",
21
+ "default": "./index.cjs"
22
+ }
23
+ }
24
+ }