@apso/cli 0.4.2 → 0.6.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 (71) hide show
  1. package/README.md +796 -6
  2. package/dist/commands/server/new.js +1 -1
  3. package/dist/commands/server/scaffold.js +20 -9
  4. package/dist/lib/apsorc-parser.d.ts +3 -0
  5. package/dist/lib/apsorc-parser.js +11 -6
  6. package/dist/lib/controller.js +3 -1
  7. package/dist/lib/dto.js +9 -9
  8. package/dist/lib/entity.js +6 -5
  9. package/dist/lib/guards.d.ts +47 -0
  10. package/dist/lib/guards.js +176 -0
  11. package/dist/lib/index.d.ts +1 -0
  12. package/dist/lib/index.js +5 -1
  13. package/dist/lib/module.js +3 -1
  14. package/dist/lib/templates/guards/auth.guard.eta +228 -0
  15. package/dist/lib/templates/guards/guards.module.eta +114 -0
  16. package/dist/lib/templates/guards/index.eta +22 -0
  17. package/dist/lib/templates/guards/scope.guard.eta +327 -0
  18. package/dist/lib/types/auth.d.ts +203 -0
  19. package/dist/lib/types/auth.js +60 -0
  20. package/dist/lib/types/entity.d.ts +36 -1
  21. package/dist/lib/types/index.d.ts +2 -1
  22. package/dist/lib/types/index.js +8 -0
  23. package/dist/lib/types/relationship.d.ts +1 -1
  24. package/dist/lib/utils/casing.js +5 -5
  25. package/dist/lib/utils/field.js +2 -1
  26. package/dist/lib/utils/file-system.js +1 -1
  27. package/dist/lib/utils/relationships/parse-v1.js +2 -1
  28. package/dist/lib/utils/relationships/parse.js +117 -32
  29. package/dist/lib/utils/relationships/parse.spec.js +168 -141
  30. package/dist/test-nested-relationships.js +10 -10
  31. package/dist/tests/templates/entities/types/array-template.spec.js +45 -45
  32. package/dist/tests/templates/entities/types/bigint-template.spec.js +43 -43
  33. package/dist/tests/templates/entities/types/boolean-template.spec.js +44 -44
  34. package/dist/tests/templates/entities/types/bytea-template.spec.js +35 -35
  35. package/dist/tests/templates/entities/types/char-template.spec.js +35 -35
  36. package/dist/tests/templates/entities/types/date-template.spec.js +29 -29
  37. package/dist/tests/templates/entities/types/decimal-field-validation.spec.js +46 -46
  38. package/dist/tests/templates/entities/types/decimal-template.spec.js +46 -46
  39. package/dist/tests/templates/entities/types/double-template.spec.js +37 -34
  40. package/dist/tests/templates/entities/types/enum-template.spec.js +42 -42
  41. package/dist/tests/templates/entities/types/float-template.spec.js +37 -34
  42. package/dist/tests/templates/entities/types/geometry-template.spec.js +31 -31
  43. package/dist/tests/templates/entities/types/inet-template.spec.js +36 -36
  44. package/dist/tests/templates/entities/types/int4range-template.spec.js +45 -33
  45. package/dist/tests/templates/entities/types/integer-template.spec.js +40 -40
  46. package/dist/tests/templates/entities/types/interval-template.spec.js +35 -35
  47. package/dist/tests/templates/entities/types/json-plain-template.spec.js +27 -27
  48. package/dist/tests/templates/entities/types/json-template.spec.js +19 -19
  49. package/dist/tests/templates/entities/types/jsonb-template.spec.js +19 -19
  50. package/dist/tests/templates/entities/types/money-template.spec.js +35 -35
  51. package/dist/tests/templates/entities/types/numeric-template.spec.js +44 -44
  52. package/dist/tests/templates/entities/types/point-template.spec.js +37 -31
  53. package/dist/tests/templates/entities/types/polygon-template.spec.js +43 -31
  54. package/dist/tests/templates/entities/types/real-template.spec.js +34 -34
  55. package/dist/tests/templates/entities/types/serial-template.spec.js +62 -62
  56. package/dist/tests/templates/entities/types/smallint-template.spec.js +43 -43
  57. package/dist/tests/templates/entities/types/string-template.spec.js +59 -59
  58. package/dist/tests/templates/entities/types/text-template.spec.js +37 -37
  59. package/dist/tests/templates/entities/types/time-template.spec.js +35 -35
  60. package/dist/tests/templates/entities/types/timestamp-template.spec.js +39 -39
  61. package/dist/tests/templates/entities/types/timetz-template.spec.js +35 -35
  62. package/dist/tests/templates/entities/types/tsvector-template.spec.js +28 -28
  63. package/dist/tests/templates/entities/types/uuid-template.spec.js +43 -43
  64. package/dist/tests/templates/entities/types/varchar-template.spec.js +59 -56
  65. package/dist/tests/templates/entities/types/xml-template.spec.js +28 -28
  66. package/dist/tests/templates/entities/utils/template-test-utils.d.ts +1 -1
  67. package/dist/tests/templates/entities/utils/template-test-utils.js +3 -3
  68. package/dist/tests/templates/entities/uuid-entity-template.spec.js +138 -138
  69. package/dist/tests/utils/relationship-deduplication.spec.js +11 -11
  70. package/oclif.manifest.json +1 -1
  71. package/package.json +1 -1
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Supported authentication provider types
3
+ */
4
+ export type AuthProviderType = "better-auth" | "custom-db-session" | "auth0" | "clerk" | "cognito" | "api-key";
5
+ /**
6
+ * Base configuration common to all auth providers
7
+ */
8
+ export interface BaseAuthConfig {
9
+ /**
10
+ * The authentication provider type
11
+ */
12
+ provider: AuthProviderType;
13
+ }
14
+ /**
15
+ * Configuration for DB-backed session providers (Better Auth, custom-db-session)
16
+ */
17
+ export interface DbSessionAuthConfig extends BaseAuthConfig {
18
+ provider: "better-auth" | "custom-db-session";
19
+ /**
20
+ * Entity name for sessions. Default: "session"
21
+ */
22
+ sessionEntity?: string;
23
+ /**
24
+ * Entity name for users. Default: "User"
25
+ */
26
+ userEntity?: string;
27
+ /**
28
+ * Junction entity for user-organization mapping. Default: "AccountUser"
29
+ */
30
+ accountUserEntity?: string;
31
+ /**
32
+ * Cookie name prefix for session tokens. Default: service name
33
+ */
34
+ cookiePrefix?: string;
35
+ /**
36
+ * Field name on accountUserEntity for organization ID. Default: "organizationId"
37
+ */
38
+ organizationField?: string;
39
+ /**
40
+ * Field name on accountUserEntity for user's role. Default: "role"
41
+ */
42
+ roleField?: string;
43
+ }
44
+ /**
45
+ * JWT configuration for token-based providers
46
+ */
47
+ export interface JwtConfig {
48
+ /**
49
+ * The JWT issuer URL (e.g., "https://your-tenant.auth0.com/")
50
+ */
51
+ issuer: string;
52
+ /**
53
+ * Expected audience for the JWT
54
+ */
55
+ audience: string;
56
+ /**
57
+ * JWKS URI for key rotation. If not provided, uses issuer + /.well-known/jwks.json
58
+ */
59
+ jwksUri?: string;
60
+ /**
61
+ * Algorithms to accept. Default: ["RS256"]
62
+ */
63
+ algorithms?: string[];
64
+ }
65
+ /**
66
+ * Claim mapping for JWT providers
67
+ */
68
+ export interface JwtClaimsMapping {
69
+ /**
70
+ * Claim for user ID. Default: "sub"
71
+ */
72
+ userId?: string;
73
+ /**
74
+ * Claim for workspace/organization ID
75
+ */
76
+ workspaceId?: string;
77
+ /**
78
+ * Claim for organization ID (alias for workspaceId)
79
+ */
80
+ organizationId?: string;
81
+ /**
82
+ * Claim for user roles array
83
+ */
84
+ roles?: string;
85
+ /**
86
+ * Claim for user email
87
+ */
88
+ email?: string;
89
+ }
90
+ /**
91
+ * Configuration for JWT-based providers (Auth0, Cognito, Clerk)
92
+ */
93
+ export interface JwtAuthConfig extends BaseAuthConfig {
94
+ provider: "auth0" | "clerk" | "cognito";
95
+ /**
96
+ * JWT verification configuration
97
+ */
98
+ jwt: JwtConfig;
99
+ /**
100
+ * Mapping of JWT claims to AuthContext fields
101
+ */
102
+ claims?: JwtClaimsMapping;
103
+ /**
104
+ * Optional: If you want to sync/verify users against a local User entity
105
+ */
106
+ userEntity?: string;
107
+ }
108
+ /**
109
+ * Configuration for API key authentication
110
+ */
111
+ export interface ApiKeyAuthConfig extends BaseAuthConfig {
112
+ provider: "api-key";
113
+ /**
114
+ * Header name for API key. Default: "x-api-key"
115
+ */
116
+ apiKeyHeader?: string;
117
+ /**
118
+ * Entity name for API keys. Default: "ApiKey"
119
+ */
120
+ apiKeyEntity?: string;
121
+ /**
122
+ * Field name on apiKeyEntity for workspace/organization ID
123
+ */
124
+ workspaceField?: string;
125
+ /**
126
+ * Field name on apiKeyEntity for role/permissions
127
+ */
128
+ roleField?: string;
129
+ }
130
+ /**
131
+ * Union type of all auth configurations
132
+ */
133
+ export type AuthConfig = DbSessionAuthConfig | JwtAuthConfig | ApiKeyAuthConfig;
134
+ /**
135
+ * The normalized AuthContext that all providers produce.
136
+ * This is what scopeBy and RBAC consume.
137
+ */
138
+ export interface AuthContext {
139
+ /**
140
+ * The authenticated user's ID
141
+ */
142
+ userId?: string;
143
+ /**
144
+ * The user's email (if available)
145
+ */
146
+ email?: string;
147
+ /**
148
+ * The workspace/tenant ID (generic term)
149
+ */
150
+ workspaceId?: string;
151
+ /**
152
+ * The organization ID (alias for workspaceId in multi-tenant contexts)
153
+ */
154
+ organizationId?: string;
155
+ /**
156
+ * Array of roles/permissions the user has
157
+ */
158
+ roles: string[];
159
+ /**
160
+ * For API key auth: the service/key identifier
161
+ */
162
+ serviceId?: string;
163
+ /**
164
+ * Raw user object (provider-specific, for advanced use cases)
165
+ */
166
+ user?: unknown;
167
+ /**
168
+ * Raw session object (provider-specific, for advanced use cases)
169
+ */
170
+ session?: unknown;
171
+ }
172
+ /**
173
+ * Default values for DB session auth configuration
174
+ */
175
+ export declare const DB_SESSION_AUTH_DEFAULTS: Required<Omit<DbSessionAuthConfig, "provider" | "cookiePrefix">> & {
176
+ cookiePrefix?: string;
177
+ };
178
+ /**
179
+ * Default values for JWT auth configuration
180
+ */
181
+ export declare const JWT_AUTH_DEFAULTS: Required<Pick<JwtAuthConfig, "claims">> & Partial<JwtConfig>;
182
+ /**
183
+ * Default values for API key auth configuration
184
+ */
185
+ export declare const API_KEY_AUTH_DEFAULTS: Required<Omit<ApiKeyAuthConfig, "provider" | "workspaceField" | "roleField">>;
186
+ /**
187
+ * Type guard: Check if auth config is DB session based
188
+ * @param {AuthConfig} auth - The auth configuration to check
189
+ * @returns {boolean} True if the auth config is DB session based
190
+ */
191
+ export declare function isDbSessionAuth(auth: AuthConfig): auth is DbSessionAuthConfig;
192
+ /**
193
+ * Type guard: Check if auth config is JWT based
194
+ * @param {AuthConfig} auth - The auth configuration to check
195
+ * @returns {boolean} True if the auth config is JWT based
196
+ */
197
+ export declare function isJwtAuth(auth: AuthConfig): auth is JwtAuthConfig;
198
+ /**
199
+ * Type guard: Check if auth config is API key based
200
+ * @param {AuthConfig} auth - The auth configuration to check
201
+ * @returns {boolean} True if the auth config is API key based
202
+ */
203
+ export declare function isApiKeyAuth(auth: AuthConfig): auth is ApiKeyAuthConfig;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isApiKeyAuth = exports.isJwtAuth = exports.isDbSessionAuth = exports.API_KEY_AUTH_DEFAULTS = exports.JWT_AUTH_DEFAULTS = exports.DB_SESSION_AUTH_DEFAULTS = void 0;
4
+ /**
5
+ * Default values for DB session auth configuration
6
+ */
7
+ exports.DB_SESSION_AUTH_DEFAULTS = {
8
+ sessionEntity: "session",
9
+ userEntity: "User",
10
+ accountUserEntity: "AccountUser",
11
+ organizationField: "organizationId",
12
+ roleField: "role",
13
+ cookiePrefix: undefined, // Will use service name if not specified
14
+ };
15
+ /**
16
+ * Default values for JWT auth configuration
17
+ */
18
+ exports.JWT_AUTH_DEFAULTS = {
19
+ claims: {
20
+ userId: "sub",
21
+ email: "email",
22
+ roles: "roles",
23
+ },
24
+ };
25
+ /**
26
+ * Default values for API key auth configuration
27
+ */
28
+ exports.API_KEY_AUTH_DEFAULTS = {
29
+ apiKeyHeader: "x-api-key",
30
+ apiKeyEntity: "ApiKey",
31
+ };
32
+ /**
33
+ * Type guard: Check if auth config is DB session based
34
+ * @param {AuthConfig} auth - The auth configuration to check
35
+ * @returns {boolean} True if the auth config is DB session based
36
+ */
37
+ function isDbSessionAuth(auth) {
38
+ return auth.provider === "better-auth" || auth.provider === "custom-db-session";
39
+ }
40
+ exports.isDbSessionAuth = isDbSessionAuth;
41
+ /**
42
+ * Type guard: Check if auth config is JWT based
43
+ * @param {AuthConfig} auth - The auth configuration to check
44
+ * @returns {boolean} True if the auth config is JWT based
45
+ */
46
+ function isJwtAuth(auth) {
47
+ return (auth.provider === "auth0" ||
48
+ auth.provider === "clerk" ||
49
+ auth.provider === "cognito");
50
+ }
51
+ exports.isJwtAuth = isJwtAuth;
52
+ /**
53
+ * Type guard: Check if auth config is API key based
54
+ * @param {AuthConfig} auth - The auth configuration to check
55
+ * @returns {boolean} True if the auth config is API key based
56
+ */
57
+ function isApiKeyAuth(auth) {
58
+ return auth.provider === "api-key";
59
+ }
60
+ exports.isApiKeyAuth = isApiKeyAuth;
@@ -2,13 +2,48 @@ import { Unique } from "./constraints";
2
2
  import { Field } from "./field";
3
3
  import { Index } from "./indices";
4
4
  import { Association } from "./relationship";
5
+ /**
6
+ * Operations that can have scope enforcement applied
7
+ */
8
+ export type ScopeOperation = "find" | "get" | "create" | "update" | "delete";
9
+ /**
10
+ * Configuration options for scope enforcement behavior
11
+ */
12
+ export interface ScopeOptions {
13
+ /**
14
+ * If true (default), scope fields are automatically injected from
15
+ * the request context on create when not provided.
16
+ */
17
+ injectOnCreate?: boolean;
18
+ /**
19
+ * Which operations should enforce scope. Defaults to all operations.
20
+ */
21
+ enforceOn?: ScopeOperation[];
22
+ /**
23
+ * Roles allowed to bypass scope enforcement (e.g., 'system_admin').
24
+ */
25
+ bypassRoles?: string[];
26
+ }
5
27
  export interface Entity {
6
28
  name: string;
7
29
  created_at?: boolean;
8
30
  updated_at?: boolean;
9
- primaryKeyType?: 'serial' | 'uuid';
31
+ primaryKeyType?: "serial" | "uuid";
10
32
  fields?: Field[];
11
33
  indexes?: Index[];
12
34
  uniques?: Unique[];
35
+ /**
36
+ * Fields or paths that define the authorization scope for this entity.
37
+ * Can be a single field name (e.g., 'workspaceId') or an array of fields/paths
38
+ * (e.g., ['workspaceId', 'serviceId'] or ['task.workspaceId']).
39
+ *
40
+ * - Direct fields: 'workspaceId' - enforces WHERE workspaceId = ctx.workspaceId
41
+ * - Nested paths: 'task.workspaceId' - joins to related entity and enforces scope
42
+ */
43
+ scopeBy?: string | string[];
44
+ /**
45
+ * Optional configuration for how scope enforcement should behave.
46
+ */
47
+ scopeOptions?: ScopeOptions;
13
48
  associations?: Association[];
14
49
  }
@@ -1,5 +1,6 @@
1
- export { Entity } from "./entity";
1
+ export { Entity, ScopeOptions, ScopeOperation } from "./entity";
2
2
  export { FieldType, Field, ComputedField } from "./field";
3
3
  export { Index } from "./indices";
4
4
  export { JSONValue } from "./json";
5
5
  export { RelationshipType, Association, ApsorcRelationship, Relationship, RelationshipForTemplate, RelationshipMap, } from "./relationship";
6
+ export { AuthProviderType, BaseAuthConfig, DbSessionAuthConfig, JwtConfig, JwtClaimsMapping, JwtAuthConfig, ApiKeyAuthConfig, AuthConfig, AuthContext, DB_SESSION_AUTH_DEFAULTS, JWT_AUTH_DEFAULTS, API_KEY_AUTH_DEFAULTS, isDbSessionAuth, isJwtAuth, isApiKeyAuth, } from "./auth";
@@ -1,2 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isApiKeyAuth = exports.isJwtAuth = exports.isDbSessionAuth = exports.API_KEY_AUTH_DEFAULTS = exports.JWT_AUTH_DEFAULTS = exports.DB_SESSION_AUTH_DEFAULTS = void 0;
4
+ var auth_1 = require("./auth");
5
+ Object.defineProperty(exports, "DB_SESSION_AUTH_DEFAULTS", { enumerable: true, get: function () { return auth_1.DB_SESSION_AUTH_DEFAULTS; } });
6
+ Object.defineProperty(exports, "JWT_AUTH_DEFAULTS", { enumerable: true, get: function () { return auth_1.JWT_AUTH_DEFAULTS; } });
7
+ Object.defineProperty(exports, "API_KEY_AUTH_DEFAULTS", { enumerable: true, get: function () { return auth_1.API_KEY_AUTH_DEFAULTS; } });
8
+ Object.defineProperty(exports, "isDbSessionAuth", { enumerable: true, get: function () { return auth_1.isDbSessionAuth; } });
9
+ Object.defineProperty(exports, "isJwtAuth", { enumerable: true, get: function () { return auth_1.isJwtAuth; } });
10
+ Object.defineProperty(exports, "isApiKeyAuth", { enumerable: true, get: function () { return auth_1.isApiKeyAuth; } });
@@ -36,7 +36,7 @@ export interface RelationshipForTemplate extends Relationship {
36
36
  joinTable: boolean;
37
37
  biDirectional: boolean;
38
38
  inversePropertyName?: string;
39
- referencedEntityPrimaryKeyType?: 'serial' | 'uuid';
39
+ referencedEntityPrimaryKeyType?: "serial" | "uuid";
40
40
  }
41
41
  export type RelationshipMap = {
42
42
  [key: string]: Relationship[];
@@ -2,16 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.pascalCase = exports.snakeCase = exports.camelCase = void 0;
4
4
  function camelCase(str) {
5
- return str
5
+ return (str
6
6
  // Insert a space before all caps that are followed by lower case (for PascalCase)
7
- .replace(/([\da-z])([A-Z])/g, '$1 $2')
7
+ .replace(/([\da-z])([A-Z])/g, "$1 $2")
8
8
  // Replace all separators with spaces
9
- .replace(/[ _-]+/g, ' ')
9
+ .replace(/[ _-]+/g, " ")
10
10
  // Lowercase the string, then uppercase the first letter of each word (except the first)
11
11
  .toLowerCase()
12
- .replace(/ (\w)/g, (_, c) => c ? c.toUpperCase() : '')
12
+ .replace(/ (\w)/g, (_, c) => (c ? c.toUpperCase() : ""))
13
13
  // Remove spaces
14
- .replace(/ /g, '');
14
+ .replace(/ /g, ""));
15
15
  }
16
16
  exports.camelCase = camelCase;
17
17
  const snakeCase = (str) => str[0].toLowerCase() +
@@ -102,7 +102,8 @@ const validateDecimalField = (field) => {
102
102
  return;
103
103
  }
104
104
  // Validate precision
105
- if (field.precision !== undefined && (field.precision < 1 || field.precision > 131072)) {
105
+ if (field.precision !== undefined &&
106
+ (field.precision < 1 || field.precision > 131072)) {
106
107
  throw new Error(`${field.name}: Invalid precision value ${field.precision}. Must be between 1 and 131072.`);
107
108
  }
108
109
  // Validate scale
@@ -54,7 +54,7 @@ function withGeneratedMeta(data) {
54
54
  return {
55
55
  ...data,
56
56
  generatedAt: new Date().toISOString(),
57
- generatedBy: process.env.USER || process.env.USERNAME || 'unknown',
57
+ generatedBy: process.env.USER || process.env.USERNAME || "unknown",
58
58
  };
59
59
  }
60
60
  exports.withGeneratedMeta = withGeneratedMeta;
@@ -79,7 +79,8 @@ const parseV1Relationships = (entities) => {
79
79
  for (const apsorcRelationship of apsorcRelationships) {
80
80
  const parsedResult = (0, parse_1.parseRelationship)(apsorcRelationship, apsorcRelationships);
81
81
  for (const key of Object.keys(parsedResult)) {
82
- if (parsedResult[key] && parsedResult[key].length > 0) { // Ensure non-empty result
82
+ if (parsedResult[key] && parsedResult[key].length > 0) {
83
+ // Ensure non-empty result
83
84
  relationshipMap[key] = [
84
85
  ...(relationshipMap[key] || []),
85
86
  ...parsedResult[key],
@@ -13,8 +13,27 @@ const casing_1 = require("../casing");
13
13
  */
14
14
  const parseOneToMany = (relationship) => {
15
15
  return {
16
- [relationship.from]: [{ name: relationship.to, type: "OneToMany", biDirectional: true, referenceName: relationship.to_name || relationship.to, inverseReferenceName: relationship.from }],
17
- [relationship.to]: [{ name: relationship.from, type: "ManyToOne", nullable: relationship.nullable || false, biDirectional: true, index: relationship.index || false, cascadeDelete: relationship.cascadeDelete || false, referenceName: relationship.from, inverseReferenceName: relationship.to_name || relationship.to }],
16
+ [relationship.from]: [
17
+ {
18
+ name: relationship.to,
19
+ type: "OneToMany",
20
+ biDirectional: true,
21
+ referenceName: relationship.to_name || relationship.to,
22
+ inverseReferenceName: relationship.from,
23
+ },
24
+ ],
25
+ [relationship.to]: [
26
+ {
27
+ name: relationship.from,
28
+ type: "ManyToOne",
29
+ nullable: relationship.nullable || false,
30
+ biDirectional: true,
31
+ index: relationship.index || false,
32
+ cascadeDelete: relationship.cascadeDelete || false,
33
+ referenceName: relationship.from,
34
+ inverseReferenceName: relationship.to_name || relationship.to,
35
+ },
36
+ ],
18
37
  };
19
38
  };
20
39
  exports.parseOneToMany = parseOneToMany;
@@ -51,9 +70,9 @@ exports.parseManytoOne = parseManytoOne;
51
70
  */
52
71
  const parseManyToMany = (relationship, allRelationships) => {
53
72
  // Find the inverse relationship definition (if exists)
54
- const inverseDefinition = allRelationships.find(def => def.from === relationship.to &&
73
+ const inverseDefinition = allRelationships.find((def) => def.from === relationship.to &&
55
74
  def.to === relationship.from &&
56
- def.type === 'ManyToMany');
75
+ def.type === "ManyToMany");
57
76
  // Determine if the relationship should be treated as bidirectional
58
77
  const isEffectivelyBiDirectional = Boolean(relationship.bi_directional || (inverseDefinition === null || inverseDefinition === void 0 ? void 0 : inverseDefinition.bi_directional));
59
78
  // For self-referencing relationships, find the matching inverse by to_name
@@ -61,19 +80,22 @@ const parseManyToMany = (relationship, allRelationships) => {
61
80
  let selfRefInverse = null;
62
81
  if (isSelfReferencing && relationship.bi_directional) {
63
82
  // Find the other relationship that completes the pair
64
- selfRefInverse = allRelationships.find(def => def.from === relationship.from &&
83
+ selfRefInverse = allRelationships.find((def) => def.from === relationship.from &&
65
84
  def.to === relationship.to &&
66
- def.type === 'ManyToMany' &&
85
+ def.type === "ManyToMany" &&
67
86
  def.bi_directional &&
68
87
  def.to_name !== relationship.to_name);
69
88
  }
70
- let isOwningSide = relationship.joinTableName || relationship.joinColumnName || relationship.inverseJoinColumnName
89
+ let isOwningSide = relationship.joinTableName ||
90
+ relationship.joinColumnName ||
91
+ relationship.inverseJoinColumnName
71
92
  ? true
72
93
  : undefined;
73
94
  if (isOwningSide === undefined && inverseDefinition) {
74
95
  // If current doesn't have details, check if inverse does
75
- isOwningSide =
76
- !(inverseDefinition.joinTableName || inverseDefinition.joinColumnName || inverseDefinition.inverseJoinColumnName);
96
+ isOwningSide = !(inverseDefinition.joinTableName ||
97
+ inverseDefinition.joinColumnName ||
98
+ inverseDefinition.inverseJoinColumnName);
77
99
  }
78
100
  if (isOwningSide === undefined) {
79
101
  // If neither has details, default to the current definition being the owner
@@ -113,10 +135,18 @@ const parseManyToMany = (relationship, allRelationships) => {
113
135
  referenceName: fromSideRefName,
114
136
  join: isOwningSide,
115
137
  biDirectional: isEffectivelyBiDirectional,
116
- joinTableName: isOwningSide ? (relationship.joinTableName || undefined) : undefined,
117
- joinColumnName: isOwningSide ? (relationship.joinColumnName || undefined) : undefined,
118
- inverseJoinColumnName: isOwningSide ? (relationship.inverseJoinColumnName || undefined) : undefined,
119
- inverseReferenceName: isEffectivelyBiDirectional ? toSideRefName : undefined, // Only add inverse name if bidirectional
138
+ joinTableName: isOwningSide
139
+ ? relationship.joinTableName || undefined
140
+ : undefined,
141
+ joinColumnName: isOwningSide
142
+ ? relationship.joinColumnName || undefined
143
+ : undefined,
144
+ inverseJoinColumnName: isOwningSide
145
+ ? relationship.inverseJoinColumnName || undefined
146
+ : undefined,
147
+ inverseReferenceName: isEffectivelyBiDirectional
148
+ ? toSideRefName
149
+ : undefined, // Only add inverse name if bidirectional
120
150
  };
121
151
  const response = {
122
152
  [relationship.from]: [fromSideRelationship],
@@ -135,14 +165,20 @@ const parseManyToMany = (relationship, allRelationships) => {
135
165
  join: isInverseOwningSide,
136
166
  biDirectional: true,
137
167
  // Add join details ONLY if the inverse side IS the owner
138
- joinTableName: isInverseOwningSide ? ((inverseDefinition === null || inverseDefinition === void 0 ? void 0 : inverseDefinition.joinTableName) || undefined) : undefined,
139
- joinColumnName: isInverseOwningSide ? ((inverseDefinition === null || inverseDefinition === void 0 ? void 0 : inverseDefinition.joinColumnName) || undefined) : undefined,
140
- inverseJoinColumnName: isInverseOwningSide ? ((inverseDefinition === null || inverseDefinition === void 0 ? void 0 : inverseDefinition.inverseJoinColumnName) || undefined) : undefined,
168
+ joinTableName: isInverseOwningSide
169
+ ? (inverseDefinition === null || inverseDefinition === void 0 ? void 0 : inverseDefinition.joinTableName) || undefined
170
+ : undefined,
171
+ joinColumnName: isInverseOwningSide
172
+ ? (inverseDefinition === null || inverseDefinition === void 0 ? void 0 : inverseDefinition.joinColumnName) || undefined
173
+ : undefined,
174
+ inverseJoinColumnName: isInverseOwningSide
175
+ ? (inverseDefinition === null || inverseDefinition === void 0 ? void 0 : inverseDefinition.inverseJoinColumnName) || undefined
176
+ : undefined,
141
177
  inverseReferenceName: fromSideRefName, // The reference name of the other side
142
178
  };
143
179
  response[relationship.to] = [
144
180
  ...(response[relationship.to] || []),
145
- toSideRelationship
181
+ toSideRelationship,
146
182
  ];
147
183
  }
148
184
  return response;
@@ -205,13 +241,40 @@ const parseRelationship = (relationship, allRelationships) => {
205
241
  const fromSideRefName = relationship.to_name || relationship.to;
206
242
  const toSideRefName = relationship.from;
207
243
  return {
208
- [relationship.from]: [{ name: relationship.to, type: "OneToMany", biDirectional: true, referenceName: fromSideRefName, inverseReferenceName: toSideRefName }],
209
- [relationship.to]: [{ name: relationship.from, type: "ManyToOne", nullable: relationship.nullable || false, biDirectional: true, index: relationship.index || false, cascadeDelete: relationship.cascadeDelete || false, referenceName: toSideRefName, inverseReferenceName: fromSideRefName }],
244
+ [relationship.from]: [
245
+ {
246
+ name: relationship.to,
247
+ type: "OneToMany",
248
+ biDirectional: true,
249
+ referenceName: fromSideRefName,
250
+ inverseReferenceName: toSideRefName,
251
+ },
252
+ ],
253
+ [relationship.to]: [
254
+ {
255
+ name: relationship.from,
256
+ type: "ManyToOne",
257
+ nullable: relationship.nullable || false,
258
+ biDirectional: true,
259
+ index: relationship.index || false,
260
+ cascadeDelete: relationship.cascadeDelete || false,
261
+ referenceName: toSideRefName,
262
+ inverseReferenceName: fromSideRefName,
263
+ },
264
+ ],
210
265
  };
211
266
  }
212
267
  case "ManyToOne":
213
268
  return {
214
- [relationship.from]: [{ name: relationship.to, type: "ManyToOne", referenceName: relationship.to_name || relationship.to, nullable: relationship.nullable || false, index: relationship.index || false }],
269
+ [relationship.from]: [
270
+ {
271
+ name: relationship.to,
272
+ type: "ManyToOne",
273
+ referenceName: relationship.to_name || relationship.to,
274
+ nullable: relationship.nullable || false,
275
+ index: relationship.index || false,
276
+ },
277
+ ],
215
278
  };
216
279
  case "OneToOne": {
217
280
  const inverseOnetoOneDef = allRelationships.find((def) => def.from === relationship.to &&
@@ -243,7 +306,7 @@ const parseRelationship = (relationship, allRelationships) => {
243
306
  biDirectional: true,
244
307
  join: !(fromRel.join || false),
245
308
  referenceName: relationship.from,
246
- inverseReferenceName: fromRel.referenceName || undefined // Reference the 'from' side's name, default to undefined if null
309
+ inverseReferenceName: fromRel.referenceName || undefined, // Reference the 'from' side's name, default to undefined if null
247
310
  };
248
311
  responseMap[relationship.to] = [
249
312
  ...(responseMap[relationship.to] || []),
@@ -300,7 +363,7 @@ exports.getRelationshipIdField = getRelationshipIdField;
300
363
  */
301
364
  const calculateInversePropertyName = (relationship, entityName, relationshipName, needsPluralInverse) => {
302
365
  const isSelfReferencing = relationship.name === entityName;
303
- if (isSelfReferencing && relationship.type === 'ManyToMany') {
366
+ if (isSelfReferencing && relationship.type === "ManyToMany") {
304
367
  if (relationship.inverseReferenceName) {
305
368
  return (0, casing_1.camelCase)(relationship.inverseReferenceName);
306
369
  }
@@ -314,9 +377,25 @@ const calculateInversePropertyName = (relationship, entityName, relationshipName
314
377
  }
315
378
  // Check for special plural terms
316
379
  const specialPlurals = [
317
- 'children', 'people', 'men', 'women', 'mice', 'geese', 'teeth', 'feet',
318
- 'oxen', 'cacti', 'indices', 'vertices', 'analyses', 'crises', 'diagnoses',
319
- 'media', 'criteria', 'data', 'parents'
380
+ "children",
381
+ "people",
382
+ "men",
383
+ "women",
384
+ "mice",
385
+ "geese",
386
+ "teeth",
387
+ "feet",
388
+ "oxen",
389
+ "cacti",
390
+ "indices",
391
+ "vertices",
392
+ "analyses",
393
+ "crises",
394
+ "diagnoses",
395
+ "media",
396
+ "criteria",
397
+ "data",
398
+ "parents",
320
399
  ];
321
400
  if (specialPlurals.includes(relationship.inverseReferenceName)) {
322
401
  return relationship.inverseReferenceName;
@@ -341,7 +420,7 @@ const getRelationshipForTemplate = (entityName, relationships, entities) => {
341
420
  // Create entity map for looking up primary key types
342
421
  const entityMap = new Map();
343
422
  if (entities) {
344
- entities.forEach(entity => {
423
+ entities.forEach((entity) => {
345
424
  entityMap.set(entity.name, entity);
346
425
  });
347
426
  }
@@ -353,7 +432,8 @@ const getRelationshipForTemplate = (entityName, relationships, entities) => {
353
432
  const refName = relationship.referenceName || "default";
354
433
  const key = `${name}:${refName}`;
355
434
  // Use the relationship with join=true if there are duplicates (this is the owning side)
356
- if (!processedRefs.has(key) || (relationship.join && !((_a = processedRefs.get(key)) === null || _a === void 0 ? void 0 : _a.join))) {
435
+ if (!processedRefs.has(key) ||
436
+ (relationship.join && !((_a = processedRefs.get(key)) === null || _a === void 0 ? void 0 : _a.join))) {
357
437
  processedRefs.set(key, relationship);
358
438
  }
359
439
  });
@@ -361,7 +441,7 @@ const getRelationshipForTemplate = (entityName, relationships, entities) => {
361
441
  return [...processedRefs.values()].map((relationship) => {
362
442
  const thisReferenceName = relationship.referenceName;
363
443
  const relationshipName = (0, casing_1.camelCase)(thisReferenceName || relationship.name);
364
- const needsPluralInverse = relationship.type === 'ManyToOne' || relationship.type === 'ManyToMany';
444
+ const needsPluralInverse = relationship.type === "ManyToOne" || relationship.type === "ManyToMany";
365
445
  // DEBUG LOGGING
366
446
  // if (process.env.DEBUG) {
367
447
  // console.log(
@@ -373,13 +453,16 @@ const getRelationshipForTemplate = (entityName, relationships, entities) => {
373
453
  // );
374
454
  // }
375
455
  const inverseSidePropertyName = calculateInversePropertyName(relationship, entityName, relationshipName, needsPluralInverse);
376
- if (relationship.biDirectional && !relationship.inverseReferenceName &&
377
- (relationship.type === 'ManyToMany' || relationship.type === 'OneToMany' || relationship.type === 'OneToOne')) {
456
+ if (relationship.biDirectional &&
457
+ !relationship.inverseReferenceName &&
458
+ (relationship.type === "ManyToMany" ||
459
+ relationship.type === "OneToMany" ||
460
+ relationship.type === "OneToOne")) {
378
461
  console.info(`[APSO INFO] Using default property name '${inverseSidePropertyName}' for ${relationship.name}->${entityName}. To customize, add 'to_name' in your relationship definition.`);
379
462
  }
380
463
  // Look up the referenced entity's primary key type
381
464
  const referencedEntity = entityMap.get(relationship.name);
382
- const referencedEntityPrimaryKeyType = (referencedEntity === null || referencedEntity === void 0 ? void 0 : referencedEntity.primaryKeyType) || 'serial'; // Default to 'serial' if not specified
465
+ const referencedEntityPrimaryKeyType = (referencedEntity === null || referencedEntity === void 0 ? void 0 : referencedEntity.primaryKeyType) || "serial"; // Default to 'serial' if not specified
383
466
  return {
384
467
  ...relationship,
385
468
  relationshipName,
@@ -477,7 +560,9 @@ function* generateNestedRelationships(entityName, relationshipMap, maxDepth = 4,
477
560
  exports.generateNestedRelationships = generateNestedRelationships;
478
561
  // In-memory collector
479
562
  function getNestedRelationships(entityName, relationshipMap, maxDepth) {
480
- return [...generateNestedRelationships(entityName, relationshipMap, maxDepth)];
563
+ return [
564
+ ...generateNestedRelationships(entityName, relationshipMap, maxDepth),
565
+ ];
481
566
  }
482
567
  exports.getNestedRelationships = getNestedRelationships;
483
568
  // Streaming function