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.
Files changed (40) hide show
  1. package/README.md +186 -35
  2. package/dist/adapters/drizzle.d.mts +8 -2
  3. package/dist/adapters/drizzle.d.ts +8 -2
  4. package/dist/adapters/drizzle.js +11 -3
  5. package/dist/adapters/drizzle.mjs +11 -3
  6. package/dist/adapters/knex.d.mts +1 -0
  7. package/dist/adapters/knex.d.ts +1 -0
  8. package/dist/adapters/knex.js +8 -2
  9. package/dist/adapters/knex.mjs +8 -2
  10. package/dist/adapters/kysely.js +8 -2
  11. package/dist/adapters/kysely.mjs +8 -2
  12. package/dist/adapters/mikro-orm.d.mts +34 -0
  13. package/dist/adapters/mikro-orm.d.ts +34 -0
  14. package/dist/adapters/mikro-orm.js +47 -0
  15. package/dist/adapters/mikro-orm.mjs +22 -0
  16. package/dist/adapters/mongoose.d.mts +39 -0
  17. package/dist/adapters/mongoose.d.ts +39 -0
  18. package/dist/adapters/mongoose.js +52 -0
  19. package/dist/adapters/mongoose.mjs +27 -0
  20. package/dist/adapters/prisma.js +3 -2
  21. package/dist/adapters/prisma.mjs +3 -2
  22. package/dist/adapters/raw.d.mts +2 -2
  23. package/dist/adapters/raw.d.ts +2 -2
  24. package/dist/adapters/raw.js +3 -2
  25. package/dist/adapters/raw.mjs +3 -2
  26. package/dist/adapters/sequelize.d.mts +49 -0
  27. package/dist/adapters/sequelize.d.ts +49 -0
  28. package/dist/adapters/sequelize.js +54 -0
  29. package/dist/adapters/sequelize.mjs +29 -0
  30. package/dist/adapters/typeorm.d.mts +43 -0
  31. package/dist/adapters/typeorm.d.ts +43 -0
  32. package/dist/adapters/typeorm.js +56 -0
  33. package/dist/adapters/typeorm.mjs +31 -0
  34. package/dist/cli.js +117 -22
  35. package/dist/cli.mjs +117 -22
  36. package/dist/index.d.mts +102 -7
  37. package/dist/index.d.ts +102 -7
  38. package/dist/index.js +99 -8
  39. package/dist/index.mjs +98 -8
  40. package/package.json +43 -2
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # namespace-guard
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/namespace-guard.svg)](https://www.npmjs.com/package/namespace-guard)
4
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/namespace-guard)](https://bundlephobia.com/package/namespace-guard)
4
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
5
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
- ## Real-World Example
495
+ ## Case-Insensitive Matching
434
496
 
435
- Here's how you might use this in a signup flow:
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
- // In your API route / server action
439
- async function createUser(handle: string, email: string) {
440
- // Normalize first
441
- const normalizedHandle = guard.normalize(handle);
500
+ const guard = createNamespaceGuard({
501
+ sources: [/* ... */],
502
+ caseInsensitive: true,
503
+ }, adapter);
504
+ ```
442
505
 
443
- // Check availability (will also validate format)
444
- const result = await guard.check(normalizedHandle);
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
- if (!result.available) {
447
- return { error: result.message };
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: normalizedHandle, email },
570
+ data: { handle: guard.normalize(handle), email },
453
571
  });
454
-
455
572
  return { user };
456
573
  }
457
574
  ```
458
575
 
459
- Or in an update flow with ownership scoping:
576
+ ### Express Middleware
460
577
 
461
578
  ```typescript
462
- async function updateUserHandle(userId: string, newHandle: string) {
463
- const normalized = guard.normalize(newHandle);
579
+ import express from "express";
580
+ import { guard } from "./lib/guard";
464
581
 
465
- // Pass userId to avoid collision with own current handle
466
- const result = await guard.check(normalized, { id: userId });
582
+ const app = express();
467
583
 
468
- if (!result.available) {
469
- return { error: result.message };
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
- await prisma.user.update({
473
- where: { id: userId },
474
- data: { handle: normalized },
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 type {
487
- NamespaceConfig,
488
- NamespaceSource,
489
- NamespaceAdapter,
490
- NamespaceGuard,
491
- CheckResult,
492
- OwnershipScope,
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 EqFn = (column: unknown, value: unknown) => unknown;
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>, eq: EqFn): NamespaceAdapter;
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 EqFn = (column: unknown, value: unknown) => unknown;
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>, eq: EqFn): NamespaceAdapter;
46
+ declare function createDrizzleAdapter(db: DrizzleDb, tables: Record<string, DrizzleTable>, eqOrOptions: ComparisonFn | DrizzleAdapterOptions): NamespaceAdapter;
41
47
 
42
48
  export { createDrizzleAdapter };
@@ -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, eq) {
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: eq(column, value),
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, eq) {
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: eq(column, value),
27
+ where: compareFn(column, value),
20
28
  columns: {
21
29
  [idColumn]: true,
22
30
  ...source.scopeKey && source.scopeKey !== idColumn ? { [source.scopeKey]: true } : {}
@@ -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 = {
@@ -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 = {
@@ -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
- const row = await knex(source.name).select(columns).where(source.column, value).first();
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
  };
@@ -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
- const row = await knex(source.name).select(columns).where(source.column, value).first();
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
  };
@@ -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
- const row = await db.selectFrom(source.name).select(columns).where(source.column, "=", value).limit(1).executeTakeFirst();
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
  };
@@ -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
- const row = await db.selectFrom(source.name).select(columns).where(source.column, "=", value).limit(1).executeTakeFirst();
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 };