@smartive/datocms-utils 3.0.0-next.10 → 3.0.0-next.11

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 CHANGED
@@ -14,13 +14,13 @@ npm install @smartive/datocms-utils
14
14
 
15
15
  #### `classNames`
16
16
 
17
- Cleans and joins an array of class names, filtering out undefined and boolean values.
17
+ Cleans and joins an array of class names (strings and numbers), filtering out undefined and boolean values.
18
18
 
19
19
  ```typescript
20
20
  import { classNames } from '@smartive/datocms-utils';
21
21
 
22
- const className = classNames('btn', isActive && 'btn-active', undefined, 'btn-primary');
23
- // Result: "btn btn-active btn-primary"
22
+ const className = classNames('btn', isActive && 'btn-active', 42, undefined, 'btn-primary');
23
+ // Result: "btn btn-active 42 btn-primary"
24
24
  ```
25
25
 
26
26
  #### `getTelLink`
@@ -118,7 +118,7 @@ npm install ioredis
118
118
  import { RedisCacheTagsProvider } from '@smartive/datocms-utils/cache-tags/redis';
119
119
 
120
120
  const provider = new RedisCacheTagsProvider({
121
- url: process.env.REDIS_URL!,
121
+ connectionUrl: process.env.REDIS_URL!,
122
122
  keyPrefix: 'prod:', // Optional: namespace for multi-environment setups
123
123
  });
124
124
 
@@ -29,5 +29,12 @@ export declare class NeonCacheTagsProvider implements CacheTagsProvider {
29
29
  queriesReferencingCacheTags(cacheTags: CacheTag[]): Promise<string[]>;
30
30
  deleteCacheTags(cacheTags: CacheTag[]): Promise<number>;
31
31
  truncateCacheTags(): Promise<number>;
32
+ /**
33
+ * Validates and quotes a PostgreSQL identifier (table name, column name, etc.) to prevent SQL injection.
34
+ * @param identifier The identifier to validate and quote
35
+ * @returns The properly quoted identifier
36
+ * @throws Error if the identifier is invalid
37
+ */
38
+ private static quoteIdentifier;
32
39
  }
33
40
  export {};
@@ -7,7 +7,7 @@ export class NeonCacheTagsProvider {
7
7
  table;
8
8
  constructor({ connectionUrl, table }) {
9
9
  this.sql = neon(connectionUrl, { fullResults: true });
10
- this.table = table;
10
+ this.table = NeonCacheTagsProvider.quoteIdentifier(table);
11
11
  }
12
12
  async storeQueryCacheTags(queryId, cacheTags) {
13
13
  if (!cacheTags?.length) {
@@ -31,7 +31,7 @@ export class NeonCacheTagsProvider {
31
31
  }, []);
32
32
  }
33
33
  async deleteCacheTags(cacheTags) {
34
- if (cacheTags.length === 0) {
34
+ if (!cacheTags?.length) {
35
35
  return 0;
36
36
  }
37
37
  const placeholders = cacheTags.map((_, i) => `$${i + 1}`).join(',');
@@ -40,5 +40,26 @@ export class NeonCacheTagsProvider {
40
40
  async truncateCacheTags() {
41
41
  return (await this.sql.query(`DELETE FROM ${this.table}`)).rowCount ?? 0;
42
42
  }
43
+ /**
44
+ * Validates and quotes a PostgreSQL identifier (table name, column name, etc.) to prevent SQL injection.
45
+ * @param identifier The identifier to validate and quote
46
+ * @returns The properly quoted identifier
47
+ * @throws Error if the identifier is invalid
48
+ */
49
+ static quoteIdentifier(identifier) {
50
+ // Validate that the identifier contains only valid characters
51
+ // PostgreSQL identifiers can contain letters, digits, underscores, and dollar signs
52
+ // They can also contain dots for schema-qualified names
53
+ if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)?$/.test(identifier)) {
54
+ throw new Error(`Invalid table name: ${identifier}. Table names must start with a letter, underscore, or dollar sign and contain only letters, digits, underscores, and dollar signs. Schema-qualified names (e.g., "schema.table") are supported.`);
55
+ }
56
+ // Quote the identifier using double quotes to prevent SQL injection
57
+ // Handle schema-qualified names (e.g., "schema.table")
58
+ // Escape any double quotes within the identifier by doubling them
59
+ return identifier
60
+ .split('.')
61
+ .map((part) => `"${part.replace(/"/g, '""')}"`)
62
+ .join('.');
63
+ }
43
64
  }
44
65
  //# sourceMappingURL=neon.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"neon.js","sourceRoot":"","sources":["../../../src/cache-tags/provider/neon.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAuBhD;;GAEG;AACH,MAAM,OAAO,qBAAqB;IACf,GAAG,CAAC;IACJ,KAAK,CAAC;IAEvB,YAAY,EAAE,aAAa,EAAE,KAAK,EAA+B;QAC/D,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,aAAa,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAEM,KAAK,CAAC,mBAAmB,CAAC,OAAe,EAAE,SAAqB;QACrE,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClE,MAAM,YAAY,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEzF,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,eAAe,IAAI,CAAC,KAAK,WAAW,YAAY,yBAAyB,EAAE,IAAI,CAAC,CAAC;IACxG,CAAC;IAEM,KAAK,CAAC,2BAA2B,CAAC,SAAqB;QAC5D,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;YACvB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,YAAY,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEpE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CACnC,iCAAiC,IAAI,CAAC,KAAK,wBAAwB,YAAY,GAAG,EAClF,SAAS,CACV,CAAC;QAEF,OAAO,IAAI,CAAC,MAAM,CAAW,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE;YAC7C,IAAI,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBACrC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC9B,CAAC;YAED,OAAO,QAAQ,CAAC;QAClB,CAAC,EAAE,EAAE,CAAC,CAAC;IACT,CAAC;IAEM,KAAK,CAAC,eAAe,CAAC,SAAqB;QAChD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,YAAY,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEpE,OAAO,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,eAAe,IAAI,CAAC,KAAK,wBAAwB,YAAY,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC;IAC3H,CAAC;IAEM,KAAK,CAAC,iBAAiB;QAC5B,OAAO,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,eAAe,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC;IAC3E,CAAC;CACF"}
1
+ {"version":3,"file":"neon.js","sourceRoot":"","sources":["../../../src/cache-tags/provider/neon.ts"],"names":[],"mappings":"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"}
@@ -50,7 +50,7 @@ export interface CacheTagsProvider {
50
50
  * run again, fresh cache tag mappings will be created.
51
51
  *
52
52
  * @param {CacheTag[]} cacheTags Array of cache tags to delete
53
- * @returns Number of keys deleted, or null if there was an error
53
+ * @returns Number of keys deleted
54
54
  *
55
55
  */
56
56
  deleteCacheTags(cacheTags: CacheTag[]): Promise<number>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartive/datocms-utils",
3
- "version": "3.0.0-next.10",
3
+ "version": "3.0.0-next.11",
4
4
  "description": "A set of utilities and helpers to work with DatoCMS in a Next.js project.",
5
5
  "type": "module",
6
6
  "source": "./src/index.ts",
@@ -30,7 +30,7 @@ export class NeonCacheTagsProvider implements CacheTagsProvider {
30
30
 
31
31
  constructor({ connectionUrl, table }: NeonCacheTagsProviderConfig) {
32
32
  this.sql = neon(connectionUrl, { fullResults: true });
33
- this.table = table;
33
+ this.table = NeonCacheTagsProvider.quoteIdentifier(table);
34
34
  }
35
35
 
36
36
  public async storeQueryCacheTags(queryId: string, cacheTags: CacheTag[]) {
@@ -66,7 +66,7 @@ export class NeonCacheTagsProvider implements CacheTagsProvider {
66
66
  }
67
67
 
68
68
  public async deleteCacheTags(cacheTags: CacheTag[]) {
69
- if (cacheTags.length === 0) {
69
+ if (!cacheTags?.length) {
70
70
  return 0;
71
71
  }
72
72
  const placeholders = cacheTags.map((_, i) => `$${i + 1}`).join(',');
@@ -77,4 +77,29 @@ export class NeonCacheTagsProvider implements CacheTagsProvider {
77
77
  public async truncateCacheTags() {
78
78
  return (await this.sql.query(`DELETE FROM ${this.table}`)).rowCount ?? 0;
79
79
  }
80
+
81
+ /**
82
+ * Validates and quotes a PostgreSQL identifier (table name, column name, etc.) to prevent SQL injection.
83
+ * @param identifier The identifier to validate and quote
84
+ * @returns The properly quoted identifier
85
+ * @throws Error if the identifier is invalid
86
+ */
87
+ private static quoteIdentifier(identifier: string): string {
88
+ // Validate that the identifier contains only valid characters
89
+ // PostgreSQL identifiers can contain letters, digits, underscores, and dollar signs
90
+ // They can also contain dots for schema-qualified names
91
+ if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*(\.[a-zA-Z_$][a-zA-Z0-9_$]*)?$/.test(identifier)) {
92
+ throw new Error(
93
+ `Invalid table name: ${identifier}. Table names must start with a letter, underscore, or dollar sign and contain only letters, digits, underscores, and dollar signs. Schema-qualified names (e.g., "schema.table") are supported.`,
94
+ );
95
+ }
96
+
97
+ // Quote the identifier using double quotes to prevent SQL injection
98
+ // Handle schema-qualified names (e.g., "schema.table")
99
+ // Escape any double quotes within the identifier by doubling them
100
+ return identifier
101
+ .split('.')
102
+ .map((part) => `"${part.replace(/"/g, '""')}"`)
103
+ .join('.');
104
+ }
80
105
  }
@@ -52,7 +52,7 @@ export interface CacheTagsProvider {
52
52
  * run again, fresh cache tag mappings will be created.
53
53
  *
54
54
  * @param {CacheTag[]} cacheTags Array of cache tags to delete
55
- * @returns Number of keys deleted, or null if there was an error
55
+ * @returns Number of keys deleted
56
56
  *
57
57
  */
58
58
  deleteCacheTags(cacheTags: CacheTag[]): Promise<number>;