@objectstack/runtime 7.2.1 → 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 +453 -253
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -4
- package/dist/index.d.ts +24 -4
- package/dist/index.js +444 -244
- 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
|
|
@@ -1267,6 +1313,7 @@ var init_app_plugin = __esm({
|
|
|
1267
1313
|
"src/app-plugin.ts"() {
|
|
1268
1314
|
"use strict";
|
|
1269
1315
|
init_seed_loader();
|
|
1316
|
+
init_package_state_store();
|
|
1270
1317
|
init_quickjs_runner();
|
|
1271
1318
|
init_body_runner();
|
|
1272
1319
|
AppPlugin = class {
|
|
@@ -1292,6 +1339,24 @@ var init_app_plugin = __esm({
|
|
|
1292
1339
|
console.warn(
|
|
1293
1340
|
`[AppPlugin:init] appId=${appId} keys=${Object.keys(servicePayload).join(",")} flows=${Array.isArray(servicePayload.flows) ? servicePayload.flows.length : "n/a"}`
|
|
1294
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
|
+
}
|
|
1295
1360
|
ctx.getService("manifest").register(servicePayload);
|
|
1296
1361
|
};
|
|
1297
1362
|
this.start = async (ctx) => {
|
|
@@ -1836,6 +1901,170 @@ var init_app_plugin = __esm({
|
|
|
1836
1901
|
}
|
|
1837
1902
|
});
|
|
1838
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
|
+
|
|
1839
2068
|
// src/cloud/platform-sso.ts
|
|
1840
2069
|
var platform_sso_exports = {};
|
|
1841
2070
|
__export(platform_sso_exports, {
|
|
@@ -2240,173 +2469,19 @@ var Runtime = class {
|
|
|
2240
2469
|
}
|
|
2241
2470
|
};
|
|
2242
2471
|
|
|
2243
|
-
// src/
|
|
2244
|
-
|
|
2245
|
-
import { resolve as resolvePath2 } from "path";
|
|
2246
|
-
import { mkdirSync } from "fs";
|
|
2247
|
-
import { homedir } from "os";
|
|
2248
|
-
import { z } from "zod";
|
|
2249
|
-
import { readEnvWithDeprecation as readEnvWithDeprecation2 } from "@objectstack/types";
|
|
2250
|
-
function resolveObjectStackHome() {
|
|
2251
|
-
const raw = process.env.OS_HOME?.trim();
|
|
2252
|
-
if (raw && raw.length > 0) {
|
|
2253
|
-
if (raw.startsWith("~")) return resolvePath2(homedir(), raw.slice(1).replace(/^[/\\]/, ""));
|
|
2254
|
-
return resolvePath2(raw);
|
|
2255
|
-
}
|
|
2256
|
-
return resolvePath2(homedir(), ".objectstack");
|
|
2257
|
-
}
|
|
2258
|
-
var StandaloneStackConfigSchema = z.object({
|
|
2259
|
-
databaseUrl: z.string().optional(),
|
|
2260
|
-
databaseAuthToken: z.string().optional(),
|
|
2261
|
-
databaseDriver: z.enum(["sqlite", "sqlite-wasm", "memory", "postgres", "mongodb"]).optional(),
|
|
2262
|
-
environmentId: z.string().optional(),
|
|
2263
|
-
artifactPath: z.string().optional(),
|
|
2264
|
-
/**
|
|
2265
|
-
* Project root directory. When set (typically by the CLI after locating
|
|
2266
|
-
* `objectstack.config.ts`), the default sqlite database is placed under
|
|
2267
|
-
* `<projectRoot>/.objectstack/data/standalone.db` instead of the global
|
|
2268
|
-
* `~/.objectstack/data/standalone.db`. This keeps per-project data
|
|
2269
|
-
* scoped to the project folder so different examples / apps don't
|
|
2270
|
-
* share a single database by accident.
|
|
2271
|
-
*
|
|
2272
|
-
* Explicit `databaseUrl` / `OS_DATABASE_URL` / `OS_HOME` still take
|
|
2273
|
-
* precedence over this default.
|
|
2274
|
-
*/
|
|
2275
|
-
projectRoot: z.string().optional()
|
|
2276
|
-
});
|
|
2277
|
-
function detectDriverFromUrl(dbUrl) {
|
|
2278
|
-
if (/^memory:\/\//i.test(dbUrl)) return "memory";
|
|
2279
|
-
if (/^(postgres(ql)?|pg):\/\//i.test(dbUrl)) return "postgres";
|
|
2280
|
-
if (/^mongodb(\+srv)?:\/\//i.test(dbUrl)) return "mongodb";
|
|
2281
|
-
if (/^wasm-sqlite:\/\//i.test(dbUrl)) return "sqlite-wasm";
|
|
2282
|
-
if (/\.wasm\.db$/i.test(dbUrl)) return "sqlite-wasm";
|
|
2283
|
-
if (/^file:/i.test(dbUrl)) return "sqlite";
|
|
2284
|
-
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(dbUrl)) return "sqlite";
|
|
2285
|
-
throw new Error(
|
|
2286
|
-
`[StandaloneStack] Unsupported database URL scheme: ${dbUrl}. Supported schemes: memory://, postgres://, pg://, mongodb://, mongodb+srv://, file:`
|
|
2287
|
-
);
|
|
2288
|
-
}
|
|
2289
|
-
async function createStandaloneStack(config) {
|
|
2290
|
-
const cfg = StandaloneStackConfigSchema.parse(config ?? {});
|
|
2291
|
-
const { ObjectQLPlugin } = await import("@objectstack/objectql");
|
|
2292
|
-
const { MetadataPlugin } = await import("@objectstack/metadata");
|
|
2293
|
-
const { DriverPlugin: DriverPlugin2 } = await Promise.resolve().then(() => (init_driver_plugin(), driver_plugin_exports));
|
|
2294
|
-
const { AppPlugin: AppPlugin2 } = await Promise.resolve().then(() => (init_app_plugin(), app_plugin_exports));
|
|
2295
|
-
const cwd = process.cwd();
|
|
2296
|
-
const environmentId = cfg.environmentId ?? process.env.OS_ENVIRONMENT_ID ?? "proj_local";
|
|
2297
|
-
const artifactPathInput = cfg.artifactPath ?? process.env.OS_ARTIFACT_PATH ?? resolvePath2(cwd, "dist/objectstack.json");
|
|
2298
|
-
const artifactPath = isHttpUrl(artifactPathInput) ? artifactPathInput : artifactPathInput.startsWith("/") ? artifactPathInput : resolvePath2(cwd, artifactPathInput);
|
|
2299
|
-
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")}`);
|
|
2300
|
-
const explicitDriver = cfg.databaseDriver ?? process.env.OS_DATABASE_DRIVER?.trim();
|
|
2301
|
-
const dbDriver = explicitDriver ?? detectDriverFromUrl(dbUrl);
|
|
2302
|
-
let driverPlugin;
|
|
2303
|
-
if (dbDriver === "memory") {
|
|
2304
|
-
const { InMemoryDriver } = await import("@objectstack/driver-memory");
|
|
2305
|
-
driverPlugin = new DriverPlugin2(new InMemoryDriver());
|
|
2306
|
-
} else if (dbDriver === "postgres") {
|
|
2307
|
-
const { SqlDriver } = await import("@objectstack/driver-sql");
|
|
2308
|
-
driverPlugin = new DriverPlugin2(
|
|
2309
|
-
new SqlDriver({
|
|
2310
|
-
client: "pg",
|
|
2311
|
-
connection: dbUrl,
|
|
2312
|
-
pool: { min: 0, max: 5 }
|
|
2313
|
-
})
|
|
2314
|
-
);
|
|
2315
|
-
} else if (dbDriver === "mongodb") {
|
|
2316
|
-
let MongoDBDriver;
|
|
2317
|
-
try {
|
|
2318
|
-
({ MongoDBDriver } = await import("@objectstack/driver-mongodb"));
|
|
2319
|
-
} catch (err) {
|
|
2320
|
-
throw new Error(
|
|
2321
|
-
`[StandaloneStack] mongodb URL detected but @objectstack/driver-mongodb is not installed. Add it as a dependency or pass an explicit driverPlugin. (${err?.message ?? err})`
|
|
2322
|
-
);
|
|
2323
|
-
}
|
|
2324
|
-
driverPlugin = new DriverPlugin2(new MongoDBDriver({ url: dbUrl }));
|
|
2325
|
-
} else if (dbDriver === "sqlite-wasm") {
|
|
2326
|
-
const { SqliteWasmDriver } = await import("@objectstack/driver-sqlite-wasm");
|
|
2327
|
-
const filename = dbUrl.replace(/^wasm-sqlite:(\/\/)?/i, "").replace(/^file:(\/\/)?/i, "");
|
|
2328
|
-
if (filename && filename !== ":memory:") {
|
|
2329
|
-
mkdirSync(resolvePath2(filename, ".."), { recursive: true });
|
|
2330
|
-
}
|
|
2331
|
-
driverPlugin = new DriverPlugin2(
|
|
2332
|
-
new SqliteWasmDriver({
|
|
2333
|
-
filename: filename || ":memory:",
|
|
2334
|
-
persist: filename && filename !== ":memory:" ? "on-write" : void 0
|
|
2335
|
-
})
|
|
2336
|
-
);
|
|
2337
|
-
} else {
|
|
2338
|
-
const { SqlDriver } = await import("@objectstack/driver-sql");
|
|
2339
|
-
const filename = dbUrl.replace(/^file:(\/\/)?/, "");
|
|
2340
|
-
if (!filename || /^[a-z][a-z0-9+.-]*:\/\//i.test(filename)) {
|
|
2341
|
-
throw new Error(
|
|
2342
|
-
`[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.`
|
|
2343
|
-
);
|
|
2344
|
-
}
|
|
2345
|
-
mkdirSync(resolvePath2(filename, ".."), { recursive: true });
|
|
2346
|
-
driverPlugin = new DriverPlugin2(
|
|
2347
|
-
new SqlDriver({
|
|
2348
|
-
client: "better-sqlite3",
|
|
2349
|
-
connection: { filename },
|
|
2350
|
-
useNullAsDefault: true
|
|
2351
|
-
})
|
|
2352
|
-
);
|
|
2353
|
-
}
|
|
2354
|
-
const artifactBundle = await loadArtifactBundle(artifactPath, {
|
|
2355
|
-
tag: "[StandaloneStack]",
|
|
2356
|
-
unwrapEnvelope: true
|
|
2357
|
-
});
|
|
2358
|
-
if (artifactBundle) {
|
|
2359
|
-
const flowsCount = Array.isArray(artifactBundle?.flows) ? artifactBundle.flows.length : "n/a";
|
|
2360
|
-
console.warn(
|
|
2361
|
-
`[StandaloneStack] artifact loaded: path=${artifactPath} keys=${Object.keys(artifactBundle).join(",")} flows=${flowsCount}`
|
|
2362
|
-
);
|
|
2363
|
-
}
|
|
2364
|
-
const plugins = [
|
|
2365
|
-
driverPlugin,
|
|
2366
|
-
new MetadataPlugin({
|
|
2367
|
-
// Source-file scanner OFF — declarative metadata is loaded
|
|
2368
|
-
// from the compiled artifact, not from yaml/json files on
|
|
2369
|
-
// disk. Scanning would also recursively watch the project
|
|
2370
|
-
// root (incl. node_modules), which is expensive and prone
|
|
2371
|
-
// to EMFILE.
|
|
2372
|
-
watch: false,
|
|
2373
|
-
// Artifact-file HMR ON in non-production so edits to
|
|
2374
|
-
// `*.view.ts` / `*.flow.ts` (which the CLI dev-mode watcher
|
|
2375
|
-
// recompiles into `dist/objectstack.json`) are picked up by
|
|
2376
|
-
// the running server WITHOUT requiring a manual restart.
|
|
2377
|
-
// Uses polling under the hood (see plugin.ts) to avoid
|
|
2378
|
-
// `fs.watch` EMFILE on macOS / busy dev hosts.
|
|
2379
|
-
artifactWatch: process.env.NODE_ENV !== "production",
|
|
2380
|
-
environmentId,
|
|
2381
|
-
artifactSource: { mode: "local-file", path: artifactPath }
|
|
2382
|
-
}),
|
|
2383
|
-
new ObjectQLPlugin({ environmentId })
|
|
2384
|
-
];
|
|
2385
|
-
if (artifactBundle) plugins.push(new AppPlugin2(artifactBundle));
|
|
2386
|
-
const requires = Array.isArray(artifactBundle?.requires) ? artifactBundle.requires.filter((c) => typeof c === "string") : void 0;
|
|
2387
|
-
const objects = Array.isArray(artifactBundle?.objects) ? artifactBundle.objects : void 0;
|
|
2388
|
-
const manifest = artifactBundle?.manifest;
|
|
2389
|
-
return {
|
|
2390
|
-
plugins,
|
|
2391
|
-
api: {
|
|
2392
|
-
enableProjectScoping: false,
|
|
2393
|
-
projectResolution: "none"
|
|
2394
|
-
},
|
|
2395
|
-
...requires ? { requires } : {},
|
|
2396
|
-
...objects ? { objects } : {},
|
|
2397
|
-
...manifest ? { manifest } : {}
|
|
2398
|
-
};
|
|
2399
|
-
}
|
|
2472
|
+
// src/index.ts
|
|
2473
|
+
init_standalone_stack();
|
|
2400
2474
|
|
|
2401
2475
|
// src/default-host.ts
|
|
2402
|
-
|
|
2403
|
-
import { existsSync, mkdirSync as mkdirSync2, writeFileSync } from "fs";
|
|
2476
|
+
init_standalone_stack();
|
|
2404
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";
|
|
2405
2480
|
function resolveDefaultArtifactPath(explicitPath, cwd = process.cwd()) {
|
|
2406
2481
|
const candidate = explicitPath ?? process.env.OS_ARTIFACT_PATH ?? resolvePath3(cwd, "dist/objectstack.json");
|
|
2407
2482
|
if (isHttpUrl(candidate)) return candidate;
|
|
2408
2483
|
if (explicitPath || process.env.OS_ARTIFACT_PATH) return candidate;
|
|
2409
|
-
return
|
|
2484
|
+
return existsSync2(candidate) ? candidate : void 0;
|
|
2410
2485
|
}
|
|
2411
2486
|
async function createDefaultHostConfig(options = {}) {
|
|
2412
2487
|
const { requireArtifact = true, ...standaloneOpts } = options;
|
|
@@ -2419,9 +2494,9 @@ async function createDefaultHostConfig(options = {}) {
|
|
|
2419
2494
|
if (!resolvedArtifact && !requireArtifact) {
|
|
2420
2495
|
const home = resolveObjectStackHome();
|
|
2421
2496
|
const stubPath = resolvePath3(home, "dist/objectstack.json");
|
|
2422
|
-
if (!
|
|
2423
|
-
|
|
2424
|
-
|
|
2497
|
+
if (!existsSync2(stubPath)) {
|
|
2498
|
+
mkdirSync3(resolvePath3(stubPath, ".."), { recursive: true });
|
|
2499
|
+
writeFileSync2(
|
|
2425
2500
|
stubPath,
|
|
2426
2501
|
JSON.stringify(
|
|
2427
2502
|
{
|
|
@@ -2458,10 +2533,11 @@ init_app_plugin();
|
|
|
2458
2533
|
init_seed_loader();
|
|
2459
2534
|
|
|
2460
2535
|
// src/http-dispatcher.ts
|
|
2536
|
+
init_package_state_store();
|
|
2461
2537
|
import { getEnv, resolveLocale } from "@objectstack/core";
|
|
2462
2538
|
import { readEnvWithDeprecation as readEnvWithDeprecation3 } from "@objectstack/types";
|
|
2463
2539
|
import { CoreServiceName } from "@objectstack/spec/system";
|
|
2464
|
-
import { pluralToSingular } from "@objectstack/spec/shared";
|
|
2540
|
+
import { pluralToSingular, PLURAL_TO_SINGULAR } from "@objectstack/spec/shared";
|
|
2465
2541
|
|
|
2466
2542
|
// src/security/resolve-execution-context.ts
|
|
2467
2543
|
function readHeader(headers, name) {
|
|
@@ -3316,7 +3392,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3316
3392
|
if (protocol && typeof protocol.saveMetaItem === "function") {
|
|
3317
3393
|
try {
|
|
3318
3394
|
const organizationId = await this.resolveActiveOrganizationId(_context);
|
|
3319
|
-
const result = await protocol.saveMetaItem({ type, name, item: body, organizationId });
|
|
3395
|
+
const result = await protocol.saveMetaItem({ type, name, item: body, organizationId, ...packageId ? { packageId } : {} });
|
|
3320
3396
|
return { handled: true, response: this.success(result) };
|
|
3321
3397
|
} catch (e) {
|
|
3322
3398
|
return { handled: true, response: this.error(e.message, 400) };
|
|
@@ -3656,12 +3732,22 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3656
3732
|
const id = decodeURIComponent(parts[0]);
|
|
3657
3733
|
const pkg = registry.enablePackage(id);
|
|
3658
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
|
+
}
|
|
3659
3740
|
return { handled: true, response: this.success(pkg) };
|
|
3660
3741
|
}
|
|
3661
3742
|
if (parts.length === 2 && parts[1] === "disable" && m === "PATCH") {
|
|
3662
3743
|
const id = decodeURIComponent(parts[0]);
|
|
3663
3744
|
const pkg = registry.disablePackage(id);
|
|
3664
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
|
+
}
|
|
3665
3751
|
return { handled: true, response: this.success(pkg) };
|
|
3666
3752
|
}
|
|
3667
3753
|
if (parts.length === 2 && parts[1] === "publish" && m === "POST") {
|
|
@@ -3682,6 +3768,14 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3682
3768
|
}
|
|
3683
3769
|
return { handled: true, response: this.error("Metadata service not available", 503) };
|
|
3684
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
|
+
}
|
|
3685
3779
|
if (parts.length === 1 && m === "GET") {
|
|
3686
3780
|
const id = decodeURIComponent(parts[0]);
|
|
3687
3781
|
const pkg = registry.getPackage(id);
|
|
@@ -3699,6 +3793,83 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3699
3793
|
}
|
|
3700
3794
|
return { handled: false };
|
|
3701
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
|
+
}
|
|
3702
3873
|
/**
|
|
3703
3874
|
* Cloud / Environment Control-Plane routes.
|
|
3704
3875
|
*
|
|
@@ -6291,6 +6462,14 @@ function createDispatcherPlugin(config = {}) {
|
|
|
6291
6462
|
errorResponse(err, res);
|
|
6292
6463
|
}
|
|
6293
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
|
+
});
|
|
6294
6473
|
server.get(`${prefix}/packages/:id`, async (req, res) => {
|
|
6295
6474
|
try {
|
|
6296
6475
|
const result = await dispatcher.handlePackages(`/${req.params.id}`, "GET", {}, req.query, { request: req });
|
|
@@ -8387,7 +8566,7 @@ var AuthProxyPlugin = class {
|
|
|
8387
8566
|
};
|
|
8388
8567
|
|
|
8389
8568
|
// src/cloud/cloud-url.ts
|
|
8390
|
-
var DEFAULT_CLOUD_URL = "https://cloud.objectos.
|
|
8569
|
+
var DEFAULT_CLOUD_URL = "https://cloud.objectos.ai";
|
|
8391
8570
|
function resolveCloudUrl(explicit) {
|
|
8392
8571
|
const raw = (explicit ?? process.env.OS_CLOUD_URL ?? "").trim();
|
|
8393
8572
|
const lower = raw.toLowerCase();
|
|
@@ -9062,8 +9241,8 @@ async function createObjectOSStack(config) {
|
|
|
9062
9241
|
}
|
|
9063
9242
|
|
|
9064
9243
|
// src/cloud/marketplace-install-local-plugin.ts
|
|
9065
|
-
import { existsSync as
|
|
9066
|
-
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";
|
|
9067
9246
|
import { readEnvWithDeprecation as readEnvWithDeprecation5 } from "@objectstack/types";
|
|
9068
9247
|
var ROUTE_BASE = "/api/v1/marketplace/install-local";
|
|
9069
9248
|
var DEFAULT_DIR = ".objectstack/installed-packages";
|
|
@@ -9139,9 +9318,6 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9139
9318
|
}
|
|
9140
9319
|
};
|
|
9141
9320
|
this.handleInstall = async (c, ctx) => {
|
|
9142
|
-
if (!this.cloudUrl) {
|
|
9143
|
-
return c.json({ success: false, error: { code: "marketplace_unavailable", message: "OS_CLOUD_URL not configured." } }, 503);
|
|
9144
|
-
}
|
|
9145
9321
|
const userId = await this.requireAuthenticatedUser(c, ctx);
|
|
9146
9322
|
if (!userId) {
|
|
9147
9323
|
return c.json({ success: false, error: { code: "unauthorized", message: "Authentication required to install packages." } }, 401);
|
|
@@ -9151,69 +9327,87 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9151
9327
|
body = await c.req.json();
|
|
9152
9328
|
} catch {
|
|
9153
9329
|
}
|
|
9154
|
-
const
|
|
9155
|
-
|
|
9156
|
-
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
|
|
9162
|
-
|
|
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
|
+
}
|
|
9163
9361
|
fetchAttempts.push({
|
|
9164
|
-
label: "
|
|
9165
|
-
url: `${
|
|
9362
|
+
label: "cloud",
|
|
9363
|
+
url: `${this.cloudUrl}/api/v1/marketplace/packages/${encodeURIComponent(packageId)}/versions/${encodeURIComponent(versionId)}/manifest`
|
|
9166
9364
|
});
|
|
9167
|
-
|
|
9168
|
-
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
|
|
9172
|
-
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
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;
|
|
9183
9382
|
}
|
|
9184
|
-
|
|
9185
|
-
|
|
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`);
|
|
9186
9389
|
continue;
|
|
9187
9390
|
}
|
|
9188
|
-
|
|
9189
|
-
|
|
9190
|
-
|
|
9191
|
-
|
|
9192
|
-
break;
|
|
9193
|
-
} catch (err) {
|
|
9194
|
-
if (attempt.label === "public-r2") {
|
|
9195
|
-
ctx.logger?.warn?.(`[MarketplaceInstallLocal] public-r2 fetch error: ${err?.message ?? err}, falling back to cloud`);
|
|
9196
|
-
continue;
|
|
9391
|
+
return c.json({
|
|
9392
|
+
success: false,
|
|
9393
|
+
error: { code: "cloud_fetch_failed", message: err?.message ?? String(err) }
|
|
9394
|
+
}, 502);
|
|
9197
9395
|
}
|
|
9396
|
+
}
|
|
9397
|
+
if (!payload) {
|
|
9198
9398
|
return c.json({
|
|
9199
9399
|
success: false,
|
|
9200
|
-
error: { code: "cloud_fetch_failed", message:
|
|
9201
|
-
}, 502);
|
|
9400
|
+
error: { code: "cloud_fetch_failed", message: `Cloud returned ${lastErrStatus}: ${lastErrText}` }
|
|
9401
|
+
}, lastErrStatus === 404 ? 404 : 502);
|
|
9202
9402
|
}
|
|
9403
|
+
const data = payload?.data ?? payload;
|
|
9404
|
+
manifest = data?.manifest;
|
|
9405
|
+
resolvedVersionId = String(data?.version_id ?? versionId);
|
|
9406
|
+
version = String(data?.version ?? "unknown");
|
|
9203
9407
|
}
|
|
9204
|
-
if (!payload) {
|
|
9205
|
-
return c.json({
|
|
9206
|
-
success: false,
|
|
9207
|
-
error: { code: "cloud_fetch_failed", message: `Cloud returned ${lastErrStatus}: ${lastErrText}` }
|
|
9208
|
-
}, lastErrStatus === 404 ? 404 : 502);
|
|
9209
|
-
}
|
|
9210
|
-
const data = payload?.data ?? payload;
|
|
9211
|
-
const manifest = data?.manifest;
|
|
9212
|
-
const resolvedVersionId = String(data?.version_id ?? versionId);
|
|
9213
|
-
const version = String(data?.version ?? "unknown");
|
|
9214
9408
|
const manifestId = String(manifest?.id ?? manifest?.name ?? "");
|
|
9215
9409
|
if (!manifest || !manifestId) {
|
|
9216
|
-
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);
|
|
9217
9411
|
}
|
|
9218
9412
|
const conflict = this.findConflict(ctx, manifestId);
|
|
9219
9413
|
if (conflict === "user-code") {
|
|
@@ -9225,6 +9419,18 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9225
9419
|
}
|
|
9226
9420
|
}, 409);
|
|
9227
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
|
+
}
|
|
9228
9434
|
const entry = {
|
|
9229
9435
|
packageId,
|
|
9230
9436
|
versionId: resolvedVersionId,
|
|
@@ -9236,20 +9442,14 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9236
9442
|
withSampleData: false
|
|
9237
9443
|
};
|
|
9238
9444
|
try {
|
|
9239
|
-
|
|
9240
|
-
|
|
9445
|
+
mkdirSync4(this.storageDir, { recursive: true });
|
|
9446
|
+
writeFileSync3(join2(this.storageDir, safeFilename(manifestId)), JSON.stringify(entry, null, 2), "utf8");
|
|
9241
9447
|
} catch (err) {
|
|
9242
9448
|
return c.json({
|
|
9243
9449
|
success: false,
|
|
9244
9450
|
error: { code: "storage_failed", message: `Failed to persist manifest: ${err?.message ?? err}` }
|
|
9245
9451
|
}, 500);
|
|
9246
9452
|
}
|
|
9247
|
-
try {
|
|
9248
|
-
const manifestService = ctx.getService("manifest");
|
|
9249
|
-
manifestService.register(manifest);
|
|
9250
|
-
} catch (err) {
|
|
9251
|
-
ctx.logger?.warn?.(`[MarketplaceInstallLocal] hot-register failed for ${manifestId} (will load on next restart): ${err?.message ?? err}`);
|
|
9252
|
-
}
|
|
9253
9453
|
try {
|
|
9254
9454
|
const ql = ctx.getService("objectql");
|
|
9255
9455
|
if (ql && typeof ql.syncSchemas === "function") {
|
|
@@ -9263,7 +9463,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9263
9463
|
if (seededSummary.seeded.mode === "inline" && (seededSummary.seeded.inserted ?? 0) + (seededSummary.seeded.updated ?? 0) > 0) {
|
|
9264
9464
|
entry.withSampleData = true;
|
|
9265
9465
|
try {
|
|
9266
|
-
|
|
9466
|
+
writeFileSync3(join2(this.storageDir, safeFilename(manifestId)), JSON.stringify(entry, null, 2), "utf8");
|
|
9267
9467
|
} catch {
|
|
9268
9468
|
}
|
|
9269
9469
|
}
|
|
@@ -9310,8 +9510,8 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9310
9510
|
if (!manifestId) {
|
|
9311
9511
|
return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
|
|
9312
9512
|
}
|
|
9313
|
-
const file =
|
|
9314
|
-
if (!
|
|
9513
|
+
const file = join2(this.storageDir, safeFilename(manifestId));
|
|
9514
|
+
if (!existsSync3(file)) {
|
|
9315
9515
|
return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
|
|
9316
9516
|
}
|
|
9317
9517
|
try {
|
|
@@ -9338,7 +9538,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9338
9538
|
* (refuse to avoid silently overwriting authored code)
|
|
9339
9539
|
*/
|
|
9340
9540
|
this.findConflict = (ctx, manifestId) => {
|
|
9341
|
-
if (
|
|
9541
|
+
if (existsSync3(join2(this.storageDir, safeFilename(manifestId)))) {
|
|
9342
9542
|
return "marketplace";
|
|
9343
9543
|
}
|
|
9344
9544
|
try {
|
|
@@ -9380,13 +9580,13 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9380
9580
|
if (!manifestId) {
|
|
9381
9581
|
return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
|
|
9382
9582
|
}
|
|
9383
|
-
const file =
|
|
9384
|
-
if (!
|
|
9583
|
+
const file = join2(this.storageDir, safeFilename(manifestId));
|
|
9584
|
+
if (!existsSync3(file)) {
|
|
9385
9585
|
return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
|
|
9386
9586
|
}
|
|
9387
9587
|
let entry;
|
|
9388
9588
|
try {
|
|
9389
|
-
entry = JSON.parse(
|
|
9589
|
+
entry = JSON.parse(readFileSync2(file, "utf8"));
|
|
9390
9590
|
} catch (err) {
|
|
9391
9591
|
return c.json({ success: false, error: { code: "storage_failed", message: `Failed to read manifest cache: ${err?.message ?? err}` } }, 500);
|
|
9392
9592
|
}
|
|
@@ -9402,7 +9602,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9402
9602
|
}
|
|
9403
9603
|
try {
|
|
9404
9604
|
entry.withSampleData = true;
|
|
9405
|
-
|
|
9605
|
+
writeFileSync3(file, JSON.stringify(entry, null, 2), "utf8");
|
|
9406
9606
|
} catch {
|
|
9407
9607
|
}
|
|
9408
9608
|
return c.json({
|
|
@@ -9434,13 +9634,13 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9434
9634
|
if (!manifestId) {
|
|
9435
9635
|
return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
|
|
9436
9636
|
}
|
|
9437
|
-
const file =
|
|
9438
|
-
if (!
|
|
9637
|
+
const file = join2(this.storageDir, safeFilename(manifestId));
|
|
9638
|
+
if (!existsSync3(file)) {
|
|
9439
9639
|
return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
|
|
9440
9640
|
}
|
|
9441
9641
|
let entry;
|
|
9442
9642
|
try {
|
|
9443
|
-
entry = JSON.parse(
|
|
9643
|
+
entry = JSON.parse(readFileSync2(file, "utf8"));
|
|
9444
9644
|
} catch (err) {
|
|
9445
9645
|
return c.json({ success: false, error: { code: "storage_failed", message: `Failed to read manifest cache: ${err?.message ?? err}` } }, 500);
|
|
9446
9646
|
}
|
|
@@ -9489,7 +9689,7 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9489
9689
|
}
|
|
9490
9690
|
try {
|
|
9491
9691
|
entry.withSampleData = false;
|
|
9492
|
-
|
|
9692
|
+
writeFileSync3(file, JSON.stringify(entry, null, 2), "utf8");
|
|
9493
9693
|
} catch {
|
|
9494
9694
|
}
|
|
9495
9695
|
ctx.logger?.info?.(`[MarketplaceInstallLocal] purged ${manifestId}: deleted=${deleted} skipped=${skipped} errors=${errors}`);
|
|
@@ -9688,12 +9888,12 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9688
9888
|
return null;
|
|
9689
9889
|
};
|
|
9690
9890
|
this.readAll = () => {
|
|
9691
|
-
if (!
|
|
9891
|
+
if (!existsSync3(this.storageDir)) return [];
|
|
9692
9892
|
const out = [];
|
|
9693
9893
|
for (const name of readdirSync(this.storageDir)) {
|
|
9694
9894
|
if (!name.endsWith(".json")) continue;
|
|
9695
9895
|
try {
|
|
9696
|
-
const raw =
|
|
9896
|
+
const raw = readFileSync2(join2(this.storageDir, name), "utf8");
|
|
9697
9897
|
out.push(JSON.parse(raw));
|
|
9698
9898
|
} catch {
|
|
9699
9899
|
}
|