@objectstack/runtime 7.2.0 → 7.3.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/dist/index.cjs +469 -259
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -4
- package/dist/index.d.ts +25 -4
- package/dist/index.js +457 -249
- package/dist/index.js.map +1 -1
- package/package.json +18 -18
package/dist/index.js
CHANGED
|
@@ -702,6 +702,52 @@ var init_seed_loader = __esm({
|
|
|
702
702
|
}
|
|
703
703
|
});
|
|
704
704
|
|
|
705
|
+
// src/package-state-store.ts
|
|
706
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
707
|
+
import { dirname as dirname2, join } from "path";
|
|
708
|
+
function sanitizeEnvironmentId(environmentId) {
|
|
709
|
+
const raw = (environmentId ?? process.env.OS_ENVIRONMENT_ID ?? DEFAULT_ENVIRONMENT_ID).trim();
|
|
710
|
+
const safe = raw.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
711
|
+
return safe.length > 0 ? safe : DEFAULT_ENVIRONMENT_ID;
|
|
712
|
+
}
|
|
713
|
+
function stateFilePath(environmentId) {
|
|
714
|
+
return join(resolveObjectStackHome(), "package-state", `${sanitizeEnvironmentId(environmentId)}.json`);
|
|
715
|
+
}
|
|
716
|
+
function readState(environmentId) {
|
|
717
|
+
const file = stateFilePath(environmentId);
|
|
718
|
+
if (!existsSync(file)) return {};
|
|
719
|
+
try {
|
|
720
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
721
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
722
|
+
} catch {
|
|
723
|
+
return {};
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
function writeState(environmentId, state) {
|
|
727
|
+
const file = stateFilePath(environmentId);
|
|
728
|
+
mkdirSync(dirname2(file), { recursive: true });
|
|
729
|
+
writeFileSync(file, `${JSON.stringify(state, null, 2)}
|
|
730
|
+
`, "utf8");
|
|
731
|
+
}
|
|
732
|
+
function loadDisabledPackageIds(environmentId) {
|
|
733
|
+
const disabled = readState(environmentId).disabled;
|
|
734
|
+
return new Set(Array.isArray(disabled) ? disabled.filter((id) => typeof id === "string") : []);
|
|
735
|
+
}
|
|
736
|
+
function setPackageDisabled(environmentId, packageId, disabled) {
|
|
737
|
+
const ids = loadDisabledPackageIds(environmentId);
|
|
738
|
+
if (disabled) ids.add(packageId);
|
|
739
|
+
else ids.delete(packageId);
|
|
740
|
+
writeState(environmentId, { disabled: Array.from(ids).sort() });
|
|
741
|
+
}
|
|
742
|
+
var DEFAULT_ENVIRONMENT_ID;
|
|
743
|
+
var init_package_state_store = __esm({
|
|
744
|
+
"src/package-state-store.ts"() {
|
|
745
|
+
"use strict";
|
|
746
|
+
init_standalone_stack();
|
|
747
|
+
DEFAULT_ENVIRONMENT_ID = "default";
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
|
|
705
751
|
// src/sandbox/quickjs-runner.ts
|
|
706
752
|
import {
|
|
707
753
|
newAsyncContext
|
|
@@ -1202,6 +1248,7 @@ __export(app_plugin_exports, {
|
|
|
1202
1248
|
collectBundleFunctions: () => collectBundleFunctions,
|
|
1203
1249
|
collectBundleHooks: () => collectBundleHooks
|
|
1204
1250
|
});
|
|
1251
|
+
import { readEnvWithDeprecation } from "@objectstack/types";
|
|
1205
1252
|
function collectBundleHooks(bundle) {
|
|
1206
1253
|
const out = [];
|
|
1207
1254
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1266,6 +1313,7 @@ var init_app_plugin = __esm({
|
|
|
1266
1313
|
"src/app-plugin.ts"() {
|
|
1267
1314
|
"use strict";
|
|
1268
1315
|
init_seed_loader();
|
|
1316
|
+
init_package_state_store();
|
|
1269
1317
|
init_quickjs_runner();
|
|
1270
1318
|
init_body_runner();
|
|
1271
1319
|
AppPlugin = class {
|
|
@@ -1291,6 +1339,24 @@ var init_app_plugin = __esm({
|
|
|
1291
1339
|
console.warn(
|
|
1292
1340
|
`[AppPlugin:init] appId=${appId} keys=${Object.keys(servicePayload).join(",")} flows=${Array.isArray(servicePayload.flows) ? servicePayload.flows.length : "n/a"}`
|
|
1293
1341
|
);
|
|
1342
|
+
try {
|
|
1343
|
+
const ql = ctx.getService("objectql");
|
|
1344
|
+
const setter = ql?.registry?.setInitialDisabledPackageIds;
|
|
1345
|
+
if (typeof setter === "function") {
|
|
1346
|
+
const disabled = loadDisabledPackageIds(this.projectContext?.environmentId);
|
|
1347
|
+
if (disabled.size > 0) {
|
|
1348
|
+
setter.call(ql.registry, disabled);
|
|
1349
|
+
ctx.logger.info("[AppPlugin] seeded persisted disabled packages", {
|
|
1350
|
+
environmentId: this.projectContext?.environmentId,
|
|
1351
|
+
disabled: Array.from(disabled)
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
} catch (err) {
|
|
1356
|
+
ctx.logger.warn("[AppPlugin] failed to seed persisted package state", {
|
|
1357
|
+
error: err?.message ?? String(err)
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1294
1360
|
ctx.getService("manifest").register(servicePayload);
|
|
1295
1361
|
};
|
|
1296
1362
|
this.start = async (ctx) => {
|
|
@@ -1591,7 +1657,7 @@ var init_app_plugin = __esm({
|
|
|
1591
1657
|
} catch (e) {
|
|
1592
1658
|
ctx.logger.warn("[Seeder] Failed to register seed-datasets/seed-replayer service", { error: e?.message });
|
|
1593
1659
|
}
|
|
1594
|
-
const multiTenant = String(
|
|
1660
|
+
const multiTenant = String(readEnvWithDeprecation("OS_MULTI_ORG_ENABLED", "OS_MULTI_TENANT") ?? "false").toLowerCase() !== "false";
|
|
1595
1661
|
if (multiTenant) {
|
|
1596
1662
|
ctx.logger.info("[Seeder] multi-tenant mode \u2014 skipping inline seed; per-org replay will run on sys_organization insert");
|
|
1597
1663
|
} else {
|
|
@@ -1835,6 +1901,170 @@ var init_app_plugin = __esm({
|
|
|
1835
1901
|
}
|
|
1836
1902
|
});
|
|
1837
1903
|
|
|
1904
|
+
// src/standalone-stack.ts
|
|
1905
|
+
import { resolve as resolvePath2 } from "path";
|
|
1906
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
1907
|
+
import { homedir } from "os";
|
|
1908
|
+
import { z } from "zod";
|
|
1909
|
+
import { readEnvWithDeprecation as readEnvWithDeprecation2 } from "@objectstack/types";
|
|
1910
|
+
function resolveObjectStackHome() {
|
|
1911
|
+
const raw = process.env.OS_HOME?.trim();
|
|
1912
|
+
if (raw && raw.length > 0) {
|
|
1913
|
+
if (raw.startsWith("~")) return resolvePath2(homedir(), raw.slice(1).replace(/^[/\\]/, ""));
|
|
1914
|
+
return resolvePath2(raw);
|
|
1915
|
+
}
|
|
1916
|
+
return resolvePath2(homedir(), ".objectstack");
|
|
1917
|
+
}
|
|
1918
|
+
function detectDriverFromUrl(dbUrl) {
|
|
1919
|
+
if (/^memory:\/\//i.test(dbUrl)) return "memory";
|
|
1920
|
+
if (/^(postgres(ql)?|pg):\/\//i.test(dbUrl)) return "postgres";
|
|
1921
|
+
if (/^mongodb(\+srv)?:\/\//i.test(dbUrl)) return "mongodb";
|
|
1922
|
+
if (/^wasm-sqlite:\/\//i.test(dbUrl)) return "sqlite-wasm";
|
|
1923
|
+
if (/\.wasm\.db$/i.test(dbUrl)) return "sqlite-wasm";
|
|
1924
|
+
if (/^file:/i.test(dbUrl)) return "sqlite";
|
|
1925
|
+
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(dbUrl)) return "sqlite";
|
|
1926
|
+
throw new Error(
|
|
1927
|
+
`[StandaloneStack] Unsupported database URL scheme: ${dbUrl}. Supported schemes: memory://, postgres://, pg://, mongodb://, mongodb+srv://, file:`
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
async function createStandaloneStack(config) {
|
|
1931
|
+
const cfg = StandaloneStackConfigSchema.parse(config ?? {});
|
|
1932
|
+
const { ObjectQLPlugin } = await import("@objectstack/objectql");
|
|
1933
|
+
const { MetadataPlugin } = await import("@objectstack/metadata");
|
|
1934
|
+
const { DriverPlugin: DriverPlugin2 } = await Promise.resolve().then(() => (init_driver_plugin(), driver_plugin_exports));
|
|
1935
|
+
const { AppPlugin: AppPlugin2 } = await Promise.resolve().then(() => (init_app_plugin(), app_plugin_exports));
|
|
1936
|
+
const cwd = process.cwd();
|
|
1937
|
+
const environmentId = cfg.environmentId ?? process.env.OS_ENVIRONMENT_ID ?? "proj_local";
|
|
1938
|
+
const artifactPathInput = cfg.artifactPath ?? process.env.OS_ARTIFACT_PATH ?? resolvePath2(cwd, "dist/objectstack.json");
|
|
1939
|
+
const artifactPath = isHttpUrl(artifactPathInput) ? artifactPathInput : artifactPathInput.startsWith("/") ? artifactPathInput : resolvePath2(cwd, artifactPathInput);
|
|
1940
|
+
const dbUrl = cfg.databaseUrl ?? readEnvWithDeprecation2("OS_DATABASE_URL", "DATABASE_URL")?.trim() ?? process.env.TURSO_DATABASE_URL?.trim() ?? (process.env.OS_HOME?.trim() ? `file:${resolvePath2(resolveObjectStackHome(), "data/standalone.db")}` : cfg.projectRoot ? `file:${resolvePath2(cfg.projectRoot, ".objectstack/data/standalone.db")}` : `file:${resolvePath2(resolveObjectStackHome(), "data/standalone.db")}`);
|
|
1941
|
+
const explicitDriver = cfg.databaseDriver ?? process.env.OS_DATABASE_DRIVER?.trim();
|
|
1942
|
+
const dbDriver = explicitDriver ?? detectDriverFromUrl(dbUrl);
|
|
1943
|
+
let driverPlugin;
|
|
1944
|
+
if (dbDriver === "memory") {
|
|
1945
|
+
const { InMemoryDriver } = await import("@objectstack/driver-memory");
|
|
1946
|
+
driverPlugin = new DriverPlugin2(new InMemoryDriver());
|
|
1947
|
+
} else if (dbDriver === "postgres") {
|
|
1948
|
+
const { SqlDriver } = await import("@objectstack/driver-sql");
|
|
1949
|
+
driverPlugin = new DriverPlugin2(
|
|
1950
|
+
new SqlDriver({
|
|
1951
|
+
client: "pg",
|
|
1952
|
+
connection: dbUrl,
|
|
1953
|
+
pool: { min: 0, max: 5 }
|
|
1954
|
+
})
|
|
1955
|
+
);
|
|
1956
|
+
} else if (dbDriver === "mongodb") {
|
|
1957
|
+
let MongoDBDriver;
|
|
1958
|
+
try {
|
|
1959
|
+
({ MongoDBDriver } = await import("@objectstack/driver-mongodb"));
|
|
1960
|
+
} catch (err) {
|
|
1961
|
+
throw new Error(
|
|
1962
|
+
`[StandaloneStack] mongodb URL detected but @objectstack/driver-mongodb is not installed. Add it as a dependency or pass an explicit driverPlugin. (${err?.message ?? err})`
|
|
1963
|
+
);
|
|
1964
|
+
}
|
|
1965
|
+
driverPlugin = new DriverPlugin2(new MongoDBDriver({ url: dbUrl }));
|
|
1966
|
+
} else if (dbDriver === "sqlite-wasm") {
|
|
1967
|
+
const { SqliteWasmDriver } = await import("@objectstack/driver-sqlite-wasm");
|
|
1968
|
+
const filename = dbUrl.replace(/^wasm-sqlite:(\/\/)?/i, "").replace(/^file:(\/\/)?/i, "");
|
|
1969
|
+
if (filename && filename !== ":memory:") {
|
|
1970
|
+
mkdirSync2(resolvePath2(filename, ".."), { recursive: true });
|
|
1971
|
+
}
|
|
1972
|
+
driverPlugin = new DriverPlugin2(
|
|
1973
|
+
new SqliteWasmDriver({
|
|
1974
|
+
filename: filename || ":memory:",
|
|
1975
|
+
persist: filename && filename !== ":memory:" ? "on-write" : void 0
|
|
1976
|
+
})
|
|
1977
|
+
);
|
|
1978
|
+
} else {
|
|
1979
|
+
const { SqlDriver } = await import("@objectstack/driver-sql");
|
|
1980
|
+
const filename = dbUrl.replace(/^file:(\/\/)?/, "");
|
|
1981
|
+
if (!filename || /^[a-z][a-z0-9+.-]*:\/\//i.test(filename)) {
|
|
1982
|
+
throw new Error(
|
|
1983
|
+
`[StandaloneStack] sqlite driver was selected but the URL does not look like a file path: "${dbUrl}". Use file:/path/to/db.sqlite, or set OS_DATABASE_DRIVER explicitly.`
|
|
1984
|
+
);
|
|
1985
|
+
}
|
|
1986
|
+
mkdirSync2(resolvePath2(filename, ".."), { recursive: true });
|
|
1987
|
+
driverPlugin = new DriverPlugin2(
|
|
1988
|
+
new SqlDriver({
|
|
1989
|
+
client: "better-sqlite3",
|
|
1990
|
+
connection: { filename },
|
|
1991
|
+
useNullAsDefault: true
|
|
1992
|
+
})
|
|
1993
|
+
);
|
|
1994
|
+
}
|
|
1995
|
+
const artifactBundle = await loadArtifactBundle(artifactPath, {
|
|
1996
|
+
tag: "[StandaloneStack]",
|
|
1997
|
+
unwrapEnvelope: true
|
|
1998
|
+
});
|
|
1999
|
+
if (artifactBundle) {
|
|
2000
|
+
const flowsCount = Array.isArray(artifactBundle?.flows) ? artifactBundle.flows.length : "n/a";
|
|
2001
|
+
console.warn(
|
|
2002
|
+
`[StandaloneStack] artifact loaded: path=${artifactPath} keys=${Object.keys(artifactBundle).join(",")} flows=${flowsCount}`
|
|
2003
|
+
);
|
|
2004
|
+
}
|
|
2005
|
+
const plugins = [
|
|
2006
|
+
driverPlugin,
|
|
2007
|
+
new MetadataPlugin({
|
|
2008
|
+
// Source-file scanner OFF — declarative metadata is loaded
|
|
2009
|
+
// from the compiled artifact, not from yaml/json files on
|
|
2010
|
+
// disk. Scanning would also recursively watch the project
|
|
2011
|
+
// root (incl. node_modules), which is expensive and prone
|
|
2012
|
+
// to EMFILE.
|
|
2013
|
+
watch: false,
|
|
2014
|
+
// Artifact-file HMR ON in non-production so edits to
|
|
2015
|
+
// `*.view.ts` / `*.flow.ts` (which the CLI dev-mode watcher
|
|
2016
|
+
// recompiles into `dist/objectstack.json`) are picked up by
|
|
2017
|
+
// the running server WITHOUT requiring a manual restart.
|
|
2018
|
+
// Uses polling under the hood (see plugin.ts) to avoid
|
|
2019
|
+
// `fs.watch` EMFILE on macOS / busy dev hosts.
|
|
2020
|
+
artifactWatch: process.env.NODE_ENV !== "production",
|
|
2021
|
+
environmentId,
|
|
2022
|
+
artifactSource: { mode: "local-file", path: artifactPath }
|
|
2023
|
+
}),
|
|
2024
|
+
new ObjectQLPlugin({ environmentId })
|
|
2025
|
+
];
|
|
2026
|
+
if (artifactBundle) plugins.push(new AppPlugin2(artifactBundle));
|
|
2027
|
+
const requires = Array.isArray(artifactBundle?.requires) ? artifactBundle.requires.filter((c) => typeof c === "string") : void 0;
|
|
2028
|
+
const objects = Array.isArray(artifactBundle?.objects) ? artifactBundle.objects : void 0;
|
|
2029
|
+
const manifest = artifactBundle?.manifest;
|
|
2030
|
+
return {
|
|
2031
|
+
plugins,
|
|
2032
|
+
api: {
|
|
2033
|
+
enableProjectScoping: false,
|
|
2034
|
+
projectResolution: "none"
|
|
2035
|
+
},
|
|
2036
|
+
...requires ? { requires } : {},
|
|
2037
|
+
...objects ? { objects } : {},
|
|
2038
|
+
...manifest ? { manifest } : {}
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
var StandaloneStackConfigSchema;
|
|
2042
|
+
var init_standalone_stack = __esm({
|
|
2043
|
+
"src/standalone-stack.ts"() {
|
|
2044
|
+
"use strict";
|
|
2045
|
+
init_load_artifact_bundle();
|
|
2046
|
+
StandaloneStackConfigSchema = z.object({
|
|
2047
|
+
databaseUrl: z.string().optional(),
|
|
2048
|
+
databaseAuthToken: z.string().optional(),
|
|
2049
|
+
databaseDriver: z.enum(["sqlite", "sqlite-wasm", "memory", "postgres", "mongodb"]).optional(),
|
|
2050
|
+
environmentId: z.string().optional(),
|
|
2051
|
+
artifactPath: z.string().optional(),
|
|
2052
|
+
/**
|
|
2053
|
+
* Project root directory. When set (typically by the CLI after locating
|
|
2054
|
+
* `objectstack.config.ts`), the default sqlite database is placed under
|
|
2055
|
+
* `<projectRoot>/.objectstack/data/standalone.db` instead of the global
|
|
2056
|
+
* `~/.objectstack/data/standalone.db`. This keeps per-project data
|
|
2057
|
+
* scoped to the project folder so different examples / apps don't
|
|
2058
|
+
* share a single database by accident.
|
|
2059
|
+
*
|
|
2060
|
+
* Explicit `databaseUrl` / `OS_DATABASE_URL` / `OS_HOME` still take
|
|
2061
|
+
* precedence over this default.
|
|
2062
|
+
*/
|
|
2063
|
+
projectRoot: z.string().optional()
|
|
2064
|
+
});
|
|
2065
|
+
}
|
|
2066
|
+
});
|
|
2067
|
+
|
|
1838
2068
|
// src/cloud/platform-sso.ts
|
|
1839
2069
|
var platform_sso_exports = {};
|
|
1840
2070
|
__export(platform_sso_exports, {
|
|
@@ -2239,172 +2469,19 @@ var Runtime = class {
|
|
|
2239
2469
|
}
|
|
2240
2470
|
};
|
|
2241
2471
|
|
|
2242
|
-
// src/
|
|
2243
|
-
|
|
2244
|
-
import { resolve as resolvePath2 } from "path";
|
|
2245
|
-
import { mkdirSync } from "fs";
|
|
2246
|
-
import { homedir } from "os";
|
|
2247
|
-
import { z } from "zod";
|
|
2248
|
-
function resolveObjectStackHome() {
|
|
2249
|
-
const raw = process.env.OS_HOME?.trim();
|
|
2250
|
-
if (raw && raw.length > 0) {
|
|
2251
|
-
if (raw.startsWith("~")) return resolvePath2(homedir(), raw.slice(1).replace(/^[/\\]/, ""));
|
|
2252
|
-
return resolvePath2(raw);
|
|
2253
|
-
}
|
|
2254
|
-
return resolvePath2(homedir(), ".objectstack");
|
|
2255
|
-
}
|
|
2256
|
-
var StandaloneStackConfigSchema = z.object({
|
|
2257
|
-
databaseUrl: z.string().optional(),
|
|
2258
|
-
databaseAuthToken: z.string().optional(),
|
|
2259
|
-
databaseDriver: z.enum(["sqlite", "sqlite-wasm", "memory", "postgres", "mongodb"]).optional(),
|
|
2260
|
-
environmentId: z.string().optional(),
|
|
2261
|
-
artifactPath: z.string().optional(),
|
|
2262
|
-
/**
|
|
2263
|
-
* Project root directory. When set (typically by the CLI after locating
|
|
2264
|
-
* `objectstack.config.ts`), the default sqlite database is placed under
|
|
2265
|
-
* `<projectRoot>/.objectstack/data/standalone.db` instead of the global
|
|
2266
|
-
* `~/.objectstack/data/standalone.db`. This keeps per-project data
|
|
2267
|
-
* scoped to the project folder so different examples / apps don't
|
|
2268
|
-
* share a single database by accident.
|
|
2269
|
-
*
|
|
2270
|
-
* Explicit `databaseUrl` / `OS_DATABASE_URL` / `OS_HOME` still take
|
|
2271
|
-
* precedence over this default.
|
|
2272
|
-
*/
|
|
2273
|
-
projectRoot: z.string().optional()
|
|
2274
|
-
});
|
|
2275
|
-
function detectDriverFromUrl(dbUrl) {
|
|
2276
|
-
if (/^memory:\/\//i.test(dbUrl)) return "memory";
|
|
2277
|
-
if (/^(postgres(ql)?|pg):\/\//i.test(dbUrl)) return "postgres";
|
|
2278
|
-
if (/^mongodb(\+srv)?:\/\//i.test(dbUrl)) return "mongodb";
|
|
2279
|
-
if (/^wasm-sqlite:\/\//i.test(dbUrl)) return "sqlite-wasm";
|
|
2280
|
-
if (/\.wasm\.db$/i.test(dbUrl)) return "sqlite-wasm";
|
|
2281
|
-
if (/^file:/i.test(dbUrl)) return "sqlite";
|
|
2282
|
-
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(dbUrl)) return "sqlite";
|
|
2283
|
-
throw new Error(
|
|
2284
|
-
`[StandaloneStack] Unsupported database URL scheme: ${dbUrl}. Supported schemes: memory://, postgres://, pg://, mongodb://, mongodb+srv://, file:`
|
|
2285
|
-
);
|
|
2286
|
-
}
|
|
2287
|
-
async function createStandaloneStack(config) {
|
|
2288
|
-
const cfg = StandaloneStackConfigSchema.parse(config ?? {});
|
|
2289
|
-
const { ObjectQLPlugin } = await import("@objectstack/objectql");
|
|
2290
|
-
const { MetadataPlugin } = await import("@objectstack/metadata");
|
|
2291
|
-
const { DriverPlugin: DriverPlugin2 } = await Promise.resolve().then(() => (init_driver_plugin(), driver_plugin_exports));
|
|
2292
|
-
const { AppPlugin: AppPlugin2 } = await Promise.resolve().then(() => (init_app_plugin(), app_plugin_exports));
|
|
2293
|
-
const cwd = process.cwd();
|
|
2294
|
-
const environmentId = cfg.environmentId ?? process.env.OS_ENVIRONMENT_ID ?? "proj_local";
|
|
2295
|
-
const artifactPathInput = cfg.artifactPath ?? process.env.OS_ARTIFACT_PATH ?? resolvePath2(cwd, "dist/objectstack.json");
|
|
2296
|
-
const artifactPath = isHttpUrl(artifactPathInput) ? artifactPathInput : artifactPathInput.startsWith("/") ? artifactPathInput : resolvePath2(cwd, artifactPathInput);
|
|
2297
|
-
const dbUrl = cfg.databaseUrl ?? process.env.OS_DATABASE_URL?.trim() ?? process.env.TURSO_DATABASE_URL?.trim() ?? (process.env.OS_HOME?.trim() ? `file:${resolvePath2(resolveObjectStackHome(), "data/standalone.db")}` : cfg.projectRoot ? `file:${resolvePath2(cfg.projectRoot, ".objectstack/data/standalone.db")}` : `file:${resolvePath2(resolveObjectStackHome(), "data/standalone.db")}`);
|
|
2298
|
-
const explicitDriver = cfg.databaseDriver ?? process.env.OS_DATABASE_DRIVER?.trim();
|
|
2299
|
-
const dbDriver = explicitDriver ?? detectDriverFromUrl(dbUrl);
|
|
2300
|
-
let driverPlugin;
|
|
2301
|
-
if (dbDriver === "memory") {
|
|
2302
|
-
const { InMemoryDriver } = await import("@objectstack/driver-memory");
|
|
2303
|
-
driverPlugin = new DriverPlugin2(new InMemoryDriver());
|
|
2304
|
-
} else if (dbDriver === "postgres") {
|
|
2305
|
-
const { SqlDriver } = await import("@objectstack/driver-sql");
|
|
2306
|
-
driverPlugin = new DriverPlugin2(
|
|
2307
|
-
new SqlDriver({
|
|
2308
|
-
client: "pg",
|
|
2309
|
-
connection: dbUrl,
|
|
2310
|
-
pool: { min: 0, max: 5 }
|
|
2311
|
-
})
|
|
2312
|
-
);
|
|
2313
|
-
} else if (dbDriver === "mongodb") {
|
|
2314
|
-
let MongoDBDriver;
|
|
2315
|
-
try {
|
|
2316
|
-
({ MongoDBDriver } = await import("@objectstack/driver-mongodb"));
|
|
2317
|
-
} catch (err) {
|
|
2318
|
-
throw new Error(
|
|
2319
|
-
`[StandaloneStack] mongodb URL detected but @objectstack/driver-mongodb is not installed. Add it as a dependency or pass an explicit driverPlugin. (${err?.message ?? err})`
|
|
2320
|
-
);
|
|
2321
|
-
}
|
|
2322
|
-
driverPlugin = new DriverPlugin2(new MongoDBDriver({ url: dbUrl }));
|
|
2323
|
-
} else if (dbDriver === "sqlite-wasm") {
|
|
2324
|
-
const { SqliteWasmDriver } = await import("@objectstack/driver-sqlite-wasm");
|
|
2325
|
-
const filename = dbUrl.replace(/^wasm-sqlite:(\/\/)?/i, "").replace(/^file:(\/\/)?/i, "");
|
|
2326
|
-
if (filename && filename !== ":memory:") {
|
|
2327
|
-
mkdirSync(resolvePath2(filename, ".."), { recursive: true });
|
|
2328
|
-
}
|
|
2329
|
-
driverPlugin = new DriverPlugin2(
|
|
2330
|
-
new SqliteWasmDriver({
|
|
2331
|
-
filename: filename || ":memory:",
|
|
2332
|
-
persist: filename && filename !== ":memory:" ? "on-write" : void 0
|
|
2333
|
-
})
|
|
2334
|
-
);
|
|
2335
|
-
} else {
|
|
2336
|
-
const { SqlDriver } = await import("@objectstack/driver-sql");
|
|
2337
|
-
const filename = dbUrl.replace(/^file:(\/\/)?/, "");
|
|
2338
|
-
if (!filename || /^[a-z][a-z0-9+.-]*:\/\//i.test(filename)) {
|
|
2339
|
-
throw new Error(
|
|
2340
|
-
`[StandaloneStack] sqlite driver was selected but the URL does not look like a file path: "${dbUrl}". Use file:/path/to/db.sqlite, or set OS_DATABASE_DRIVER explicitly.`
|
|
2341
|
-
);
|
|
2342
|
-
}
|
|
2343
|
-
mkdirSync(resolvePath2(filename, ".."), { recursive: true });
|
|
2344
|
-
driverPlugin = new DriverPlugin2(
|
|
2345
|
-
new SqlDriver({
|
|
2346
|
-
client: "better-sqlite3",
|
|
2347
|
-
connection: { filename },
|
|
2348
|
-
useNullAsDefault: true
|
|
2349
|
-
})
|
|
2350
|
-
);
|
|
2351
|
-
}
|
|
2352
|
-
const artifactBundle = await loadArtifactBundle(artifactPath, {
|
|
2353
|
-
tag: "[StandaloneStack]",
|
|
2354
|
-
unwrapEnvelope: true
|
|
2355
|
-
});
|
|
2356
|
-
if (artifactBundle) {
|
|
2357
|
-
const flowsCount = Array.isArray(artifactBundle?.flows) ? artifactBundle.flows.length : "n/a";
|
|
2358
|
-
console.warn(
|
|
2359
|
-
`[StandaloneStack] artifact loaded: path=${artifactPath} keys=${Object.keys(artifactBundle).join(",")} flows=${flowsCount}`
|
|
2360
|
-
);
|
|
2361
|
-
}
|
|
2362
|
-
const plugins = [
|
|
2363
|
-
driverPlugin,
|
|
2364
|
-
new MetadataPlugin({
|
|
2365
|
-
// Source-file scanner OFF — declarative metadata is loaded
|
|
2366
|
-
// from the compiled artifact, not from yaml/json files on
|
|
2367
|
-
// disk. Scanning would also recursively watch the project
|
|
2368
|
-
// root (incl. node_modules), which is expensive and prone
|
|
2369
|
-
// to EMFILE.
|
|
2370
|
-
watch: false,
|
|
2371
|
-
// Artifact-file HMR ON in non-production so edits to
|
|
2372
|
-
// `*.view.ts` / `*.flow.ts` (which the CLI dev-mode watcher
|
|
2373
|
-
// recompiles into `dist/objectstack.json`) are picked up by
|
|
2374
|
-
// the running server WITHOUT requiring a manual restart.
|
|
2375
|
-
// Uses polling under the hood (see plugin.ts) to avoid
|
|
2376
|
-
// `fs.watch` EMFILE on macOS / busy dev hosts.
|
|
2377
|
-
artifactWatch: process.env.NODE_ENV !== "production",
|
|
2378
|
-
environmentId,
|
|
2379
|
-
artifactSource: { mode: "local-file", path: artifactPath }
|
|
2380
|
-
}),
|
|
2381
|
-
new ObjectQLPlugin({ environmentId })
|
|
2382
|
-
];
|
|
2383
|
-
if (artifactBundle) plugins.push(new AppPlugin2(artifactBundle));
|
|
2384
|
-
const requires = Array.isArray(artifactBundle?.requires) ? artifactBundle.requires.filter((c) => typeof c === "string") : void 0;
|
|
2385
|
-
const objects = Array.isArray(artifactBundle?.objects) ? artifactBundle.objects : void 0;
|
|
2386
|
-
const manifest = artifactBundle?.manifest;
|
|
2387
|
-
return {
|
|
2388
|
-
plugins,
|
|
2389
|
-
api: {
|
|
2390
|
-
enableProjectScoping: false,
|
|
2391
|
-
projectResolution: "none"
|
|
2392
|
-
},
|
|
2393
|
-
...requires ? { requires } : {},
|
|
2394
|
-
...objects ? { objects } : {},
|
|
2395
|
-
...manifest ? { manifest } : {}
|
|
2396
|
-
};
|
|
2397
|
-
}
|
|
2472
|
+
// src/index.ts
|
|
2473
|
+
init_standalone_stack();
|
|
2398
2474
|
|
|
2399
2475
|
// src/default-host.ts
|
|
2400
|
-
|
|
2401
|
-
import { existsSync, mkdirSync as mkdirSync2, writeFileSync } from "fs";
|
|
2476
|
+
init_standalone_stack();
|
|
2402
2477
|
init_load_artifact_bundle();
|
|
2478
|
+
import { resolve as resolvePath3 } from "path";
|
|
2479
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
2403
2480
|
function resolveDefaultArtifactPath(explicitPath, cwd = process.cwd()) {
|
|
2404
2481
|
const candidate = explicitPath ?? process.env.OS_ARTIFACT_PATH ?? resolvePath3(cwd, "dist/objectstack.json");
|
|
2405
2482
|
if (isHttpUrl(candidate)) return candidate;
|
|
2406
2483
|
if (explicitPath || process.env.OS_ARTIFACT_PATH) return candidate;
|
|
2407
|
-
return
|
|
2484
|
+
return existsSync2(candidate) ? candidate : void 0;
|
|
2408
2485
|
}
|
|
2409
2486
|
async function createDefaultHostConfig(options = {}) {
|
|
2410
2487
|
const { requireArtifact = true, ...standaloneOpts } = options;
|
|
@@ -2417,9 +2494,9 @@ async function createDefaultHostConfig(options = {}) {
|
|
|
2417
2494
|
if (!resolvedArtifact && !requireArtifact) {
|
|
2418
2495
|
const home = resolveObjectStackHome();
|
|
2419
2496
|
const stubPath = resolvePath3(home, "dist/objectstack.json");
|
|
2420
|
-
if (!
|
|
2421
|
-
|
|
2422
|
-
|
|
2497
|
+
if (!existsSync2(stubPath)) {
|
|
2498
|
+
mkdirSync3(resolvePath3(stubPath, ".."), { recursive: true });
|
|
2499
|
+
writeFileSync2(
|
|
2423
2500
|
stubPath,
|
|
2424
2501
|
JSON.stringify(
|
|
2425
2502
|
{
|
|
@@ -2456,9 +2533,11 @@ init_app_plugin();
|
|
|
2456
2533
|
init_seed_loader();
|
|
2457
2534
|
|
|
2458
2535
|
// src/http-dispatcher.ts
|
|
2536
|
+
init_package_state_store();
|
|
2459
2537
|
import { getEnv, resolveLocale } from "@objectstack/core";
|
|
2538
|
+
import { readEnvWithDeprecation as readEnvWithDeprecation3 } from "@objectstack/types";
|
|
2460
2539
|
import { CoreServiceName } from "@objectstack/spec/system";
|
|
2461
|
-
import { pluralToSingular } from "@objectstack/spec/shared";
|
|
2540
|
+
import { pluralToSingular, PLURAL_TO_SINGULAR } from "@objectstack/spec/shared";
|
|
2462
2541
|
|
|
2463
2542
|
// src/security/resolve-execution-context.ts
|
|
2464
2543
|
function readHeader(headers, name) {
|
|
@@ -3313,7 +3392,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3313
3392
|
if (protocol && typeof protocol.saveMetaItem === "function") {
|
|
3314
3393
|
try {
|
|
3315
3394
|
const organizationId = await this.resolveActiveOrganizationId(_context);
|
|
3316
|
-
const result = await protocol.saveMetaItem({ type, name, item: body, organizationId });
|
|
3395
|
+
const result = await protocol.saveMetaItem({ type, name, item: body, organizationId, ...packageId ? { packageId } : {} });
|
|
3317
3396
|
return { handled: true, response: this.success(result) };
|
|
3318
3397
|
} catch (e) {
|
|
3319
3398
|
return { handled: true, response: this.error(e.message, 400) };
|
|
@@ -3653,12 +3732,22 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3653
3732
|
const id = decodeURIComponent(parts[0]);
|
|
3654
3733
|
const pkg = registry.enablePackage(id);
|
|
3655
3734
|
if (!pkg) return { handled: true, response: this.error(`Package '${id}' not found`, 404) };
|
|
3735
|
+
try {
|
|
3736
|
+
setPackageDisabled(_context?.environmentId, id, false);
|
|
3737
|
+
} catch (err) {
|
|
3738
|
+
console.warn("[handlePackages] failed to persist enable state", { id, error: err?.message });
|
|
3739
|
+
}
|
|
3656
3740
|
return { handled: true, response: this.success(pkg) };
|
|
3657
3741
|
}
|
|
3658
3742
|
if (parts.length === 2 && parts[1] === "disable" && m === "PATCH") {
|
|
3659
3743
|
const id = decodeURIComponent(parts[0]);
|
|
3660
3744
|
const pkg = registry.disablePackage(id);
|
|
3661
3745
|
if (!pkg) return { handled: true, response: this.error(`Package '${id}' not found`, 404) };
|
|
3746
|
+
try {
|
|
3747
|
+
setPackageDisabled(_context?.environmentId, id, true);
|
|
3748
|
+
} catch (err) {
|
|
3749
|
+
console.warn("[handlePackages] failed to persist disable state", { id, error: err?.message });
|
|
3750
|
+
}
|
|
3662
3751
|
return { handled: true, response: this.success(pkg) };
|
|
3663
3752
|
}
|
|
3664
3753
|
if (parts.length === 2 && parts[1] === "publish" && m === "POST") {
|
|
@@ -3679,6 +3768,14 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3679
3768
|
}
|
|
3680
3769
|
return { handled: true, response: this.error("Metadata service not available", 503) };
|
|
3681
3770
|
}
|
|
3771
|
+
if (parts.length === 2 && parts[1] === "export" && m === "GET") {
|
|
3772
|
+
const id = decodeURIComponent(parts[0]);
|
|
3773
|
+
const manifest = await this.assemblePackageManifest(id, registry, _context);
|
|
3774
|
+
if (!manifest) {
|
|
3775
|
+
return { handled: true, response: this.error(`Package '${id}' not found`, 404) };
|
|
3776
|
+
}
|
|
3777
|
+
return { handled: true, response: this.success(manifest) };
|
|
3778
|
+
}
|
|
3682
3779
|
if (parts.length === 1 && m === "GET") {
|
|
3683
3780
|
const id = decodeURIComponent(parts[0]);
|
|
3684
3781
|
const pkg = registry.getPackage(id);
|
|
@@ -3696,6 +3793,83 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3696
3793
|
}
|
|
3697
3794
|
return { handled: false };
|
|
3698
3795
|
}
|
|
3796
|
+
/**
|
|
3797
|
+
* Assemble a portable, offline-installable package manifest from the
|
|
3798
|
+
* `sys_metadata` overlay rows bound to `packageId`.
|
|
3799
|
+
*
|
|
3800
|
+
* The resulting shape mirrors what `marketplace-install-local` →
|
|
3801
|
+
* `manifestService.register()` → `engine.registerApp()` consumes:
|
|
3802
|
+
* `{ id, name, version, objects:[…], views:[…], flows:[…], … }`
|
|
3803
|
+
* where each category key is the PLURAL manifest name and its value is
|
|
3804
|
+
* an array of clean metadata bodies (provenance decorations stripped).
|
|
3805
|
+
*
|
|
3806
|
+
* Only the metadata categories that `registerApp` can actually consume
|
|
3807
|
+
* are exported. `datasources` and `emailTemplates` are intentionally
|
|
3808
|
+
* excluded (not registered by the import path). `tools` / `skills` ARE
|
|
3809
|
+
* round-tripped: they are registered by `registerApp` on import and
|
|
3810
|
+
* surfaced by `getMetaItems('tool' | 'skill')` on export.
|
|
3811
|
+
*
|
|
3812
|
+
* @returns the manifest object, or `null` if the package id is unknown
|
|
3813
|
+
* AND has no overlay-authored metadata.
|
|
3814
|
+
*/
|
|
3815
|
+
async assemblePackageManifest(packageId, registry, context) {
|
|
3816
|
+
const protocol = await this.resolveService("protocol");
|
|
3817
|
+
if (!protocol || typeof protocol.getMetaItems !== "function") return null;
|
|
3818
|
+
const organizationId = await this.resolveActiveOrganizationId(context);
|
|
3819
|
+
const PROVENANCE_KEYS = /* @__PURE__ */ new Set([
|
|
3820
|
+
"_packageId",
|
|
3821
|
+
"_packageVersionId",
|
|
3822
|
+
"_provenance",
|
|
3823
|
+
"_state",
|
|
3824
|
+
"_version",
|
|
3825
|
+
"_organizationId",
|
|
3826
|
+
"_source",
|
|
3827
|
+
"_id",
|
|
3828
|
+
"_rowId"
|
|
3829
|
+
]);
|
|
3830
|
+
const clean = (item) => {
|
|
3831
|
+
if (!item || typeof item !== "object") return item;
|
|
3832
|
+
const out = {};
|
|
3833
|
+
for (const [k, v] of Object.entries(item)) {
|
|
3834
|
+
if (k.startsWith("_") || PROVENANCE_KEYS.has(k)) continue;
|
|
3835
|
+
out[k] = v;
|
|
3836
|
+
}
|
|
3837
|
+
return out;
|
|
3838
|
+
};
|
|
3839
|
+
const exportPluralKeys = Object.keys(PLURAL_TO_SINGULAR).filter(
|
|
3840
|
+
(k) => k !== "datasources" && k !== "emailTemplates"
|
|
3841
|
+
);
|
|
3842
|
+
const manifest = {};
|
|
3843
|
+
let total = 0;
|
|
3844
|
+
for (const plural of exportPluralKeys) {
|
|
3845
|
+
const singular = PLURAL_TO_SINGULAR[plural];
|
|
3846
|
+
let items = [];
|
|
3847
|
+
try {
|
|
3848
|
+
const res = await protocol.getMetaItems({ type: singular, packageId, organizationId });
|
|
3849
|
+
items = Array.isArray(res?.items) ? res.items : [];
|
|
3850
|
+
} catch {
|
|
3851
|
+
continue;
|
|
3852
|
+
}
|
|
3853
|
+
if (items.length === 0) continue;
|
|
3854
|
+
manifest[plural] = items.map(clean);
|
|
3855
|
+
total += items.length;
|
|
3856
|
+
}
|
|
3857
|
+
const pkg = (() => {
|
|
3858
|
+
try {
|
|
3859
|
+
return registry?.getPackage?.(packageId);
|
|
3860
|
+
} catch {
|
|
3861
|
+
return void 0;
|
|
3862
|
+
}
|
|
3863
|
+
})();
|
|
3864
|
+
if (total === 0 && !pkg) return null;
|
|
3865
|
+
manifest.id = packageId;
|
|
3866
|
+
manifest.name = pkg?.manifest?.name ?? pkg?.name ?? packageId;
|
|
3867
|
+
manifest.version = pkg?.manifest?.version ?? pkg?.version ?? "1.0.0";
|
|
3868
|
+
if (pkg?.manifest?.label ?? pkg?.label) {
|
|
3869
|
+
manifest.label = pkg?.manifest?.label ?? pkg?.label;
|
|
3870
|
+
}
|
|
3871
|
+
return manifest;
|
|
3872
|
+
}
|
|
3699
3873
|
/**
|
|
3700
3874
|
* Cloud / Environment Control-Plane routes.
|
|
3701
3875
|
*
|
|
@@ -3947,7 +4121,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3947
4121
|
}
|
|
3948
4122
|
}
|
|
3949
4123
|
if (parts.length === 3 && parts[0] === "admin" && parts[1] === "platform-sso" && parts[2] === "backfill" && m === "POST") {
|
|
3950
|
-
const baseSecret = (
|
|
4124
|
+
const baseSecret = (readEnvWithDeprecation3("OS_AUTH_SECRET", ["AUTH_SECRET", "BETTER_AUTH_SECRET"]) ?? "").trim();
|
|
3951
4125
|
if (!baseSecret) {
|
|
3952
4126
|
return { handled: true, response: this.error("OS_AUTH_SECRET not configured on this worker", 503) };
|
|
3953
4127
|
}
|
|
@@ -4137,7 +4311,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4137
4311
|
});
|
|
4138
4312
|
try {
|
|
4139
4313
|
const { seedPlatformSsoClient: seedPlatformSsoClient2 } = await Promise.resolve().then(() => (init_platform_sso(), platform_sso_exports));
|
|
4140
|
-
const baseSecret = (
|
|
4314
|
+
const baseSecret = (readEnvWithDeprecation3("OS_AUTH_SECRET", ["AUTH_SECRET", "BETTER_AUTH_SECRET"]) ?? "").trim();
|
|
4141
4315
|
if (baseSecret) {
|
|
4142
4316
|
await seedPlatformSsoClient2({
|
|
4143
4317
|
ql,
|
|
@@ -6288,6 +6462,14 @@ function createDispatcherPlugin(config = {}) {
|
|
|
6288
6462
|
errorResponse(err, res);
|
|
6289
6463
|
}
|
|
6290
6464
|
});
|
|
6465
|
+
server.get(`${prefix}/packages/:id/export`, async (req, res) => {
|
|
6466
|
+
try {
|
|
6467
|
+
const result = await dispatcher.handlePackages(`/${req.params.id}/export`, "GET", {}, req.query, { request: req });
|
|
6468
|
+
sendResult(result, res);
|
|
6469
|
+
} catch (err) {
|
|
6470
|
+
errorResponse(err, res);
|
|
6471
|
+
}
|
|
6472
|
+
});
|
|
6291
6473
|
server.get(`${prefix}/packages/:id`, async (req, res) => {
|
|
6292
6474
|
try {
|
|
6293
6475
|
const result = await dispatcher.handlePackages(`/${req.params.id}`, "GET", {}, req.query, { request: req });
|
|
@@ -7607,6 +7789,7 @@ init_driver_plugin();
|
|
|
7607
7789
|
init_app_plugin();
|
|
7608
7790
|
import { createHmac as createHmac2 } from "crypto";
|
|
7609
7791
|
import { ObjectKernel as ObjectKernel3 } from "@objectstack/core";
|
|
7792
|
+
import { readEnvWithDeprecation as readEnvWithDeprecation4 } from "@objectstack/types";
|
|
7610
7793
|
|
|
7611
7794
|
// src/cloud/capability-loader.ts
|
|
7612
7795
|
var CAPABILITY_PROVIDERS = {
|
|
@@ -7744,7 +7927,7 @@ var ArtifactKernelFactory = class {
|
|
|
7744
7927
|
this.envRegistry = config.envRegistry;
|
|
7745
7928
|
this.logger = config.logger ?? console;
|
|
7746
7929
|
this.kernelConfig = config.kernelConfig;
|
|
7747
|
-
this.authBaseSecret = (config.authBaseSecret ??
|
|
7930
|
+
this.authBaseSecret = (config.authBaseSecret ?? readEnvWithDeprecation4("OS_AUTH_SECRET", ["AUTH_SECRET", "BETTER_AUTH_SECRET"]) ?? "").trim();
|
|
7748
7931
|
}
|
|
7749
7932
|
async create(environmentId) {
|
|
7750
7933
|
let cached = this.envRegistry.peekById(environmentId);
|
|
@@ -7857,7 +8040,7 @@ var ArtifactKernelFactory = class {
|
|
|
7857
8040
|
this.logger.warn?.("[ArtifactKernelFactory] OS_AUTH_SECRET not set \u2014 per-project AuthPlugin skipped (auth endpoints will return 404)", { environmentId });
|
|
7858
8041
|
}
|
|
7859
8042
|
try {
|
|
7860
|
-
const multiTenant = String(
|
|
8043
|
+
const multiTenant = String(readEnvWithDeprecation4("OS_MULTI_ORG_ENABLED", "OS_MULTI_TENANT") ?? "false").toLowerCase() !== "false";
|
|
7861
8044
|
if (multiTenant) {
|
|
7862
8045
|
try {
|
|
7863
8046
|
const { OrgScopingPlugin } = await import("@objectstack/plugin-org-scoping");
|
|
@@ -8383,7 +8566,7 @@ var AuthProxyPlugin = class {
|
|
|
8383
8566
|
};
|
|
8384
8567
|
|
|
8385
8568
|
// src/cloud/cloud-url.ts
|
|
8386
|
-
var DEFAULT_CLOUD_URL = "https://cloud.objectos.
|
|
8569
|
+
var DEFAULT_CLOUD_URL = "https://cloud.objectos.ai";
|
|
8387
8570
|
function resolveCloudUrl(explicit) {
|
|
8388
8571
|
const raw = (explicit ?? process.env.OS_CLOUD_URL ?? "").trim();
|
|
8389
8572
|
const lower = raw.toLowerCase();
|
|
@@ -9058,8 +9241,9 @@ async function createObjectOSStack(config) {
|
|
|
9058
9241
|
}
|
|
9059
9242
|
|
|
9060
9243
|
// src/cloud/marketplace-install-local-plugin.ts
|
|
9061
|
-
import { existsSync as
|
|
9062
|
-
import { join, resolve } from "path";
|
|
9244
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync2, readdirSync, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
9245
|
+
import { join as join2, resolve } from "path";
|
|
9246
|
+
import { readEnvWithDeprecation as readEnvWithDeprecation5 } from "@objectstack/types";
|
|
9063
9247
|
var ROUTE_BASE = "/api/v1/marketplace/install-local";
|
|
9064
9248
|
var DEFAULT_DIR = ".objectstack/installed-packages";
|
|
9065
9249
|
function safeFilename(manifestId) {
|
|
@@ -9134,9 +9318,6 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9134
9318
|
}
|
|
9135
9319
|
};
|
|
9136
9320
|
this.handleInstall = async (c, ctx) => {
|
|
9137
|
-
if (!this.cloudUrl) {
|
|
9138
|
-
return c.json({ success: false, error: { code: "marketplace_unavailable", message: "OS_CLOUD_URL not configured." } }, 503);
|
|
9139
|
-
}
|
|
9140
9321
|
const userId = await this.requireAuthenticatedUser(c, ctx);
|
|
9141
9322
|
if (!userId) {
|
|
9142
9323
|
return c.json({ success: false, error: { code: "unauthorized", message: "Authentication required to install packages." } }, 401);
|
|
@@ -9146,69 +9327,87 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9146
9327
|
body = await c.req.json();
|
|
9147
9328
|
} catch {
|
|
9148
9329
|
}
|
|
9149
|
-
const
|
|
9150
|
-
|
|
9151
|
-
|
|
9152
|
-
|
|
9153
|
-
|
|
9154
|
-
|
|
9155
|
-
|
|
9156
|
-
|
|
9157
|
-
|
|
9330
|
+
const inlineManifest = body?.manifest && typeof body.manifest === "object" ? body.manifest : null;
|
|
9331
|
+
let manifest;
|
|
9332
|
+
let resolvedVersionId;
|
|
9333
|
+
let version;
|
|
9334
|
+
let packageId;
|
|
9335
|
+
if (inlineManifest) {
|
|
9336
|
+
manifest = inlineManifest;
|
|
9337
|
+
packageId = String(manifest.id ?? manifest.name ?? "").trim();
|
|
9338
|
+
version = String(manifest.version ?? "unknown");
|
|
9339
|
+
resolvedVersionId = String(body?.versionId ?? version);
|
|
9340
|
+
if (!packageId) {
|
|
9341
|
+
return c.json({ success: false, error: { code: "invalid_manifest", message: 'Inline manifest must have an "id" or "name".' } }, 400);
|
|
9342
|
+
}
|
|
9343
|
+
} else {
|
|
9344
|
+
if (!this.cloudUrl) {
|
|
9345
|
+
return c.json({ success: false, error: { code: "marketplace_unavailable", message: "OS_CLOUD_URL not configured." } }, 503);
|
|
9346
|
+
}
|
|
9347
|
+
packageId = String(body?.packageId ?? "").trim();
|
|
9348
|
+
const versionId = String(body?.versionId ?? "latest").trim() || "latest";
|
|
9349
|
+
if (!packageId) {
|
|
9350
|
+
return c.json({ success: false, error: { code: "bad_request", message: "packageId is required." } }, 400);
|
|
9351
|
+
}
|
|
9352
|
+
let payload;
|
|
9353
|
+
const publicBase = resolveMarketplacePublicBaseUrl();
|
|
9354
|
+
const fetchAttempts = [];
|
|
9355
|
+
if (publicBase) {
|
|
9356
|
+
fetchAttempts.push({
|
|
9357
|
+
label: "public-r2",
|
|
9358
|
+
url: `${publicBase}/packages/${encodeURIComponent(packageId)}/versions/${encodeURIComponent(versionId)}/manifest.json`
|
|
9359
|
+
});
|
|
9360
|
+
}
|
|
9158
9361
|
fetchAttempts.push({
|
|
9159
|
-
label: "
|
|
9160
|
-
url: `${
|
|
9362
|
+
label: "cloud",
|
|
9363
|
+
url: `${this.cloudUrl}/api/v1/marketplace/packages/${encodeURIComponent(packageId)}/versions/${encodeURIComponent(versionId)}/manifest`
|
|
9161
9364
|
});
|
|
9162
|
-
|
|
9163
|
-
|
|
9164
|
-
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
|
|
9168
|
-
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
|
|
9172
|
-
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
|
|
9365
|
+
let lastErrStatus = 0;
|
|
9366
|
+
let lastErrText = "";
|
|
9367
|
+
for (const attempt of fetchAttempts) {
|
|
9368
|
+
try {
|
|
9369
|
+
const resp = await fetch(attempt.url, { headers: { "Accept": "application/json" } });
|
|
9370
|
+
if (!resp.ok) {
|
|
9371
|
+
lastErrStatus = resp.status;
|
|
9372
|
+
lastErrText = (await resp.text().catch(() => "")).slice(0, 200);
|
|
9373
|
+
if (attempt.label === "public-r2" && resp.status === 404) {
|
|
9374
|
+
ctx.logger?.info?.(`[MarketplaceInstallLocal] public-r2 miss for ${packageId}@${versionId}, falling back to cloud`);
|
|
9375
|
+
continue;
|
|
9376
|
+
}
|
|
9377
|
+
if (attempt.label === "public-r2" && resp.status >= 500) {
|
|
9378
|
+
ctx.logger?.warn?.(`[MarketplaceInstallLocal] public-r2 ${resp.status}, falling back to cloud`);
|
|
9379
|
+
continue;
|
|
9380
|
+
}
|
|
9381
|
+
break;
|
|
9178
9382
|
}
|
|
9179
|
-
|
|
9180
|
-
|
|
9383
|
+
payload = await resp.json();
|
|
9384
|
+
lastErrStatus = 0;
|
|
9385
|
+
break;
|
|
9386
|
+
} catch (err) {
|
|
9387
|
+
if (attempt.label === "public-r2") {
|
|
9388
|
+
ctx.logger?.warn?.(`[MarketplaceInstallLocal] public-r2 fetch error: ${err?.message ?? err}, falling back to cloud`);
|
|
9181
9389
|
continue;
|
|
9182
9390
|
}
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9186
|
-
|
|
9187
|
-
break;
|
|
9188
|
-
} catch (err) {
|
|
9189
|
-
if (attempt.label === "public-r2") {
|
|
9190
|
-
ctx.logger?.warn?.(`[MarketplaceInstallLocal] public-r2 fetch error: ${err?.message ?? err}, falling back to cloud`);
|
|
9191
|
-
continue;
|
|
9391
|
+
return c.json({
|
|
9392
|
+
success: false,
|
|
9393
|
+
error: { code: "cloud_fetch_failed", message: err?.message ?? String(err) }
|
|
9394
|
+
}, 502);
|
|
9192
9395
|
}
|
|
9396
|
+
}
|
|
9397
|
+
if (!payload) {
|
|
9193
9398
|
return c.json({
|
|
9194
9399
|
success: false,
|
|
9195
|
-
error: { code: "cloud_fetch_failed", message:
|
|
9196
|
-
}, 502);
|
|
9400
|
+
error: { code: "cloud_fetch_failed", message: `Cloud returned ${lastErrStatus}: ${lastErrText}` }
|
|
9401
|
+
}, lastErrStatus === 404 ? 404 : 502);
|
|
9197
9402
|
}
|
|
9403
|
+
const data = payload?.data ?? payload;
|
|
9404
|
+
manifest = data?.manifest;
|
|
9405
|
+
resolvedVersionId = String(data?.version_id ?? versionId);
|
|
9406
|
+
version = String(data?.version ?? "unknown");
|
|
9198
9407
|
}
|
|
9199
|
-
if (!payload) {
|
|
9200
|
-
return c.json({
|
|
9201
|
-
success: false,
|
|
9202
|
-
error: { code: "cloud_fetch_failed", message: `Cloud returned ${lastErrStatus}: ${lastErrText}` }
|
|
9203
|
-
}, lastErrStatus === 404 ? 404 : 502);
|
|
9204
|
-
}
|
|
9205
|
-
const data = payload?.data ?? payload;
|
|
9206
|
-
const manifest = data?.manifest;
|
|
9207
|
-
const resolvedVersionId = String(data?.version_id ?? versionId);
|
|
9208
|
-
const version = String(data?.version ?? "unknown");
|
|
9209
9408
|
const manifestId = String(manifest?.id ?? manifest?.name ?? "");
|
|
9210
9409
|
if (!manifest || !manifestId) {
|
|
9211
|
-
return c.json({ success: false, error: { code: "invalid_manifest", message: "
|
|
9410
|
+
return c.json({ success: false, error: { code: "invalid_manifest", message: "Invalid manifest payload." } }, inlineManifest ? 400 : 502);
|
|
9212
9411
|
}
|
|
9213
9412
|
const conflict = this.findConflict(ctx, manifestId);
|
|
9214
9413
|
if (conflict === "user-code") {
|
|
@@ -9220,6 +9419,18 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9220
9419
|
}
|
|
9221
9420
|
}, 409);
|
|
9222
9421
|
}
|
|
9422
|
+
try {
|
|
9423
|
+
const manifestService = ctx.getService("manifest");
|
|
9424
|
+
manifestService.register(manifest);
|
|
9425
|
+
} catch (err) {
|
|
9426
|
+
if (inlineManifest) {
|
|
9427
|
+
return c.json({
|
|
9428
|
+
success: false,
|
|
9429
|
+
error: { code: "register_failed", message: `Failed to register imported manifest: ${err?.message ?? err}` }
|
|
9430
|
+
}, 422);
|
|
9431
|
+
}
|
|
9432
|
+
ctx.logger?.warn?.(`[MarketplaceInstallLocal] hot-register failed for ${manifestId} (will load on next restart): ${err?.message ?? err}`);
|
|
9433
|
+
}
|
|
9223
9434
|
const entry = {
|
|
9224
9435
|
packageId,
|
|
9225
9436
|
versionId: resolvedVersionId,
|
|
@@ -9231,20 +9442,14 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9231
9442
|
withSampleData: false
|
|
9232
9443
|
};
|
|
9233
9444
|
try {
|
|
9234
|
-
|
|
9235
|
-
|
|
9445
|
+
mkdirSync4(this.storageDir, { recursive: true });
|
|
9446
|
+
writeFileSync3(join2(this.storageDir, safeFilename(manifestId)), JSON.stringify(entry, null, 2), "utf8");
|
|
9236
9447
|
} catch (err) {
|
|
9237
9448
|
return c.json({
|
|
9238
9449
|
success: false,
|
|
9239
9450
|
error: { code: "storage_failed", message: `Failed to persist manifest: ${err?.message ?? err}` }
|
|
9240
9451
|
}, 500);
|
|
9241
9452
|
}
|
|
9242
|
-
try {
|
|
9243
|
-
const manifestService = ctx.getService("manifest");
|
|
9244
|
-
manifestService.register(manifest);
|
|
9245
|
-
} catch (err) {
|
|
9246
|
-
ctx.logger?.warn?.(`[MarketplaceInstallLocal] hot-register failed for ${manifestId} (will load on next restart): ${err?.message ?? err}`);
|
|
9247
|
-
}
|
|
9248
9453
|
try {
|
|
9249
9454
|
const ql = ctx.getService("objectql");
|
|
9250
9455
|
if (ql && typeof ql.syncSchemas === "function") {
|
|
@@ -9258,7 +9463,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9258
9463
|
if (seededSummary.seeded.mode === "inline" && (seededSummary.seeded.inserted ?? 0) + (seededSummary.seeded.updated ?? 0) > 0) {
|
|
9259
9464
|
entry.withSampleData = true;
|
|
9260
9465
|
try {
|
|
9261
|
-
|
|
9466
|
+
writeFileSync3(join2(this.storageDir, safeFilename(manifestId)), JSON.stringify(entry, null, 2), "utf8");
|
|
9262
9467
|
} catch {
|
|
9263
9468
|
}
|
|
9264
9469
|
}
|
|
@@ -9305,8 +9510,8 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9305
9510
|
if (!manifestId) {
|
|
9306
9511
|
return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
|
|
9307
9512
|
}
|
|
9308
|
-
const file =
|
|
9309
|
-
if (!
|
|
9513
|
+
const file = join2(this.storageDir, safeFilename(manifestId));
|
|
9514
|
+
if (!existsSync3(file)) {
|
|
9310
9515
|
return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
|
|
9311
9516
|
}
|
|
9312
9517
|
try {
|
|
@@ -9333,7 +9538,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9333
9538
|
* (refuse to avoid silently overwriting authored code)
|
|
9334
9539
|
*/
|
|
9335
9540
|
this.findConflict = (ctx, manifestId) => {
|
|
9336
|
-
if (
|
|
9541
|
+
if (existsSync3(join2(this.storageDir, safeFilename(manifestId)))) {
|
|
9337
9542
|
return "marketplace";
|
|
9338
9543
|
}
|
|
9339
9544
|
try {
|
|
@@ -9375,13 +9580,13 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9375
9580
|
if (!manifestId) {
|
|
9376
9581
|
return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
|
|
9377
9582
|
}
|
|
9378
|
-
const file =
|
|
9379
|
-
if (!
|
|
9583
|
+
const file = join2(this.storageDir, safeFilename(manifestId));
|
|
9584
|
+
if (!existsSync3(file)) {
|
|
9380
9585
|
return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
|
|
9381
9586
|
}
|
|
9382
9587
|
let entry;
|
|
9383
9588
|
try {
|
|
9384
|
-
entry = JSON.parse(
|
|
9589
|
+
entry = JSON.parse(readFileSync2(file, "utf8"));
|
|
9385
9590
|
} catch (err) {
|
|
9386
9591
|
return c.json({ success: false, error: { code: "storage_failed", message: `Failed to read manifest cache: ${err?.message ?? err}` } }, 500);
|
|
9387
9592
|
}
|
|
@@ -9397,7 +9602,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9397
9602
|
}
|
|
9398
9603
|
try {
|
|
9399
9604
|
entry.withSampleData = true;
|
|
9400
|
-
|
|
9605
|
+
writeFileSync3(file, JSON.stringify(entry, null, 2), "utf8");
|
|
9401
9606
|
} catch {
|
|
9402
9607
|
}
|
|
9403
9608
|
return c.json({
|
|
@@ -9429,13 +9634,13 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9429
9634
|
if (!manifestId) {
|
|
9430
9635
|
return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
|
|
9431
9636
|
}
|
|
9432
|
-
const file =
|
|
9433
|
-
if (!
|
|
9637
|
+
const file = join2(this.storageDir, safeFilename(manifestId));
|
|
9638
|
+
if (!existsSync3(file)) {
|
|
9434
9639
|
return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
|
|
9435
9640
|
}
|
|
9436
9641
|
let entry;
|
|
9437
9642
|
try {
|
|
9438
|
-
entry = JSON.parse(
|
|
9643
|
+
entry = JSON.parse(readFileSync2(file, "utf8"));
|
|
9439
9644
|
} catch (err) {
|
|
9440
9645
|
return c.json({ success: false, error: { code: "storage_failed", message: `Failed to read manifest cache: ${err?.message ?? err}` } }, 500);
|
|
9441
9646
|
}
|
|
@@ -9484,7 +9689,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9484
9689
|
}
|
|
9485
9690
|
try {
|
|
9486
9691
|
entry.withSampleData = false;
|
|
9487
|
-
|
|
9692
|
+
writeFileSync3(file, JSON.stringify(entry, null, 2), "utf8");
|
|
9488
9693
|
} catch {
|
|
9489
9694
|
}
|
|
9490
9695
|
ctx.logger?.info?.(`[MarketplaceInstallLocal] purged ${manifestId}: deleted=${deleted} skipped=${skipped} errors=${errors}`);
|
|
@@ -9582,7 +9787,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9582
9787
|
}
|
|
9583
9788
|
}
|
|
9584
9789
|
if (opts.seedNow && datasets.length > 0) {
|
|
9585
|
-
const multiTenant = String(
|
|
9790
|
+
const multiTenant = String(readEnvWithDeprecation5("OS_MULTI_ORG_ENABLED", "OS_MULTI_TENANT") ?? "false").toLowerCase() !== "false";
|
|
9586
9791
|
try {
|
|
9587
9792
|
const ql = ctx.getService("objectql");
|
|
9588
9793
|
let metadata;
|
|
@@ -9683,12 +9888,12 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9683
9888
|
return null;
|
|
9684
9889
|
};
|
|
9685
9890
|
this.readAll = () => {
|
|
9686
|
-
if (!
|
|
9891
|
+
if (!existsSync3(this.storageDir)) return [];
|
|
9687
9892
|
const out = [];
|
|
9688
9893
|
for (const name of readdirSync(this.storageDir)) {
|
|
9689
9894
|
if (!name.endsWith(".json")) continue;
|
|
9690
9895
|
try {
|
|
9691
|
-
const raw =
|
|
9896
|
+
const raw = readFileSync2(join2(this.storageDir, name), "utf8");
|
|
9692
9897
|
out.push(JSON.parse(raw));
|
|
9693
9898
|
} catch {
|
|
9694
9899
|
}
|
|
@@ -9733,6 +9938,7 @@ import {
|
|
|
9733
9938
|
createRestApiPlugin
|
|
9734
9939
|
} from "@objectstack/rest";
|
|
9735
9940
|
export * from "@objectstack/core";
|
|
9941
|
+
import { readEnvWithDeprecation as readEnvWithDeprecation6, _resetEnvDeprecationWarnings } from "@objectstack/types";
|
|
9736
9942
|
export {
|
|
9737
9943
|
AppPlugin,
|
|
9738
9944
|
ArtifactApiClient,
|
|
@@ -9770,6 +9976,7 @@ export {
|
|
|
9770
9976
|
SandboxError,
|
|
9771
9977
|
SeedLoaderService,
|
|
9772
9978
|
UnimplementedScriptRunner,
|
|
9979
|
+
_resetEnvDeprecationWarnings,
|
|
9773
9980
|
actionBodyRunnerFactory,
|
|
9774
9981
|
backfillPlatformSsoClients,
|
|
9775
9982
|
buildPlatformSsoRedirectUri,
|
|
@@ -9794,6 +10001,7 @@ export {
|
|
|
9794
10001
|
mergeRuntimeModule,
|
|
9795
10002
|
parseTraceparent,
|
|
9796
10003
|
readArtifactSource,
|
|
10004
|
+
readEnvWithDeprecation6 as readEnvWithDeprecation,
|
|
9797
10005
|
resolveCloudUrl,
|
|
9798
10006
|
resolveDefaultArtifactPath,
|
|
9799
10007
|
resolveErrorReporter,
|