@smartive/datocms-utils 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": ["@smartive/eslint-config"]
3
+ }
@@ -0,0 +1,24 @@
1
+ name: Release npm package
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ release:
10
+ name: Build & release
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v3
15
+ - uses: actions/setup-node@v3
16
+ with:
17
+ node-version: 20
18
+ - run: npm ci
19
+ - run: npm run build
20
+ - name: semantic release
21
+ uses: cycjimmy/semantic-release-action@v3
22
+ env:
23
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,24 @@
1
+ name: Test
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - '**'
7
+
8
+ concurrency:
9
+ group: tests-${{ github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ jobs:
13
+ test:
14
+ name: Lint & test
15
+ runs-on: ubuntu-latest
16
+
17
+ steps:
18
+ - uses: actions/checkout@v3
19
+ - uses: actions/setup-node@v3
20
+ with:
21
+ node-version: 20
22
+ - run: npm ci
23
+ - run: npm run lint
24
+ - run: npm run build
@@ -0,0 +1 @@
1
+ "@smartive/prettier-config"
@@ -0,0 +1,10 @@
1
+ {
2
+ "branches": ["main"],
3
+ "plugins": [
4
+ "@semantic-release/commit-analyzer",
5
+ "@semantic-release/release-notes-generator",
6
+ "@semantic-release/changelog",
7
+ "@semantic-release/npm",
8
+ "@semantic-release/github"
9
+ ]
10
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ # 1.0.0 (2024-09-25)
2
+
3
+
4
+ ### Features
5
+
6
+ * update README ([8ffb069](https://github.com/smartive/datocms-utils/commit/8ffb069a8d3dceed6fffc400b0b77f7f481904f4))
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 smartive
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # smartive DatoCMS Utilities
2
+
3
+ A set of utilities and helpers to work with DatoCMS in a Next.js project.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @smartive/datocms-utils
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ Import and use the utilities you need in your project. The following utilities are available.
14
+
15
+ ## Utilities
16
+
17
+ ### Utilities for DatoCMS Cache Tags
18
+
19
+ The following utilities are used to work with [DatoCMS cache tags](https://www.datocms.com/docs/content-delivery-api/cache-tags) and a [Vercel Postgres database](https://vercel.com/docs/storage/vercel-postgres).
20
+
21
+ - `storeQueryCacheTags`: Stores the cache tags of a query in the database.
22
+ - `queriesReferencingCacheTags`: Retrieves the queries that reference cache tags.
23
+ - `deleteQueries`: Deletes the cache tags of a query from the database.
24
+
25
+ #### Setup Postgres database
26
+
27
+ In order for the above utilites to work, you need to setup a the following database. You can use the following SQL script to do that:
28
+
29
+ ```sql
30
+ CREATE TABLE IF NOT EXISTS query_cache_tags (
31
+ query_id TEXT NOT NULL,
32
+ cache_tag TEXT NOT NULL,
33
+ PRIMARY KEY (query_id, cache_tag)
34
+ );
35
+ ```
36
+
37
+ ### Other Utilities
38
+
39
+ - `classNames`: Cleans and joins an array of inputs with possible undefined or boolean values. Useful for tailwind classnames.
@@ -0,0 +1,38 @@
1
+ import { DocumentNode } from 'graphql';
2
+ import { CacheTag } from './types';
3
+ /**
4
+ * Converts the value of DatoCMS's `X-Cache-Tags` header into an array of strings typed as `CacheTag`.
5
+ * For example, it transforms `'tag-a tag-2 other-tag'` into `['tag-a', 'tag-2', 'other-tag']`.
6
+ *
7
+ * @param string String value of the `X-Cache-Tags` header
8
+ * @returns Array of strings typed as `CacheTag`
9
+ */
10
+ export declare function parseXCacheTagsResponseHeader(string?: null | string): CacheTag[];
11
+ /**
12
+ * Generates a unique query ID based on the query document and its variables.
13
+ *
14
+ * @param {DocumentNode} document Query document
15
+ * @param {Record<string, unknown>} variables Query variables
16
+ * @returns Unique query ID
17
+ */
18
+ export declare const generateQueryId: (document: DocumentNode, variables?: Record<string, unknown>) => string;
19
+ /**
20
+ * Stores the cache tags of a query in the database.
21
+ *
22
+ * @param {string} queryId Unique query ID
23
+ * @param {CacheTag[]} cacheTags Array of cache tags
24
+ */
25
+ export declare const storeQueryCacheTags: (queryId: string, cacheTags: CacheTag[]) => Promise<void>;
26
+ /**
27
+ * Retrieves the queries that reference cache tags.
28
+ *
29
+ * @param {CacheTag[]} cacheTags Array of cache tags
30
+ * @returns Array of query IDs
31
+ */
32
+ export declare const queriesReferencingCacheTags: (cacheTags: CacheTag[]) => Promise<string[]>;
33
+ /**
34
+ * Deletes the cache tags of a query from the database.
35
+ *
36
+ * @param {string} queryId Unique query ID
37
+ */
38
+ export declare const deleteQueries: (queryIds: string[]) => Promise<void>;
@@ -0,0 +1,57 @@
1
+ import { sql } from '@vercel/postgres';
2
+ import { createHash } from 'crypto';
3
+ import { print } from 'graphql';
4
+ /**
5
+ * Converts the value of DatoCMS's `X-Cache-Tags` header into an array of strings typed as `CacheTag`.
6
+ * For example, it transforms `'tag-a tag-2 other-tag'` into `['tag-a', 'tag-2', 'other-tag']`.
7
+ *
8
+ * @param string String value of the `X-Cache-Tags` header
9
+ * @returns Array of strings typed as `CacheTag`
10
+ */
11
+ export function parseXCacheTagsResponseHeader(string) {
12
+ if (!string) {
13
+ return [];
14
+ }
15
+ return (string.split(' ') || []).map((tag) => tag);
16
+ }
17
+ /**
18
+ * Generates a unique query ID based on the query document and its variables.
19
+ *
20
+ * @param {DocumentNode} document Query document
21
+ * @param {Record<string, unknown>} variables Query variables
22
+ * @returns Unique query ID
23
+ */
24
+ export const generateQueryId = (document, variables) => {
25
+ return createHash('sha1')
26
+ .update(print(document))
27
+ .update(JSON.stringify(variables) || '')
28
+ .digest('hex');
29
+ };
30
+ /**
31
+ * Stores the cache tags of a query in the database.
32
+ *
33
+ * @param {string} queryId Unique query ID
34
+ * @param {CacheTag[]} cacheTags Array of cache tags
35
+ */
36
+ export const storeQueryCacheTags = async (queryId, cacheTags) => {
37
+ await sql.query(`INSERT INTO query_cache_tags VALUES ${cacheTags.map((cacheTag) => `('${queryId}', '${cacheTag}')`).join()} ON CONFLICT DO NOTHING`);
38
+ };
39
+ /**
40
+ * Retrieves the queries that reference cache tags.
41
+ *
42
+ * @param {CacheTag[]} cacheTags Array of cache tags
43
+ * @returns Array of query IDs
44
+ */
45
+ export const queriesReferencingCacheTags = async (cacheTags) => {
46
+ const { rows } = await sql.query(`SELECT DISTINCT query_id FROM query_cache_tags WHERE cache_tag IN (${cacheTags.map((cacheTag) => `'${cacheTag}'`).join(', ')})`);
47
+ return rows.map((row) => row.query_id);
48
+ };
49
+ /**
50
+ * Deletes the cache tags of a query from the database.
51
+ *
52
+ * @param {string} queryId Unique query ID
53
+ */
54
+ export const deleteQueries = async (queryIds) => {
55
+ await sql.query(`DELETE FROM query_cache_tags WHERE query_id IN (${queryIds.map((id) => `'${id}'`).join(', ')})`);
56
+ };
57
+ //# sourceMappingURL=cache-tags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-tags.js","sourceRoot":"","sources":["../src/cache-tags.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAgB,KAAK,EAAE,MAAM,SAAS,CAAC;AAG9C;;;;;;GAMG;AAEH,MAAM,UAAU,6BAA6B,CAAC,MAAsB;IAClE,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAe,CAAC,CAAC;AACjE,CAAC;AAED;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,QAAsB,EAAE,SAAmC,EAAU,EAAE;IACrG,OAAO,UAAU,CAAC,MAAM,CAAC;SACtB,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;SACvB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;SACvC,MAAM,CAAC,KAAK,CAAC,CAAC;AACnB,CAAC,CAAC;AAEF;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,KAAK,EAAE,OAAe,EAAE,SAAqB,EAAE,EAAE;IAClF,MAAM,GAAG,CAAC,KAAK,CACb,uCAAuC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,KAAK,OAAO,OAAO,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,yBAAyB,CACpI,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,2BAA2B,GAAG,KAAK,EAAE,SAAqB,EAAqB,EAAE;IAC5F,MAAM,EAAE,IAAI,EAAE,GAAqC,MAAM,GAAG,CAAC,KAAK,CAChE,sEAAsE,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACjI,CAAC;IAEF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AACzC,CAAC,CAAC;AAEF;;;;GAIG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAAE,QAAkB,EAAE,EAAE;IACxD,MAAM,GAAG,CAAC,KAAK,CAAC,mDAAmD,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACpH,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Cleans and joins an array of inputs with possible undefined or boolean values.
3
+ *
4
+ * @param classNames Array of class names
5
+ * @returns Clean string to be used for class name
6
+ */
7
+ export declare const classNames: (...classNames: (string | undefined | boolean)[]) => string;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Cleans and joins an array of inputs with possible undefined or boolean values.
3
+ *
4
+ * @param classNames Array of class names
5
+ * @returns Clean string to be used for class name
6
+ */
7
+ export const classNames = (...classNames) => classNames.filter(Boolean).join(' ');
8
+ //# sourceMappingURL=classnames.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classnames.js","sourceRoot":"","sources":["../src/classnames.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,GAAG,UAA4C,EAAU,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './cache-tags';
2
+ export * from './classnames';
3
+ export * from './types';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export * from './cache-tags';
2
+ export * from './classnames';
3
+ export * from './types';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,SAAS,CAAC"}
@@ -0,0 +1,3 @@
1
+ export type CacheTag = string & {
2
+ readonly _: unique symbol;
3
+ };
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@smartive/datocms-utils",
3
+ "version": "1.0.0",
4
+ "description": "A set of utilities and helpers to work with DatoCMS in a Next.js project.",
5
+ "source": "src/index.ts",
6
+ "main": "dist/index.js",
7
+ "type": "module",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "lint": "prettier --check src && eslint src",
11
+ "publish": "semantic-release"
12
+ },
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "keywords": [
17
+ "datocms"
18
+ ],
19
+ "author": "smartive AG",
20
+ "license": "MIT",
21
+ "devDependencies": {
22
+ "@semantic-release/changelog": "6.0.3",
23
+ "@semantic-release/commit-analyzer": "13.0.0",
24
+ "@semantic-release/gitlab": "^13.2.1",
25
+ "@semantic-release/npm": "12.0.1",
26
+ "@semantic-release/release-notes-generator": "14.0.1",
27
+ "@smartive/eslint-config": "^5.1.0",
28
+ "@smartive/prettier-config": "^3.1.2",
29
+ "@types/node": "^22.7.0",
30
+ "eslint": "^8.57.0",
31
+ "prettier": "^3.3.3",
32
+ "semantic-release": "^24.1.1",
33
+ "typescript": "^5.6.2"
34
+ },
35
+ "dependencies": {
36
+ "@vercel/postgres": "^0.10.0",
37
+ "graphql": "^16.9.0"
38
+ }
39
+ }
package/renovate.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": ["local>smartive/renovate-config"]
4
+ }
@@ -0,0 +1,73 @@
1
+ import { sql } from '@vercel/postgres';
2
+ import { createHash } from 'crypto';
3
+ import { DocumentNode, print } from 'graphql';
4
+ import { CacheTag } from './types';
5
+
6
+ /**
7
+ * Converts the value of DatoCMS's `X-Cache-Tags` header into an array of strings typed as `CacheTag`.
8
+ * For example, it transforms `'tag-a tag-2 other-tag'` into `['tag-a', 'tag-2', 'other-tag']`.
9
+ *
10
+ * @param string String value of the `X-Cache-Tags` header
11
+ * @returns Array of strings typed as `CacheTag`
12
+ */
13
+
14
+ export function parseXCacheTagsResponseHeader(string?: null | string) {
15
+ if (!string) {
16
+ return [];
17
+ }
18
+
19
+ return (string.split(' ') || []).map((tag) => tag as CacheTag);
20
+ }
21
+
22
+ /**
23
+ * Generates a unique query ID based on the query document and its variables.
24
+ *
25
+ * @param {DocumentNode} document Query document
26
+ * @param {Record<string, unknown>} variables Query variables
27
+ * @returns Unique query ID
28
+ */
29
+
30
+ export const generateQueryId = (document: DocumentNode, variables?: Record<string, unknown>): string => {
31
+ return createHash('sha1')
32
+ .update(print(document))
33
+ .update(JSON.stringify(variables) || '')
34
+ .digest('hex');
35
+ };
36
+
37
+ /**
38
+ * Stores the cache tags of a query in the database.
39
+ *
40
+ * @param {string} queryId Unique query ID
41
+ * @param {CacheTag[]} cacheTags Array of cache tags
42
+ */
43
+
44
+ export const storeQueryCacheTags = async (queryId: string, cacheTags: CacheTag[]) => {
45
+ await sql.query(
46
+ `INSERT INTO query_cache_tags VALUES ${cacheTags.map((cacheTag) => `('${queryId}', '${cacheTag}')`).join()} ON CONFLICT DO NOTHING`,
47
+ );
48
+ };
49
+
50
+ /**
51
+ * Retrieves the queries that reference cache tags.
52
+ *
53
+ * @param {CacheTag[]} cacheTags Array of cache tags
54
+ * @returns Array of query IDs
55
+ */
56
+
57
+ export const queriesReferencingCacheTags = async (cacheTags: CacheTag[]): Promise<string[]> => {
58
+ const { rows }: { rows: { query_id: string }[] } = await sql.query(
59
+ `SELECT DISTINCT query_id FROM query_cache_tags WHERE cache_tag IN (${cacheTags.map((cacheTag) => `'${cacheTag}'`).join(', ')})`,
60
+ );
61
+
62
+ return rows.map((row) => row.query_id);
63
+ };
64
+
65
+ /**
66
+ * Deletes the cache tags of a query from the database.
67
+ *
68
+ * @param {string} queryId Unique query ID
69
+ */
70
+
71
+ export const deleteQueries = async (queryIds: string[]) => {
72
+ await sql.query(`DELETE FROM query_cache_tags WHERE query_id IN (${queryIds.map((id) => `'${id}'`).join(', ')})`);
73
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Cleans and joins an array of inputs with possible undefined or boolean values.
3
+ *
4
+ * @param classNames Array of class names
5
+ * @returns Clean string to be used for class name
6
+ */
7
+
8
+ export const classNames = (...classNames: (string | undefined | boolean)[]): string => classNames.filter(Boolean).join(' ');
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './cache-tags';
2
+ export * from './classnames';
3
+ export * from './types';
package/src/types.ts ADDED
@@ -0,0 +1 @@
1
+ export type CacheTag = string & { readonly _: unique symbol };
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "declaration": true,
4
+ "outDir": "./dist/",
5
+ "rootDirs": ["./src"],
6
+ "sourceMap": true,
7
+ "strict": true,
8
+ "noImplicitReturns": true,
9
+ "noImplicitAny": true,
10
+ "moduleResolution": "node",
11
+ "allowSyntheticDefaultImports": true,
12
+ "allowJs": true,
13
+ "resolveJsonModule": true,
14
+ "module": "esnext",
15
+ "target": "esnext"
16
+ },
17
+ "include": ["./src/**/*"]
18
+ }