@m1212e/rumble 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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":"oTAsBA,SAASA,EACRC,CAC6C,CAAA,CAC7C,OAAO,OAAOA,CAAAA,EAAc,UAC7B,CAEA,SAASC,CAAAA,CACRD,EACgE,CAChE,OACC,OAAOA,CAAc,EAAA,UAAA,EACrBA,EAAU,WAAY,CAAA,IAAA,GAAS,eAEjC,CAWO,IAAME,CAAAA,CAAuB,CAIlC,CACD,EAAA,CAAAC,CACA,CAAA,OAAA,CAAAC,CAAU,CAAA,CAAC,SAAU,MAAQ,CAAA,QAAA,CAAU,QAAQ,CAChD,CAGM,GAAA,CAGL,IAAMC,CAEF,CAAA,GAEEC,CAQF,CAAA,GAEEC,CAAsBC,CAAAA,CAAAA,GAA4B,CACvD,KAAA,CAAQC,CAA8B,EAAA,CAGrC,IAAIC,CAAsBJ,CAAAA,CAAAA,CAAqBE,CAAS,CAAA,CACnDE,CACJA,GAAAA,CAAAA,CAAsB,EACtBJ,CAAAA,CAAAA,CAAqBE,CAAS,CAAA,CAAIE,CAGnC,CAAA,CAAA,IAAMN,EAAU,KAAM,CAAA,OAAA,CAAQK,CAAM,CAAIA,CAAAA,CAAAA,CAAS,CAACA,CAAM,CAAA,CACxD,IAAWA,IAAAA,CAAAA,IAAUL,CAAS,CAAA,CAC7B,IAAIO,CAA+BD,CAAAA,CAAAA,CAAoBD,CAAM,CAAA,CACxDE,CACJA,GAAAA,CAAAA,CAA+B,EAC/BD,CAAAA,CAAAA,CAAoBD,CAAM,CAAA,CAAIE,CAEhC,EAAA,CAEA,OAAO,CACN,IAAA,CAAOX,GAAoD,CAC1D,IAAA,IAAWS,KAAUL,CACiBM,CAAAA,CAAAA,CAAoBD,CAAM,CAAA,CAClC,IAAKT,CAAAA,CAAS,EAE7C,CACD,CACD,CACD,CAAA,CAAA,CAEA,IAAWQ,IAAAA,CAAAA,IAAa,OAAO,IAAKL,CAAAA,CAAAA,CAAG,KAAK,CAAA,CAC3CE,CAAQG,CAAAA,CAAS,EAAID,CAAmBC,CAAAA,CAAS,EAElD,OAAO,CACN,GAAGH,CACH,CAAA,oBAAA,CAAAC,CACA,CAAA,oBAAA,CAAuBM,CAA6B,EAAA,CACnD,IAAMP,CAEF,CAAA,EAEEE,CAAAA,CAAAA,CAAsBC,CAA4B,GAAA,CACvD,OAASC,CAAmB,EAAA,CAC3B,IAAMC,CAAAA,CAAsBJ,CAAqBE,CAAAA,CAAS,EAC1D,GAAI,CAACE,EACJ,MAAM,qDAAA,CAGP,IAAMC,CAA+BD,CAAAA,CAAAA,CAAoBD,CAAM,CAAA,CAC/D,GAAI,CAACE,EACJ,MAAM,qDAAA,CAGP,IAAME,CAAAA,CACLF,CAA6B,CAAA,MAAA,CAAOZ,CAAiB,CAEhDe,CAAAA,CAAAA,CAAyBH,CAC7B,CAAA,MAAA,CAAOV,CAAuB,CAAA,CAC9B,IAAKD,CAAcA,EAAAA,CAAAA,CAAUY,CAAW,CAAC,CAAA,CAQrCG,EAAsB,CAC3B,GAAGF,CACH,CAAA,GAAGC,CAEJ,CAAA,CAEIE,EACJ,IAAWC,IAAAA,CAAAA,IAAmBF,CACzBE,CAAAA,CAAAA,CAAgB,KAElBD,GAAAA,CAAAA,GAAiB,QACjBC,CAAgB,CAAA,KAAA,CAAQD,CAExBA,CAAAA,GAAAA,CAAAA,CAAeC,CAAgB,CAAA,KAAA,CAAA,CAKlC,IAAIC,CAEJ,CAAA,IAAA,IAAWD,KAAmBF,CACzBE,CAAAA,CAAAA,CAAgB,UACfC,CAA2B,GAAA,KAAA,CAAA,CAC9BA,CAAyBD,CAAAA,CAAAA,CAAgB,OAEzCC,CAAAA,CAAAA,CAAyB,CACxB,GAAGA,CAAAA,CACH,GAAGD,CAAgB,CAAA,OACpB,GAKH,IAAME,CAAAA,CAA6BJ,CACjC,CAAA,MAAA,CAAQK,CAAMA,EAAAA,CAAAA,CAAE,KAAK,CACrB,CAAA,GAAA,CAAKA,GAAMA,CAAE,CAAA,KAAK,EAOpB,OAAO,CACN,KALAD,CAAAA,CAAAA,CAA2B,MAAS,CAAA,CAAA,CACjCE,cAAG,GAAGF,CAA0B,CAChC,CAAA,KAAA,CAAA,CAIH,OAASD,CAAAA,CAAAA,CACT,MAAOF,CACR,CACD,CACD,CAAA,CAAA,CAEA,IAAWR,IAAAA,CAAAA,IAAa,OAAO,IAAKL,CAAAA,CAAAA,CAAG,KAAK,CAC3CE,CAAAA,CAAAA,CAAQG,CAAS,CAAID,CAAAA,CAAAA,CAAmBC,CAAS,CAAA,CAGlD,OAAOH,CACR,CACD,CACD,CAAA,CC9LaiB,IAAAA,CAAAA,CAAS,MAIpB,CACD,GAAAnB,CACA,CAAA,mBAAA,CAAAoB,CACA,CAAA,OAAA,CAASC,CACV,CAAA,GAkBM,CACL,IAAMC,CAAAA,CAAiBvB,EAAsC,CAC5D,EAAA,CAAAC,CACD,CAAC,CAAA,CAEKuB,CAAc,CAAA,MAAOC,CAAsB,EAAA,CAChD,IAAMf,CAAcY,CAAAA,CAAAA,CACjB,MAAMA,CAAAA,CAAgBG,CAAG,CAAA,CACxB,EACJ,CAAA,OAAO,CACN,GAAGf,CACH,CAAA,SAAA,CAAWa,EAAe,oBAAqBb,CAAAA,CAAW,CAC3D,CACD,CAAA,CAEMgB,EAAgB,IAAIC,kBAAAA,CAgBvB,CACF,OAAA,CAAS,CAACC,kBAAa,EACvB,OAAS,CAAA,CACR,MAAQ3B,CAAAA,CACT,CACA,CAAA,uBAAA,CAAyB,GACzB,6BAA+B,CAAA,CAAA,CAChC,CAAC,CAAA,CAED,OAAAyB,CAAAA,CAAc,UAAU,EAAE,EAC1BA,CAAc,CAAA,YAAA,CAAa,EAAE,CAAA,CAEtB,CAiBN,cAAA,CAAAH,CAIA,CAAA,aAAA,CAAeG,EAcf,IAAM,CAAA,IACLG,sBAAyB,CAAA,CACxB,GAAGR,CAAAA,CACH,OAAQK,CAAc,CAAA,QAAA,EACtB,CAAA,OAAA,CAASF,CACV,CAAC,CACH,CACD,MCrHaM,CAAN,CAAA,cAA0B,KAAM,CACtC,WAAA,CAAYC,CAAiB,CAAA,CAC5B,KAAMA,CAAAA,CAAO,EACb,IAAK,CAAA,IAAA,CAAO,cACb,CACD,EC2BO,IAAMC,EAA4BC,CAA4B,EAAA,CACpE,GAAI,CAACA,CAAO,CAAA,MAAM,IAAIH,CAAY,CAAA,0CAA0C,EAC5E,OAAOG,CACR,EAyCaC,CAA6BD,CAAAA,CAAAA,EAAkB,CAC3D,IAAME,CAAIF,CAAAA,CAAAA,CAAM,GAAG,CAAC,CAAA,CACpB,GAAI,CAACE,CAAG,CAAA,MAAM,IAAIL,CAAY,CAAA,2CAA2C,CACzE,CAAA,OAAOK,CACR","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
+ }