@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 +4 -4
- package/dist/cache-tags/provider/neon.d.ts +7 -0
- package/dist/cache-tags/provider/neon.js +23 -2
- package/dist/cache-tags/provider/neon.js.map +1 -1
- package/dist/cache-tags/types.d.ts +1 -1
- package/package.json +1 -1
- package/src/cache-tags/provider/neon.ts +27 -2
- package/src/cache-tags/types.ts +1 -1
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
|
-
|
|
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
|
|
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;
|
|
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
|
|
53
|
+
* @returns Number of keys deleted
|
|
54
54
|
*
|
|
55
55
|
*/
|
|
56
56
|
deleteCacheTags(cacheTags: CacheTag[]): Promise<number>;
|
package/package.json
CHANGED
|
@@ -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
|
|
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
|
}
|
package/src/cache-tags/types.ts
CHANGED
|
@@ -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
|
|
55
|
+
* @returns Number of keys deleted
|
|
56
56
|
*
|
|
57
57
|
*/
|
|
58
58
|
deleteCacheTags(cacheTags: CacheTag[]): Promise<number>;
|