@objectstack/runtime 3.2.0 → 3.2.2
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +20 -0
- package/dist/index.cjs +156 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.js +156 -26
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/http-dispatcher.test.ts +273 -3
- package/src/http-dispatcher.ts +193 -32
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/runtime@3.2.
|
|
2
|
+
> @objectstack/runtime@3.2.2 build /home/runner/work/spec/spec/packages/runtime
|
|
3
3
|
> tsup --config ../../tsup.config.ts
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
14
|
-
[32mESM[39m [1mdist/index.js.map [22m[
|
|
15
|
-
[32mESM[39m ⚡️ Build success in
|
|
16
|
-
[32mCJS[39m [1mdist/index.cjs [22m[
|
|
17
|
-
[32mCJS[39m [1mdist/index.cjs.map [22m[
|
|
18
|
-
[32mCJS[39m ⚡️ Build success in
|
|
13
|
+
[32mESM[39m [1mdist/index.js [22m[32m60.47 KB[39m
|
|
14
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m133.78 KB[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 148ms
|
|
16
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m62.41 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/index.cjs.map [22m[32m133.84 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 174ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.cts [22m[
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 7626ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m21.77 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.cts [22m[32m21.77 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @objectstack/runtime
|
|
2
2
|
|
|
3
|
+
## 3.2.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [46defbb]
|
|
8
|
+
- @objectstack/spec@3.2.2
|
|
9
|
+
- @objectstack/core@3.2.2
|
|
10
|
+
- @objectstack/rest@3.2.2
|
|
11
|
+
- @objectstack/types@3.2.2
|
|
12
|
+
|
|
13
|
+
## 3.2.1
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [850b546]
|
|
18
|
+
- @objectstack/spec@3.2.1
|
|
19
|
+
- @objectstack/core@3.2.1
|
|
20
|
+
- @objectstack/rest@3.2.1
|
|
21
|
+
- @objectstack/types@3.2.1
|
|
22
|
+
|
|
3
23
|
## 3.2.0
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/dist/index.cjs
CHANGED
|
@@ -207,6 +207,16 @@ var AppPlugin = class {
|
|
|
207
207
|
// src/http-dispatcher.ts
|
|
208
208
|
var import_core2 = require("@objectstack/core");
|
|
209
209
|
var import_system = require("@objectstack/spec/system");
|
|
210
|
+
function randomUUID() {
|
|
211
|
+
if (globalThis.crypto && typeof globalThis.crypto.randomUUID === "function") {
|
|
212
|
+
return globalThis.crypto.randomUUID();
|
|
213
|
+
}
|
|
214
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
215
|
+
const r = Math.random() * 16 | 0;
|
|
216
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
217
|
+
return v.toString(16);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
210
220
|
var HttpDispatcher = class {
|
|
211
221
|
// Casting to any to access dynamic props like broker, services, graphql
|
|
212
222
|
constructor(kernel) {
|
|
@@ -349,9 +359,64 @@ var HttpDispatcher = class {
|
|
|
349
359
|
}
|
|
350
360
|
const normalizedPath = path.replace(/^\/+/, "");
|
|
351
361
|
if (normalizedPath === "login" && method.toUpperCase() === "POST") {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
362
|
+
try {
|
|
363
|
+
const broker = this.ensureBroker();
|
|
364
|
+
const data = await broker.call("auth.login", body, { request: context.request });
|
|
365
|
+
return { handled: true, response: { status: 200, body: data } };
|
|
366
|
+
} catch (error) {
|
|
367
|
+
const statusCode = error?.statusCode ?? error?.status;
|
|
368
|
+
if (statusCode !== 500 || !error?.message?.includes("Broker not available")) {
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return this.mockAuthFallback(normalizedPath, method, body);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Provides mock auth responses for core better-auth endpoints when
|
|
377
|
+
* AuthPlugin is not loaded (e.g. MSW/browser-only environments).
|
|
378
|
+
* This ensures registration/sign-in flows do not 404 in mock mode.
|
|
379
|
+
*/
|
|
380
|
+
mockAuthFallback(path, method, body) {
|
|
381
|
+
const m = method.toUpperCase();
|
|
382
|
+
const MOCK_SESSION_EXPIRY_MS = 864e5;
|
|
383
|
+
if ((path === "sign-up/email" || path === "register") && m === "POST") {
|
|
384
|
+
const id = `mock_${randomUUID()}`;
|
|
385
|
+
return {
|
|
386
|
+
handled: true,
|
|
387
|
+
response: {
|
|
388
|
+
status: 200,
|
|
389
|
+
body: {
|
|
390
|
+
user: { id, name: body?.name || "Mock User", email: body?.email || "mock@test.local", emailVerified: false, createdAt: (/* @__PURE__ */ new Date()).toISOString(), updatedAt: (/* @__PURE__ */ new Date()).toISOString() },
|
|
391
|
+
session: { id: `session_${id}`, userId: id, token: `mock_token_${id}`, expiresAt: new Date(Date.now() + MOCK_SESSION_EXPIRY_MS).toISOString() }
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
if ((path === "sign-in/email" || path === "login") && m === "POST") {
|
|
397
|
+
const id = `mock_${randomUUID()}`;
|
|
398
|
+
return {
|
|
399
|
+
handled: true,
|
|
400
|
+
response: {
|
|
401
|
+
status: 200,
|
|
402
|
+
body: {
|
|
403
|
+
user: { id, name: "Mock User", email: body?.email || "mock@test.local", emailVerified: true, createdAt: (/* @__PURE__ */ new Date()).toISOString(), updatedAt: (/* @__PURE__ */ new Date()).toISOString() },
|
|
404
|
+
session: { id: `session_${id}`, userId: id, token: `mock_token_${id}`, expiresAt: new Date(Date.now() + MOCK_SESSION_EXPIRY_MS).toISOString() }
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
if (path === "get-session" && m === "GET") {
|
|
410
|
+
return {
|
|
411
|
+
handled: true,
|
|
412
|
+
response: { status: 200, body: { session: null, user: null } }
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
if (path === "sign-out" && m === "POST") {
|
|
416
|
+
return {
|
|
417
|
+
handled: true,
|
|
418
|
+
response: { status: 200, body: { success: true } }
|
|
419
|
+
};
|
|
355
420
|
}
|
|
356
421
|
return { handled: false };
|
|
357
422
|
}
|
|
@@ -364,7 +429,7 @@ var HttpDispatcher = class {
|
|
|
364
429
|
const broker = this.ensureBroker();
|
|
365
430
|
const parts = path.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
366
431
|
if (parts[0] === "types") {
|
|
367
|
-
const protocol =
|
|
432
|
+
const protocol = await this.resolveService("protocol");
|
|
368
433
|
if (protocol && typeof protocol.getMetaTypes === "function") {
|
|
369
434
|
const result = await protocol.getMetaTypes({});
|
|
370
435
|
return { handled: true, response: this.success(result) };
|
|
@@ -394,7 +459,7 @@ var HttpDispatcher = class {
|
|
|
394
459
|
if (parts.length === 2) {
|
|
395
460
|
const [type, name] = parts;
|
|
396
461
|
if (method === "PUT" && body) {
|
|
397
|
-
const protocol =
|
|
462
|
+
const protocol = await this.resolveService("protocol");
|
|
398
463
|
if (protocol && typeof protocol.saveMetaItem === "function") {
|
|
399
464
|
try {
|
|
400
465
|
const result = await protocol.saveMetaItem({ type, name, item: body });
|
|
@@ -416,7 +481,7 @@ var HttpDispatcher = class {
|
|
|
416
481
|
return { handled: true, response: this.success(data2) };
|
|
417
482
|
}
|
|
418
483
|
const singularType = type.endsWith("s") ? type.slice(0, -1) : type;
|
|
419
|
-
const protocol =
|
|
484
|
+
const protocol = await this.resolveService("protocol");
|
|
420
485
|
if (protocol && typeof protocol.getMetaItem === "function") {
|
|
421
486
|
try {
|
|
422
487
|
const data2 = await protocol.getMetaItem({ type: singularType, name });
|
|
@@ -434,7 +499,7 @@ var HttpDispatcher = class {
|
|
|
434
499
|
if (parts.length === 1) {
|
|
435
500
|
const typeOrName = parts[0];
|
|
436
501
|
const packageId = query?.package || void 0;
|
|
437
|
-
const protocol =
|
|
502
|
+
const protocol = await this.resolveService("protocol");
|
|
438
503
|
if (protocol && typeof protocol.getMetaItems === "function") {
|
|
439
504
|
try {
|
|
440
505
|
const data = await protocol.getMetaItems({ type: typeOrName, packageId });
|
|
@@ -463,7 +528,7 @@ var HttpDispatcher = class {
|
|
|
463
528
|
}
|
|
464
529
|
}
|
|
465
530
|
if (parts.length === 0) {
|
|
466
|
-
const protocol =
|
|
531
|
+
const protocol = await this.resolveService("protocol");
|
|
467
532
|
if (protocol && typeof protocol.getMetaTypes === "function") {
|
|
468
533
|
const result = await protocol.getMetaTypes({});
|
|
469
534
|
return { handled: true, response: this.success(result) };
|
|
@@ -536,25 +601,72 @@ var HttpDispatcher = class {
|
|
|
536
601
|
* Handles Analytics requests
|
|
537
602
|
* path: sub-path after /analytics/
|
|
538
603
|
*/
|
|
539
|
-
async handleAnalytics(path, method, body,
|
|
604
|
+
async handleAnalytics(path, method, body, _context) {
|
|
540
605
|
const analyticsService = await this.getService(import_system.CoreServiceName.enum.analytics);
|
|
541
606
|
if (!analyticsService) return { handled: false };
|
|
542
607
|
const m = method.toUpperCase();
|
|
543
608
|
const subPath = path.replace(/^\/+/, "");
|
|
544
609
|
if (subPath === "query" && m === "POST") {
|
|
545
|
-
const result = await analyticsService.query(body
|
|
610
|
+
const result = await analyticsService.query(body);
|
|
546
611
|
return { handled: true, response: this.success(result) };
|
|
547
612
|
}
|
|
548
613
|
if (subPath === "meta" && m === "GET") {
|
|
549
|
-
const result = await analyticsService.
|
|
614
|
+
const result = await analyticsService.getMeta();
|
|
550
615
|
return { handled: true, response: this.success(result) };
|
|
551
616
|
}
|
|
552
617
|
if (subPath === "sql" && m === "POST") {
|
|
553
|
-
const result = await analyticsService.generateSql(body
|
|
618
|
+
const result = await analyticsService.generateSql(body);
|
|
554
619
|
return { handled: true, response: this.success(result) };
|
|
555
620
|
}
|
|
556
621
|
return { handled: false };
|
|
557
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* Handles i18n requests
|
|
625
|
+
* path: sub-path after /i18n/
|
|
626
|
+
*
|
|
627
|
+
* Routes:
|
|
628
|
+
* GET /locales → getLocales
|
|
629
|
+
* GET /translations/:locale → getTranslations (locale from path)
|
|
630
|
+
* GET /translations?locale=xx → getTranslations (locale from query)
|
|
631
|
+
* GET /labels/:object/:locale → getFieldLabels (both from path)
|
|
632
|
+
* GET /labels/:object?locale=xx → getFieldLabels (locale from query)
|
|
633
|
+
*/
|
|
634
|
+
async handleI18n(path, method, query, _context) {
|
|
635
|
+
const i18nService = await this.getService(import_system.CoreServiceName.enum.i18n);
|
|
636
|
+
if (!i18nService) return { handled: true, response: this.error("i18n service not available", 501) };
|
|
637
|
+
const m = method.toUpperCase();
|
|
638
|
+
const parts = path.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
639
|
+
if (m !== "GET") return { handled: false };
|
|
640
|
+
if (parts[0] === "locales" && parts.length === 1) {
|
|
641
|
+
const locales = i18nService.getLocales();
|
|
642
|
+
return { handled: true, response: this.success({ locales }) };
|
|
643
|
+
}
|
|
644
|
+
if (parts[0] === "translations") {
|
|
645
|
+
const locale = parts[1] ? decodeURIComponent(parts[1]) : query?.locale;
|
|
646
|
+
if (!locale) return { handled: true, response: this.error("Missing locale parameter", 400) };
|
|
647
|
+
const translations = i18nService.getTranslations(locale);
|
|
648
|
+
return { handled: true, response: this.success({ locale, translations }) };
|
|
649
|
+
}
|
|
650
|
+
if (parts[0] === "labels" && parts.length >= 2) {
|
|
651
|
+
const objectName = decodeURIComponent(parts[1]);
|
|
652
|
+
const locale = parts[2] ? decodeURIComponent(parts[2]) : query?.locale;
|
|
653
|
+
if (!locale) return { handled: true, response: this.error("Missing locale parameter", 400) };
|
|
654
|
+
if (typeof i18nService.getFieldLabels === "function") {
|
|
655
|
+
const labels2 = i18nService.getFieldLabels(objectName, locale);
|
|
656
|
+
return { handled: true, response: this.success({ object: objectName, locale, labels: labels2 }) };
|
|
657
|
+
}
|
|
658
|
+
const translations = i18nService.getTranslations(locale);
|
|
659
|
+
const prefix = `o.${objectName}.fields.`;
|
|
660
|
+
const labels = {};
|
|
661
|
+
for (const [key, value] of Object.entries(translations)) {
|
|
662
|
+
if (key.startsWith(prefix)) {
|
|
663
|
+
labels[key.substring(prefix.length)] = value;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return { handled: true, response: this.success({ object: objectName, locale, labels }) };
|
|
667
|
+
}
|
|
668
|
+
return { handled: false };
|
|
669
|
+
}
|
|
558
670
|
/**
|
|
559
671
|
* Handles Package Management requests
|
|
560
672
|
*
|
|
@@ -745,7 +857,7 @@ var HttpDispatcher = class {
|
|
|
745
857
|
if (parts[0] === "view" && parts[1]) {
|
|
746
858
|
const objectName = parts[1];
|
|
747
859
|
const type = parts[2] || query?.type || "list";
|
|
748
|
-
const protocol =
|
|
860
|
+
const protocol = await this.resolveService("protocol");
|
|
749
861
|
if (protocol && typeof protocol.getUiView === "function") {
|
|
750
862
|
try {
|
|
751
863
|
const result = await protocol.getUiView({ object: objectName, type });
|
|
@@ -859,33 +971,48 @@ var HttpDispatcher = class {
|
|
|
859
971
|
return this.kernel.services || {};
|
|
860
972
|
}
|
|
861
973
|
async getService(name) {
|
|
862
|
-
|
|
863
|
-
return await this.kernel.getService(name);
|
|
864
|
-
}
|
|
865
|
-
const services = this.getServicesMap();
|
|
866
|
-
return services[name];
|
|
974
|
+
return this.resolveService(name);
|
|
867
975
|
}
|
|
868
976
|
/**
|
|
869
|
-
*
|
|
870
|
-
*
|
|
977
|
+
* Resolve any service by name, supporting async factories.
|
|
978
|
+
* Fallback chain: getServiceAsync → getService (sync) → context.getService → services map.
|
|
979
|
+
* Only returns when a non-null service is found; otherwise falls through to the next step.
|
|
871
980
|
*/
|
|
872
|
-
async
|
|
981
|
+
async resolveService(name) {
|
|
982
|
+
if (typeof this.kernel.getServiceAsync === "function") {
|
|
983
|
+
try {
|
|
984
|
+
const svc = await this.kernel.getServiceAsync(name);
|
|
985
|
+
if (svc != null) return svc;
|
|
986
|
+
} catch {
|
|
987
|
+
}
|
|
988
|
+
}
|
|
873
989
|
if (typeof this.kernel.getService === "function") {
|
|
874
990
|
try {
|
|
875
|
-
const svc = await this.kernel.getService(
|
|
876
|
-
if (svc
|
|
991
|
+
const svc = await this.kernel.getService(name);
|
|
992
|
+
if (svc != null) return svc;
|
|
877
993
|
} catch {
|
|
878
994
|
}
|
|
879
995
|
}
|
|
880
996
|
if (this.kernel?.context?.getService) {
|
|
881
997
|
try {
|
|
882
|
-
const svc = await this.kernel.context.getService(
|
|
883
|
-
if (svc
|
|
998
|
+
const svc = await this.kernel.context.getService(name);
|
|
999
|
+
if (svc != null) return svc;
|
|
884
1000
|
} catch {
|
|
885
1001
|
}
|
|
886
1002
|
}
|
|
887
1003
|
const services = this.getServicesMap();
|
|
888
|
-
|
|
1004
|
+
return services[name];
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Get the ObjectQL service which provides access to SchemaRegistry.
|
|
1008
|
+
* Tries multiple access patterns since kernel structure varies.
|
|
1009
|
+
*/
|
|
1010
|
+
async getObjectQLService() {
|
|
1011
|
+
try {
|
|
1012
|
+
const svc = await this.resolveService("objectql");
|
|
1013
|
+
if (svc?.registry) return svc;
|
|
1014
|
+
} catch {
|
|
1015
|
+
}
|
|
889
1016
|
return null;
|
|
890
1017
|
}
|
|
891
1018
|
capitalize(s) {
|
|
@@ -931,6 +1058,9 @@ var HttpDispatcher = class {
|
|
|
931
1058
|
if (cleanPath.startsWith("/packages")) {
|
|
932
1059
|
return this.handlePackages(cleanPath.substring(9), method, body, query, context);
|
|
933
1060
|
}
|
|
1061
|
+
if (cleanPath.startsWith("/i18n")) {
|
|
1062
|
+
return this.handleI18n(cleanPath.substring(5), method, query, context);
|
|
1063
|
+
}
|
|
934
1064
|
if (cleanPath === "/openapi.json" && method === "GET") {
|
|
935
1065
|
const broker = this.ensureBroker();
|
|
936
1066
|
try {
|