namespace-guard 0.10.0 → 0.11.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/README.md +73 -5
- package/dist/cli.js +14 -2
- package/dist/cli.mjs +14 -2
- package/dist/index.d.mts +9 -3
- package/dist/index.d.ts +9 -3
- package/dist/index.js +14 -2
- package/dist/index.mjs +14 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -92,6 +92,8 @@ const adapter = createPrismaAdapter(prisma);
|
|
|
92
92
|
|
|
93
93
|
### Drizzle
|
|
94
94
|
|
|
95
|
+
> **Note:** The Drizzle adapter uses `db.query` (the relational query API). Make sure your Drizzle client is set up with `drizzle(client, { schema })` so that `db.query.<tableName>` is available.
|
|
96
|
+
|
|
95
97
|
```typescript
|
|
96
98
|
import { eq } from "drizzle-orm";
|
|
97
99
|
import { createDrizzleAdapter } from "namespace-guard/adapters/drizzle";
|
|
@@ -163,7 +165,9 @@ import { User, Organization } from "./models";
|
|
|
163
165
|
const adapter = createMongooseAdapter({ user: User, organization: Organization });
|
|
164
166
|
```
|
|
165
167
|
|
|
166
|
-
### Raw SQL (pg, mysql2, etc.)
|
|
168
|
+
### Raw SQL (pg, mysql2, better-sqlite3, etc.)
|
|
169
|
+
|
|
170
|
+
The raw adapter generates PostgreSQL-style SQL (`$1` placeholders, double-quoted identifiers). For pg this works directly. For MySQL or SQLite, translate the parameter syntax in your executor wrapper.
|
|
167
171
|
|
|
168
172
|
```typescript
|
|
169
173
|
import { Pool } from "pg";
|
|
@@ -173,6 +177,34 @@ const pool = new Pool();
|
|
|
173
177
|
const adapter = createRawAdapter((sql, params) => pool.query(sql, params));
|
|
174
178
|
```
|
|
175
179
|
|
|
180
|
+
**MySQL2 wrapper** (translates `$1` to `?` and `"col"` to `` `col` ``):
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import mysql from "mysql2/promise";
|
|
184
|
+
import { createRawAdapter } from "namespace-guard/adapters/raw";
|
|
185
|
+
|
|
186
|
+
const pool = mysql.createPool({ uri: process.env.DATABASE_URL });
|
|
187
|
+
const adapter = createRawAdapter(async (sql, params) => {
|
|
188
|
+
const mysqlSql = sql.replace(/\$\d+/g, "?").replace(/"/g, "`");
|
|
189
|
+
const [rows] = await pool.execute(mysqlSql, params);
|
|
190
|
+
return { rows: rows as Record<string, unknown>[] };
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**better-sqlite3 wrapper** (translates `$1` to `?` and strips identifier quotes):
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
import Database from "better-sqlite3";
|
|
198
|
+
import { createRawAdapter } from "namespace-guard/adapters/raw";
|
|
199
|
+
|
|
200
|
+
const db = new Database("app.db");
|
|
201
|
+
const adapter = createRawAdapter(async (sql, params) => {
|
|
202
|
+
const sqliteSql = sql.replace(/\$\d+/g, "?").replace(/"/g, "");
|
|
203
|
+
const rows = db.prepare(sqliteSql).all(...params);
|
|
204
|
+
return { rows: rows as Record<string, unknown>[] };
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
176
208
|
## Configuration
|
|
177
209
|
|
|
178
210
|
```typescript
|
|
@@ -623,9 +655,16 @@ Check if an identifier is available.
|
|
|
623
655
|
|
|
624
656
|
---
|
|
625
657
|
|
|
626
|
-
### `guard.checkMany(identifiers, scope?)`
|
|
658
|
+
### `guard.checkMany(identifiers, scope?, options?)`
|
|
659
|
+
|
|
660
|
+
Check multiple identifiers in parallel. Suggestions are skipped by default for performance.
|
|
661
|
+
|
|
662
|
+
**Parameters:**
|
|
663
|
+
- `identifiers` - Array of slugs/handles to check
|
|
664
|
+
- `scope` - Optional ownership scope applied to all checks
|
|
665
|
+
- `options` - Optional `{ skipSuggestions?: boolean }` (default: `true`)
|
|
627
666
|
|
|
628
|
-
|
|
667
|
+
Pass `{ skipSuggestions: false }` to include suggestions for taken identifiers.
|
|
629
668
|
|
|
630
669
|
**Returns:** `Record<string, CheckResult>`
|
|
631
670
|
|
|
@@ -639,9 +678,37 @@ Same as `check()`, but throws an `Error` if not available.
|
|
|
639
678
|
|
|
640
679
|
### `guard.validateFormat(identifier)`
|
|
641
680
|
|
|
642
|
-
Validate format
|
|
681
|
+
Validate format, purely-numeric restriction, and reserved name status without querying the database.
|
|
682
|
+
|
|
683
|
+
**Returns:** Error message string if invalid or reserved, `null` if OK.
|
|
684
|
+
|
|
685
|
+
---
|
|
686
|
+
|
|
687
|
+
### `guard.validateFormatOnly(identifier)`
|
|
688
|
+
|
|
689
|
+
Validate only the identifier's format and purely-numeric restriction. Does not check reserved names or query the database. Useful for instant client-side feedback on input shape.
|
|
690
|
+
|
|
691
|
+
**Returns:** Error message string if the format is invalid, `null` if OK.
|
|
692
|
+
|
|
693
|
+
---
|
|
694
|
+
|
|
695
|
+
### `guard.normalize(identifier)`
|
|
696
|
+
|
|
697
|
+
Convenience re-export of the standalone `normalize()` function. Note: always applies NFKC normalization regardless of the guard's `normalizeUnicode` setting. Use `normalize(id, { unicode: false })` directly if you need to skip NFKC.
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
### `guard.clearCache()`
|
|
702
|
+
|
|
703
|
+
Clear the in-memory cache and reset hit/miss counters. No-op if caching is not enabled.
|
|
704
|
+
|
|
705
|
+
---
|
|
706
|
+
|
|
707
|
+
### `guard.cacheStats()`
|
|
708
|
+
|
|
709
|
+
Get cache performance statistics.
|
|
643
710
|
|
|
644
|
-
**Returns:**
|
|
711
|
+
**Returns:** `{ size: number; hits: number; misses: number }`
|
|
645
712
|
|
|
646
713
|
---
|
|
647
714
|
|
|
@@ -813,6 +880,7 @@ import {
|
|
|
813
880
|
type OwnershipScope,
|
|
814
881
|
type SuggestStrategyName,
|
|
815
882
|
type SkeletonOptions,
|
|
883
|
+
type CheckManyOptions,
|
|
816
884
|
} from "namespace-guard";
|
|
817
885
|
```
|
|
818
886
|
|
package/dist/cli.js
CHANGED
|
@@ -339,6 +339,16 @@ function createNamespaceGuard(config, adapter) {
|
|
|
339
339
|
}
|
|
340
340
|
return null;
|
|
341
341
|
}
|
|
342
|
+
function validateFormatOnly(identifier) {
|
|
343
|
+
const normalized = normalize(identifier, normalizeOpts);
|
|
344
|
+
if (!pattern.test(normalized)) {
|
|
345
|
+
return invalidMsg;
|
|
346
|
+
}
|
|
347
|
+
if (!allowPurelyNumeric && /^\d+(-\d+)*$/.test(normalized)) {
|
|
348
|
+
return purelyNumericMsg;
|
|
349
|
+
}
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
342
352
|
function isOwnedByScope(existing, source, scope) {
|
|
343
353
|
if (!source.scopeKey) return false;
|
|
344
354
|
const scopeValue = scope[source.scopeKey];
|
|
@@ -457,10 +467,11 @@ function createNamespaceGuard(config, adapter) {
|
|
|
457
467
|
throw new Error(result.message);
|
|
458
468
|
}
|
|
459
469
|
}
|
|
460
|
-
async function checkMany(identifiers, scope = {}) {
|
|
470
|
+
async function checkMany(identifiers, scope = {}, options) {
|
|
471
|
+
const skip = options?.skipSuggestions ?? true;
|
|
461
472
|
const entries = await Promise.all(
|
|
462
473
|
identifiers.map(async (id) => {
|
|
463
|
-
const result = await check(id, scope, { skipSuggestions:
|
|
474
|
+
const result = await check(id, scope, { skipSuggestions: skip });
|
|
464
475
|
return [id, result];
|
|
465
476
|
})
|
|
466
477
|
);
|
|
@@ -477,6 +488,7 @@ function createNamespaceGuard(config, adapter) {
|
|
|
477
488
|
return {
|
|
478
489
|
normalize,
|
|
479
490
|
validateFormat,
|
|
491
|
+
validateFormatOnly,
|
|
480
492
|
check,
|
|
481
493
|
assertAvailable,
|
|
482
494
|
checkMany,
|
package/dist/cli.mjs
CHANGED
|
@@ -316,6 +316,16 @@ function createNamespaceGuard(config, adapter) {
|
|
|
316
316
|
}
|
|
317
317
|
return null;
|
|
318
318
|
}
|
|
319
|
+
function validateFormatOnly(identifier) {
|
|
320
|
+
const normalized = normalize(identifier, normalizeOpts);
|
|
321
|
+
if (!pattern.test(normalized)) {
|
|
322
|
+
return invalidMsg;
|
|
323
|
+
}
|
|
324
|
+
if (!allowPurelyNumeric && /^\d+(-\d+)*$/.test(normalized)) {
|
|
325
|
+
return purelyNumericMsg;
|
|
326
|
+
}
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
319
329
|
function isOwnedByScope(existing, source, scope) {
|
|
320
330
|
if (!source.scopeKey) return false;
|
|
321
331
|
const scopeValue = scope[source.scopeKey];
|
|
@@ -434,10 +444,11 @@ function createNamespaceGuard(config, adapter) {
|
|
|
434
444
|
throw new Error(result.message);
|
|
435
445
|
}
|
|
436
446
|
}
|
|
437
|
-
async function checkMany(identifiers, scope = {}) {
|
|
447
|
+
async function checkMany(identifiers, scope = {}, options) {
|
|
448
|
+
const skip = options?.skipSuggestions ?? true;
|
|
438
449
|
const entries = await Promise.all(
|
|
439
450
|
identifiers.map(async (id) => {
|
|
440
|
-
const result = await check(id, scope, { skipSuggestions:
|
|
451
|
+
const result = await check(id, scope, { skipSuggestions: skip });
|
|
441
452
|
return [id, result];
|
|
442
453
|
})
|
|
443
454
|
);
|
|
@@ -454,6 +465,7 @@ function createNamespaceGuard(config, adapter) {
|
|
|
454
465
|
return {
|
|
455
466
|
normalize,
|
|
456
467
|
validateFormat,
|
|
468
|
+
validateFormatOnly,
|
|
457
469
|
check,
|
|
458
470
|
assertAvailable,
|
|
459
471
|
checkMany,
|
package/dist/index.d.mts
CHANGED
|
@@ -82,6 +82,11 @@ type CheckResult = {
|
|
|
82
82
|
category?: string;
|
|
83
83
|
suggestions?: string[];
|
|
84
84
|
};
|
|
85
|
+
/** Options for `checkMany()`. */
|
|
86
|
+
type CheckManyOptions = {
|
|
87
|
+
/** Skip suggestion generation for taken identifiers (default: `true`). */
|
|
88
|
+
skipSuggestions?: boolean;
|
|
89
|
+
};
|
|
85
90
|
/** Options for the `skeleton()` and `areConfusable()` functions. */
|
|
86
91
|
type SkeletonOptions = {
|
|
87
92
|
/** Confusable character map to use.
|
|
@@ -250,7 +255,7 @@ declare function areConfusable(a: string, b: string, options?: SkeletonOptions):
|
|
|
250
255
|
*
|
|
251
256
|
* @param config - Reserved names, data sources, validation pattern, and optional features
|
|
252
257
|
* @param adapter - Database adapter implementing the `findOne` lookup (use a built-in adapter or write your own)
|
|
253
|
-
* @returns A guard with `check`, `checkMany`, `assertAvailable`, `validateFormat`, `clearCache`, and `cacheStats` methods
|
|
258
|
+
* @returns A guard with `check`, `checkMany`, `assertAvailable`, `validateFormat`, `validateFormatOnly`, `clearCache`, and `cacheStats` methods
|
|
254
259
|
*
|
|
255
260
|
* @example
|
|
256
261
|
* ```ts
|
|
@@ -277,11 +282,12 @@ declare function areConfusable(a: string, b: string, options?: SkeletonOptions):
|
|
|
277
282
|
declare function createNamespaceGuard(config: NamespaceConfig, adapter: NamespaceAdapter): {
|
|
278
283
|
normalize: typeof normalize;
|
|
279
284
|
validateFormat: (identifier: string) => string | null;
|
|
285
|
+
validateFormatOnly: (identifier: string) => string | null;
|
|
280
286
|
check: (identifier: string, scope?: OwnershipScope, options?: {
|
|
281
287
|
skipSuggestions?: boolean;
|
|
282
288
|
}) => Promise<CheckResult>;
|
|
283
289
|
assertAvailable: (identifier: string, scope?: OwnershipScope) => Promise<void>;
|
|
284
|
-
checkMany: (identifiers: string[], scope?: OwnershipScope) => Promise<Record<string, CheckResult>>;
|
|
290
|
+
checkMany: (identifiers: string[], scope?: OwnershipScope, options?: CheckManyOptions) => Promise<Record<string, CheckResult>>;
|
|
285
291
|
clearCache: () => void;
|
|
286
292
|
cacheStats: () => {
|
|
287
293
|
size: number;
|
|
@@ -292,4 +298,4 @@ declare function createNamespaceGuard(config: NamespaceConfig, adapter: Namespac
|
|
|
292
298
|
/** The guard instance returned by `createNamespaceGuard`. */
|
|
293
299
|
type NamespaceGuard = ReturnType<typeof createNamespaceGuard>;
|
|
294
300
|
|
|
295
|
-
export { CONFUSABLE_MAP, CONFUSABLE_MAP_FULL, type CheckResult, type FindOneOptions, type NamespaceAdapter, type NamespaceConfig, type NamespaceGuard, type NamespaceSource, type OwnershipScope, type SkeletonOptions, type SuggestStrategyName, areConfusable, createHomoglyphValidator, createNamespaceGuard, createProfanityValidator, normalize, skeleton };
|
|
301
|
+
export { CONFUSABLE_MAP, CONFUSABLE_MAP_FULL, type CheckManyOptions, type CheckResult, type FindOneOptions, type NamespaceAdapter, type NamespaceConfig, type NamespaceGuard, type NamespaceSource, type OwnershipScope, type SkeletonOptions, type SuggestStrategyName, areConfusable, createHomoglyphValidator, createNamespaceGuard, createProfanityValidator, normalize, skeleton };
|
package/dist/index.d.ts
CHANGED
|
@@ -82,6 +82,11 @@ type CheckResult = {
|
|
|
82
82
|
category?: string;
|
|
83
83
|
suggestions?: string[];
|
|
84
84
|
};
|
|
85
|
+
/** Options for `checkMany()`. */
|
|
86
|
+
type CheckManyOptions = {
|
|
87
|
+
/** Skip suggestion generation for taken identifiers (default: `true`). */
|
|
88
|
+
skipSuggestions?: boolean;
|
|
89
|
+
};
|
|
85
90
|
/** Options for the `skeleton()` and `areConfusable()` functions. */
|
|
86
91
|
type SkeletonOptions = {
|
|
87
92
|
/** Confusable character map to use.
|
|
@@ -250,7 +255,7 @@ declare function areConfusable(a: string, b: string, options?: SkeletonOptions):
|
|
|
250
255
|
*
|
|
251
256
|
* @param config - Reserved names, data sources, validation pattern, and optional features
|
|
252
257
|
* @param adapter - Database adapter implementing the `findOne` lookup (use a built-in adapter or write your own)
|
|
253
|
-
* @returns A guard with `check`, `checkMany`, `assertAvailable`, `validateFormat`, `clearCache`, and `cacheStats` methods
|
|
258
|
+
* @returns A guard with `check`, `checkMany`, `assertAvailable`, `validateFormat`, `validateFormatOnly`, `clearCache`, and `cacheStats` methods
|
|
254
259
|
*
|
|
255
260
|
* @example
|
|
256
261
|
* ```ts
|
|
@@ -277,11 +282,12 @@ declare function areConfusable(a: string, b: string, options?: SkeletonOptions):
|
|
|
277
282
|
declare function createNamespaceGuard(config: NamespaceConfig, adapter: NamespaceAdapter): {
|
|
278
283
|
normalize: typeof normalize;
|
|
279
284
|
validateFormat: (identifier: string) => string | null;
|
|
285
|
+
validateFormatOnly: (identifier: string) => string | null;
|
|
280
286
|
check: (identifier: string, scope?: OwnershipScope, options?: {
|
|
281
287
|
skipSuggestions?: boolean;
|
|
282
288
|
}) => Promise<CheckResult>;
|
|
283
289
|
assertAvailable: (identifier: string, scope?: OwnershipScope) => Promise<void>;
|
|
284
|
-
checkMany: (identifiers: string[], scope?: OwnershipScope) => Promise<Record<string, CheckResult>>;
|
|
290
|
+
checkMany: (identifiers: string[], scope?: OwnershipScope, options?: CheckManyOptions) => Promise<Record<string, CheckResult>>;
|
|
285
291
|
clearCache: () => void;
|
|
286
292
|
cacheStats: () => {
|
|
287
293
|
size: number;
|
|
@@ -292,4 +298,4 @@ declare function createNamespaceGuard(config: NamespaceConfig, adapter: Namespac
|
|
|
292
298
|
/** The guard instance returned by `createNamespaceGuard`. */
|
|
293
299
|
type NamespaceGuard = ReturnType<typeof createNamespaceGuard>;
|
|
294
300
|
|
|
295
|
-
export { CONFUSABLE_MAP, CONFUSABLE_MAP_FULL, type CheckResult, type FindOneOptions, type NamespaceAdapter, type NamespaceConfig, type NamespaceGuard, type NamespaceSource, type OwnershipScope, type SkeletonOptions, type SuggestStrategyName, areConfusable, createHomoglyphValidator, createNamespaceGuard, createProfanityValidator, normalize, skeleton };
|
|
301
|
+
export { CONFUSABLE_MAP, CONFUSABLE_MAP_FULL, type CheckManyOptions, type CheckResult, type FindOneOptions, type NamespaceAdapter, type NamespaceConfig, type NamespaceGuard, type NamespaceSource, type OwnershipScope, type SkeletonOptions, type SuggestStrategyName, areConfusable, createHomoglyphValidator, createNamespaceGuard, createProfanityValidator, normalize, skeleton };
|
package/dist/index.js
CHANGED
|
@@ -2837,6 +2837,16 @@ function createNamespaceGuard(config, adapter) {
|
|
|
2837
2837
|
}
|
|
2838
2838
|
return null;
|
|
2839
2839
|
}
|
|
2840
|
+
function validateFormatOnly(identifier) {
|
|
2841
|
+
const normalized = normalize(identifier, normalizeOpts);
|
|
2842
|
+
if (!pattern.test(normalized)) {
|
|
2843
|
+
return invalidMsg;
|
|
2844
|
+
}
|
|
2845
|
+
if (!allowPurelyNumeric && /^\d+(-\d+)*$/.test(normalized)) {
|
|
2846
|
+
return purelyNumericMsg;
|
|
2847
|
+
}
|
|
2848
|
+
return null;
|
|
2849
|
+
}
|
|
2840
2850
|
function isOwnedByScope(existing, source, scope) {
|
|
2841
2851
|
if (!source.scopeKey) return false;
|
|
2842
2852
|
const scopeValue = scope[source.scopeKey];
|
|
@@ -2955,10 +2965,11 @@ function createNamespaceGuard(config, adapter) {
|
|
|
2955
2965
|
throw new Error(result.message);
|
|
2956
2966
|
}
|
|
2957
2967
|
}
|
|
2958
|
-
async function checkMany(identifiers, scope = {}) {
|
|
2968
|
+
async function checkMany(identifiers, scope = {}, options) {
|
|
2969
|
+
const skip = options?.skipSuggestions ?? true;
|
|
2959
2970
|
const entries = await Promise.all(
|
|
2960
2971
|
identifiers.map(async (id) => {
|
|
2961
|
-
const result = await check(id, scope, { skipSuggestions:
|
|
2972
|
+
const result = await check(id, scope, { skipSuggestions: skip });
|
|
2962
2973
|
return [id, result];
|
|
2963
2974
|
})
|
|
2964
2975
|
);
|
|
@@ -2975,6 +2986,7 @@ function createNamespaceGuard(config, adapter) {
|
|
|
2975
2986
|
return {
|
|
2976
2987
|
normalize,
|
|
2977
2988
|
validateFormat,
|
|
2989
|
+
validateFormatOnly,
|
|
2978
2990
|
check,
|
|
2979
2991
|
assertAvailable,
|
|
2980
2992
|
checkMany,
|
package/dist/index.mjs
CHANGED
|
@@ -2806,6 +2806,16 @@ function createNamespaceGuard(config, adapter) {
|
|
|
2806
2806
|
}
|
|
2807
2807
|
return null;
|
|
2808
2808
|
}
|
|
2809
|
+
function validateFormatOnly(identifier) {
|
|
2810
|
+
const normalized = normalize(identifier, normalizeOpts);
|
|
2811
|
+
if (!pattern.test(normalized)) {
|
|
2812
|
+
return invalidMsg;
|
|
2813
|
+
}
|
|
2814
|
+
if (!allowPurelyNumeric && /^\d+(-\d+)*$/.test(normalized)) {
|
|
2815
|
+
return purelyNumericMsg;
|
|
2816
|
+
}
|
|
2817
|
+
return null;
|
|
2818
|
+
}
|
|
2809
2819
|
function isOwnedByScope(existing, source, scope) {
|
|
2810
2820
|
if (!source.scopeKey) return false;
|
|
2811
2821
|
const scopeValue = scope[source.scopeKey];
|
|
@@ -2924,10 +2934,11 @@ function createNamespaceGuard(config, adapter) {
|
|
|
2924
2934
|
throw new Error(result.message);
|
|
2925
2935
|
}
|
|
2926
2936
|
}
|
|
2927
|
-
async function checkMany(identifiers, scope = {}) {
|
|
2937
|
+
async function checkMany(identifiers, scope = {}, options) {
|
|
2938
|
+
const skip = options?.skipSuggestions ?? true;
|
|
2928
2939
|
const entries = await Promise.all(
|
|
2929
2940
|
identifiers.map(async (id) => {
|
|
2930
|
-
const result = await check(id, scope, { skipSuggestions:
|
|
2941
|
+
const result = await check(id, scope, { skipSuggestions: skip });
|
|
2931
2942
|
return [id, result];
|
|
2932
2943
|
})
|
|
2933
2944
|
);
|
|
@@ -2944,6 +2955,7 @@ function createNamespaceGuard(config, adapter) {
|
|
|
2944
2955
|
return {
|
|
2945
2956
|
normalize,
|
|
2946
2957
|
validateFormat,
|
|
2958
|
+
validateFormatOnly,
|
|
2947
2959
|
check,
|
|
2948
2960
|
assertAvailable,
|
|
2949
2961
|
checkMany,
|
package/package.json
CHANGED