namespace-guard 0.1.2 → 0.4.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 +186 -35
- package/dist/adapters/drizzle.d.mts +8 -2
- package/dist/adapters/drizzle.d.ts +8 -2
- package/dist/adapters/drizzle.js +11 -3
- package/dist/adapters/drizzle.mjs +11 -3
- package/dist/adapters/knex.d.mts +1 -0
- package/dist/adapters/knex.d.ts +1 -0
- package/dist/adapters/knex.js +8 -2
- package/dist/adapters/knex.mjs +8 -2
- package/dist/adapters/kysely.js +8 -2
- package/dist/adapters/kysely.mjs +8 -2
- package/dist/adapters/mikro-orm.d.mts +34 -0
- package/dist/adapters/mikro-orm.d.ts +34 -0
- package/dist/adapters/mikro-orm.js +47 -0
- package/dist/adapters/mikro-orm.mjs +22 -0
- package/dist/adapters/mongoose.d.mts +39 -0
- package/dist/adapters/mongoose.d.ts +39 -0
- package/dist/adapters/mongoose.js +52 -0
- package/dist/adapters/mongoose.mjs +27 -0
- package/dist/adapters/prisma.js +3 -2
- package/dist/adapters/prisma.mjs +3 -2
- package/dist/adapters/raw.d.mts +2 -2
- package/dist/adapters/raw.d.ts +2 -2
- package/dist/adapters/raw.js +3 -2
- package/dist/adapters/raw.mjs +3 -2
- package/dist/adapters/sequelize.d.mts +49 -0
- package/dist/adapters/sequelize.d.ts +49 -0
- package/dist/adapters/sequelize.js +54 -0
- package/dist/adapters/sequelize.mjs +29 -0
- package/dist/adapters/typeorm.d.mts +43 -0
- package/dist/adapters/typeorm.d.ts +43 -0
- package/dist/adapters/typeorm.js +56 -0
- package/dist/adapters/typeorm.mjs +31 -0
- package/dist/cli.js +117 -22
- package/dist/cli.mjs +117 -22
- package/dist/index.d.mts +102 -7
- package/dist/index.d.ts +102 -7
- package/dist/index.js +99 -8
- package/dist/index.mjs +98 -8
- package/package.json +43 -2
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# namespace-guard
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/namespace-guard)
|
|
4
|
+
[](https://bundlephobia.com/package/namespace-guard)
|
|
4
5
|
[](https://www.typescriptlang.org/)
|
|
5
6
|
[](https://opensource.org/licenses/MIT)
|
|
6
7
|
|
|
@@ -74,7 +75,7 @@ if (result.available) {
|
|
|
74
75
|
| Conflict suggestions | Auto-suggest alternatives | Not built |
|
|
75
76
|
| Async validators | Custom hooks (profanity, etc.) | Manual wiring |
|
|
76
77
|
| Batch checking | `checkMany()` | Loop it yourself |
|
|
77
|
-
| ORM agnostic | Prisma, Drizzle, Kysely, Knex, raw SQL | Tied to your ORM |
|
|
78
|
+
| ORM agnostic | Prisma, Drizzle, Kysely, Knex, TypeORM, MikroORM, Sequelize, Mongoose, raw SQL | Tied to your ORM |
|
|
78
79
|
| CLI | `npx namespace-guard check` | None |
|
|
79
80
|
|
|
80
81
|
## Adapters
|
|
@@ -120,6 +121,47 @@ const knex = Knex({ client: "pg", connection: process.env.DATABASE_URL });
|
|
|
120
121
|
const adapter = createKnexAdapter(knex);
|
|
121
122
|
```
|
|
122
123
|
|
|
124
|
+
### TypeORM
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { DataSource } from "typeorm";
|
|
128
|
+
import { createTypeORMAdapter } from "namespace-guard/adapters/typeorm";
|
|
129
|
+
import { User, Organization } from "./entities";
|
|
130
|
+
|
|
131
|
+
const dataSource = new DataSource({ /* ... */ });
|
|
132
|
+
const adapter = createTypeORMAdapter(dataSource, { user: User, organization: Organization });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### MikroORM
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { MikroORM } from "@mikro-orm/core";
|
|
139
|
+
import { createMikroORMAdapter } from "namespace-guard/adapters/mikro-orm";
|
|
140
|
+
import { User, Organization } from "./entities";
|
|
141
|
+
|
|
142
|
+
const orm = await MikroORM.init(config);
|
|
143
|
+
const adapter = createMikroORMAdapter(orm.em, { user: User, organization: Organization });
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Sequelize
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import { createSequelizeAdapter } from "namespace-guard/adapters/sequelize";
|
|
150
|
+
import { User, Organization } from "./models";
|
|
151
|
+
|
|
152
|
+
const adapter = createSequelizeAdapter({ user: User, organization: Organization });
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Mongoose
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { createMongooseAdapter } from "namespace-guard/adapters/mongoose";
|
|
159
|
+
import { User, Organization } from "./models";
|
|
160
|
+
|
|
161
|
+
// Note: Mongoose sources typically use idColumn: "_id"
|
|
162
|
+
const adapter = createMongooseAdapter({ user: User, organization: Organization });
|
|
163
|
+
```
|
|
164
|
+
|
|
123
165
|
### Raw SQL (pg, mysql2, etc.)
|
|
124
166
|
|
|
125
167
|
```typescript
|
|
@@ -234,6 +276,26 @@ const guard = createNamespaceGuard({
|
|
|
234
276
|
|
|
235
277
|
Validators run sequentially and stop at the first rejection. They receive the normalized identifier.
|
|
236
278
|
|
|
279
|
+
### Built-in Profanity Validator
|
|
280
|
+
|
|
281
|
+
Use `createProfanityValidator` for a turnkey profanity filter — supply your own word list:
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
import { createNamespaceGuard, createProfanityValidator } from "namespace-guard";
|
|
285
|
+
|
|
286
|
+
const guard = createNamespaceGuard({
|
|
287
|
+
sources: [/* ... */],
|
|
288
|
+
validators: [
|
|
289
|
+
createProfanityValidator(["badword", "offensive", "slur"], {
|
|
290
|
+
message: "Please choose an appropriate name.", // optional custom message
|
|
291
|
+
checkSubstrings: true, // default: true
|
|
292
|
+
}),
|
|
293
|
+
],
|
|
294
|
+
}, adapter);
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
No words are bundled — use any word list you like (e.g., the `bad-words` npm package, your own list, or an external API wrapped in a custom validator).
|
|
298
|
+
|
|
237
299
|
## Conflict Suggestions
|
|
238
300
|
|
|
239
301
|
When a slug is taken, automatically suggest available alternatives:
|
|
@@ -430,52 +492,137 @@ normalize(" @Sarah "); // "sarah"
|
|
|
430
492
|
normalize("ACME-Corp"); // "acme-corp"
|
|
431
493
|
```
|
|
432
494
|
|
|
433
|
-
##
|
|
495
|
+
## Case-Insensitive Matching
|
|
434
496
|
|
|
435
|
-
|
|
497
|
+
By default, slug lookups are case-sensitive. Enable case-insensitive matching to catch collisions regardless of stored casing:
|
|
436
498
|
|
|
437
499
|
```typescript
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
500
|
+
const guard = createNamespaceGuard({
|
|
501
|
+
sources: [/* ... */],
|
|
502
|
+
caseInsensitive: true,
|
|
503
|
+
}, adapter);
|
|
504
|
+
```
|
|
442
505
|
|
|
443
|
-
|
|
444
|
-
|
|
506
|
+
Each adapter handles this differently:
|
|
507
|
+
- **Prisma**: Uses `mode: "insensitive"` on the where clause
|
|
508
|
+
- **Drizzle**: Uses `ilike` instead of `eq` (pass `ilike` to the adapter: `createDrizzleAdapter(db, tables, { eq, ilike })`)
|
|
509
|
+
- **Kysely**: Uses `ilike` operator
|
|
510
|
+
- **Knex**: Uses `LOWER()` in a raw where clause
|
|
511
|
+
- **TypeORM**: Uses `ILike` (pass it to the adapter: `createTypeORMAdapter(dataSource, entities, ILike)`)
|
|
512
|
+
- **MikroORM**: Uses `$ilike` operator
|
|
513
|
+
- **Sequelize**: Uses `LOWER()` via Sequelize helpers (pass `{ where: Sequelize.where, fn: Sequelize.fn, col: Sequelize.col }`)
|
|
514
|
+
- **Mongoose**: Uses collation `{ locale: "en", strength: 2 }`
|
|
515
|
+
- **Raw SQL**: Wraps both sides in `LOWER()`
|
|
445
516
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
517
|
+
## Caching
|
|
518
|
+
|
|
519
|
+
Enable in-memory caching to reduce database calls during rapid checks (e.g., live form validation, suggestion generation):
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
const guard = createNamespaceGuard({
|
|
523
|
+
sources: [/* ... */],
|
|
524
|
+
cache: {
|
|
525
|
+
ttl: 5000, // milliseconds (default: 5000)
|
|
526
|
+
},
|
|
527
|
+
}, adapter);
|
|
528
|
+
|
|
529
|
+
// Manually clear the cache after writes
|
|
530
|
+
guard.clearCache();
|
|
531
|
+
|
|
532
|
+
// Monitor cache performance
|
|
533
|
+
const stats = guard.cacheStats();
|
|
534
|
+
// { size: 12, hits: 48, misses: 12 }
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
## Framework Integration
|
|
538
|
+
|
|
539
|
+
### Next.js (Server Actions)
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
// lib/guard.ts
|
|
543
|
+
import { createNamespaceGuard } from "namespace-guard";
|
|
544
|
+
import { createPrismaAdapter } from "namespace-guard/adapters/prisma";
|
|
545
|
+
import { prisma } from "./db";
|
|
546
|
+
|
|
547
|
+
export const guard = createNamespaceGuard({
|
|
548
|
+
reserved: ["admin", "api", "settings"],
|
|
549
|
+
sources: [
|
|
550
|
+
{ name: "user", column: "handle", scopeKey: "id" },
|
|
551
|
+
{ name: "organization", column: "slug", scopeKey: "id" },
|
|
552
|
+
],
|
|
553
|
+
suggest: {},
|
|
554
|
+
}, createPrismaAdapter(prisma));
|
|
555
|
+
|
|
556
|
+
// app/signup/actions.ts
|
|
557
|
+
"use server";
|
|
558
|
+
|
|
559
|
+
import { guard } from "@/lib/guard";
|
|
560
|
+
|
|
561
|
+
export async function checkHandle(handle: string) {
|
|
562
|
+
return guard.check(handle);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
export async function createUser(handle: string, email: string) {
|
|
566
|
+
const result = await guard.check(handle);
|
|
567
|
+
if (!result.available) return { error: result.message };
|
|
449
568
|
|
|
450
|
-
// Safe to create
|
|
451
569
|
const user = await prisma.user.create({
|
|
452
|
-
data: { handle:
|
|
570
|
+
data: { handle: guard.normalize(handle), email },
|
|
453
571
|
});
|
|
454
|
-
|
|
455
572
|
return { user };
|
|
456
573
|
}
|
|
457
574
|
```
|
|
458
575
|
|
|
459
|
-
|
|
576
|
+
### Express Middleware
|
|
460
577
|
|
|
461
578
|
```typescript
|
|
462
|
-
|
|
463
|
-
|
|
579
|
+
import express from "express";
|
|
580
|
+
import { guard } from "./lib/guard";
|
|
464
581
|
|
|
465
|
-
|
|
466
|
-
const result = await guard.check(normalized, { id: userId });
|
|
582
|
+
const app = express();
|
|
467
583
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
584
|
+
// Reusable middleware
|
|
585
|
+
function validateSlug(req, res, next) {
|
|
586
|
+
const slug = req.body.handle || req.body.slug;
|
|
587
|
+
if (!slug) return res.status(400).json({ error: "Slug is required" });
|
|
471
588
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
589
|
+
guard.check(slug, { id: req.user?.id }).then((result) => {
|
|
590
|
+
if (!result.available) return res.status(409).json(result);
|
|
591
|
+
req.normalizedSlug = guard.normalize(slug);
|
|
592
|
+
next();
|
|
475
593
|
});
|
|
476
|
-
|
|
477
|
-
return { success: true };
|
|
478
594
|
}
|
|
595
|
+
|
|
596
|
+
app.post("/api/users", validateSlug, async (req, res) => {
|
|
597
|
+
const user = await db.user.create({ handle: req.normalizedSlug, ... });
|
|
598
|
+
res.json({ user });
|
|
599
|
+
});
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
### tRPC
|
|
603
|
+
|
|
604
|
+
```typescript
|
|
605
|
+
import { z } from "zod";
|
|
606
|
+
import { router, protectedProcedure } from "./trpc";
|
|
607
|
+
import { guard } from "./lib/guard";
|
|
608
|
+
|
|
609
|
+
export const namespaceRouter = router({
|
|
610
|
+
check: protectedProcedure
|
|
611
|
+
.input(z.object({ slug: z.string() }))
|
|
612
|
+
.query(async ({ input, ctx }) => {
|
|
613
|
+
return guard.check(input.slug, { id: ctx.user.id });
|
|
614
|
+
}),
|
|
615
|
+
|
|
616
|
+
claim: protectedProcedure
|
|
617
|
+
.input(z.object({ slug: z.string() }))
|
|
618
|
+
.mutation(async ({ input, ctx }) => {
|
|
619
|
+
await guard.assertAvailable(input.slug, { id: ctx.user.id });
|
|
620
|
+
return ctx.db.user.update({
|
|
621
|
+
where: { id: ctx.user.id },
|
|
622
|
+
data: { handle: guard.normalize(input.slug) },
|
|
623
|
+
});
|
|
624
|
+
}),
|
|
625
|
+
});
|
|
479
626
|
```
|
|
480
627
|
|
|
481
628
|
## TypeScript
|
|
@@ -483,13 +630,17 @@ async function updateUserHandle(userId: string, newHandle: string) {
|
|
|
483
630
|
Full TypeScript support with exported types:
|
|
484
631
|
|
|
485
632
|
```typescript
|
|
486
|
-
import
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
633
|
+
import {
|
|
634
|
+
createNamespaceGuard,
|
|
635
|
+
createProfanityValidator,
|
|
636
|
+
normalize,
|
|
637
|
+
type NamespaceConfig,
|
|
638
|
+
type NamespaceSource,
|
|
639
|
+
type NamespaceAdapter,
|
|
640
|
+
type NamespaceGuard,
|
|
641
|
+
type CheckResult,
|
|
642
|
+
type FindOneOptions,
|
|
643
|
+
type OwnershipScope,
|
|
493
644
|
} from "namespace-guard";
|
|
494
645
|
```
|
|
495
646
|
|
|
@@ -13,7 +13,13 @@ type DrizzleDb = {
|
|
|
13
13
|
};
|
|
14
14
|
};
|
|
15
15
|
};
|
|
16
|
-
type
|
|
16
|
+
type ComparisonFn = (column: unknown, value: unknown) => unknown;
|
|
17
|
+
type DrizzleAdapterOptions = {
|
|
18
|
+
/** The `eq` function from drizzle-orm */
|
|
19
|
+
eq: ComparisonFn;
|
|
20
|
+
/** The `ilike` function from drizzle-orm (required when using caseInsensitive) */
|
|
21
|
+
ilike?: ComparisonFn;
|
|
22
|
+
};
|
|
17
23
|
/**
|
|
18
24
|
* Create a namespace adapter for Drizzle ORM
|
|
19
25
|
*
|
|
@@ -37,6 +43,6 @@ type EqFn = (column: unknown, value: unknown) => unknown;
|
|
|
37
43
|
* );
|
|
38
44
|
* ```
|
|
39
45
|
*/
|
|
40
|
-
declare function createDrizzleAdapter(db: DrizzleDb, tables: Record<string, DrizzleTable>,
|
|
46
|
+
declare function createDrizzleAdapter(db: DrizzleDb, tables: Record<string, DrizzleTable>, eqOrOptions: ComparisonFn | DrizzleAdapterOptions): NamespaceAdapter;
|
|
41
47
|
|
|
42
48
|
export { createDrizzleAdapter };
|
|
@@ -13,7 +13,13 @@ type DrizzleDb = {
|
|
|
13
13
|
};
|
|
14
14
|
};
|
|
15
15
|
};
|
|
16
|
-
type
|
|
16
|
+
type ComparisonFn = (column: unknown, value: unknown) => unknown;
|
|
17
|
+
type DrizzleAdapterOptions = {
|
|
18
|
+
/** The `eq` function from drizzle-orm */
|
|
19
|
+
eq: ComparisonFn;
|
|
20
|
+
/** The `ilike` function from drizzle-orm (required when using caseInsensitive) */
|
|
21
|
+
ilike?: ComparisonFn;
|
|
22
|
+
};
|
|
17
23
|
/**
|
|
18
24
|
* Create a namespace adapter for Drizzle ORM
|
|
19
25
|
*
|
|
@@ -37,6 +43,6 @@ type EqFn = (column: unknown, value: unknown) => unknown;
|
|
|
37
43
|
* );
|
|
38
44
|
* ```
|
|
39
45
|
*/
|
|
40
|
-
declare function createDrizzleAdapter(db: DrizzleDb, tables: Record<string, DrizzleTable>,
|
|
46
|
+
declare function createDrizzleAdapter(db: DrizzleDb, tables: Record<string, DrizzleTable>, eqOrOptions: ComparisonFn | DrizzleAdapterOptions): NamespaceAdapter;
|
|
41
47
|
|
|
42
48
|
export { createDrizzleAdapter };
|
package/dist/adapters/drizzle.js
CHANGED
|
@@ -23,9 +23,10 @@ __export(drizzle_exports, {
|
|
|
23
23
|
createDrizzleAdapter: () => createDrizzleAdapter
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(drizzle_exports);
|
|
26
|
-
function createDrizzleAdapter(db, tables,
|
|
26
|
+
function createDrizzleAdapter(db, tables, eqOrOptions) {
|
|
27
|
+
const ops = typeof eqOrOptions === "function" ? { eq: eqOrOptions } : eqOrOptions;
|
|
27
28
|
return {
|
|
28
|
-
async findOne(source, value) {
|
|
29
|
+
async findOne(source, value, findOptions) {
|
|
29
30
|
const queryHandler = db.query[source.name];
|
|
30
31
|
if (!queryHandler) {
|
|
31
32
|
throw new Error(`Drizzle query handler for "${source.name}" not found. Make sure relational queries are set up.`);
|
|
@@ -39,8 +40,15 @@ function createDrizzleAdapter(db, tables, eq) {
|
|
|
39
40
|
throw new Error(`Column "${source.column}" not found in table "${source.name}"`);
|
|
40
41
|
}
|
|
41
42
|
const idColumn = source.idColumn ?? "id";
|
|
43
|
+
let compareFn = ops.eq;
|
|
44
|
+
if (findOptions?.caseInsensitive) {
|
|
45
|
+
if (!ops.ilike) {
|
|
46
|
+
throw new Error("caseInsensitive requires passing ilike to createDrizzleAdapter");
|
|
47
|
+
}
|
|
48
|
+
compareFn = ops.ilike;
|
|
49
|
+
}
|
|
42
50
|
return queryHandler.findFirst({
|
|
43
|
-
where:
|
|
51
|
+
where: compareFn(column, value),
|
|
44
52
|
columns: {
|
|
45
53
|
[idColumn]: true,
|
|
46
54
|
...source.scopeKey && source.scopeKey !== idColumn ? { [source.scopeKey]: true } : {}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// src/adapters/drizzle.ts
|
|
2
|
-
function createDrizzleAdapter(db, tables,
|
|
2
|
+
function createDrizzleAdapter(db, tables, eqOrOptions) {
|
|
3
|
+
const ops = typeof eqOrOptions === "function" ? { eq: eqOrOptions } : eqOrOptions;
|
|
3
4
|
return {
|
|
4
|
-
async findOne(source, value) {
|
|
5
|
+
async findOne(source, value, findOptions) {
|
|
5
6
|
const queryHandler = db.query[source.name];
|
|
6
7
|
if (!queryHandler) {
|
|
7
8
|
throw new Error(`Drizzle query handler for "${source.name}" not found. Make sure relational queries are set up.`);
|
|
@@ -15,8 +16,15 @@ function createDrizzleAdapter(db, tables, eq) {
|
|
|
15
16
|
throw new Error(`Column "${source.column}" not found in table "${source.name}"`);
|
|
16
17
|
}
|
|
17
18
|
const idColumn = source.idColumn ?? "id";
|
|
19
|
+
let compareFn = ops.eq;
|
|
20
|
+
if (findOptions?.caseInsensitive) {
|
|
21
|
+
if (!ops.ilike) {
|
|
22
|
+
throw new Error("caseInsensitive requires passing ilike to createDrizzleAdapter");
|
|
23
|
+
}
|
|
24
|
+
compareFn = ops.ilike;
|
|
25
|
+
}
|
|
18
26
|
return queryHandler.findFirst({
|
|
19
|
-
where:
|
|
27
|
+
where: compareFn(column, value),
|
|
20
28
|
columns: {
|
|
21
29
|
[idColumn]: true,
|
|
22
30
|
...source.scopeKey && source.scopeKey !== idColumn ? { [source.scopeKey]: true } : {}
|
package/dist/adapters/knex.d.mts
CHANGED
|
@@ -3,6 +3,7 @@ import { NamespaceAdapter } from '../index.mjs';
|
|
|
3
3
|
type KnexQueryBuilder = {
|
|
4
4
|
select: (columns: string[]) => KnexQueryBuilder;
|
|
5
5
|
where: (column: string, value: unknown) => KnexQueryBuilder;
|
|
6
|
+
whereRaw: (raw: string, bindings: unknown[]) => KnexQueryBuilder;
|
|
6
7
|
first: () => Promise<Record<string, unknown> | undefined>;
|
|
7
8
|
};
|
|
8
9
|
type KnexInstance = {
|
package/dist/adapters/knex.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { NamespaceAdapter } from '../index.js';
|
|
|
3
3
|
type KnexQueryBuilder = {
|
|
4
4
|
select: (columns: string[]) => KnexQueryBuilder;
|
|
5
5
|
where: (column: string, value: unknown) => KnexQueryBuilder;
|
|
6
|
+
whereRaw: (raw: string, bindings: unknown[]) => KnexQueryBuilder;
|
|
6
7
|
first: () => Promise<Record<string, unknown> | undefined>;
|
|
7
8
|
};
|
|
8
9
|
type KnexInstance = {
|
package/dist/adapters/knex.js
CHANGED
|
@@ -25,10 +25,16 @@ __export(knex_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(knex_exports);
|
|
26
26
|
function createKnexAdapter(knex) {
|
|
27
27
|
return {
|
|
28
|
-
async findOne(source, value) {
|
|
28
|
+
async findOne(source, value, options) {
|
|
29
29
|
const idColumn = source.idColumn ?? "id";
|
|
30
30
|
const columns = source.scopeKey && source.scopeKey !== idColumn ? [idColumn, source.scopeKey] : [idColumn];
|
|
31
|
-
|
|
31
|
+
let query = knex(source.name).select(columns);
|
|
32
|
+
if (options?.caseInsensitive) {
|
|
33
|
+
query = query.whereRaw(`LOWER(??) = LOWER(?)`, [source.column, value]);
|
|
34
|
+
} else {
|
|
35
|
+
query = query.where(source.column, value);
|
|
36
|
+
}
|
|
37
|
+
const row = await query.first();
|
|
32
38
|
return row ?? null;
|
|
33
39
|
}
|
|
34
40
|
};
|
package/dist/adapters/knex.mjs
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
// src/adapters/knex.ts
|
|
2
2
|
function createKnexAdapter(knex) {
|
|
3
3
|
return {
|
|
4
|
-
async findOne(source, value) {
|
|
4
|
+
async findOne(source, value, options) {
|
|
5
5
|
const idColumn = source.idColumn ?? "id";
|
|
6
6
|
const columns = source.scopeKey && source.scopeKey !== idColumn ? [idColumn, source.scopeKey] : [idColumn];
|
|
7
|
-
|
|
7
|
+
let query = knex(source.name).select(columns);
|
|
8
|
+
if (options?.caseInsensitive) {
|
|
9
|
+
query = query.whereRaw(`LOWER(??) = LOWER(?)`, [source.column, value]);
|
|
10
|
+
} else {
|
|
11
|
+
query = query.where(source.column, value);
|
|
12
|
+
}
|
|
13
|
+
const row = await query.first();
|
|
8
14
|
return row ?? null;
|
|
9
15
|
}
|
|
10
16
|
};
|
package/dist/adapters/kysely.js
CHANGED
|
@@ -25,10 +25,16 @@ __export(kysely_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(kysely_exports);
|
|
26
26
|
function createKyselyAdapter(db) {
|
|
27
27
|
return {
|
|
28
|
-
async findOne(source, value) {
|
|
28
|
+
async findOne(source, value, options) {
|
|
29
29
|
const idColumn = source.idColumn ?? "id";
|
|
30
30
|
const columns = source.scopeKey && source.scopeKey !== idColumn ? [idColumn, source.scopeKey] : [idColumn];
|
|
31
|
-
|
|
31
|
+
let query = db.selectFrom(source.name).select(columns);
|
|
32
|
+
if (options?.caseInsensitive) {
|
|
33
|
+
query = query.where(source.column, "ilike", value);
|
|
34
|
+
} else {
|
|
35
|
+
query = query.where(source.column, "=", value);
|
|
36
|
+
}
|
|
37
|
+
const row = await query.limit(1).executeTakeFirst();
|
|
32
38
|
return row ?? null;
|
|
33
39
|
}
|
|
34
40
|
};
|
package/dist/adapters/kysely.mjs
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
// src/adapters/kysely.ts
|
|
2
2
|
function createKyselyAdapter(db) {
|
|
3
3
|
return {
|
|
4
|
-
async findOne(source, value) {
|
|
4
|
+
async findOne(source, value, options) {
|
|
5
5
|
const idColumn = source.idColumn ?? "id";
|
|
6
6
|
const columns = source.scopeKey && source.scopeKey !== idColumn ? [idColumn, source.scopeKey] : [idColumn];
|
|
7
|
-
|
|
7
|
+
let query = db.selectFrom(source.name).select(columns);
|
|
8
|
+
if (options?.caseInsensitive) {
|
|
9
|
+
query = query.where(source.column, "ilike", value);
|
|
10
|
+
} else {
|
|
11
|
+
query = query.where(source.column, "=", value);
|
|
12
|
+
}
|
|
13
|
+
const row = await query.limit(1).executeTakeFirst();
|
|
8
14
|
return row ?? null;
|
|
9
15
|
}
|
|
10
16
|
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { NamespaceAdapter } from '../index.mjs';
|
|
2
|
+
|
|
3
|
+
type MikroORMEntityManager = {
|
|
4
|
+
findOne: (entityName: unknown, where: Record<string, unknown>, options?: {
|
|
5
|
+
fields?: string[];
|
|
6
|
+
}) => Promise<Record<string, unknown> | null>;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Create a namespace adapter for MikroORM
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { MikroORM } from "@mikro-orm/core";
|
|
14
|
+
* import { createNamespaceGuard } from "namespace-guard";
|
|
15
|
+
* import { createMikroORMAdapter } from "namespace-guard/adapters/mikro-orm";
|
|
16
|
+
* import { User, Organization } from "./entities";
|
|
17
|
+
*
|
|
18
|
+
* const orm = await MikroORM.init(config);
|
|
19
|
+
*
|
|
20
|
+
* const guard = createNamespaceGuard(
|
|
21
|
+
* {
|
|
22
|
+
* reserved: ["admin", "api", "settings"],
|
|
23
|
+
* sources: [
|
|
24
|
+
* { name: "user", column: "handle", scopeKey: "id" },
|
|
25
|
+
* { name: "organization", column: "slug", scopeKey: "id" },
|
|
26
|
+
* ],
|
|
27
|
+
* },
|
|
28
|
+
* createMikroORMAdapter(orm.em, { user: User, organization: Organization })
|
|
29
|
+
* );
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
declare function createMikroORMAdapter(em: MikroORMEntityManager, entities: Record<string, unknown>): NamespaceAdapter;
|
|
33
|
+
|
|
34
|
+
export { createMikroORMAdapter };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { NamespaceAdapter } from '../index.js';
|
|
2
|
+
|
|
3
|
+
type MikroORMEntityManager = {
|
|
4
|
+
findOne: (entityName: unknown, where: Record<string, unknown>, options?: {
|
|
5
|
+
fields?: string[];
|
|
6
|
+
}) => Promise<Record<string, unknown> | null>;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Create a namespace adapter for MikroORM
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { MikroORM } from "@mikro-orm/core";
|
|
14
|
+
* import { createNamespaceGuard } from "namespace-guard";
|
|
15
|
+
* import { createMikroORMAdapter } from "namespace-guard/adapters/mikro-orm";
|
|
16
|
+
* import { User, Organization } from "./entities";
|
|
17
|
+
*
|
|
18
|
+
* const orm = await MikroORM.init(config);
|
|
19
|
+
*
|
|
20
|
+
* const guard = createNamespaceGuard(
|
|
21
|
+
* {
|
|
22
|
+
* reserved: ["admin", "api", "settings"],
|
|
23
|
+
* sources: [
|
|
24
|
+
* { name: "user", column: "handle", scopeKey: "id" },
|
|
25
|
+
* { name: "organization", column: "slug", scopeKey: "id" },
|
|
26
|
+
* ],
|
|
27
|
+
* },
|
|
28
|
+
* createMikroORMAdapter(orm.em, { user: User, organization: Organization })
|
|
29
|
+
* );
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
declare function createMikroORMAdapter(em: MikroORMEntityManager, entities: Record<string, unknown>): NamespaceAdapter;
|
|
33
|
+
|
|
34
|
+
export { createMikroORMAdapter };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/adapters/mikro-orm.ts
|
|
21
|
+
var mikro_orm_exports = {};
|
|
22
|
+
__export(mikro_orm_exports, {
|
|
23
|
+
createMikroORMAdapter: () => createMikroORMAdapter
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(mikro_orm_exports);
|
|
26
|
+
function createMikroORMAdapter(em, entities) {
|
|
27
|
+
return {
|
|
28
|
+
async findOne(source, value, options) {
|
|
29
|
+
const entity = entities[source.name];
|
|
30
|
+
if (!entity) {
|
|
31
|
+
throw new Error(`MikroORM entity "${source.name}" not found in provided entities object`);
|
|
32
|
+
}
|
|
33
|
+
const idColumn = source.idColumn ?? "id";
|
|
34
|
+
const fields = source.scopeKey && source.scopeKey !== idColumn ? [idColumn, source.scopeKey] : [idColumn];
|
|
35
|
+
const whereValue = options?.caseInsensitive ? { $ilike: value } : value;
|
|
36
|
+
return em.findOne(
|
|
37
|
+
entity,
|
|
38
|
+
{ [source.column]: whereValue },
|
|
39
|
+
{ fields }
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
45
|
+
0 && (module.exports = {
|
|
46
|
+
createMikroORMAdapter
|
|
47
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// src/adapters/mikro-orm.ts
|
|
2
|
+
function createMikroORMAdapter(em, entities) {
|
|
3
|
+
return {
|
|
4
|
+
async findOne(source, value, options) {
|
|
5
|
+
const entity = entities[source.name];
|
|
6
|
+
if (!entity) {
|
|
7
|
+
throw new Error(`MikroORM entity "${source.name}" not found in provided entities object`);
|
|
8
|
+
}
|
|
9
|
+
const idColumn = source.idColumn ?? "id";
|
|
10
|
+
const fields = source.scopeKey && source.scopeKey !== idColumn ? [idColumn, source.scopeKey] : [idColumn];
|
|
11
|
+
const whereValue = options?.caseInsensitive ? { $ilike: value } : value;
|
|
12
|
+
return em.findOne(
|
|
13
|
+
entity,
|
|
14
|
+
{ [source.column]: whereValue },
|
|
15
|
+
{ fields }
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
createMikroORMAdapter
|
|
22
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { NamespaceAdapter } from '../index.mjs';
|
|
2
|
+
|
|
3
|
+
type MongooseModel = {
|
|
4
|
+
findOne: (conditions: Record<string, unknown>, projection?: Record<string, number> | string) => {
|
|
5
|
+
lean: () => Promise<Record<string, unknown> | null>;
|
|
6
|
+
collation: (options: Record<string, unknown>) => {
|
|
7
|
+
lean: () => Promise<Record<string, unknown> | null>;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Create a namespace adapter for Mongoose
|
|
13
|
+
*
|
|
14
|
+
* Note: For Mongoose sources, `idColumn` defaults to "_id" instead of "id".
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import mongoose from "mongoose";
|
|
19
|
+
* import { createNamespaceGuard } from "namespace-guard";
|
|
20
|
+
* import { createMongooseAdapter } from "namespace-guard/adapters/mongoose";
|
|
21
|
+
*
|
|
22
|
+
* const User = mongoose.model("User", userSchema);
|
|
23
|
+
* const Organization = mongoose.model("Organization", orgSchema);
|
|
24
|
+
*
|
|
25
|
+
* const guard = createNamespaceGuard(
|
|
26
|
+
* {
|
|
27
|
+
* reserved: ["admin", "api", "settings"],
|
|
28
|
+
* sources: [
|
|
29
|
+
* { name: "user", column: "handle", idColumn: "_id", scopeKey: "_id" },
|
|
30
|
+
* { name: "organization", column: "slug", idColumn: "_id", scopeKey: "_id" },
|
|
31
|
+
* ],
|
|
32
|
+
* },
|
|
33
|
+
* createMongooseAdapter({ user: User, organization: Organization })
|
|
34
|
+
* );
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
declare function createMongooseAdapter(models: Record<string, MongooseModel>): NamespaceAdapter;
|
|
38
|
+
|
|
39
|
+
export { createMongooseAdapter };
|