@objectstack/runtime 3.2.0 → 3.2.1

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @objectstack/runtime@3.2.0 build /home/runner/work/spec/spec/packages/runtime
2
+ > @objectstack/runtime@3.2.1 build /home/runner/work/spec/spec/packages/runtime
3
3
  > tsup --config ../../tsup.config.ts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -10,13 +10,13 @@
10
10
  CLI Cleaning output folder
11
11
  ESM Build start
12
12
  CJS Build start
13
- ESM dist/index.js 55.51 KB
14
- ESM dist/index.js.map 123.66 KB
15
- ESM ⚡️ Build success in 91ms
16
- CJS dist/index.cjs 57.43 KB
17
- CJS dist/index.cjs.map 123.73 KB
18
- CJS ⚡️ Build success in 92ms
13
+ ESM dist/index.js 60.56 KB
14
+ ESM dist/index.js.map 133.94 KB
15
+ ESM ⚡️ Build success in 138ms
16
+ CJS dist/index.cjs 62.50 KB
17
+ CJS dist/index.cjs.map 134.00 KB
18
+ CJS ⚡️ Build success in 145ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 8046ms
21
- DTS dist/index.d.ts 20.64 KB
22
- DTS dist/index.d.cts 20.64 KB
20
+ DTS ⚡️ Build success in 7219ms
21
+ DTS dist/index.d.ts 21.77 KB
22
+ DTS dist/index.d.cts 21.77 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @objectstack/runtime
2
2
 
3
+ ## 3.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [850b546]
8
+ - @objectstack/spec@3.2.1
9
+ - @objectstack/core@3.2.1
10
+ - @objectstack/rest@3.2.1
11
+ - @objectstack/types@3.2.1
12
+
3
13
  ## 3.2.0
4
14
 
5
15
  ### 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
- const broker = this.ensureBroker();
353
- const data = await broker.call("auth.login", body, { request: context.request });
354
- return { handled: true, response: { status: 200, body: data } };
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 = this.kernel?.context?.getService ? await this.kernel.context.getService("protocol") : null;
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 = this.kernel?.context?.getService ? await this.kernel.context.getService("protocol") : null;
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 = this.kernel?.context?.getService ? await this.kernel.context.getService("protocol") : null;
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 = this.kernel?.context?.getService ? await this.kernel.context.getService("protocol") : null;
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 = this.kernel?.context?.getService ? await this.kernel.context.getService("protocol") : null;
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) };
@@ -555,6 +620,53 @@ var HttpDispatcher = class {
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 = this.kernel?.context?.getService ? await this.kernel.context.getService("protocol") : null;
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
- if (typeof this.kernel.getService === "function") {
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
- * Get the ObjectQL service which provides access to SchemaRegistry.
870
- * Tries multiple access patterns since kernel structure varies.
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 getObjectQLService() {
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("objectql");
876
- if (svc?.registry) return 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("objectql");
883
- if (svc?.registry) return 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
- if (services["objectql"]?.registry) return services["objectql"];
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 {