@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.
- package/README.md +796 -6
- package/dist/commands/server/new.js +1 -1
- package/dist/commands/server/scaffold.js +20 -9
- package/dist/lib/apsorc-parser.d.ts +3 -0
- package/dist/lib/apsorc-parser.js +11 -6
- package/dist/lib/controller.js +3 -1
- package/dist/lib/dto.js +9 -9
- package/dist/lib/entity.js +6 -5
- package/dist/lib/guards.d.ts +47 -0
- package/dist/lib/guards.js +176 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.js +5 -1
- package/dist/lib/module.js +3 -1
- package/dist/lib/templates/guards/auth.guard.eta +228 -0
- package/dist/lib/templates/guards/guards.module.eta +114 -0
- package/dist/lib/templates/guards/index.eta +22 -0
- package/dist/lib/templates/guards/scope.guard.eta +327 -0
- package/dist/lib/types/auth.d.ts +203 -0
- package/dist/lib/types/auth.js +60 -0
- package/dist/lib/types/entity.d.ts +36 -1
- package/dist/lib/types/index.d.ts +2 -1
- package/dist/lib/types/index.js +8 -0
- package/dist/lib/types/relationship.d.ts +1 -1
- package/dist/lib/utils/casing.js +5 -5
- package/dist/lib/utils/field.js +2 -1
- package/dist/lib/utils/file-system.js +1 -1
- package/dist/lib/utils/relationships/parse-v1.js +2 -1
- package/dist/lib/utils/relationships/parse.js +117 -32
- package/dist/lib/utils/relationships/parse.spec.js +168 -141
- package/dist/test-nested-relationships.js +10 -10
- package/dist/tests/templates/entities/types/array-template.spec.js +45 -45
- package/dist/tests/templates/entities/types/bigint-template.spec.js +43 -43
- package/dist/tests/templates/entities/types/boolean-template.spec.js +44 -44
- package/dist/tests/templates/entities/types/bytea-template.spec.js +35 -35
- package/dist/tests/templates/entities/types/char-template.spec.js +35 -35
- package/dist/tests/templates/entities/types/date-template.spec.js +29 -29
- package/dist/tests/templates/entities/types/decimal-field-validation.spec.js +46 -46
- package/dist/tests/templates/entities/types/decimal-template.spec.js +46 -46
- package/dist/tests/templates/entities/types/double-template.spec.js +37 -34
- package/dist/tests/templates/entities/types/enum-template.spec.js +42 -42
- package/dist/tests/templates/entities/types/float-template.spec.js +37 -34
- package/dist/tests/templates/entities/types/geometry-template.spec.js +31 -31
- package/dist/tests/templates/entities/types/inet-template.spec.js +36 -36
- package/dist/tests/templates/entities/types/int4range-template.spec.js +45 -33
- package/dist/tests/templates/entities/types/integer-template.spec.js +40 -40
- package/dist/tests/templates/entities/types/interval-template.spec.js +35 -35
- package/dist/tests/templates/entities/types/json-plain-template.spec.js +27 -27
- package/dist/tests/templates/entities/types/json-template.spec.js +19 -19
- package/dist/tests/templates/entities/types/jsonb-template.spec.js +19 -19
- package/dist/tests/templates/entities/types/money-template.spec.js +35 -35
- package/dist/tests/templates/entities/types/numeric-template.spec.js +44 -44
- package/dist/tests/templates/entities/types/point-template.spec.js +37 -31
- package/dist/tests/templates/entities/types/polygon-template.spec.js +43 -31
- package/dist/tests/templates/entities/types/real-template.spec.js +34 -34
- package/dist/tests/templates/entities/types/serial-template.spec.js +62 -62
- package/dist/tests/templates/entities/types/smallint-template.spec.js +43 -43
- package/dist/tests/templates/entities/types/string-template.spec.js +59 -59
- package/dist/tests/templates/entities/types/text-template.spec.js +37 -37
- package/dist/tests/templates/entities/types/time-template.spec.js +35 -35
- package/dist/tests/templates/entities/types/timestamp-template.spec.js +39 -39
- package/dist/tests/templates/entities/types/timetz-template.spec.js +35 -35
- package/dist/tests/templates/entities/types/tsvector-template.spec.js +28 -28
- package/dist/tests/templates/entities/types/uuid-template.spec.js +43 -43
- package/dist/tests/templates/entities/types/varchar-template.spec.js +59 -56
- package/dist/tests/templates/entities/types/xml-template.spec.js +28 -28
- package/dist/tests/templates/entities/utils/template-test-utils.d.ts +1 -1
- package/dist/tests/templates/entities/utils/template-test-utils.js +3 -3
- package/dist/tests/templates/entities/uuid-entity-template.spec.js +138 -138
- package/dist/tests/utils/relationship-deduplication.spec.js +11 -11
- package/oclif.manifest.json +1 -1
- 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?:
|
|
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";
|
package/dist/lib/types/index.js
CHANGED
|
@@ -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?:
|
|
39
|
+
referencedEntityPrimaryKeyType?: "serial" | "uuid";
|
|
40
40
|
}
|
|
41
41
|
export type RelationshipMap = {
|
|
42
42
|
[key: string]: Relationship[];
|
package/dist/lib/utils/casing.js
CHANGED
|
@@ -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,
|
|
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() +
|
package/dist/lib/utils/field.js
CHANGED
|
@@ -102,7 +102,8 @@ const validateDecimalField = (field) => {
|
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
104
|
// Validate precision
|
|
105
|
-
if (field.precision !== undefined &&
|
|
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 ||
|
|
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) {
|
|
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]: [
|
|
17
|
-
|
|
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 ===
|
|
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 ===
|
|
85
|
+
def.type === "ManyToMany" &&
|
|
67
86
|
def.bi_directional &&
|
|
68
87
|
def.to_name !== relationship.to_name);
|
|
69
88
|
}
|
|
70
|
-
let isOwningSide = relationship.joinTableName ||
|
|
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
|
-
|
|
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
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
139
|
-
|
|
140
|
-
|
|
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]: [
|
|
209
|
-
|
|
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]: [
|
|
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 ===
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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) ||
|
|
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 ===
|
|
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 &&
|
|
377
|
-
|
|
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) ||
|
|
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 [
|
|
563
|
+
return [
|
|
564
|
+
...generateNestedRelationships(entityName, relationshipMap, maxDepth),
|
|
565
|
+
];
|
|
481
566
|
}
|
|
482
567
|
exports.getNestedRelationships = getNestedRelationships;
|
|
483
568
|
// Streaming function
|