namespace-guard 0.8.1 → 0.8.2
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 +2 -1
- package/dist/adapters/raw.d.mts +0 -26
- package/dist/adapters/raw.d.ts +0 -26
- package/dist/adapters/raw.js +10 -0
- package/dist/adapters/raw.mjs +10 -0
- package/dist/cli.js +21 -9
- package/dist/cli.mjs +21 -9
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +10 -10
- package/dist/index.mjs +10 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -648,7 +648,8 @@ Enable in-memory caching to reduce database calls during rapid checks (e.g., liv
|
|
|
648
648
|
const guard = createNamespaceGuard({
|
|
649
649
|
sources: [/* ... */],
|
|
650
650
|
cache: {
|
|
651
|
-
ttl: 5000,
|
|
651
|
+
ttl: 5000, // milliseconds (default: 5000)
|
|
652
|
+
maxSize: 1000, // max cached entries before LRU eviction (default: 1000)
|
|
652
653
|
},
|
|
653
654
|
}, adapter);
|
|
654
655
|
|
package/dist/adapters/raw.d.mts
CHANGED
|
@@ -3,32 +3,6 @@ import { NamespaceAdapter } from '../index.mjs';
|
|
|
3
3
|
type QueryExecutor = (sql: string, params: unknown[]) => Promise<{
|
|
4
4
|
rows: Record<string, unknown>[];
|
|
5
5
|
}>;
|
|
6
|
-
/**
|
|
7
|
-
* Create a namespace adapter for raw SQL queries
|
|
8
|
-
*
|
|
9
|
-
* Works with PostgreSQL-compatible clients that use $1-style parameter placeholders (pg).
|
|
10
|
-
* For MySQL or SQLite, wrap the executor to translate parameter syntax.
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```ts
|
|
14
|
-
* import { Pool } from "pg";
|
|
15
|
-
* import { createNamespaceGuard } from "namespace-guard";
|
|
16
|
-
* import { createRawAdapter } from "namespace-guard/adapters/raw";
|
|
17
|
-
*
|
|
18
|
-
* const pool = new Pool();
|
|
19
|
-
*
|
|
20
|
-
* const guard = createNamespaceGuard(
|
|
21
|
-
* {
|
|
22
|
-
* reserved: ["admin", "api", "settings"],
|
|
23
|
-
* sources: [
|
|
24
|
-
* { name: "users", column: "handle", scopeKey: "id" },
|
|
25
|
-
* { name: "organizations", column: "slug", scopeKey: "id" },
|
|
26
|
-
* ],
|
|
27
|
-
* },
|
|
28
|
-
* createRawAdapter((sql, params) => pool.query(sql, params))
|
|
29
|
-
* );
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
6
|
declare function createRawAdapter(execute: QueryExecutor): NamespaceAdapter;
|
|
33
7
|
|
|
34
8
|
export { createRawAdapter };
|
package/dist/adapters/raw.d.ts
CHANGED
|
@@ -3,32 +3,6 @@ import { NamespaceAdapter } from '../index.js';
|
|
|
3
3
|
type QueryExecutor = (sql: string, params: unknown[]) => Promise<{
|
|
4
4
|
rows: Record<string, unknown>[];
|
|
5
5
|
}>;
|
|
6
|
-
/**
|
|
7
|
-
* Create a namespace adapter for raw SQL queries
|
|
8
|
-
*
|
|
9
|
-
* Works with PostgreSQL-compatible clients that use $1-style parameter placeholders (pg).
|
|
10
|
-
* For MySQL or SQLite, wrap the executor to translate parameter syntax.
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```ts
|
|
14
|
-
* import { Pool } from "pg";
|
|
15
|
-
* import { createNamespaceGuard } from "namespace-guard";
|
|
16
|
-
* import { createRawAdapter } from "namespace-guard/adapters/raw";
|
|
17
|
-
*
|
|
18
|
-
* const pool = new Pool();
|
|
19
|
-
*
|
|
20
|
-
* const guard = createNamespaceGuard(
|
|
21
|
-
* {
|
|
22
|
-
* reserved: ["admin", "api", "settings"],
|
|
23
|
-
* sources: [
|
|
24
|
-
* { name: "users", column: "handle", scopeKey: "id" },
|
|
25
|
-
* { name: "organizations", column: "slug", scopeKey: "id" },
|
|
26
|
-
* ],
|
|
27
|
-
* },
|
|
28
|
-
* createRawAdapter((sql, params) => pool.query(sql, params))
|
|
29
|
-
* );
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
6
|
declare function createRawAdapter(execute: QueryExecutor): NamespaceAdapter;
|
|
33
7
|
|
|
34
8
|
export { createRawAdapter };
|
package/dist/adapters/raw.js
CHANGED
|
@@ -23,10 +23,20 @@ __export(raw_exports, {
|
|
|
23
23
|
createRawAdapter: () => createRawAdapter
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(raw_exports);
|
|
26
|
+
var SAFE_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
27
|
+
function assertSafeIdentifier(name, label) {
|
|
28
|
+
if (!SAFE_IDENTIFIER.test(name)) {
|
|
29
|
+
throw new Error(`Unsafe ${label}: ${JSON.stringify(name)}. Use only letters, digits, and underscores.`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
26
32
|
function createRawAdapter(execute) {
|
|
27
33
|
return {
|
|
28
34
|
async findOne(source, value, options) {
|
|
29
35
|
const idColumn = source.idColumn ?? "id";
|
|
36
|
+
assertSafeIdentifier(source.name, "table name");
|
|
37
|
+
assertSafeIdentifier(source.column, "column name");
|
|
38
|
+
assertSafeIdentifier(idColumn, "id column name");
|
|
39
|
+
if (source.scopeKey) assertSafeIdentifier(source.scopeKey, "scope key");
|
|
30
40
|
const columns = source.scopeKey && source.scopeKey !== idColumn ? `"${idColumn}", "${source.scopeKey}"` : `"${idColumn}"`;
|
|
31
41
|
const whereClause = options?.caseInsensitive ? `LOWER("${source.column}") = LOWER($1)` : `"${source.column}" = $1`;
|
|
32
42
|
const sql = `SELECT ${columns} FROM "${source.name}" WHERE ${whereClause} LIMIT 1`;
|
package/dist/adapters/raw.mjs
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
// src/adapters/raw.ts
|
|
2
|
+
var SAFE_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
3
|
+
function assertSafeIdentifier(name, label) {
|
|
4
|
+
if (!SAFE_IDENTIFIER.test(name)) {
|
|
5
|
+
throw new Error(`Unsafe ${label}: ${JSON.stringify(name)}. Use only letters, digits, and underscores.`);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
2
8
|
function createRawAdapter(execute) {
|
|
3
9
|
return {
|
|
4
10
|
async findOne(source, value, options) {
|
|
5
11
|
const idColumn = source.idColumn ?? "id";
|
|
12
|
+
assertSafeIdentifier(source.name, "table name");
|
|
13
|
+
assertSafeIdentifier(source.column, "column name");
|
|
14
|
+
assertSafeIdentifier(idColumn, "id column name");
|
|
15
|
+
if (source.scopeKey) assertSafeIdentifier(source.scopeKey, "scope key");
|
|
6
16
|
const columns = source.scopeKey && source.scopeKey !== idColumn ? `"${idColumn}", "${source.scopeKey}"` : `"${idColumn}"`;
|
|
7
17
|
const whereClause = options?.caseInsensitive ? `LOWER("${source.column}") = LOWER($1)` : `"${source.column}" = $1`;
|
|
8
18
|
const sql = `SELECT ${columns} FROM "${source.name}" WHERE ${whereClause} LIMIT 1`;
|
package/dist/cli.js
CHANGED
|
@@ -144,14 +144,12 @@ function createScrambleStrategy(_pattern) {
|
|
|
144
144
|
const candidates = [];
|
|
145
145
|
const chars = identifier.split("");
|
|
146
146
|
for (let i = 0; i < chars.length - 1; i++) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
candidates.push(candidate);
|
|
154
|
-
}
|
|
147
|
+
const swapped = [...chars];
|
|
148
|
+
[swapped[i], swapped[i + 1]] = [swapped[i + 1], swapped[i]];
|
|
149
|
+
const candidate = swapped.join("");
|
|
150
|
+
if (candidate !== identifier && !seen.has(candidate)) {
|
|
151
|
+
seen.add(candidate);
|
|
152
|
+
candidates.push(candidate);
|
|
155
153
|
}
|
|
156
154
|
}
|
|
157
155
|
return candidates;
|
|
@@ -298,7 +296,7 @@ function createNamespaceGuard(config, adapter) {
|
|
|
298
296
|
const purelyNumericMsg = configMessages.purelyNumeric ?? "Identifiers cannot be purely numeric.";
|
|
299
297
|
const cacheEnabled = !!config.cache;
|
|
300
298
|
const cacheTtl = config.cache?.ttl ?? 5e3;
|
|
301
|
-
const cacheMaxSize = 1e3;
|
|
299
|
+
const cacheMaxSize = config.cache?.maxSize ?? 1e3;
|
|
302
300
|
const cacheMap = /* @__PURE__ */ new Map();
|
|
303
301
|
let cacheHits = 0;
|
|
304
302
|
let cacheMisses = 0;
|
|
@@ -488,10 +486,20 @@ function createNamespaceGuard(config, adapter) {
|
|
|
488
486
|
}
|
|
489
487
|
|
|
490
488
|
// src/adapters/raw.ts
|
|
489
|
+
var SAFE_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
490
|
+
function assertSafeIdentifier(name, label) {
|
|
491
|
+
if (!SAFE_IDENTIFIER.test(name)) {
|
|
492
|
+
throw new Error(`Unsafe ${label}: ${JSON.stringify(name)}. Use only letters, digits, and underscores.`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
491
495
|
function createRawAdapter(execute) {
|
|
492
496
|
return {
|
|
493
497
|
async findOne(source, value, options) {
|
|
494
498
|
const idColumn = source.idColumn ?? "id";
|
|
499
|
+
assertSafeIdentifier(source.name, "table name");
|
|
500
|
+
assertSafeIdentifier(source.column, "column name");
|
|
501
|
+
assertSafeIdentifier(idColumn, "id column name");
|
|
502
|
+
if (source.scopeKey) assertSafeIdentifier(source.scopeKey, "scope key");
|
|
495
503
|
const columns = source.scopeKey && source.scopeKey !== idColumn ? `"${idColumn}", "${source.scopeKey}"` : `"${idColumn}"`;
|
|
496
504
|
const whereClause = options?.caseInsensitive ? `LOWER("${source.column}") = LOWER($1)` : `"${source.column}" = $1`;
|
|
497
505
|
const sql = `SELECT ${columns} FROM "${source.name}" WHERE ${whereClause} LIMIT 1`;
|
|
@@ -574,6 +582,10 @@ async function createDatabaseAdapter(url) {
|
|
|
574
582
|
process.exit(1);
|
|
575
583
|
}
|
|
576
584
|
const Pool = pg.default?.Pool ?? pg.Pool;
|
|
585
|
+
if (!Pool) {
|
|
586
|
+
console.error("Could not find Pool export from pg package. Check your pg version.");
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
577
589
|
const pool = new Pool({ connectionString: url });
|
|
578
590
|
return {
|
|
579
591
|
adapter: createRawAdapter((sql, params) => pool.query(sql, params)),
|
package/dist/cli.mjs
CHANGED
|
@@ -121,14 +121,12 @@ function createScrambleStrategy(_pattern) {
|
|
|
121
121
|
const candidates = [];
|
|
122
122
|
const chars = identifier.split("");
|
|
123
123
|
for (let i = 0; i < chars.length - 1; i++) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
candidates.push(candidate);
|
|
131
|
-
}
|
|
124
|
+
const swapped = [...chars];
|
|
125
|
+
[swapped[i], swapped[i + 1]] = [swapped[i + 1], swapped[i]];
|
|
126
|
+
const candidate = swapped.join("");
|
|
127
|
+
if (candidate !== identifier && !seen.has(candidate)) {
|
|
128
|
+
seen.add(candidate);
|
|
129
|
+
candidates.push(candidate);
|
|
132
130
|
}
|
|
133
131
|
}
|
|
134
132
|
return candidates;
|
|
@@ -275,7 +273,7 @@ function createNamespaceGuard(config, adapter) {
|
|
|
275
273
|
const purelyNumericMsg = configMessages.purelyNumeric ?? "Identifiers cannot be purely numeric.";
|
|
276
274
|
const cacheEnabled = !!config.cache;
|
|
277
275
|
const cacheTtl = config.cache?.ttl ?? 5e3;
|
|
278
|
-
const cacheMaxSize = 1e3;
|
|
276
|
+
const cacheMaxSize = config.cache?.maxSize ?? 1e3;
|
|
279
277
|
const cacheMap = /* @__PURE__ */ new Map();
|
|
280
278
|
let cacheHits = 0;
|
|
281
279
|
let cacheMisses = 0;
|
|
@@ -465,10 +463,20 @@ function createNamespaceGuard(config, adapter) {
|
|
|
465
463
|
}
|
|
466
464
|
|
|
467
465
|
// src/adapters/raw.ts
|
|
466
|
+
var SAFE_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
467
|
+
function assertSafeIdentifier(name, label) {
|
|
468
|
+
if (!SAFE_IDENTIFIER.test(name)) {
|
|
469
|
+
throw new Error(`Unsafe ${label}: ${JSON.stringify(name)}. Use only letters, digits, and underscores.`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
468
472
|
function createRawAdapter(execute) {
|
|
469
473
|
return {
|
|
470
474
|
async findOne(source, value, options) {
|
|
471
475
|
const idColumn = source.idColumn ?? "id";
|
|
476
|
+
assertSafeIdentifier(source.name, "table name");
|
|
477
|
+
assertSafeIdentifier(source.column, "column name");
|
|
478
|
+
assertSafeIdentifier(idColumn, "id column name");
|
|
479
|
+
if (source.scopeKey) assertSafeIdentifier(source.scopeKey, "scope key");
|
|
472
480
|
const columns = source.scopeKey && source.scopeKey !== idColumn ? `"${idColumn}", "${source.scopeKey}"` : `"${idColumn}"`;
|
|
473
481
|
const whereClause = options?.caseInsensitive ? `LOWER("${source.column}") = LOWER($1)` : `"${source.column}" = $1`;
|
|
474
482
|
const sql = `SELECT ${columns} FROM "${source.name}" WHERE ${whereClause} LIMIT 1`;
|
|
@@ -551,6 +559,10 @@ async function createDatabaseAdapter(url) {
|
|
|
551
559
|
process.exit(1);
|
|
552
560
|
}
|
|
553
561
|
const Pool = pg.default?.Pool ?? pg.Pool;
|
|
562
|
+
if (!Pool) {
|
|
563
|
+
console.error("Could not find Pool export from pg package. Check your pg version.");
|
|
564
|
+
process.exit(1);
|
|
565
|
+
}
|
|
554
566
|
const pool = new Pool({ connectionString: url });
|
|
555
567
|
return {
|
|
556
568
|
adapter: createRawAdapter((sql, params) => pool.query(sql, params)),
|
package/dist/index.d.mts
CHANGED
|
@@ -53,6 +53,8 @@ type NamespaceConfig = {
|
|
|
53
53
|
cache?: {
|
|
54
54
|
/** Time-to-live in milliseconds (default: 5000) */
|
|
55
55
|
ttl?: number;
|
|
56
|
+
/** Maximum number of cached entries before LRU eviction (default: 1000) */
|
|
57
|
+
maxSize?: number;
|
|
56
58
|
};
|
|
57
59
|
};
|
|
58
60
|
/** Options passed to adapter `findOne` calls. */
|
package/dist/index.d.ts
CHANGED
|
@@ -53,6 +53,8 @@ type NamespaceConfig = {
|
|
|
53
53
|
cache?: {
|
|
54
54
|
/** Time-to-live in milliseconds (default: 5000) */
|
|
55
55
|
ttl?: number;
|
|
56
|
+
/** Maximum number of cached entries before LRU eviction (default: 1000) */
|
|
57
|
+
maxSize?: number;
|
|
56
58
|
};
|
|
57
59
|
};
|
|
58
60
|
/** Options passed to adapter `findOne` calls. */
|
package/dist/index.js
CHANGED
|
@@ -143,14 +143,12 @@ function createScrambleStrategy(_pattern) {
|
|
|
143
143
|
const candidates = [];
|
|
144
144
|
const chars = identifier.split("");
|
|
145
145
|
for (let i = 0; i < chars.length - 1; i++) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
candidates.push(candidate);
|
|
153
|
-
}
|
|
146
|
+
const swapped = [...chars];
|
|
147
|
+
[swapped[i], swapped[i + 1]] = [swapped[i + 1], swapped[i]];
|
|
148
|
+
const candidate = swapped.join("");
|
|
149
|
+
if (candidate !== identifier && !seen.has(candidate)) {
|
|
150
|
+
seen.add(candidate);
|
|
151
|
+
candidates.push(candidate);
|
|
154
152
|
}
|
|
155
153
|
}
|
|
156
154
|
return candidates;
|
|
@@ -971,7 +969,9 @@ function createHomoglyphValidator(options) {
|
|
|
971
969
|
Object.assign(map, options.additionalMappings);
|
|
972
970
|
}
|
|
973
971
|
const confusableChars = Object.keys(map);
|
|
974
|
-
const confusableRegex = confusableChars.length > 0 ? new RegExp(
|
|
972
|
+
const confusableRegex = confusableChars.length > 0 ? new RegExp(
|
|
973
|
+
"[" + confusableChars.map((c) => c.replace(/[\\\]^-]/g, "\\$&")).join("") + "]"
|
|
974
|
+
) : null;
|
|
975
975
|
const nonLatinRegex = /[\u0370-\u03FF\u0400-\u04FF\u0500-\u052F\u0530-\u058F\u0590-\u05FF\u0600-\u074F\u0900-\u0DFF\u0E00-\u0EFF\u1000-\u109F\u10A0-\u10FF\u1200-\u137F\u13A0-\u13FF\u1400-\u167F\u16A0-\u16FF\u1780-\u17FF\u2C80-\u2CFF\u2D30-\u2D7F\uA4D0-\uA4FF\uA6A0-\uA6FF\uAB70-\uABBF]/;
|
|
976
976
|
const latinRegex = /[a-zA-Z]/;
|
|
977
977
|
return async (value) => {
|
|
@@ -1015,7 +1015,7 @@ function createNamespaceGuard(config, adapter) {
|
|
|
1015
1015
|
const purelyNumericMsg = configMessages.purelyNumeric ?? "Identifiers cannot be purely numeric.";
|
|
1016
1016
|
const cacheEnabled = !!config.cache;
|
|
1017
1017
|
const cacheTtl = config.cache?.ttl ?? 5e3;
|
|
1018
|
-
const cacheMaxSize = 1e3;
|
|
1018
|
+
const cacheMaxSize = config.cache?.maxSize ?? 1e3;
|
|
1019
1019
|
const cacheMap = /* @__PURE__ */ new Map();
|
|
1020
1020
|
let cacheHits = 0;
|
|
1021
1021
|
let cacheMisses = 0;
|
package/dist/index.mjs
CHANGED
|
@@ -115,14 +115,12 @@ function createScrambleStrategy(_pattern) {
|
|
|
115
115
|
const candidates = [];
|
|
116
116
|
const chars = identifier.split("");
|
|
117
117
|
for (let i = 0; i < chars.length - 1; i++) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
candidates.push(candidate);
|
|
125
|
-
}
|
|
118
|
+
const swapped = [...chars];
|
|
119
|
+
[swapped[i], swapped[i + 1]] = [swapped[i + 1], swapped[i]];
|
|
120
|
+
const candidate = swapped.join("");
|
|
121
|
+
if (candidate !== identifier && !seen.has(candidate)) {
|
|
122
|
+
seen.add(candidate);
|
|
123
|
+
candidates.push(candidate);
|
|
126
124
|
}
|
|
127
125
|
}
|
|
128
126
|
return candidates;
|
|
@@ -943,7 +941,9 @@ function createHomoglyphValidator(options) {
|
|
|
943
941
|
Object.assign(map, options.additionalMappings);
|
|
944
942
|
}
|
|
945
943
|
const confusableChars = Object.keys(map);
|
|
946
|
-
const confusableRegex = confusableChars.length > 0 ? new RegExp(
|
|
944
|
+
const confusableRegex = confusableChars.length > 0 ? new RegExp(
|
|
945
|
+
"[" + confusableChars.map((c) => c.replace(/[\\\]^-]/g, "\\$&")).join("") + "]"
|
|
946
|
+
) : null;
|
|
947
947
|
const nonLatinRegex = /[\u0370-\u03FF\u0400-\u04FF\u0500-\u052F\u0530-\u058F\u0590-\u05FF\u0600-\u074F\u0900-\u0DFF\u0E00-\u0EFF\u1000-\u109F\u10A0-\u10FF\u1200-\u137F\u13A0-\u13FF\u1400-\u167F\u16A0-\u16FF\u1780-\u17FF\u2C80-\u2CFF\u2D30-\u2D7F\uA4D0-\uA4FF\uA6A0-\uA6FF\uAB70-\uABBF]/;
|
|
948
948
|
const latinRegex = /[a-zA-Z]/;
|
|
949
949
|
return async (value) => {
|
|
@@ -987,7 +987,7 @@ function createNamespaceGuard(config, adapter) {
|
|
|
987
987
|
const purelyNumericMsg = configMessages.purelyNumeric ?? "Identifiers cannot be purely numeric.";
|
|
988
988
|
const cacheEnabled = !!config.cache;
|
|
989
989
|
const cacheTtl = config.cache?.ttl ?? 5e3;
|
|
990
|
-
const cacheMaxSize = 1e3;
|
|
990
|
+
const cacheMaxSize = config.cache?.maxSize ?? 1e3;
|
|
991
991
|
const cacheMap = /* @__PURE__ */ new Map();
|
|
992
992
|
let cacheHits = 0;
|
|
993
993
|
let cacheMisses = 0;
|
package/package.json
CHANGED