├── .npmignore
├── .gitignore
├── src
├── utils
│ ├── generateSort.ts
│ ├── generateSelect.ts
│ └── generateFilter.ts
└── index.ts
├── .changeset
├── config.json
└── README.md
├── tsconfig.json
├── .github
├── dependabot.yml
└── workflows
│ ├── release.yml
│ └── scorecard.yml
├── package.json
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | # npm
2 | node_modules
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # npm
2 | node_modules
3 | # build files
4 | dist
--------------------------------------------------------------------------------
/src/utils/generateSort.ts:
--------------------------------------------------------------------------------
1 | import { CrudSorting } from "@refinedev/core";
2 |
3 | export const generateSort = (sorters: CrudSorting): string[] => {
4 | return sorters.map(sorter => `${sorter.field} ${sorter.order}`);
5 | };
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json",
3 | "changelog": ["@changesets/changelog-github", { "repo": "hirenf14/refine-sanity" }],
4 | "commit": false,
5 | "fixed": [],
6 | "linked": [],
7 | "access": "public",
8 | "baseBranch": "main",
9 | "updateInternalDependencies": "patch",
10 | "ignore": []
11 | }
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "module": "esnext",
10 | "moduleResolution": "node",
11 | "resolveJsonModule": true,
12 | "skipLibCheck": true,
13 | "strict": true,
14 | "target": "esnext",
15 | "rootDir": "./src",
16 | "outDir": "./dist",
17 | },
18 | "include": ["src"]
19 | }
--------------------------------------------------------------------------------
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 |
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "npm" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/src/utils/generateSelect.ts:
--------------------------------------------------------------------------------
1 | import { MetaQuery } from "@refinedev/core";
2 |
3 |
4 | export const generateSelect = (fields: MetaQuery["fields"]): string => {
5 | if(!fields) {
6 | return '';
7 | }
8 | if(typeof fields === 'string') return fields;
9 | if(!Array.isArray(fields)) {
10 | console.log(fields);
11 | }
12 | const selected: string[] = fields.map(f => {
13 | if(typeof f === 'string') return f;
14 | const field = f as any;
15 | if(field.operation || field.fields) {
16 | // TODO: Doesn't support nested yet, have to add it.
17 | throw new Error("Sorry, nested fields are not implemented yet");
18 | }
19 | return Object.keys(field).map((key: string) => `"${key}": ${generateSelect(field[key])}`).join(", ");
20 | });
21 | return `{${selected.join(", ")}}`;
22 | }
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 | on:
3 | push:
4 | branches:
5 | - main
6 | concurrency: ${{ github.workflow }}-${{ github.ref }}
7 | jobs:
8 | release:
9 | name: Release
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v3
13 | - uses: actions/setup-node@v3
14 | with:
15 | node-version: "18.x"
16 |
17 | - name: Install Dependencies & Build
18 | run: |
19 | npm ci
20 | npm run prepublish
21 |
22 | - name: Create Release Pull Request
23 | uses: changesets/action@v1
24 | with:
25 | version: npx changeset version
26 | publish: npx changeset publish
27 | commit: "ci(changesets): version packages"
28 | title: "ci(changesets): version packages"
29 | env:
30 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "refine-sanity",
3 | "version": "1.1.1",
4 | "description": "Data provider for refine with sanity. refine is a React-based framework for building internal tools, rapidly. Sanity is headless CMS.",
5 | "type": "module",
6 | "author": {
7 | "name": "Hiren F",
8 | "email": "hirenf14@gmail.com",
9 | "url": "https://github.com/hirenf14"
10 | },
11 | "types": "./dist/index.d.ts",
12 | "exports": "./dist/index.js",
13 | "homepage": "https://github.com/hirenf14/refine-sanity#readme",
14 | "private": false,
15 | "keywords": [
16 | "refine",
17 | "refinedev",
18 | "sanity",
19 | "data provider",
20 | "react"
21 | ],
22 | "scripts": {
23 | "build": "tsc",
24 | "prepublish": "tsc"
25 | },
26 | "repository": {
27 | "type": "git",
28 | "url": "https://github.com/hirenf14/refine-sanity.git"
29 | },
30 | "bugs": {
31 | "url": "https://github.com/hirenf14/refine-sanity/issues"
32 | },
33 | "peerDependencies": {
34 | "@refinedev/core": "^4.28.2",
35 | "@sanity/client": "^6.4.12"
36 | },
37 | "devDependencies": {
38 | "@changesets/changelog-github": "^0.5.0",
39 | "@changesets/cli": "^2.26.2",
40 | "typescript": "^5.2.2"
41 | },
42 | "dependencies": {
43 | "groqd": "^0.15.9"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/utils/generateFilter.ts:
--------------------------------------------------------------------------------
1 | import { CrudFilters, CrudOperators } from "@refinedev/core";
2 |
3 | const mapOperator = (operator: CrudOperators) => {
4 | switch (operator) {
5 | case "eq":
6 | return "==";
7 | case "ne":
8 | return "!=";
9 | case "lt":
10 | return "<";
11 | case "gt":
12 | return ">";
13 | case "lte":
14 | return "<=";
15 | case "gte":
16 | return ">=";
17 | case "in":
18 | case "nin":
19 | return "in";
20 | case "contains":
21 | case "ncontains":
22 | return "match";
23 | default:
24 | throw new Error(`Does't support Operator ${operator} yet.`);
25 | }
26 | }
27 | const negativeFilters = ["nin", "ncontains"];
28 | export const generateFilter = (filters?: CrudFilters) => {
29 | const queryFilters = filters
30 | ?.map((filter) => {
31 | if (Array.isArray(filter?.value) && filter.value?.length === 0) {
32 | return undefined;
33 | }
34 |
35 | if ("field" in filter) {
36 | const { field, operator, value } = filter;
37 | const mappedOperator = mapOperator(operator);
38 | const isNegative = negativeFilters.includes(operator);
39 | const filterStr = `${field} ${mappedOperator} "${value}"`;
40 |
41 | return isNegative ? `!(${filterStr})` : filterStr;
42 | }
43 | return undefined;
44 | })
45 | .filter((v) => v);
46 |
47 | return queryFilters?.join(" && ");
48 | };
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # refine-sanity
2 |
3 | [](https://opensource.org/licenses/MIT)
4 | [](https://www.npmjs.com/package/refine-sanity)
5 | [](https://www.npmjs.com/package/refine-sanity)
6 | 
7 | 
8 |
9 |
10 | 
11 | 
12 | 
13 | 
14 |
15 |
16 |
17 | `refine-sanity` is a data provider for [Refine](https://refine.dev/) that enables seamless integration with [Sanity](https://www.sanity.io/). It simplifies the management of your Sanity data within Refine-powered React applications.
18 |
19 | ## Installation
20 |
21 | Install `refine-sanity` via npm or yarn:
22 |
23 | ```bash
24 | npm install @sanity/client refine-sanity
25 | # or
26 | yarn add @sanity/client refine-sanity
27 | ```
28 |
29 | ## Usage
30 |
31 | ```tsx
32 | import dataProvider from "refine-sanity";
33 | import { createClient } from "@sanity/client";
34 |
35 | const client = createClient({
36 | token: "EDITOR_SANITY_ACCESS_TOKEN",
37 | projectId: "SANITY_PROJECT_ID",
38 | dataset: "SANITY_DATASET"
39 | });
40 |
41 | const App = () => {
42 | return (
43 |
47 | {/* ... */}
48 |
49 | );
50 | };
51 |
52 | ```
53 |
54 |
55 | ## Documentation
56 | - For more detailed information and usage, refer to [the refine data provider documentation](https://refine.dev/docs/api-reference/core/providers/data-provider/).
57 | - [Refer to documentation for more info about refine](https://refine.dev/docs/)
58 | - [Step up to refine tutorials.](https://refine.dev/docs/tutorial/introduction/index/)
--------------------------------------------------------------------------------
/.github/workflows/scorecard.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub. They are provided
2 | # by a third-party and are governed by separate terms of service, privacy
3 | # policy, and support documentation.
4 |
5 | name: Scorecard supply-chain security
6 | on:
7 | # For Branch-Protection check. Only the default branch is supported. See
8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
9 | branch_protection_rule:
10 | # To guarantee Maintained check is occasionally updated. See
11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
12 | schedule:
13 | - cron: '25 0 * * 5'
14 | push:
15 | branches: [ "main" ]
16 |
17 | # Declare default permissions as read only.
18 | permissions: read-all
19 |
20 | jobs:
21 | analysis:
22 | name: Scorecard analysis
23 | runs-on: ubuntu-latest
24 | permissions:
25 | # Needed to upload the results to code-scanning dashboard.
26 | security-events: write
27 | # Needed to publish results and get a badge (see publish_results below).
28 | id-token: write
29 | # Uncomment the permissions below if installing in a private repository.
30 | # contents: read
31 | # actions: read
32 |
33 | steps:
34 | - name: "Checkout code"
35 | uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
36 | with:
37 | persist-credentials: false
38 |
39 | - name: "Run analysis"
40 | uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2
41 | with:
42 | results_file: results.sarif
43 | results_format: sarif
44 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
45 | # - you want to enable the Branch-Protection check on a *public* repository, or
46 | # - you are installing Scorecard on a *private* repository
47 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
48 | # repo_token: ${{ secrets.SCORECARD_TOKEN }}
49 |
50 | # Public repositories:
51 | # - Publish results to OpenSSF REST API for easy access by consumers
52 | # - Allows the repository to include the Scorecard badge.
53 | # - See https://github.com/ossf/scorecard-action#publishing-results.
54 | # For private repositories:
55 | # - `publish_results` will always be set to `false`, regardless
56 | # of the value entered here.
57 | publish_results: true
58 |
59 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
60 | # format to the repository Actions tab.
61 | - name: "Upload artifact"
62 | uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0
63 | with:
64 | name: SARIF file
65 | path: results.sarif
66 | retention-days: 5
67 |
68 | # Upload the results to GitHub's code scanning dashboard.
69 | - name: "Upload to code-scanning"
70 | uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4
71 | with:
72 | sarif_file: results.sarif
73 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { SanityClient } from "@sanity/client";
2 |
3 | import {
4 | DataProvider,
5 | } from "@refinedev/core";
6 |
7 | import { q } from 'groqd';
8 | import { generateFilter } from "./utils/generateFilter";
9 | import { generateSelect } from "./utils/generateSelect";
10 | import { generateSort } from "./utils/generateSort";
11 |
12 |
13 | export const dataProvider = (client: SanityClient): DataProvider => {
14 | return {
15 | async getList({ resource, pagination, sorters, filters, meta }: Parameters[0]) {
16 | const {
17 | current = 1,
18 | pageSize = 10,
19 | } = pagination ?? {};
20 | const start = (current - 1) * pageSize;
21 | const end = start + pageSize - 1;
22 | let dataQuery: any = q("*").filterByType(resource);
23 | const filterStr = generateFilter(filters);
24 | if (filterStr) {
25 | dataQuery = dataQuery.filter(filterStr); // Apply filters if any result's achieved
26 | }
27 | const totalQuery = dataQuery.query; // Separate query to avoid sliced total
28 | if (sorters?.length) {
29 | dataQuery = dataQuery.order(...generateSort(sorters));
30 | }
31 | dataQuery = dataQuery.slice(start, end);
32 | const paginatedQuery = q(`{
33 | "data": ${dataQuery.query}${generateSelect(meta?.fields)},
34 | "total": count(${totalQuery}._id)
35 | }`);
36 | const response = await client.fetch(paginatedQuery.query);
37 | return {
38 | data: response.data,
39 | total: response.total
40 | };
41 | },
42 |
43 | async getOne({ resource, id, meta }: Parameters[0]) {
44 | const { query } = q("*").filterByType(resource).filter(`_id == "${id}"`).slice(0);
45 | const dataQuery = q(`{
46 | "data": ${query}${generateSelect(meta?.fields)}
47 | }`);
48 | // const data = await client.getDocument(id as string);
49 | const response = await client.fetch(dataQuery.query);
50 | return {
51 | data: response.data
52 | }
53 | },
54 |
55 | async create({ resource, variables }: Parameters[0]) {
56 | const response = await client.create(
57 | {
58 | _type: resource,
59 | ...(variables as any)
60 | },
61 | {
62 | autoGenerateArrayKeys: true
63 | });
64 | return {
65 | data: {
66 | ...response,
67 | id: response._id
68 | } as any
69 | }
70 | },
71 |
72 | async update({ id, variables }: Parameters[0]) {
73 | const response = await client
74 | .patch(id as string)
75 | .set({
76 | _id: id,
77 | ...(variables as any),
78 | })
79 | .commit({ autoGenerateArrayKeys: true });
80 | return {
81 | data: {
82 | ...response,
83 | id: response._id,
84 | } as any,
85 | }
86 | },
87 |
88 | async deleteOne({ id }: Parameters[0]) {
89 | const response = await client.delete(id as string);
90 | return {
91 | data: response as any
92 | }
93 | },
94 |
95 | async deleteMany({ ids, resource, meta }) {
96 | const idsStr = ids.map((id) => `"${id}"`).join(", ");
97 | const { query } = q(
98 | `*[_id in [${idsStr}]]${generateSelect(meta?.fields)}`,
99 | ).filterByType(resource);
100 |
101 | const response = await client.delete({ query });
102 |
103 | return {
104 | data: response as any,
105 | }
106 | },
107 |
108 | getApiUrl(): string {
109 | throw Error("Not implemented on refine-sanity data provider.");
110 | },
111 | };
112 | }
113 | export default dataProvider;
--------------------------------------------------------------------------------