rads-db 3.2.36 → 3.2.38

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/dist/config.cjs CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
+ const fg = require('fast-glob');
5
6
  const typescript = require('typescript');
6
7
  const _ = require('lodash');
7
8
 
@@ -9,6 +10,7 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau
9
10
 
10
11
  const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
11
12
  const path__default = /*#__PURE__*/_interopDefaultCompat(path);
13
+ const fg__default = /*#__PURE__*/_interopDefaultCompat(fg);
12
14
  const ___default = /*#__PURE__*/_interopDefaultCompat(_);
13
15
 
14
16
  const supportedPrimitiveTypes = ["string", "number", "boolean", "Record<string, string>", "Record<string, any>"];
@@ -598,21 +600,98 @@ function verifyRelationFields(result) {
598
600
  function defineRadsConfig(config) {
599
601
  return config;
600
602
  }
601
- function schemaFromFiles(entitiesDir) {
602
- return async () => {
603
- if (!fs__default.existsSync(entitiesDir))
604
- await fs__default.promises.mkdir(entitiesDir, { recursive: true });
605
- const response = await fs__default.promises.readdir(entitiesDir, { withFileTypes: true });
606
- const entities = {};
607
- for (const file of response) {
608
- if (!file.isFile())
603
+ async function expandToDirectories(patterns) {
604
+ const dirs = [];
605
+ for (const p of patterns) {
606
+ if (fg__default.isDynamicPattern(p)) {
607
+ const matches = await fg__default(p, { onlyDirectories: true, absolute: true });
608
+ dirs.push(...matches);
609
+ } else {
610
+ dirs.push(path__default.resolve(p));
611
+ }
612
+ }
613
+ return dirs;
614
+ }
615
+ async function resolveDirs(pathsArray) {
616
+ const resolvedDirs = [];
617
+ for (const p of pathsArray) {
618
+ if (fg__default.isDynamicPattern(p)) {
619
+ const matches = await fg__default(p, { onlyDirectories: true, absolute: true });
620
+ resolvedDirs.push(...matches);
621
+ } else {
622
+ const resolved = path__default.resolve(p);
623
+ if (!fs__default.existsSync(resolved))
624
+ await fs__default.promises.mkdir(resolved, { recursive: true });
625
+ resolvedDirs.push(resolved);
626
+ }
627
+ }
628
+ return resolvedDirs;
629
+ }
630
+ async function scanDirs(resolvedDirs) {
631
+ const entities = {};
632
+ const entityFilePaths = {};
633
+ const entityDirs = {};
634
+ for (const dir of resolvedDirs) {
635
+ const entries = await fs__default.promises.readdir(dir, { withFileTypes: true });
636
+ for (const entry of entries) {
637
+ if (!entry.isFile() || !entry.name.endsWith(".ts"))
609
638
  continue;
610
- if (!file.name.endsWith(".ts"))
639
+ const filePath = path__default.join(dir, entry.name);
640
+ const content = await fs__default.promises.readFile(filePath, "utf-8");
641
+ const stem = entry.name.slice(0, -3);
642
+ const hasEntity = content.includes("@entity");
643
+ if (hasEntity && entityFilePaths[stem]) {
644
+ throw new Error(
645
+ `Entity name collision: "${stem}" is defined in multiple locations:
646
+ ${entityFilePaths[stem]}
647
+ ${filePath}`
648
+ );
649
+ }
650
+ entities[stem] = content;
651
+ if (hasEntity) {
652
+ entityFilePaths[stem] = filePath;
653
+ entityDirs[stem] = dir;
654
+ }
655
+ }
656
+ }
657
+ return { entities, entityFilePaths, entityDirs };
658
+ }
659
+ async function validateEntityRules(entityFilePaths, entityDirs, entityRules) {
660
+ const resolvedRules = await Promise.all(
661
+ entityRules.map(async (rule) => {
662
+ const rulePaths = Array.isArray(rule.path) ? rule.path : [rule.path];
663
+ const dirs = await expandToDirectories(rulePaths);
664
+ return { rule, dirs };
665
+ })
666
+ );
667
+ for (const [stem, filePath] of Object.entries(entityFilePaths)) {
668
+ const entityDir = entityDirs[stem];
669
+ for (const { rule, dirs } of resolvedRules) {
670
+ const applies = dirs.some((d) => entityDir === d || entityDir.startsWith(d + path__default.sep));
671
+ if (!applies)
611
672
  continue;
612
- const text = await fs__default.promises.readFile(path__default.resolve(entitiesDir, file.name), "utf-8");
613
- entities[file.name.slice(0, -3)] = text;
673
+ if (rule.namePrefix && !stem.startsWith(rule.namePrefix)) {
674
+ throw new Error(`Entity "${stem}" at "${filePath}" must have name prefix "${rule.namePrefix}"`);
675
+ }
676
+ if (rule.validate) {
677
+ const error = rule.validate(stem, filePath);
678
+ if (error)
679
+ throw new Error(error);
680
+ }
681
+ }
682
+ }
683
+ }
684
+ function schemaFromFiles(paths, options) {
685
+ return async () => {
686
+ const pathsArray = Array.isArray(paths) ? paths : [paths];
687
+ const resolvedDirs = await resolveDirs(pathsArray);
688
+ const { entities, entityFilePaths, entityDirs } = await scanDirs(resolvedDirs);
689
+ if (options?.entityRules?.length) {
690
+ await validateEntityRules(entityFilePaths, entityDirs, options.entityRules);
614
691
  }
615
- return { schema: parseSchema(entities), entitiesDir };
692
+ const isSingleLiteralPath = pathsArray.length === 1 && !fg__default.isDynamicPattern(pathsArray[0]);
693
+ const entitiesDir = isSingleLiteralPath ? pathsArray[0] : void 0;
694
+ return { schema: parseSchema(entities), entitiesDir, entitiesDirs: resolvedDirs };
616
695
  };
617
696
  }
618
697
  function schemaFromRadsApi(radsApiUrl) {
package/dist/config.d.ts CHANGED
@@ -1,16 +1,30 @@
1
- import { g as RadsConfig, T as TypeDefinition } from './types-eb31ea63.js';
1
+ import { g as RadsConfig, T as TypeDefinition } from './types-f64a81e8.js';
2
2
  import 'mssql';
3
3
  import '_rads-db';
4
4
 
5
5
  declare function defineRadsConfig(config: RadsConfig): RadsConfig;
6
+ interface SchemaFromFilesEntityRule {
7
+ /** Directory path(s) or glob pattern(s) this rule applies to */
8
+ path: string | string[];
9
+ /** Entity class name must start with this prefix */
10
+ namePrefix?: string;
11
+ /** Custom validator — return an error message to reject the entity, or throw */
12
+ validate?: (entityName: string, filePath: string) => string | void;
13
+ }
14
+ interface SchemaFromFilesOptions {
15
+ entityRules?: SchemaFromFilesEntityRule[];
16
+ }
6
17
  /**
7
- * Loads all typescript files in given folder, parses them and outputs schema.
8
- * @param entitiesDir - path from project root to folder with typescript files, e.g. './entities'
9
- * @returns schema object
18
+ * Loads all typescript files with @entity() decorated classes from the given path(s),
19
+ * parses them and outputs a merged schema.
20
+ * @param paths - path(s) from project root to folder(s) with entity files; may include glob patterns
21
+ * @param options - optional validation rules per path
22
+ * @returns schema loader function
10
23
  */
11
- declare function schemaFromFiles(entitiesDir: string): () => Promise<{
24
+ declare function schemaFromFiles(paths: string | string[], options?: SchemaFromFilesOptions): () => Promise<{
12
25
  schema: Record<string, TypeDefinition>;
13
- entitiesDir: string;
26
+ entitiesDir: string | undefined;
27
+ entitiesDirs: string[];
14
28
  }>;
15
29
  /**
16
30
  * Loads schema from the rads api.
@@ -21,4 +35,4 @@ declare function schemaFromRadsApi(radsApiUrl: string): () => Promise<{
21
35
  schema: any;
22
36
  }>;
23
37
 
24
- export { defineRadsConfig, schemaFromFiles, schemaFromRadsApi };
38
+ export { SchemaFromFilesEntityRule, SchemaFromFilesOptions, defineRadsConfig, schemaFromFiles, schemaFromRadsApi };
package/dist/config.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import fg from 'fast-glob';
3
4
  import { SyntaxKind, createSourceFile, ScriptTarget } from 'typescript';
4
5
  import _ from 'lodash';
5
6
 
@@ -590,21 +591,98 @@ function verifyRelationFields(result) {
590
591
  function defineRadsConfig(config) {
591
592
  return config;
592
593
  }
593
- function schemaFromFiles(entitiesDir) {
594
- return async () => {
595
- if (!fs.existsSync(entitiesDir))
596
- await fs.promises.mkdir(entitiesDir, { recursive: true });
597
- const response = await fs.promises.readdir(entitiesDir, { withFileTypes: true });
598
- const entities = {};
599
- for (const file of response) {
600
- if (!file.isFile())
594
+ async function expandToDirectories(patterns) {
595
+ const dirs = [];
596
+ for (const p of patterns) {
597
+ if (fg.isDynamicPattern(p)) {
598
+ const matches = await fg(p, { onlyDirectories: true, absolute: true });
599
+ dirs.push(...matches);
600
+ } else {
601
+ dirs.push(path.resolve(p));
602
+ }
603
+ }
604
+ return dirs;
605
+ }
606
+ async function resolveDirs(pathsArray) {
607
+ const resolvedDirs = [];
608
+ for (const p of pathsArray) {
609
+ if (fg.isDynamicPattern(p)) {
610
+ const matches = await fg(p, { onlyDirectories: true, absolute: true });
611
+ resolvedDirs.push(...matches);
612
+ } else {
613
+ const resolved = path.resolve(p);
614
+ if (!fs.existsSync(resolved))
615
+ await fs.promises.mkdir(resolved, { recursive: true });
616
+ resolvedDirs.push(resolved);
617
+ }
618
+ }
619
+ return resolvedDirs;
620
+ }
621
+ async function scanDirs(resolvedDirs) {
622
+ const entities = {};
623
+ const entityFilePaths = {};
624
+ const entityDirs = {};
625
+ for (const dir of resolvedDirs) {
626
+ const entries = await fs.promises.readdir(dir, { withFileTypes: true });
627
+ for (const entry of entries) {
628
+ if (!entry.isFile() || !entry.name.endsWith(".ts"))
601
629
  continue;
602
- if (!file.name.endsWith(".ts"))
630
+ const filePath = path.join(dir, entry.name);
631
+ const content = await fs.promises.readFile(filePath, "utf-8");
632
+ const stem = entry.name.slice(0, -3);
633
+ const hasEntity = content.includes("@entity");
634
+ if (hasEntity && entityFilePaths[stem]) {
635
+ throw new Error(
636
+ `Entity name collision: "${stem}" is defined in multiple locations:
637
+ ${entityFilePaths[stem]}
638
+ ${filePath}`
639
+ );
640
+ }
641
+ entities[stem] = content;
642
+ if (hasEntity) {
643
+ entityFilePaths[stem] = filePath;
644
+ entityDirs[stem] = dir;
645
+ }
646
+ }
647
+ }
648
+ return { entities, entityFilePaths, entityDirs };
649
+ }
650
+ async function validateEntityRules(entityFilePaths, entityDirs, entityRules) {
651
+ const resolvedRules = await Promise.all(
652
+ entityRules.map(async (rule) => {
653
+ const rulePaths = Array.isArray(rule.path) ? rule.path : [rule.path];
654
+ const dirs = await expandToDirectories(rulePaths);
655
+ return { rule, dirs };
656
+ })
657
+ );
658
+ for (const [stem, filePath] of Object.entries(entityFilePaths)) {
659
+ const entityDir = entityDirs[stem];
660
+ for (const { rule, dirs } of resolvedRules) {
661
+ const applies = dirs.some((d) => entityDir === d || entityDir.startsWith(d + path.sep));
662
+ if (!applies)
603
663
  continue;
604
- const text = await fs.promises.readFile(path.resolve(entitiesDir, file.name), "utf-8");
605
- entities[file.name.slice(0, -3)] = text;
664
+ if (rule.namePrefix && !stem.startsWith(rule.namePrefix)) {
665
+ throw new Error(`Entity "${stem}" at "${filePath}" must have name prefix "${rule.namePrefix}"`);
666
+ }
667
+ if (rule.validate) {
668
+ const error = rule.validate(stem, filePath);
669
+ if (error)
670
+ throw new Error(error);
671
+ }
672
+ }
673
+ }
674
+ }
675
+ function schemaFromFiles(paths, options) {
676
+ return async () => {
677
+ const pathsArray = Array.isArray(paths) ? paths : [paths];
678
+ const resolvedDirs = await resolveDirs(pathsArray);
679
+ const { entities, entityFilePaths, entityDirs } = await scanDirs(resolvedDirs);
680
+ if (options?.entityRules?.length) {
681
+ await validateEntityRules(entityFilePaths, entityDirs, options.entityRules);
606
682
  }
607
- return { schema: parseSchema(entities), entitiesDir };
683
+ const isSingleLiteralPath = pathsArray.length === 1 && !fg.isDynamicPattern(pathsArray[0]);
684
+ const entitiesDir = isSingleLiteralPath ? pathsArray[0] : void 0;
685
+ return { schema: parseSchema(entities), entitiesDir, entitiesDirs: resolvedDirs };
608
686
  };
609
687
  }
610
688
  function schemaFromRadsApi(radsApiUrl) {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { E as EntityDecoratorArgs, U as UiDecoratorArgs, a as UiFieldDecoratorArgs, V as ValidateEntityDecoratorArgs, b as ValidateFieldDecoratorArgs, F as FieldDecoratorArgs, C as ComputedDecoratorArgs, S as Schema, D as DriverConstructor, c as Driver, d as ComputedContext, R as RadsRequestContext, e as CreateRadsDbArgs, f as CreateRadsDbClientArgs } from './types-eb31ea63.js';
2
- export { O as Change, x as ComputedContextGlobal, k as CreateRadsArgsDrivers, m as CreateRadsDbArgsNormalized, an as DeepKeys, ai as DeepPartial, af as DeepPartialArrayItem, ad as DeepPartialWithNulls, ae as DeepPartialWithNullsItem, ag as DeepPartialWithoutNulls, ah as DeepPartialWithoutNullsItem, ao as EntityMethods, w as EnumDefinition, v as FieldDefinition, J as FileSystemNode, u as FileUploadArgs, q as FileUploadDriver, p as FileUploadResult, H as GenerateClientNormalizedOptions, B as GenerateClientOptions, a9 as Get, $ as GetAggArgs, a0 as GetAggArgsAgg, a3 as GetAggArgsAny, a6 as GetAggResponse, _ as GetArgs, a2 as GetArgsAny, a5 as GetArgsInclude, Q as GetManyArgs, a1 as GetManyArgsAny, a7 as GetManyResponse, a8 as GetResponse, aa as GetResponseInclude, ab as GetResponseIncludeSelect, ac as GetResponseNoInclude, s as GetRestRoutesArgs, G as GetRestRoutesOptions, t as GetRestRoutesResponse, ak as InverseRelation, am as JsonPutOperations, M as MinimalDriver, al as Put, P as PutEffect, g as RadsConfig, h as RadsConfigDataSource, r as RadsDbInstance, N as RadsFeature, y as RadsHookDoc, L as RadsUiSlotDefinition, K as RadsUiSlotName, I as RadsVitePluginOptions, aj as Relation, i as RequiredFields, l as RestDriverOptions, A as RestFileUploadDriverOptions, n as SchemaLoadResult, o as SchemaValidators, z as SqlDriverOptions, T as TypeDefinition, j as ValidateStringDecoratorArgs, X as VerifyManyArgs, a4 as VerifyManyArgsAny, Y as VerifyManyResponse, Z as Where, W as WhereJsonContains } from './types-eb31ea63.js';
1
+ import { E as EntityDecoratorArgs, U as UiDecoratorArgs, a as UiFieldDecoratorArgs, V as ValidateEntityDecoratorArgs, b as ValidateFieldDecoratorArgs, F as FieldDecoratorArgs, C as ComputedDecoratorArgs, S as Schema, D as DriverConstructor, c as Driver, d as ComputedContext, R as RadsRequestContext, e as CreateRadsDbArgs, f as CreateRadsDbClientArgs } from './types-f64a81e8.js';
2
+ export { O as Change, x as ComputedContextGlobal, k as CreateRadsArgsDrivers, m as CreateRadsDbArgsNormalized, an as DeepKeys, ai as DeepPartial, af as DeepPartialArrayItem, ad as DeepPartialWithNulls, ae as DeepPartialWithNullsItem, ag as DeepPartialWithoutNulls, ah as DeepPartialWithoutNullsItem, ao as EntityMethods, w as EnumDefinition, v as FieldDefinition, J as FileSystemNode, u as FileUploadArgs, q as FileUploadDriver, p as FileUploadResult, H as GenerateClientNormalizedOptions, B as GenerateClientOptions, a9 as Get, $ as GetAggArgs, a0 as GetAggArgsAgg, a3 as GetAggArgsAny, a6 as GetAggResponse, _ as GetArgs, a2 as GetArgsAny, a5 as GetArgsInclude, Q as GetManyArgs, a1 as GetManyArgsAny, a7 as GetManyResponse, a8 as GetResponse, aa as GetResponseInclude, ab as GetResponseIncludeSelect, ac as GetResponseNoInclude, s as GetRestRoutesArgs, G as GetRestRoutesOptions, t as GetRestRoutesResponse, ak as InverseRelation, am as JsonPutOperations, M as MinimalDriver, al as Put, P as PutEffect, g as RadsConfig, h as RadsConfigDataSource, r as RadsDbInstance, N as RadsFeature, y as RadsHookDoc, L as RadsUiSlotDefinition, K as RadsUiSlotName, I as RadsVitePluginOptions, aj as Relation, i as RequiredFields, l as RestDriverOptions, A as RestFileUploadDriverOptions, n as SchemaLoadResult, o as SchemaValidators, z as SqlDriverOptions, T as TypeDefinition, j as ValidateStringDecoratorArgs, X as VerifyManyArgs, a4 as VerifyManyArgsAny, Y as VerifyManyResponse, Z as Where, W as WhereJsonContains } from './types-f64a81e8.js';
3
3
  import { RadsDb } from '_rads-db';
4
4
  export { RadsDb } from '_rads-db';
5
5
  import 'mssql';
@@ -287,6 +287,7 @@ type Schema = Record<string, TypeDefinition>;
287
287
  interface SchemaLoadResult {
288
288
  schema: Schema;
289
289
  entitiesDir?: string;
290
+ entitiesDirs?: string[];
290
291
  }
291
292
  type SchemaValidators = Record<string, (item: any, oldDoc?: any) => any>;
292
293
  interface TypeDefinition {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rads-db",
3
- "version": "3.2.36",
3
+ "version": "3.2.38",
4
4
  "description": "Say goodbye to boilerplate code and hello to efficient and elegant syntax.",
5
5
  "author": "",
6
6
  "license": "ISC",
@@ -83,6 +83,7 @@
83
83
  "dependencies": {
84
84
  "@fastify/deepmerge": "^1.3.0",
85
85
  "dataloader": "^2.2.2",
86
+ "fast-glob": "^3.3.3",
86
87
  "ignore": "^5.2.4",
87
88
  "jiti": "^2.4.2",
88
89
  "klaw": "^4.1.0",