{"_id":"@ardsh/slonik-dataloaders","_rev":"1-d61b6c0bec7e130c3f52ba3bd20fe7ca","name":"@ardsh/slonik-dataloaders","dist-tags":{"latest":"2.1.1"},"versions":{"2.1.0":{"name":"@ardsh/slonik-dataloaders","version":"2.1.0","description":"Utilities for creating DataLoaders using Slonik","main":"dist/index.js","scripts":{"prepublishOnly":"rimraf dist && tsc","test":"jest"},"keywords":["dataloader","graphql","slonik","sql","postgres","postgresql"],"author":{"name":"Daniel Rearden","email":"danielrearden@gmail.com"},"license":"MIT","devDependencies":{"graphql":"^16.6.0","jest":"29.0.2","prettier":"2.7.1","rimraf":"^3.0.2","slonik":"^30.3.1","slonik-interceptor-query-logging":"^1.4.7","ts-jest":"^28.0.8","typescript":"^4.8.2","zod":"^3.18.0"},"dependencies":{"@types/jest":"^29.0.0","dataloader":"^2.1.0","snake-case":"^3.0.4"},"types":"./dist/index.d.ts","gitHead":"bf06d3ba1e43bd68dd8ed887d8fbe3993a366b59","_id":"@ardsh/slonik-dataloaders@2.1.0","_nodeVersion":"16.18.0","_npmVersion":"8.19.2","dist":{"integrity":"sha512-cOkeGJEQg+vJzig4BYk2SnyffL9zb5JPZtaq+Dt2UALkoLgDzpbCxfj7CyAFLmJsVYkOAGAuHSOCHQZ2CNL+gA==","shasum":"26eb38b13c522edc0aac41c38f8c8fe3cb5b76b4","tarball":"https://registry.npmjs.org/@ardsh/slonik-dataloaders/-/slonik-dataloaders-2.1.0.tgz","fileCount":22,"unpackedSize":27432,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIFi8n1lOsFyFuiQROhwn8Vi+CridDj/wti1+tqYEpMcWAiAU64Zo0rZWLHavHbv2Q6x0ulEjatlVWOjwc+OtcL86Hw=="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjcqOPACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrxmQ//b0K5AU624ozLsN9e0fH4IW+an6pBB+B9C/l2XfUTvFQRhpOh\r\nd27gIfArW9guJ3Qna2NrY0DeZlI+idcgs/biP7qS26mcJk9weRHshsh/WBUP\r\nS/5YHpprFXVTeOoLLL8FdQiyZcjaChcTK3Xh3RsY4qNBRe7ySXFz0dik4vcr\r\net5gemwGIMnH7zj5YqSFsmu3uTcX0JNRFVR/8P6stOdcVF0We2uqPTLDd4iV\r\nzTOfqyuQBGSL4b7S0lmmjN7Tf23DV5EUnLWYZHLn8AV7gc2khTSAfkA1JorZ\r\n7lOuhDwWCSPCnaptFGnlkysnKigfj3zxN9P4UzkEeuNJjtkG9rlmqlFou89U\r\nQKOt1WOAZtONn+l9ABEF+YRSJD0/irr+ptUVSftpF64vVVXPKCql8/Ui8GkS\r\nw7cjggFwHirg7UUhoA9tc0pZRWtLhAH5CYfW8nFVARc30bpQJaOYIjoBteAD\r\nlAkdyUNLabQXBjc/tZTFoKHE0BYTbwQiynpPZaZ1AyxAW8Mqa7ZracQdRNJf\r\nJGI+h0LjWtu4eM7e4KOYMxa39AMfOuEEJfTxdJ4hZ+Wi76hQ8R4AtPP1zEiy\r\nibAJN5PgOqssZD9KRz8rtTy7TMt3n+C4XAzBxAMzUPmdKtztNuGNAY71U6Ge\r\ngQYxMHyNrJg22tL1q6/sTZn6tG0Z7nt3xUw=\r\n=EEQ3\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"ardsh","email":"ardeshirtershnjaku@gmail.com"},"directories":{},"maintainers":[{"name":"ardsh","email":"ardeshirtershnjaku@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/slonik-dataloaders_2.1.0_1668457359454_0.8485908027439824"},"_hasShrinkwrap":false},"2.1.1":{"name":"@ardsh/slonik-dataloaders","version":"2.1.1","description":"Utilities for creating DataLoaders using Slonik","main":"dist/index.js","scripts":{"prepublishOnly":"rimraf dist && tsc","test":"jest"},"keywords":["dataloader","graphql","slonik","sql","postgres","postgresql"],"author":{"name":"Daniel Rearden","email":"danielrearden@gmail.com"},"license":"MIT","devDependencies":{"graphql":"^16.6.0","jest":"29.0.2","prettier":"2.7.1","rimraf":"^3.0.2","slonik":"^30.3.1","slonik-interceptor-query-logging":"^1.4.7","ts-jest":"^28.0.8","typescript":"^4.8.2","zod":"^3.18.0"},"dependencies":{"@types/jest":"^29.0.0","dataloader":"^2.1.0","snake-case":"^3.0.4"},"types":"./dist/index.d.ts","gitHead":"1f4f7fdd8827a9f374306b2df60c794badabad7e","_id":"@ardsh/slonik-dataloaders@2.1.1","_nodeVersion":"16.18.0","_npmVersion":"8.19.2","dist":{"integrity":"sha512-un5L42YhitUYUImRepsScJO9cnzxX7s/WHwo8en/Ifoi5xHLMrZsg7/HFEnhHhvMXMMeLTOBgxLX3Y9vYd2Uow==","shasum":"6d7bfa7bb66018ee960eff61d05a62644d40bb52","tarball":"https://registry.npmjs.org/@ardsh/slonik-dataloaders/-/slonik-dataloaders-2.1.1.tgz","fileCount":22,"unpackedSize":27379,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQD1+QCR07m7xgUQbWrf+On7rckVLYIMO6RnO6/2z8vOVAIgJoPajqM5PGmcb04NCAWmZNcUh+LZ3eVI+3EQRwd4+1o="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjcqyHACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmocYw//dLTrMCOXVrp6supAMZYeL28dO1uBeNhcSaIHfXjUgk0CxonV\r\nv+R/sQj0cPDg8yXAnV8ziJsFUjWnTjNXxp0OVvB+RUmaaEXr1sO6ac4Okzto\r\nn+BOsnWUuyznSm6wg9sRh6NVblO9BwIyy8EDtg7ZFcDWTZgVYL8fhwaq3T4W\r\nEouDCl5McnJBJUvrQd7iB8xbuTCQAmEh8PhZHMlgpvZQMJD+zUgHFymAiaJz\r\nbXQlVa+U32dHU+qm/adM5qSXni1spQXu6Z6K7apwRYM1sfPi+s2PDdiLiiWY\r\nxVz0btEe3O7GmNs3o2TYyBGVCseGETq4M9DT+hFdBZJnoHIfwDkyDsoNNnzH\r\nHWMgeMfP2mP3LgLG9TLCL3T44g2eyJqQTSrKfRLm2fbjjWgVpkHSHrjr1e5P\r\n3DY/WAeV82irLLebMLtcENEfscT9SUPLlG6LL2Qg1QtysulyWt5flvcyIBGv\r\n4zXGiFwKY0bRw5IlRH2L4VDibmgoydJVMN70GoCeOxY4M3L8hqrWWHCtoyKA\r\nk4N6rGU6h1PeD6tIFbIMNwJcdhIOEEwOLKbv2Bpp3B4YEDhOF17xCpig/93j\r\nU4/4XLrdTMNVq4vJVjCeJE/cdigKgQSE6Abv+rYoy+P8TWl7RJHO+yZjiJUE\r\nEXrr0K0cGMZCcnspY2d1vOQGfstlJWVnv98=\r\n=an76\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"ardsh","email":"ardeshirtershnjaku@gmail.com"},"directories":{},"maintainers":[{"name":"ardsh","email":"ardeshirtershnjaku@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/slonik-dataloaders_2.1.1_1668459655631_0.8229197203572416"},"_hasShrinkwrap":false}},"time":{"created":"2022-11-14T20:22:39.392Z","2.1.0":"2022-11-14T20:22:39.628Z","modified":"2022-11-14T21:00:55.887Z","2.1.1":"2022-11-14T21:00:55.791Z"},"maintainers":[{"name":"ardsh","email":"ardeshirtershnjaku@gmail.com"}],"description":"Utilities for creating DataLoaders using Slonik","keywords":["dataloader","graphql","slonik","sql","postgres","postgresql"],"author":{"name":"Daniel Rearden","email":"danielrearden@gmail.com"},"license":"MIT","readme":"# `slonik-dataloaders`\n\n`slonik-dataloaders` is a set of utilities for creating [DataLoaders](https://github.com/graphql/dataloader) using [Slonik](https://github.com/gajus/slonik). These DataLoaders abstract away some of the complexity of working with cursor-style pagination when working with a SQL database, while still maintaining the flexibility that comes with writing raw SQL statements.\n\n### `createNodeByIdLoaderClass`\n\nExample usage:\n\n```ts\nconst UserByIdLoader = createNodeByIdLoaderClass<User>({\n  query: sql`\n    SELECT\n      *\n    FROM user\n  `,\n});\nconst pool = createPool(\"postgresql://\");\nconst loader = new UserByIdLoader(pool);\nconst user = await loader.load(99);\n```\n\nBy default, the loader will look for an integer column named `id` to use as the key. You can specify a different column to use like this:\n\n```ts\nconst UserByIdLoader = createNodeByIdLoaderClass<User>({\n  column: {\n    name: 'unique_id',\n    type: 'text',\n  }\n  query: sql`\n    SELECT\n      *\n    FROM user\n  `,\n});\n```\n\n### `createConnectionLoaderClass`\n\nExample usage\n\n```ts\nconst UserConnectionLoader = createConnectionLoaderClass<User>({\n  query: sql`\n    SELECT\n      *\n    FROM user\n  `,\n});\nconst pool = createPool(\"postgresql://\");\nconst loader = new UserByIdLoader(pool);\nconst connection = await loader.load({\n  where: ({ firstName }) => sql`${firstName} = 'Susan'`,\n  orderBy: ({ firstName }) => [[firstName, \"ASC\"]],\n});\n```\n\nWhen calling `load`, you can include `where` and `orderBy` expression factories that will be used to generate each respective clause. These factory functions allow for type-safe loader usage and abstract away the actual table alias used inside the generated SQL query. Note that the column names passed to each factory reflect the type provided when creating the loader class (i.e. `User` in the example above); however, each column name is transformed using `columnNameTransformer` as described below.\n\nUsage example with forward pagination:\n\n```ts\nconst connection = await loader.load({\n  orderBy: ({ firstName }) => [[firstName, \"ASC\"]],\n  limit: first,\n  cursor: after,\n});\n```\n\nUsage example with backward pagination:\n\n```ts\nconst connection = await loader.load({\n  orderBy: ({ firstName }) => [[firstName, \"ASC\"]],\n  limit: last,\n  cursor: before,\n  reverse: true,\n});\n```\n\n#### Conditionally fetching edges and count based on requested fields\n\nIn addition to the standard `edges` and `pageInfo` fields, each connection returned by the loader also includes a `count` field. This field reflects the total number of results that _would_ be returned if no limit was applied. In order to fetch both the edges and the count, the loader makes two separate database queries. However, the loader can determine whether it needs to request only one or both of the queries by looking at the GraphQL fields that were actually requested. To do this, we pass in the `GraphQLResolveInfo` parameter provided to every GraphQL resolver:\n\n```ts\nconst connection = await loader.load({\n  orderBy: ({ firstName }) => [[firstName, \"ASC\"]],\n  limit: first,\n  cursor: after,\n  info,\n});\n```\n\n#### Working with edge fields\n\nIt's possible to request columns that will be exposed as fields on the edge type in your schema, as opposed to on the node type. These fields should be included in your query and the TypeScript type provided to the loader. The loader returns each row of the results as both the `edge` and the `node`, so all requested columns are available inside the resolvers for either type. Note: each requested column should be unique, so if there's a name conflict, you should use an appropriate alias. For example:\n\n```ts\nconst UserConnectionLoader = createConnectionLoaderClass<\n  User & { edgeCreatedAt }\n>({\n  query: sql`\n    SELECT\n      user.id,\n      user.name,\n      user.created_at,\n      friend.created_at edge_created_at\n    FROM user\n    INNER JOIN friend ON\n      user.id = friend.user_id\n  `,\n});\n```\n\nIn the example above, if the field on the Edge type in the schema is named `createdAt`, we just need to write a resolver for it and resolve the value to that of the `edgeCreatedAt` property.\n\n#### Dynamic queries\n\nIf you need more flexibility, it's possible to dynamically build queries by specifying a function instead of a static query.\nThe function must return a slonik query, and it takes any arguments as input. For example:\n\n```ts\nconst UserConnectionLoader = createConnectionLoaderClass<\n  User & { edgeCreatedAt }\n>({\n  query: ({ friendName }: { friendName: string }) => sql.unsafe`\n    SELECT\n      user.*\n    FROM user\n    INNER JOIN friend ON\n      user.id = friend.user_id\n    WHERE friend.name ILIKE ${sql.literalValue(args.friendName)}\n  `,\n});\n```\n\nThose arguments can then be passed down while loading\n\n```ts\nconst connection = await loader.load({\n  args: { friendName: 'bob' }\n  orderBy: ({ name }) => [[name, \"ASC\"]],\n});\n```\n\n### `columnNameTransformer`\n\nBoth types of loaders also accept an `columnNameTransformer` option. By default, the transformer used is [snake-case](https://www.npmjs.com/package/snake-case). The default assumes:\n\n- You're using conventional snake case column names; and\n- You're using either [`slonik-interceptor-field-name-transformation`](https://github.com/gajus/slonik-interceptor-field-name-transformation) or the [`slonik-interceptor-preset`](https://github.com/gajus/slonik-interceptor-preset), which means the columns are returned as camelCased in the query results\n\nBy using the `columnNameTransformer` (snake case), fields can be referenced by their names as they appear in the results when calling the loader, while still referencing the correct columns inside the query itself. If your usage doesn't meet the above two criteria, consider providing an alternative transformer, like an identify function.\n","readmeFilename":"README.md"}