@objectstack/runtime 3.2.5 → 3.2.6
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 +9 -0
- package/dist/index.cjs +173 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -4
- package/dist/index.d.ts +18 -4
- package/dist/index.js +174 -30
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/app-plugin.test.ts +167 -0
- package/src/app-plugin.ts +96 -4
- package/src/dispatcher-plugin.ts +33 -3
- package/src/http-dispatcher.test.ts +254 -0
- package/src/http-dispatcher.ts +85 -29
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/runtime@3.2.
|
|
2
|
+
> @objectstack/runtime@3.2.6 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
|
-
[
|
|
14
|
-
[
|
|
15
|
-
[
|
|
16
|
-
[
|
|
17
|
-
[
|
|
18
|
-
[
|
|
13
|
+
[32mESM[39m [1mdist/index.js [22m[32m85.70 KB[39m
|
|
14
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m184.26 KB[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 64ms
|
|
16
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m88.28 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/index.cjs.map [22m[32m184.34 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 64ms
|
|
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 2485ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m24.35 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.cts [22m[32m24.35 KB[39m
|
package/CHANGELOG.md
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -653,7 +653,11 @@ var AppPlugin = class {
|
|
|
653
653
|
this.start = async (ctx) => {
|
|
654
654
|
const sys = this.bundle.manifest || this.bundle;
|
|
655
655
|
const appId = sys.id || sys.name;
|
|
656
|
-
|
|
656
|
+
let ql;
|
|
657
|
+
try {
|
|
658
|
+
ql = ctx.getService("objectql");
|
|
659
|
+
} catch {
|
|
660
|
+
}
|
|
657
661
|
if (!ql) {
|
|
658
662
|
ctx.logger.warn("ObjectQL engine service not found", {
|
|
659
663
|
appName: this.name,
|
|
@@ -687,6 +691,7 @@ var AppPlugin = class {
|
|
|
687
691
|
} else {
|
|
688
692
|
ctx.logger.debug("No runtime.onEnable function found", { appId });
|
|
689
693
|
}
|
|
694
|
+
this.loadTranslations(ctx, appId);
|
|
690
695
|
const seedDatasets = [];
|
|
691
696
|
if (Array.isArray(this.bundle.data)) {
|
|
692
697
|
seedDatasets.push(...this.bundle.data);
|
|
@@ -757,6 +762,68 @@ var AppPlugin = class {
|
|
|
757
762
|
this.name = `plugin.app.${appId}`;
|
|
758
763
|
this.version = sys.version;
|
|
759
764
|
}
|
|
765
|
+
/**
|
|
766
|
+
* Auto-load i18n translation bundles from the app config into the
|
|
767
|
+
* kernel's i18n service. Handles both `translations` (array of
|
|
768
|
+
* TranslationBundle) and `i18n` config (default locale, etc.).
|
|
769
|
+
*
|
|
770
|
+
* Gracefully skips when the i18n service is not registered —
|
|
771
|
+
* this keeps AppPlugin resilient across server/dev/mock environments.
|
|
772
|
+
*/
|
|
773
|
+
loadTranslations(ctx, appId) {
|
|
774
|
+
let i18nService;
|
|
775
|
+
try {
|
|
776
|
+
i18nService = ctx.getService("i18n");
|
|
777
|
+
} catch {
|
|
778
|
+
}
|
|
779
|
+
const bundles = [];
|
|
780
|
+
if (Array.isArray(this.bundle.translations)) {
|
|
781
|
+
bundles.push(...this.bundle.translations);
|
|
782
|
+
}
|
|
783
|
+
const manifest = this.bundle.manifest || this.bundle;
|
|
784
|
+
if (manifest && Array.isArray(manifest.translations) && manifest.translations !== this.bundle.translations) {
|
|
785
|
+
bundles.push(...manifest.translations);
|
|
786
|
+
}
|
|
787
|
+
if (!i18nService) {
|
|
788
|
+
if (bundles.length > 0) {
|
|
789
|
+
ctx.logger.warn(
|
|
790
|
+
`[i18n] App "${appId}" has ${bundles.length} translation bundle(s) but no i18n service is registered. Translations will not be served via REST API. Register I18nServicePlugin from @objectstack/service-i18n, or use DevPlugin which auto-detects translations and registers the i18n service automatically.`
|
|
791
|
+
);
|
|
792
|
+
} else {
|
|
793
|
+
ctx.logger.debug("[i18n] No i18n service registered; skipping translation loading", { appId });
|
|
794
|
+
}
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
const i18nConfig = this.bundle.i18n || (this.bundle.manifest || this.bundle)?.i18n;
|
|
798
|
+
if (i18nConfig?.defaultLocale && typeof i18nService.setDefaultLocale === "function") {
|
|
799
|
+
i18nService.setDefaultLocale(i18nConfig.defaultLocale);
|
|
800
|
+
ctx.logger.debug("[i18n] Set default locale", { appId, locale: i18nConfig.defaultLocale });
|
|
801
|
+
}
|
|
802
|
+
if (bundles.length === 0) {
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
let loadedLocales = 0;
|
|
806
|
+
for (const bundle of bundles) {
|
|
807
|
+
for (const [locale, data] of Object.entries(bundle)) {
|
|
808
|
+
if (data && typeof data === "object") {
|
|
809
|
+
try {
|
|
810
|
+
i18nService.loadTranslations(locale, data);
|
|
811
|
+
loadedLocales++;
|
|
812
|
+
} catch (err) {
|
|
813
|
+
ctx.logger.warn("[i18n] Failed to load translations", { appId, locale, error: err.message });
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
const svcAny = i18nService;
|
|
819
|
+
if (svcAny._fallback || svcAny._dev) {
|
|
820
|
+
ctx.logger.info(
|
|
821
|
+
`[i18n] Loaded ${loadedLocales} locale(s) into in-memory i18n fallback for "${appId}". For production, consider registering I18nServicePlugin from @objectstack/service-i18n.`
|
|
822
|
+
);
|
|
823
|
+
} else {
|
|
824
|
+
ctx.logger.info("[i18n] Loaded translation bundles", { appId, bundles: bundles.length, locales: loadedLocales });
|
|
825
|
+
}
|
|
826
|
+
}
|
|
760
827
|
};
|
|
761
828
|
|
|
762
829
|
// src/http-dispatcher.ts
|
|
@@ -796,25 +863,61 @@ var HttpDispatcher = class {
|
|
|
796
863
|
return this.kernel.broker;
|
|
797
864
|
}
|
|
798
865
|
/**
|
|
799
|
-
* Generates the discovery JSON response for the API root
|
|
866
|
+
* Generates the discovery JSON response for the API root.
|
|
867
|
+
*
|
|
868
|
+
* Uses the same async `resolveService()` fallback chain that request
|
|
869
|
+
* handlers use, so the reported service status is always consistent
|
|
870
|
+
* with the actual runtime availability.
|
|
800
871
|
*/
|
|
801
|
-
getDiscoveryInfo(prefix) {
|
|
802
|
-
const
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
872
|
+
async getDiscoveryInfo(prefix) {
|
|
873
|
+
const [
|
|
874
|
+
authSvc,
|
|
875
|
+
graphqlSvc,
|
|
876
|
+
searchSvc,
|
|
877
|
+
realtimeSvc,
|
|
878
|
+
filesSvc,
|
|
879
|
+
analyticsSvc,
|
|
880
|
+
workflowSvc,
|
|
881
|
+
aiSvc,
|
|
882
|
+
notificationSvc,
|
|
883
|
+
i18nSvc,
|
|
884
|
+
uiSvc,
|
|
885
|
+
automationSvc,
|
|
886
|
+
cacheSvc,
|
|
887
|
+
queueSvc,
|
|
888
|
+
jobSvc
|
|
889
|
+
] = await Promise.all([
|
|
890
|
+
this.resolveService(import_system.CoreServiceName.enum.auth),
|
|
891
|
+
this.resolveService(import_system.CoreServiceName.enum.graphql),
|
|
892
|
+
this.resolveService(import_system.CoreServiceName.enum.search),
|
|
893
|
+
this.resolveService(import_system.CoreServiceName.enum.realtime),
|
|
894
|
+
this.resolveService(import_system.CoreServiceName.enum["file-storage"]),
|
|
895
|
+
this.resolveService(import_system.CoreServiceName.enum.analytics),
|
|
896
|
+
this.resolveService(import_system.CoreServiceName.enum.workflow),
|
|
897
|
+
this.resolveService(import_system.CoreServiceName.enum.ai),
|
|
898
|
+
this.resolveService(import_system.CoreServiceName.enum.notification),
|
|
899
|
+
this.resolveService(import_system.CoreServiceName.enum.i18n),
|
|
900
|
+
this.resolveService(import_system.CoreServiceName.enum.ui),
|
|
901
|
+
this.resolveService(import_system.CoreServiceName.enum.automation),
|
|
902
|
+
this.resolveService(import_system.CoreServiceName.enum.cache),
|
|
903
|
+
this.resolveService(import_system.CoreServiceName.enum.queue),
|
|
904
|
+
this.resolveService(import_system.CoreServiceName.enum.job)
|
|
905
|
+
]);
|
|
906
|
+
const hasAuth = !!authSvc;
|
|
907
|
+
const hasGraphQL = !!(graphqlSvc || this.kernel.graphql);
|
|
908
|
+
const hasSearch = !!searchSvc;
|
|
909
|
+
const hasWebSockets = !!realtimeSvc;
|
|
910
|
+
const hasFiles = !!filesSvc;
|
|
911
|
+
const hasAnalytics = !!analyticsSvc;
|
|
912
|
+
const hasWorkflow = !!workflowSvc;
|
|
913
|
+
const hasAi = !!aiSvc;
|
|
914
|
+
const hasNotification = !!notificationSvc;
|
|
915
|
+
const hasI18n = !!i18nSvc;
|
|
916
|
+
const hasUi = !!uiSvc;
|
|
917
|
+
const hasAutomation = !!automationSvc;
|
|
918
|
+
const hasCache = !!cacheSvc;
|
|
919
|
+
const hasQueue = !!queueSvc;
|
|
920
|
+
const hasJob = !!jobSvc;
|
|
818
921
|
const routes = {
|
|
819
922
|
data: `${prefix}/data`,
|
|
820
923
|
metadata: `${prefix}/meta`,
|
|
@@ -842,6 +945,16 @@ var HttpDispatcher = class {
|
|
|
842
945
|
status: "unavailable",
|
|
843
946
|
message: `Install a ${name} plugin to enable`
|
|
844
947
|
});
|
|
948
|
+
let locale = { default: "en", supported: ["en"], timezone: "UTC" };
|
|
949
|
+
if (hasI18n && i18nSvc) {
|
|
950
|
+
const defaultLocale = typeof i18nSvc.getDefaultLocale === "function" ? i18nSvc.getDefaultLocale() : "en";
|
|
951
|
+
const locales = typeof i18nSvc.getLocales === "function" ? i18nSvc.getLocales() : [];
|
|
952
|
+
locale = {
|
|
953
|
+
default: defaultLocale,
|
|
954
|
+
supported: locales.length > 0 ? locales : [defaultLocale],
|
|
955
|
+
timezone: "UTC"
|
|
956
|
+
};
|
|
957
|
+
}
|
|
845
958
|
return {
|
|
846
959
|
name: "ObjectOS",
|
|
847
960
|
version: "1.0.0",
|
|
@@ -881,11 +994,7 @@ var HttpDispatcher = class {
|
|
|
881
994
|
"file-storage": hasFiles ? svcAvailable(routes.storage) : svcUnavailable("file-storage"),
|
|
882
995
|
search: hasSearch ? svcAvailable() : svcUnavailable("search")
|
|
883
996
|
},
|
|
884
|
-
locale
|
|
885
|
-
default: "en",
|
|
886
|
-
supported: ["en", "zh-CN"],
|
|
887
|
-
timezone: "UTC"
|
|
888
|
-
}
|
|
997
|
+
locale
|
|
889
998
|
};
|
|
890
999
|
}
|
|
891
1000
|
/**
|
|
@@ -1199,13 +1308,24 @@ var HttpDispatcher = class {
|
|
|
1199
1308
|
if (parts[0] === "translations") {
|
|
1200
1309
|
const locale = parts[1] ? decodeURIComponent(parts[1]) : query?.locale;
|
|
1201
1310
|
if (!locale) return { handled: true, response: this.error("Missing locale parameter", 400) };
|
|
1202
|
-
|
|
1311
|
+
let translations = i18nService.getTranslations(locale);
|
|
1312
|
+
if (Object.keys(translations).length === 0) {
|
|
1313
|
+
const availableLocales = typeof i18nService.getLocales === "function" ? i18nService.getLocales() : [];
|
|
1314
|
+
const resolved = (0, import_core2.resolveLocale)(locale, availableLocales);
|
|
1315
|
+
if (resolved && resolved !== locale) {
|
|
1316
|
+
translations = i18nService.getTranslations(resolved);
|
|
1317
|
+
return { handled: true, response: this.success({ locale: resolved, requestedLocale: locale, translations }) };
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1203
1320
|
return { handled: true, response: this.success({ locale, translations }) };
|
|
1204
1321
|
}
|
|
1205
1322
|
if (parts[0] === "labels" && parts.length >= 2) {
|
|
1206
1323
|
const objectName = decodeURIComponent(parts[1]);
|
|
1207
|
-
|
|
1324
|
+
let locale = parts[2] ? decodeURIComponent(parts[2]) : query?.locale;
|
|
1208
1325
|
if (!locale) return { handled: true, response: this.error("Missing locale parameter", 400) };
|
|
1326
|
+
const availableLocales = typeof i18nService.getLocales === "function" ? i18nService.getLocales() : [];
|
|
1327
|
+
const resolved = (0, import_core2.resolveLocale)(locale, availableLocales);
|
|
1328
|
+
if (resolved) locale = resolved;
|
|
1209
1329
|
if (typeof i18nService.getFieldLabels === "function") {
|
|
1210
1330
|
const labels2 = i18nService.getFieldLabels(objectName, locale);
|
|
1211
1331
|
return { handled: true, response: this.success({ object: objectName, locale, labels: labels2 }) };
|
|
@@ -1580,7 +1700,7 @@ var HttpDispatcher = class {
|
|
|
1580
1700
|
async dispatch(method, path, body, query, context) {
|
|
1581
1701
|
const cleanPath = path.replace(/\/$/, "");
|
|
1582
1702
|
if (cleanPath === "" && method === "GET") {
|
|
1583
|
-
const info = this.getDiscoveryInfo("");
|
|
1703
|
+
const info = await this.getDiscoveryInfo("");
|
|
1584
1704
|
return {
|
|
1585
1705
|
handled: true,
|
|
1586
1706
|
response: this.success(info)
|
|
@@ -1728,10 +1848,10 @@ function createDispatcherPlugin(config = {}) {
|
|
|
1728
1848
|
const dispatcher = new HttpDispatcher(kernel);
|
|
1729
1849
|
const prefix = config.prefix || "/api/v1";
|
|
1730
1850
|
server.get("/.well-known/objectstack", async (_req, res) => {
|
|
1731
|
-
res.json({ data: dispatcher.getDiscoveryInfo(prefix) });
|
|
1851
|
+
res.json({ data: await dispatcher.getDiscoveryInfo(prefix) });
|
|
1732
1852
|
});
|
|
1733
1853
|
server.get(`${prefix}/discovery`, async (_req, res) => {
|
|
1734
|
-
res.json({ data: dispatcher.getDiscoveryInfo(prefix) });
|
|
1854
|
+
res.json({ data: await dispatcher.getDiscoveryInfo(prefix) });
|
|
1735
1855
|
});
|
|
1736
1856
|
server.post(`${prefix}/auth/login`, async (req, res) => {
|
|
1737
1857
|
try {
|
|
@@ -1853,6 +1973,30 @@ function createDispatcherPlugin(config = {}) {
|
|
|
1853
1973
|
errorResponse(err, res);
|
|
1854
1974
|
}
|
|
1855
1975
|
});
|
|
1976
|
+
server.get(`${prefix}/i18n/locales`, async (req, res) => {
|
|
1977
|
+
try {
|
|
1978
|
+
const result = await dispatcher.handleI18n("/locales", "GET", req.query, { request: req });
|
|
1979
|
+
sendResult(result, res);
|
|
1980
|
+
} catch (err) {
|
|
1981
|
+
errorResponse(err, res);
|
|
1982
|
+
}
|
|
1983
|
+
});
|
|
1984
|
+
server.get(`${prefix}/i18n/translations/:locale`, async (req, res) => {
|
|
1985
|
+
try {
|
|
1986
|
+
const result = await dispatcher.handleI18n(`/translations/${req.params.locale}`, "GET", req.query, { request: req });
|
|
1987
|
+
sendResult(result, res);
|
|
1988
|
+
} catch (err) {
|
|
1989
|
+
errorResponse(err, res);
|
|
1990
|
+
}
|
|
1991
|
+
});
|
|
1992
|
+
server.get(`${prefix}/i18n/labels/:object/:locale`, async (req, res) => {
|
|
1993
|
+
try {
|
|
1994
|
+
const result = await dispatcher.handleI18n(`/labels/${req.params.object}/${req.params.locale}`, "GET", req.query, { request: req });
|
|
1995
|
+
sendResult(result, res);
|
|
1996
|
+
} catch (err) {
|
|
1997
|
+
errorResponse(err, res);
|
|
1998
|
+
}
|
|
1999
|
+
});
|
|
1856
2000
|
server.get(`${prefix}/automation`, async (req, res) => {
|
|
1857
2001
|
try {
|
|
1858
2002
|
const result = await dispatcher.handleAutomation("", "GET", {}, { request: req });
|