@gzl10/nexus-sdk 0.12.7 → 0.13.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.
@@ -0,0 +1,364 @@
1
+ // src/fields/id.ts
2
+ var ID_TYPE_CONFIG = {
3
+ ulid: { size: 26, input: "text" },
4
+ uuid: { size: 36, input: "uuid" },
5
+ nanoid: { size: 21, input: "text" },
6
+ cuid2: { size: 24, input: "text" }
7
+ };
8
+ function calculatePatternSize(pattern, prefix, suffix) {
9
+ let size = 0;
10
+ const tokens = pattern.match(/\{[^}]+\}/g) || [];
11
+ for (const token of tokens) {
12
+ const tokenName = token.slice(1, -1);
13
+ if (tokenName === "prefix") {
14
+ size += prefix?.length ?? 0;
15
+ } else if (tokenName === "suffix") {
16
+ size += suffix?.length ?? 0;
17
+ } else if (tokenName === "year") {
18
+ size += 4;
19
+ } else if (tokenName === "year2") {
20
+ size += 2;
21
+ } else if (tokenName === "month" || tokenName === "day") {
22
+ size += 2;
23
+ } else if (tokenName.startsWith("seq:")) {
24
+ const paddingStr = tokenName.split(":")[1] ?? "6";
25
+ const padding = parseInt(paddingStr, 10) || 6;
26
+ size += padding;
27
+ } else if (tokenName === "seq") {
28
+ size += 10;
29
+ }
30
+ }
31
+ const literals = pattern.replace(/\{[^}]+\}/g, "");
32
+ size += literals.length;
33
+ return Math.max(size + 10, 50);
34
+ }
35
+ function useIdField(type = "ulid", config) {
36
+ const label = config?.label ?? "ID";
37
+ if (type === "auto") {
38
+ return {
39
+ label,
40
+ input: "number",
41
+ hidden: true,
42
+ db: { type: "integer", nullable: false, primary: true, autoIncrement: true, idType: "auto" }
43
+ };
44
+ }
45
+ if (type === "custom") {
46
+ return {
47
+ label,
48
+ input: "text",
49
+ hidden: true,
50
+ db: { type: "string", size: config?.size ?? 50, nullable: false, primary: true, idType: "custom" }
51
+ };
52
+ }
53
+ if (type === "pattern") {
54
+ const patternConfig = config?.pattern;
55
+ if (!patternConfig || !patternConfig.pattern) {
56
+ throw new Error("Pattern configuration is required for pattern ID type");
57
+ }
58
+ const size2 = calculatePatternSize(patternConfig.pattern, patternConfig.prefix, patternConfig.suffix);
59
+ return {
60
+ label,
61
+ input: "text",
62
+ hidden: true,
63
+ db: {
64
+ type: "string",
65
+ size: size2,
66
+ nullable: false,
67
+ primary: true,
68
+ idType: "pattern",
69
+ patternConfig
70
+ }
71
+ };
72
+ }
73
+ const { size, input } = ID_TYPE_CONFIG[type];
74
+ return {
75
+ label,
76
+ input,
77
+ hidden: true,
78
+ db: { type: "string", size, nullable: false, primary: true, idType: type }
79
+ };
80
+ }
81
+
82
+ // src/fields/text.ts
83
+ function useTextField(config) {
84
+ const {
85
+ label,
86
+ hint,
87
+ required,
88
+ size = 255,
89
+ minLength,
90
+ nullable,
91
+ index,
92
+ unique,
93
+ defaultValue,
94
+ placeholder,
95
+ validation,
96
+ meta,
97
+ ...overrides
98
+ } = config;
99
+ const min = minLength ?? (required ? 1 : 0);
100
+ return {
101
+ label,
102
+ input: "text",
103
+ hint,
104
+ required: required ?? false,
105
+ defaultValue,
106
+ placeholder,
107
+ db: {
108
+ type: "string",
109
+ size,
110
+ nullable: nullable ?? !required,
111
+ default: defaultValue,
112
+ index: index ?? false,
113
+ unique: unique ?? false
114
+ },
115
+ validation: {
116
+ min,
117
+ max: size,
118
+ ...validation
119
+ // Custom validation overrides/extends
120
+ },
121
+ meta: {
122
+ searchable: true,
123
+ ...meta
124
+ },
125
+ ...overrides
126
+ };
127
+ }
128
+ function useTextUniqueField(config) {
129
+ return useTextField({
130
+ ...config,
131
+ required: true,
132
+ unique: true,
133
+ index: true
134
+ });
135
+ }
136
+
137
+ // src/fields/url.ts
138
+ function useUrlField(config) {
139
+ const {
140
+ label,
141
+ hint,
142
+ required,
143
+ size = 500,
144
+ nullable,
145
+ index,
146
+ defaultValue,
147
+ placeholder,
148
+ meta,
149
+ ...overrides
150
+ } = config;
151
+ return {
152
+ label,
153
+ input: "url",
154
+ hint,
155
+ required: required ?? false,
156
+ defaultValue,
157
+ placeholder,
158
+ db: {
159
+ type: "string",
160
+ size,
161
+ nullable: nullable ?? !required,
162
+ default: defaultValue,
163
+ index: index ?? false
164
+ },
165
+ validation: {
166
+ max: size
167
+ },
168
+ meta: {
169
+ searchable: false,
170
+ ...meta
171
+ },
172
+ ...overrides
173
+ };
174
+ }
175
+
176
+ // src/fields/select.ts
177
+ function createStaticSelect(config) {
178
+ const { label, options, defaultValue, hint, required, nullable, size, index, meta, ...overrides } = config;
179
+ const normalizedOptions = options.map(
180
+ (opt) => typeof opt === "string" ? { value: opt, label: opt } : opt
181
+ );
182
+ return {
183
+ label,
184
+ input: "select",
185
+ hint,
186
+ required: required ?? false,
187
+ defaultValue,
188
+ db: {
189
+ type: "string",
190
+ size: size ?? 50,
191
+ nullable: nullable ?? !required,
192
+ default: defaultValue,
193
+ index: index ?? false
194
+ },
195
+ options: {
196
+ static: normalizedOptions
197
+ },
198
+ meta: {
199
+ searchable: true,
200
+ ...meta
201
+ },
202
+ ...overrides
203
+ };
204
+ }
205
+ function createRelationSelect(config) {
206
+ const {
207
+ label,
208
+ table,
209
+ column = "id",
210
+ endpoint,
211
+ valueField = "id",
212
+ labelField = "name",
213
+ onDelete = "SET NULL",
214
+ hint,
215
+ required,
216
+ nullable,
217
+ size = 26,
218
+ index,
219
+ meta,
220
+ ...overrides
221
+ } = config;
222
+ return {
223
+ label,
224
+ input: "select",
225
+ hint,
226
+ required: required ?? false,
227
+ db: {
228
+ type: "string",
229
+ size,
230
+ nullable: nullable ?? !required,
231
+ index: index ?? true
232
+ },
233
+ relation: {
234
+ table,
235
+ column,
236
+ onDelete
237
+ },
238
+ options: {
239
+ endpoint,
240
+ valueField,
241
+ labelField
242
+ },
243
+ meta: {
244
+ searchable: true,
245
+ ...meta
246
+ },
247
+ ...overrides
248
+ };
249
+ }
250
+ function useSelectField(config) {
251
+ if ("table" in config) {
252
+ return createRelationSelect(config);
253
+ }
254
+ return createStaticSelect(config);
255
+ }
256
+
257
+ // src/fields/number.ts
258
+ function useNumberField(config) {
259
+ const { label, hint, required, defaultValue, nullable, validation, meta, ...overrides } = config;
260
+ return {
261
+ label,
262
+ input: "number",
263
+ hint,
264
+ required: required ?? false,
265
+ defaultValue,
266
+ db: {
267
+ type: "integer",
268
+ nullable: nullable ?? !required,
269
+ ...defaultValue !== void 0 && { default: defaultValue }
270
+ },
271
+ validation,
272
+ meta: {
273
+ sortable: true,
274
+ ...meta
275
+ },
276
+ ...overrides
277
+ };
278
+ }
279
+
280
+ // src/fields/localized.ts
281
+ function useLocalizedField(config) {
282
+ const { label, hint, required = true, meta, ...overrides } = config;
283
+ return {
284
+ label,
285
+ input: "text",
286
+ hint,
287
+ required,
288
+ db: {
289
+ type: "json",
290
+ nullable: !required
291
+ },
292
+ meta: {
293
+ searchable: true,
294
+ sortable: true,
295
+ ...meta
296
+ },
297
+ ...overrides
298
+ };
299
+ }
300
+
301
+ // src/fields/icon.ts
302
+ function useIconField(config) {
303
+ const { label, hint, required, size = 100, placeholder, meta, ...overrides } = config;
304
+ return {
305
+ label,
306
+ input: "icon",
307
+ hint,
308
+ required: required ?? false,
309
+ placeholder,
310
+ db: {
311
+ type: "string",
312
+ size,
313
+ nullable: !required
314
+ },
315
+ meta: {
316
+ ...meta
317
+ },
318
+ ...overrides
319
+ };
320
+ }
321
+
322
+ // src/fields/common.ts
323
+ var idField = useIdField("ulid");
324
+ var userIdField = {
325
+ label: { en: "User", es: "Usuario" },
326
+ input: "select",
327
+ required: true,
328
+ db: { type: "string", size: 26, nullable: false, index: true },
329
+ relation: {
330
+ table: "users",
331
+ column: "id",
332
+ onDelete: "CASCADE"
333
+ },
334
+ meta: { showInForm: false }
335
+ };
336
+ var isActiveField = {
337
+ label: { en: "Active", es: "Activo" },
338
+ input: "switch",
339
+ defaultValue: true,
340
+ db: { type: "boolean", nullable: false, default: true },
341
+ meta: { sortable: true }
342
+ };
343
+ var orderField = {
344
+ label: { en: "Order", es: "Orden" },
345
+ input: "number",
346
+ defaultValue: 0,
347
+ db: { type: "integer", nullable: false, default: 0 },
348
+ meta: { sortable: true }
349
+ };
350
+
351
+ export {
352
+ useIdField,
353
+ useTextField,
354
+ useTextUniqueField,
355
+ useUrlField,
356
+ useSelectField,
357
+ useNumberField,
358
+ useLocalizedField,
359
+ useIconField,
360
+ idField,
361
+ userIdField,
362
+ isActiveField,
363
+ orderField
364
+ };
@@ -0,0 +1,256 @@
1
+ // src/types/localization.ts
2
+ function resolveLocalized(value, locale) {
3
+ if (!value) return "";
4
+ if (typeof value === "string") return value;
5
+ return value[locale] || value["en"] || Object.values(value)[0] || "";
6
+ }
7
+ function getCountLabel(label, labelPlural, count, locale) {
8
+ const resolved = count === 1 ? label : labelPlural || label;
9
+ return resolveLocalized(resolved, locale);
10
+ }
11
+
12
+ // src/types/field.ts
13
+ function refOptions(module, entityOrLabel, labelField) {
14
+ const hasEntity = labelField !== void 0;
15
+ return {
16
+ endpoint: hasEntity ? `/${module}/${entityOrLabel}` : `/${module}`,
17
+ valueField: "id",
18
+ labelField: hasEntity ? labelField : entityOrLabel ?? "name"
19
+ };
20
+ }
21
+ function refMaster(master, options = {}) {
22
+ const table = `mst_${master}`;
23
+ return {
24
+ label: options.label ?? master,
25
+ input: options.input ?? "select",
26
+ required: options.required,
27
+ defaultValue: options.default,
28
+ db: { type: "string", size: 36 },
29
+ relation: { table, column: "id" },
30
+ options: {
31
+ endpoint: `/masters/${master}`,
32
+ valueField: "id",
33
+ labelField: "name"
34
+ }
35
+ };
36
+ }
37
+
38
+ // src/types/manifest.ts
39
+ var CATEGORIES = {
40
+ data: { label: { en: "Data", es: "Datos" }, icon: "mdi:database", order: 1 },
41
+ content: { label: { en: "Content", es: "Contenido" }, icon: "mdi:file-document-outline", order: 2 },
42
+ assets: { label: { en: "Assets", es: "Recursos" }, icon: "mdi:folder-outline", order: 3 },
43
+ messaging: { label: { en: "Messaging", es: "Mensajer\xEDa" }, icon: "mdi:message-outline", order: 4 },
44
+ jobs: { label: { en: "Jobs", es: "Tareas" }, icon: "mdi:clock-outline", order: 5 },
45
+ ai: { label: { en: "AI", es: "IA" }, icon: "mdi:robot-outline", order: 6 },
46
+ analytics: { label: { en: "Analytics", es: "Anal\xEDticas" }, icon: "mdi:chart-line", order: 7 },
47
+ integrations: { label: { en: "Integrations", es: "Integraciones" }, icon: "mdi:puzzle-outline", order: 8 },
48
+ commerce: { label: { en: "Commerce", es: "Comercio" }, icon: "mdi:shopping-outline", order: 9 },
49
+ security: { label: { en: "Security", es: "Seguridad" }, icon: "mdi:shield-lock-outline", order: 10 },
50
+ legal: { label: { en: "Legal", es: "Legal" }, icon: "mdi:scale-balance", order: 11 },
51
+ settings: { label: { en: "Settings", es: "Configuraci\xF3n" }, icon: "mdi:cog-outline", order: 12 }
52
+ };
53
+ var CATEGORY_ORDER = Object.keys(CATEGORIES).sort((a, b) => CATEGORIES[a].order - CATEGORIES[b].order);
54
+
55
+ // src/generators.ts
56
+ function isPersistentEntity(entity) {
57
+ const withoutOwnTable = ["action", "external", "virtual", "computed", "single", "view"];
58
+ return !withoutOwnTable.includes(entity.type ?? "collection");
59
+ }
60
+ function isSingletonEntity(entity) {
61
+ return entity.type === "single";
62
+ }
63
+ function hasTable(entity) {
64
+ return "table" in entity && typeof entity.table === "string";
65
+ }
66
+ function getEntityName(entity) {
67
+ if ("table" in entity) {
68
+ const withoutPrefix = entity.table.replace(/^[a-z]{2,4}_/, "");
69
+ return toPascalCase(toSingular(withoutPrefix));
70
+ } else if ("key" in entity) {
71
+ return toPascalCase(entity.key);
72
+ } else {
73
+ const label = resolveLocalized(entity.label, "en");
74
+ return toSingular(label).replace(/\s+/g, "");
75
+ }
76
+ }
77
+ function getEntitySubject(entity) {
78
+ return entity.casl?.subject ?? tableToSubject(entity.table);
79
+ }
80
+ function toPascalCase(str) {
81
+ return str.split("_").map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
82
+ }
83
+ function toSingular(str) {
84
+ if (str.endsWith("ies")) return str.slice(0, -3) + "y";
85
+ if (str.endsWith("s")) return str.slice(0, -1);
86
+ return str;
87
+ }
88
+ function tableToSubject(table) {
89
+ const match = table.match(/^([a-z]{2,4})_(.+)$/);
90
+ if (!match) {
91
+ const withoutPrefix = table.replace(/^[a-z]{2,4}_/, "");
92
+ return toPascalCase(toSingular(withoutPrefix));
93
+ }
94
+ const [, prefix, rest] = match;
95
+ const prefixPascal = prefix.charAt(0).toUpperCase() + prefix.slice(1);
96
+ return prefixPascal + toPascalCase(toSingular(rest));
97
+ }
98
+
99
+ // src/oidc/oidc-client.ts
100
+ import { ofetch } from "ofetch";
101
+ import * as jose from "jose";
102
+ function createOidcClient() {
103
+ const jwksCache = /* @__PURE__ */ new Map();
104
+ async function discover(issuerUrl) {
105
+ const url = `${issuerUrl.replace(/\/$/, "")}/.well-known/openid-configuration`;
106
+ return ofetch(url);
107
+ }
108
+ function buildAuthorizationUrl(authorizationEndpoint, params) {
109
+ const url = new URL(authorizationEndpoint);
110
+ url.searchParams.set("client_id", params.clientId);
111
+ url.searchParams.set("redirect_uri", params.redirectUri);
112
+ url.searchParams.set("response_type", params.responseType || "code");
113
+ url.searchParams.set("scope", params.scopes.join(" "));
114
+ url.searchParams.set("state", params.state);
115
+ url.searchParams.set("nonce", params.nonce);
116
+ if (params.hostedDomain) {
117
+ url.searchParams.set("hd", params.hostedDomain);
118
+ }
119
+ return url.toString();
120
+ }
121
+ async function exchangeCode(params) {
122
+ const body = new URLSearchParams({
123
+ grant_type: "authorization_code",
124
+ client_id: params.clientId,
125
+ client_secret: params.clientSecret,
126
+ code: params.code,
127
+ redirect_uri: params.redirectUri
128
+ });
129
+ return ofetch(params.tokenEndpoint, {
130
+ method: "POST",
131
+ headers: {
132
+ "Content-Type": "application/x-www-form-urlencoded"
133
+ },
134
+ body: body.toString()
135
+ });
136
+ }
137
+ async function getUserInfo(accessToken, userInfoEndpoint) {
138
+ return ofetch(userInfoEndpoint, {
139
+ headers: {
140
+ Authorization: `Bearer ${accessToken}`
141
+ }
142
+ });
143
+ }
144
+ function getJwksVerifier(jwksUri) {
145
+ let verifier = jwksCache.get(jwksUri);
146
+ if (!verifier) {
147
+ verifier = jose.createRemoteJWKSet(new URL(jwksUri));
148
+ jwksCache.set(jwksUri, verifier);
149
+ }
150
+ return verifier;
151
+ }
152
+ async function validateIdToken(idToken, config) {
153
+ const jwks = getJwksVerifier(config.jwksUri);
154
+ const { payload } = await jose.jwtVerify(idToken, jwks, {
155
+ issuer: config.issuer,
156
+ audience: config.clientId
157
+ });
158
+ if (config.nonce && payload["nonce"] !== config.nonce) {
159
+ throw new Error("Invalid nonce in ID token");
160
+ }
161
+ return payload;
162
+ }
163
+ return {
164
+ discover,
165
+ buildAuthorizationUrl,
166
+ exchangeCode,
167
+ getUserInfo,
168
+ validateIdToken
169
+ };
170
+ }
171
+ var _client = null;
172
+ function getOidcClient() {
173
+ if (!_client) {
174
+ _client = createOidcClient();
175
+ }
176
+ return _client;
177
+ }
178
+
179
+ // src/oidc/state-manager.ts
180
+ var DEFAULT_TTL_MS = 10 * 60 * 1e3;
181
+ function createStateManager(ttlMs = DEFAULT_TTL_MS) {
182
+ const stateStore = /* @__PURE__ */ new Map();
183
+ function store(state, data) {
184
+ stateStore.set(state, data);
185
+ }
186
+ function verify(state) {
187
+ const stateData = stateStore.get(state);
188
+ if (!stateData) {
189
+ return null;
190
+ }
191
+ if (Date.now() - stateData.createdAt > ttlMs) {
192
+ stateStore.delete(state);
193
+ return null;
194
+ }
195
+ return stateData;
196
+ }
197
+ function clear(state) {
198
+ stateStore.delete(state);
199
+ }
200
+ function getTtl() {
201
+ return ttlMs;
202
+ }
203
+ return {
204
+ store,
205
+ verify,
206
+ clear,
207
+ getTtl
208
+ };
209
+ }
210
+
211
+ // src/oidc/domain-filter.ts
212
+ function checkAllowedDomain(email, allowedDomainsJson) {
213
+ const emailDomain = email.split("@")[1]?.toLowerCase();
214
+ if (!emailDomain) {
215
+ return { allowed: false, domain: "" };
216
+ }
217
+ if (!allowedDomainsJson) {
218
+ return { allowed: true, domain: emailDomain };
219
+ }
220
+ try {
221
+ const allowedDomains = JSON.parse(allowedDomainsJson);
222
+ if (!Array.isArray(allowedDomains) || allowedDomains.length === 0) {
223
+ return { allowed: true, domain: emailDomain };
224
+ }
225
+ const normalizedDomains = allowedDomains.map((d) => d.toLowerCase());
226
+ const allowed = normalizedDomains.includes(emailDomain);
227
+ return { allowed, domain: emailDomain };
228
+ } catch {
229
+ return { allowed: true, domain: emailDomain };
230
+ }
231
+ }
232
+ function assertAllowedDomain(email, allowedDomainsJson, errorClass = Error) {
233
+ const result = checkAllowedDomain(email, allowedDomainsJson);
234
+ if (!result.allowed) {
235
+ throw new errorClass(`Email domain '${result.domain}' is not allowed`);
236
+ }
237
+ }
238
+
239
+ export {
240
+ resolveLocalized,
241
+ getCountLabel,
242
+ refOptions,
243
+ refMaster,
244
+ CATEGORIES,
245
+ CATEGORY_ORDER,
246
+ isPersistentEntity,
247
+ isSingletonEntity,
248
+ hasTable,
249
+ getEntityName,
250
+ getEntitySubject,
251
+ createOidcClient,
252
+ getOidcClient,
253
+ createStateManager,
254
+ checkAllowedDomain,
255
+ assertAllowedDomain
256
+ };