@objectstack/runtime 6.9.0 → 7.1.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 +440 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -1
- package/dist/index.d.ts +47 -1
- package/dist/index.js +440 -89
- package/dist/index.js.map +1 -1
- package/package.json +18 -25
package/dist/index.cjs
CHANGED
|
@@ -1294,7 +1294,15 @@ var init_app_plugin = __esm({
|
|
|
1294
1294
|
AppPlugin = class {
|
|
1295
1295
|
constructor(bundle, projectContext) {
|
|
1296
1296
|
this.type = "app";
|
|
1297
|
+
/** When true, init/start become no-ops — env has no app payload. */
|
|
1298
|
+
this.empty = false;
|
|
1297
1299
|
this.init = async (ctx) => {
|
|
1300
|
+
if (this.empty) {
|
|
1301
|
+
ctx.logger.debug("[AppPlugin] empty env \u2014 no app payload, skipping init", {
|
|
1302
|
+
pluginName: this.name
|
|
1303
|
+
});
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1298
1306
|
const sys = this.bundle.manifest || this.bundle;
|
|
1299
1307
|
const appId = sys.id || sys.name;
|
|
1300
1308
|
ctx.logger.info("Registering App Service", {
|
|
@@ -1309,6 +1317,12 @@ var init_app_plugin = __esm({
|
|
|
1309
1317
|
ctx.getService("manifest").register(servicePayload);
|
|
1310
1318
|
};
|
|
1311
1319
|
this.start = async (ctx) => {
|
|
1320
|
+
if (this.empty) {
|
|
1321
|
+
ctx.logger.debug("[AppPlugin] empty env \u2014 no app payload, skipping start", {
|
|
1322
|
+
pluginName: this.name
|
|
1323
|
+
});
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1312
1326
|
const sys = this.bundle.manifest || this.bundle;
|
|
1313
1327
|
const appId = sys.id || sys.name;
|
|
1314
1328
|
let ql;
|
|
@@ -1604,47 +1618,64 @@ var init_app_plugin = __esm({
|
|
|
1604
1618
|
if (multiTenant) {
|
|
1605
1619
|
ctx.logger.info("[Seeder] multi-tenant mode \u2014 skipping inline seed; per-org replay will run on sys_organization insert");
|
|
1606
1620
|
} else {
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
const
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1621
|
+
const seedBudgetMs = Number(process.env.OS_INLINE_SEED_BUDGET_MS ?? 8e3);
|
|
1622
|
+
const seedPromise = (async () => {
|
|
1623
|
+
try {
|
|
1624
|
+
const metadata = ctx.getService("metadata");
|
|
1625
|
+
if (metadata) {
|
|
1626
|
+
const seedLoader = new SeedLoaderService(ql, metadata, ctx.logger);
|
|
1627
|
+
const { SeedLoaderRequestSchema } = await import("@objectstack/spec/data");
|
|
1628
|
+
const request = SeedLoaderRequestSchema.parse({
|
|
1629
|
+
datasets: normalizedDatasets,
|
|
1630
|
+
config: { defaultMode: "upsert", multiPass: true }
|
|
1631
|
+
});
|
|
1632
|
+
const result = await seedLoader.load(request);
|
|
1633
|
+
ctx.logger.info("[Seeder] Seed loading complete", {
|
|
1634
|
+
inserted: result.summary.totalInserted,
|
|
1635
|
+
updated: result.summary.totalUpdated,
|
|
1636
|
+
errors: result.errors.length
|
|
1637
|
+
});
|
|
1638
|
+
} else {
|
|
1639
|
+
ctx.logger.debug("[Seeder] No metadata service; using basic insert fallback");
|
|
1640
|
+
for (const dataset of normalizedDatasets) {
|
|
1641
|
+
ctx.logger.info(`[Seeder] Seeding ${dataset.records.length} records for ${dataset.object}`);
|
|
1642
|
+
for (const record of dataset.records) {
|
|
1643
|
+
try {
|
|
1644
|
+
await ql.insert(dataset.object, record, { context: { isSystem: true } });
|
|
1645
|
+
} catch (err) {
|
|
1646
|
+
ctx.logger.warn(`[Seeder] Failed to insert ${dataset.object} record:`, { error: err.message });
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
ctx.logger.info("[Seeder] Data seeding complete.");
|
|
1651
|
+
}
|
|
1652
|
+
} catch (err) {
|
|
1653
|
+
ctx.logger.warn("[Seeder] SeedLoaderService failed, falling back to basic insert", { error: err.message });
|
|
1624
1654
|
for (const dataset of normalizedDatasets) {
|
|
1625
|
-
ctx.logger.info(`[Seeder] Seeding ${dataset.records.length} records for ${dataset.object}`);
|
|
1626
1655
|
for (const record of dataset.records) {
|
|
1627
1656
|
try {
|
|
1628
1657
|
await ql.insert(dataset.object, record, { context: { isSystem: true } });
|
|
1629
|
-
} catch (
|
|
1630
|
-
ctx.logger.warn(`[Seeder] Failed to insert ${dataset.object} record:`, { error:
|
|
1658
|
+
} catch (insertErr) {
|
|
1659
|
+
ctx.logger.warn(`[Seeder] Failed to insert ${dataset.object} record:`, { error: insertErr.message });
|
|
1631
1660
|
}
|
|
1632
1661
|
}
|
|
1633
1662
|
}
|
|
1634
|
-
ctx.logger.info("[Seeder] Data seeding complete.");
|
|
1663
|
+
ctx.logger.info("[Seeder] Data seeding complete (fallback).");
|
|
1635
1664
|
}
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
}
|
|
1646
|
-
|
|
1647
|
-
|
|
1665
|
+
})();
|
|
1666
|
+
let timer;
|
|
1667
|
+
const budget = new Promise((resolve2) => {
|
|
1668
|
+
timer = setTimeout(() => resolve2("budget"), seedBudgetMs);
|
|
1669
|
+
});
|
|
1670
|
+
const winner = await Promise.race([seedPromise.then(() => "done"), budget]);
|
|
1671
|
+
if (timer) clearTimeout(timer);
|
|
1672
|
+
if (winner === "budget") {
|
|
1673
|
+
ctx.logger.warn(
|
|
1674
|
+
`[Seeder] Inline seed exceeded ${seedBudgetMs}ms budget for ${appId}; continuing in background to avoid blocking kernel start.`
|
|
1675
|
+
);
|
|
1676
|
+
seedPromise.catch((err) => {
|
|
1677
|
+
ctx.logger.warn("[Seeder] Background seed failed after budget", { appId, error: err?.message ?? String(err) });
|
|
1678
|
+
});
|
|
1648
1679
|
}
|
|
1649
1680
|
}
|
|
1650
1681
|
}
|
|
@@ -1658,6 +1689,39 @@ var init_app_plugin = __esm({
|
|
|
1658
1689
|
const sys = bundle?.manifest || bundle;
|
|
1659
1690
|
const appId = sys?.id || sys?.name;
|
|
1660
1691
|
if (!appId) {
|
|
1692
|
+
const APP_CATEGORY_KEYS = [
|
|
1693
|
+
"objects",
|
|
1694
|
+
"views",
|
|
1695
|
+
"apps",
|
|
1696
|
+
"pages",
|
|
1697
|
+
"dashboards",
|
|
1698
|
+
"reports",
|
|
1699
|
+
"flows",
|
|
1700
|
+
"workflows",
|
|
1701
|
+
"triggers",
|
|
1702
|
+
"agents",
|
|
1703
|
+
"tools",
|
|
1704
|
+
"skills",
|
|
1705
|
+
"actions",
|
|
1706
|
+
"permissions",
|
|
1707
|
+
"roles",
|
|
1708
|
+
"profiles",
|
|
1709
|
+
"translations",
|
|
1710
|
+
"sharingRules",
|
|
1711
|
+
"ragPipelines",
|
|
1712
|
+
"data",
|
|
1713
|
+
"emailTemplates"
|
|
1714
|
+
];
|
|
1715
|
+
const hasAppPayload = APP_CATEGORY_KEYS.some((k) => {
|
|
1716
|
+
const v = (bundle && bundle[k]) ?? (sys && sys[k]);
|
|
1717
|
+
return Array.isArray(v) && v.length > 0;
|
|
1718
|
+
});
|
|
1719
|
+
if (!hasAppPayload) {
|
|
1720
|
+
this.empty = true;
|
|
1721
|
+
const envSlug = projectContext?.environmentId ? projectContext.environmentId.slice(0, 8) : "empty";
|
|
1722
|
+
this.name = `plugin.app.empty-${envSlug}`;
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1661
1725
|
const bundleKeys = bundle && typeof bundle === "object" ? Object.keys(bundle).slice(0, 20).join(",") : typeof bundle;
|
|
1662
1726
|
const sysKeys = sys && typeof sys === "object" ? Object.keys(sys).slice(0, 20).join(",") : typeof sys;
|
|
1663
1727
|
const ctxHint = projectContext ? ` projectContext=${JSON.stringify({
|
|
@@ -1666,7 +1730,7 @@ var init_app_plugin = __esm({
|
|
|
1666
1730
|
source: projectContext.source
|
|
1667
1731
|
})}` : "";
|
|
1668
1732
|
throw new Error(
|
|
1669
|
-
`[AppPlugin] bundle
|
|
1733
|
+
`[AppPlugin] bundle has app payload but no manifest.id / manifest.name \u2014 cannot register as a plugin. bundleKeys=[${bundleKeys}] sysKeys=[${sysKeys}]${ctxHint}`
|
|
1670
1734
|
);
|
|
1671
1735
|
}
|
|
1672
1736
|
this.name = `plugin.app.${appId}`;
|
|
@@ -2283,7 +2347,7 @@ function resolveObjectStackHome() {
|
|
|
2283
2347
|
var StandaloneStackConfigSchema = import_zod.z.object({
|
|
2284
2348
|
databaseUrl: import_zod.z.string().optional(),
|
|
2285
2349
|
databaseAuthToken: import_zod.z.string().optional(),
|
|
2286
|
-
databaseDriver: import_zod.z.enum(["sqlite", "sqlite-wasm", "
|
|
2350
|
+
databaseDriver: import_zod.z.enum(["sqlite", "sqlite-wasm", "memory", "postgres", "mongodb"]).optional(),
|
|
2287
2351
|
environmentId: import_zod.z.string().optional(),
|
|
2288
2352
|
artifactPath: import_zod.z.string().optional(),
|
|
2289
2353
|
/**
|
|
@@ -2301,7 +2365,6 @@ var StandaloneStackConfigSchema = import_zod.z.object({
|
|
|
2301
2365
|
});
|
|
2302
2366
|
function detectDriverFromUrl(dbUrl) {
|
|
2303
2367
|
if (/^memory:\/\//i.test(dbUrl)) return "memory";
|
|
2304
|
-
if (/^(libsql|https?):\/\//i.test(dbUrl)) return "turso";
|
|
2305
2368
|
if (/^(postgres(ql)?|pg):\/\//i.test(dbUrl)) return "postgres";
|
|
2306
2369
|
if (/^mongodb(\+srv)?:\/\//i.test(dbUrl)) return "mongodb";
|
|
2307
2370
|
if (/^wasm-sqlite:\/\//i.test(dbUrl)) return "sqlite-wasm";
|
|
@@ -2309,7 +2372,7 @@ function detectDriverFromUrl(dbUrl) {
|
|
|
2309
2372
|
if (/^file:/i.test(dbUrl)) return "sqlite";
|
|
2310
2373
|
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(dbUrl)) return "sqlite";
|
|
2311
2374
|
throw new Error(
|
|
2312
|
-
`[StandaloneStack] Unsupported database URL scheme: ${dbUrl}. Supported schemes: memory://,
|
|
2375
|
+
`[StandaloneStack] Unsupported database URL scheme: ${dbUrl}. Supported schemes: memory://, postgres://, pg://, mongodb://, mongodb+srv://, file:`
|
|
2313
2376
|
);
|
|
2314
2377
|
}
|
|
2315
2378
|
async function createStandaloneStack(config) {
|
|
@@ -2323,25 +2386,12 @@ async function createStandaloneStack(config) {
|
|
|
2323
2386
|
const artifactPathInput = cfg.artifactPath ?? process.env.OS_ARTIFACT_PATH ?? (0, import_node_path2.resolve)(cwd, "dist/objectstack.json");
|
|
2324
2387
|
const artifactPath = isHttpUrl(artifactPathInput) ? artifactPathInput : artifactPathInput.startsWith("/") ? artifactPathInput : (0, import_node_path2.resolve)(cwd, artifactPathInput);
|
|
2325
2388
|
const dbUrl = cfg.databaseUrl ?? process.env.OS_DATABASE_URL?.trim() ?? process.env.TURSO_DATABASE_URL?.trim() ?? (process.env.OS_HOME?.trim() ? `file:${(0, import_node_path2.resolve)(resolveObjectStackHome(), "data/standalone.db")}` : cfg.projectRoot ? `file:${(0, import_node_path2.resolve)(cfg.projectRoot, ".objectstack/data/standalone.db")}` : `file:${(0, import_node_path2.resolve)(resolveObjectStackHome(), "data/standalone.db")}`);
|
|
2326
|
-
const dbAuthToken = cfg.databaseAuthToken ?? process.env.OS_DATABASE_AUTH_TOKEN?.trim() ?? process.env.TURSO_AUTH_TOKEN?.trim();
|
|
2327
2389
|
const explicitDriver = cfg.databaseDriver ?? process.env.OS_DATABASE_DRIVER?.trim();
|
|
2328
2390
|
const dbDriver = explicitDriver ?? detectDriverFromUrl(dbUrl);
|
|
2329
2391
|
let driverPlugin;
|
|
2330
2392
|
if (dbDriver === "memory") {
|
|
2331
2393
|
const { InMemoryDriver } = await import("@objectstack/driver-memory");
|
|
2332
2394
|
driverPlugin = new DriverPlugin2(new InMemoryDriver());
|
|
2333
|
-
} else if (dbDriver === "turso") {
|
|
2334
|
-
let TursoDriver;
|
|
2335
|
-
try {
|
|
2336
|
-
({ TursoDriver } = await import("@objectstack/driver-turso"));
|
|
2337
|
-
} catch (err) {
|
|
2338
|
-
throw new Error(
|
|
2339
|
-
`[StandaloneStack] libsql/turso URL detected ("${dbUrl}") but @objectstack/driver-turso is not installed. Install it with: npm install @objectstack/driver-turso (or use a file: URL to default to better-sqlite3). (${err?.message ?? err})`
|
|
2340
|
-
);
|
|
2341
|
-
}
|
|
2342
|
-
driverPlugin = new DriverPlugin2(
|
|
2343
|
-
new TursoDriver({ url: dbUrl, authToken: dbAuthToken })
|
|
2344
|
-
);
|
|
2345
2395
|
} else if (dbDriver === "postgres") {
|
|
2346
2396
|
const { SqlDriver } = await import("@objectstack/driver-sql");
|
|
2347
2397
|
driverPlugin = new DriverPlugin2(
|
|
@@ -4082,7 +4132,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4082
4132
|
return {
|
|
4083
4133
|
handled: true,
|
|
4084
4134
|
response: this.error(
|
|
4085
|
-
"No ObjectQL driver is registered. Register at least one DriverPlugin (e.g. InMemoryDriver or
|
|
4135
|
+
"No ObjectQL driver is registered. Register at least one DriverPlugin (e.g. InMemoryDriver or SqlDriver).",
|
|
4086
4136
|
503
|
|
4087
4137
|
)
|
|
4088
4138
|
};
|
|
@@ -7574,6 +7624,27 @@ function extractRuntimeFromMetadata(metadata) {
|
|
|
7574
7624
|
}
|
|
7575
7625
|
async function createDriver(driverType, databaseUrl, authToken) {
|
|
7576
7626
|
switch (driverType) {
|
|
7627
|
+
case "libsql":
|
|
7628
|
+
case "turso": {
|
|
7629
|
+
let TursoDriver;
|
|
7630
|
+
try {
|
|
7631
|
+
({ TursoDriver } = await import("@objectstack/driver-turso"));
|
|
7632
|
+
} catch (primaryErr) {
|
|
7633
|
+
try {
|
|
7634
|
+
const { createRequire } = await import("module");
|
|
7635
|
+
const path = await import("path");
|
|
7636
|
+
const url = await import("url");
|
|
7637
|
+
const hostRequire = createRequire(path.join(process.cwd(), "noop.js"));
|
|
7638
|
+
const resolved = hostRequire.resolve("@objectstack/driver-turso");
|
|
7639
|
+
({ TursoDriver } = await import(url.pathToFileURL(resolved).href));
|
|
7640
|
+
} catch (fallbackErr) {
|
|
7641
|
+
throw new Error(
|
|
7642
|
+
`[ArtifactEnvironmentRegistry] libsql/turso driver requested but @objectstack/driver-turso is not resolvable. Install it from the cloud monorepo (cloud/packages/driver-turso) or via npm. (primary: ${primaryErr?.message ?? primaryErr}; fallback: ${fallbackErr?.message ?? fallbackErr})`
|
|
7643
|
+
);
|
|
7644
|
+
}
|
|
7645
|
+
}
|
|
7646
|
+
return new TursoDriver({ url: databaseUrl, authToken });
|
|
7647
|
+
}
|
|
7577
7648
|
case "memory": {
|
|
7578
7649
|
const { InMemoryDriver } = await import("@objectstack/driver-memory");
|
|
7579
7650
|
const dbName = databaseUrl.replace(/^memory:\/\//, "").trim();
|
|
@@ -7592,18 +7663,6 @@ async function createDriver(driverType, databaseUrl, authToken) {
|
|
|
7592
7663
|
useNullAsDefault: true
|
|
7593
7664
|
});
|
|
7594
7665
|
}
|
|
7595
|
-
case "libsql":
|
|
7596
|
-
case "turso": {
|
|
7597
|
-
let TursoDriver;
|
|
7598
|
-
try {
|
|
7599
|
-
({ TursoDriver } = await import("@objectstack/driver-turso"));
|
|
7600
|
-
} catch (err) {
|
|
7601
|
-
throw new Error(
|
|
7602
|
-
`[ArtifactEnvironmentRegistry] libsql/turso driver requested but @objectstack/driver-turso is not installed. Install it with: npm install @objectstack/driver-turso. (${err?.message ?? err})`
|
|
7603
|
-
);
|
|
7604
|
-
}
|
|
7605
|
-
return new TursoDriver({ url: databaseUrl, authToken });
|
|
7606
|
-
}
|
|
7607
7666
|
case "postgres":
|
|
7608
7667
|
case "postgresql":
|
|
7609
7668
|
case "pg": {
|
|
@@ -7879,9 +7938,20 @@ var ArtifactKernelFactory = class {
|
|
|
7879
7938
|
this.logger.warn?.("[ArtifactKernelFactory] OS_AUTH_SECRET not set \u2014 per-project AuthPlugin skipped (auth endpoints will return 404)", { environmentId });
|
|
7880
7939
|
}
|
|
7881
7940
|
try {
|
|
7882
|
-
const { SecurityPlugin } = await import("@objectstack/plugin-security");
|
|
7883
7941
|
const multiTenant = String(process.env.OS_MULTI_TENANT ?? "false").toLowerCase() !== "false";
|
|
7884
|
-
|
|
7942
|
+
if (multiTenant) {
|
|
7943
|
+
try {
|
|
7944
|
+
const { OrgScopingPlugin } = await import("@objectstack/plugin-org-scoping");
|
|
7945
|
+
await kernel.use(new OrgScopingPlugin());
|
|
7946
|
+
} catch (err) {
|
|
7947
|
+
this.logger.warn?.("[ArtifactKernelFactory] OrgScopingPlugin not registered (multi-tenant disabled)", {
|
|
7948
|
+
environmentId,
|
|
7949
|
+
error: err?.message
|
|
7950
|
+
});
|
|
7951
|
+
}
|
|
7952
|
+
}
|
|
7953
|
+
const { SecurityPlugin } = await import("@objectstack/plugin-security");
|
|
7954
|
+
await kernel.use(new SecurityPlugin());
|
|
7885
7955
|
} catch (err) {
|
|
7886
7956
|
this.logger.warn?.("[ArtifactKernelFactory] SecurityPlugin not registered", {
|
|
7887
7957
|
environmentId,
|
|
@@ -8407,6 +8477,40 @@ function resolveCloudUrl(explicit) {
|
|
|
8407
8477
|
|
|
8408
8478
|
// src/cloud/marketplace-proxy-plugin.ts
|
|
8409
8479
|
var MARKETPLACE_PREFIX = "/api/v1/marketplace";
|
|
8480
|
+
var DEFAULT_LRU_MAX = 200;
|
|
8481
|
+
var LIST_TTL_MS = 30 * 60 * 1e3;
|
|
8482
|
+
var PACKAGE_TTL_MS = 2 * 60 * 60 * 1e3;
|
|
8483
|
+
var VERSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
8484
|
+
function ttlForPath(pathname) {
|
|
8485
|
+
if (/\/packages\/[^/]+\/versions\//.test(pathname)) return VERSION_TTL_MS;
|
|
8486
|
+
if (/\/packages\/[^/]+/.test(pathname)) return PACKAGE_TTL_MS;
|
|
8487
|
+
return LIST_TTL_MS;
|
|
8488
|
+
}
|
|
8489
|
+
var LruTtlCache = class {
|
|
8490
|
+
constructor(max) {
|
|
8491
|
+
this.max = max;
|
|
8492
|
+
this.map = /* @__PURE__ */ new Map();
|
|
8493
|
+
}
|
|
8494
|
+
get(key) {
|
|
8495
|
+
const entry = this.map.get(key);
|
|
8496
|
+
if (!entry) return void 0;
|
|
8497
|
+
this.map.delete(key);
|
|
8498
|
+
this.map.set(key, entry);
|
|
8499
|
+
return entry;
|
|
8500
|
+
}
|
|
8501
|
+
set(key, entry) {
|
|
8502
|
+
if (this.map.has(key)) this.map.delete(key);
|
|
8503
|
+
this.map.set(key, entry);
|
|
8504
|
+
while (this.map.size > this.max) {
|
|
8505
|
+
const oldest = this.map.keys().next().value;
|
|
8506
|
+
if (oldest === void 0) break;
|
|
8507
|
+
this.map.delete(oldest);
|
|
8508
|
+
}
|
|
8509
|
+
}
|
|
8510
|
+
clear() {
|
|
8511
|
+
this.map.clear();
|
|
8512
|
+
}
|
|
8513
|
+
};
|
|
8410
8514
|
var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
|
|
8411
8515
|
constructor(config = {}) {
|
|
8412
8516
|
this.name = "com.objectstack.runtime.marketplace-proxy";
|
|
@@ -8428,6 +8532,7 @@ var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
|
|
|
8428
8532
|
}
|
|
8429
8533
|
const rawApp = httpServer.getRawApp();
|
|
8430
8534
|
const cloudUrl = this.cloudUrl;
|
|
8535
|
+
const cache = this.cache;
|
|
8431
8536
|
const handler = async (c, next) => {
|
|
8432
8537
|
if (!cloudUrl) {
|
|
8433
8538
|
return c.json({
|
|
@@ -8454,24 +8559,51 @@ var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
|
|
|
8454
8559
|
}
|
|
8455
8560
|
}, 405);
|
|
8456
8561
|
}
|
|
8457
|
-
const
|
|
8458
|
-
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
|
|
8464
|
-
|
|
8562
|
+
const accept = c.req.header("accept") ?? "application/json";
|
|
8563
|
+
const acceptLang = c.req.header("accept-language") ?? "";
|
|
8564
|
+
const cacheKey = `${incomingUrl.pathname}${incomingUrl.search}|al=${acceptLang}|a=${accept}`;
|
|
8565
|
+
const reqCacheCtl = (c.req.header("cache-control") ?? "").toLowerCase();
|
|
8566
|
+
const bypass = !cache || reqCacheCtl.includes("no-cache") || reqCacheCtl.includes("no-store");
|
|
8567
|
+
const now = Date.now();
|
|
8568
|
+
if (cache && !bypass) {
|
|
8569
|
+
const hit = cache.get(cacheKey);
|
|
8570
|
+
if (hit && hit.expiresAt > now) {
|
|
8571
|
+
return buildCachedResponse(hit, method, "HIT");
|
|
8465
8572
|
}
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8573
|
+
if (hit) {
|
|
8574
|
+
const revalHeaders = {
|
|
8575
|
+
"Accept": accept,
|
|
8576
|
+
"User-Agent": `objectos-marketplace-proxy/${_MarketplaceProxyPlugin.prototype.version ?? "1.0.0"}`
|
|
8577
|
+
};
|
|
8578
|
+
if (acceptLang) revalHeaders["Accept-Language"] = acceptLang;
|
|
8579
|
+
if (hit.etag) revalHeaders["If-None-Match"] = hit.etag;
|
|
8580
|
+
if (hit.lastModified) revalHeaders["If-Modified-Since"] = hit.lastModified;
|
|
8581
|
+
const revalResp = await fetch(target, { method: "GET", headers: revalHeaders });
|
|
8582
|
+
if (revalResp.status === 304) {
|
|
8583
|
+
hit.expiresAt = now + hit.ttlMs;
|
|
8584
|
+
const newEtag = revalResp.headers.get("etag");
|
|
8585
|
+
const newLm = revalResp.headers.get("last-modified");
|
|
8586
|
+
if (newEtag) hit.etag = newEtag;
|
|
8587
|
+
if (newLm) hit.lastModified = newLm;
|
|
8588
|
+
cache.set(cacheKey, hit);
|
|
8589
|
+
return buildCachedResponse(hit, method, "REVALIDATED");
|
|
8590
|
+
}
|
|
8591
|
+
return await consumeAndMaybeCache(revalResp, cacheKey, incomingUrl.pathname, method, cache);
|
|
8592
|
+
}
|
|
8593
|
+
}
|
|
8594
|
+
const reqHeaders = {
|
|
8595
|
+
// Strip the inbound Host header — fetch will set
|
|
8596
|
+
// it to the cloud host. Forward only the
|
|
8597
|
+
// identifying headers cloud might log.
|
|
8598
|
+
"Accept": accept,
|
|
8599
|
+
"User-Agent": `objectos-marketplace-proxy/${_MarketplaceProxyPlugin.prototype.version ?? "1.0.0"}`
|
|
8600
|
+
};
|
|
8601
|
+
if (acceptLang) reqHeaders["Accept-Language"] = acceptLang;
|
|
8602
|
+
const resp = await fetch(target, { method: "GET", headers: reqHeaders });
|
|
8603
|
+
if (bypass || !cache) {
|
|
8604
|
+
return await passthroughResponse(resp, method, bypass ? "BYPASS" : "MISS");
|
|
8472
8605
|
}
|
|
8473
|
-
|
|
8474
|
-
return new Response(body, { status: resp.status, headers });
|
|
8606
|
+
return await consumeAndMaybeCache(resp, cacheKey, incomingUrl.pathname, method, cache);
|
|
8475
8607
|
} catch (err) {
|
|
8476
8608
|
const errObj = err instanceof Error ? err : new Error(err?.message ?? String(err));
|
|
8477
8609
|
ctx.logger?.error?.("[MarketplaceProxyPlugin] proxy failed", errObj);
|
|
@@ -8494,12 +8626,67 @@ var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
|
|
|
8494
8626
|
}
|
|
8495
8627
|
}
|
|
8496
8628
|
}
|
|
8497
|
-
ctx.logger?.info?.(`[MarketplaceProxyPlugin] mounted at ${MARKETPLACE_PREFIX}/* \u2192 ${cloudUrl || "(unconfigured)"}`);
|
|
8629
|
+
ctx.logger?.info?.(`[MarketplaceProxyPlugin] mounted at ${MARKETPLACE_PREFIX}/* \u2192 ${cloudUrl || "(unconfigured)"} (cache=${this.cache ? "on" : "off"})`);
|
|
8498
8630
|
});
|
|
8499
8631
|
};
|
|
8500
8632
|
this.cloudUrl = resolveCloudUrl(config.controlPlaneUrl);
|
|
8633
|
+
const envFlag = (process.env.OS_MARKETPLACE_CACHE ?? "").trim().toLowerCase();
|
|
8634
|
+
const envDisabled = ["off", "false", "0", "no", "disable", "disabled"].includes(envFlag);
|
|
8635
|
+
const disabled = config.cacheDisabled ?? envDisabled;
|
|
8636
|
+
this.cache = disabled ? null : new LruTtlCache(Math.max(8, config.cacheMaxEntries ?? DEFAULT_LRU_MAX));
|
|
8501
8637
|
}
|
|
8502
8638
|
};
|
|
8639
|
+
var PASSTHROUGH_HEADERS = ["content-type", "cache-control", "etag", "last-modified", "vary"];
|
|
8640
|
+
function collectHeaders(src) {
|
|
8641
|
+
const out = {};
|
|
8642
|
+
for (const h of PASSTHROUGH_HEADERS) {
|
|
8643
|
+
const v = src.headers.get(h);
|
|
8644
|
+
if (v) out[h] = v;
|
|
8645
|
+
}
|
|
8646
|
+
return out;
|
|
8647
|
+
}
|
|
8648
|
+
function buildCachedResponse(entry, method, xCache) {
|
|
8649
|
+
const headers = new Headers(entry.headers);
|
|
8650
|
+
headers.set("X-Cache", xCache);
|
|
8651
|
+
const ageSec = Math.max(0, Math.floor((entry.expiresAt - entry.ttlMs - Date.now()) / -1e3));
|
|
8652
|
+
headers.set("Age", String(Math.max(0, ageSec)));
|
|
8653
|
+
const body = method === "HEAD" ? null : entry.body;
|
|
8654
|
+
return new Response(body, { status: entry.status, headers });
|
|
8655
|
+
}
|
|
8656
|
+
async function passthroughResponse(resp, method, xCache) {
|
|
8657
|
+
const headers = new Headers(collectHeaders(resp));
|
|
8658
|
+
headers.set("X-Cache", xCache);
|
|
8659
|
+
if (method === "HEAD") {
|
|
8660
|
+
try {
|
|
8661
|
+
await resp.arrayBuffer();
|
|
8662
|
+
} catch {
|
|
8663
|
+
}
|
|
8664
|
+
return new Response(null, { status: resp.status, headers });
|
|
8665
|
+
}
|
|
8666
|
+
const body = await resp.arrayBuffer();
|
|
8667
|
+
return new Response(body, { status: resp.status, headers });
|
|
8668
|
+
}
|
|
8669
|
+
async function consumeAndMaybeCache(resp, key, pathname, method, cache) {
|
|
8670
|
+
const body = await resp.arrayBuffer();
|
|
8671
|
+
const headers = collectHeaders(resp);
|
|
8672
|
+
if (resp.status >= 200 && resp.status < 300) {
|
|
8673
|
+
const ttlMs = ttlForPath(pathname);
|
|
8674
|
+
const entry = {
|
|
8675
|
+
status: resp.status,
|
|
8676
|
+
body,
|
|
8677
|
+
headers,
|
|
8678
|
+
etag: resp.headers.get("etag") ?? void 0,
|
|
8679
|
+
lastModified: resp.headers.get("last-modified") ?? void 0,
|
|
8680
|
+
expiresAt: Date.now() + ttlMs,
|
|
8681
|
+
ttlMs
|
|
8682
|
+
};
|
|
8683
|
+
cache.set(key, entry);
|
|
8684
|
+
}
|
|
8685
|
+
const respHeaders = new Headers(headers);
|
|
8686
|
+
respHeaders.set("X-Cache", "MISS");
|
|
8687
|
+
const outBody = method === "HEAD" ? null : body;
|
|
8688
|
+
return new Response(outBody, { status: resp.status, headers: respHeaders });
|
|
8689
|
+
}
|
|
8503
8690
|
|
|
8504
8691
|
// src/cloud/runtime-config-plugin.ts
|
|
8505
8692
|
var RuntimeConfigPlugin = class {
|
|
@@ -8537,12 +8724,14 @@ var RuntimeConfigPlugin = class {
|
|
|
8537
8724
|
let defaultEnvironmentId;
|
|
8538
8725
|
let defaultOrgId;
|
|
8539
8726
|
let resolvedSingleEnv = this.singleEnvironment;
|
|
8540
|
-
|
|
8727
|
+
const resolveFn = typeof envRegistry?.resolveByHostname === "function" ? envRegistry.resolveByHostname.bind(envRegistry) : typeof envRegistry?.resolveHostname === "function" ? envRegistry.resolveHostname.bind(envRegistry) : null;
|
|
8728
|
+
if (resolveFn && host) {
|
|
8541
8729
|
try {
|
|
8542
|
-
const resolved = await
|
|
8730
|
+
const resolved = await resolveFn(host);
|
|
8543
8731
|
if (resolved?.environmentId) {
|
|
8544
|
-
defaultEnvironmentId = resolved.environmentId;
|
|
8545
|
-
|
|
8732
|
+
defaultEnvironmentId = String(resolved.environmentId);
|
|
8733
|
+
const orgId = resolved.organizationId ?? resolved.organization_id;
|
|
8734
|
+
if (orgId) defaultOrgId = String(orgId);
|
|
8546
8735
|
resolvedSingleEnv = true;
|
|
8547
8736
|
}
|
|
8548
8737
|
} catch {
|
|
@@ -8553,7 +8742,11 @@ var RuntimeConfigPlugin = class {
|
|
|
8553
8742
|
singleEnvironment: resolvedSingleEnv,
|
|
8554
8743
|
defaultOrgId,
|
|
8555
8744
|
defaultEnvironmentId,
|
|
8556
|
-
features
|
|
8745
|
+
features,
|
|
8746
|
+
branding: {
|
|
8747
|
+
productName: this.productName,
|
|
8748
|
+
productShortName: this.productShortName
|
|
8749
|
+
}
|
|
8557
8750
|
});
|
|
8558
8751
|
};
|
|
8559
8752
|
rawApp.get("/api/v1/runtime/config", handler);
|
|
@@ -8570,6 +8763,10 @@ var RuntimeConfigPlugin = class {
|
|
|
8570
8763
|
this.cloudUrl = config.controlPlaneUrl === "" ? "" : resolveCloudUrl(config.controlPlaneUrl) ?? "";
|
|
8571
8764
|
this.installLocal = !!config.installLocal;
|
|
8572
8765
|
this.singleEnvironment = !!config.singleEnvironment;
|
|
8766
|
+
const envName = (typeof process !== "undefined" ? process.env?.OS_PRODUCT_NAME : void 0)?.trim();
|
|
8767
|
+
const envShort = (typeof process !== "undefined" ? process.env?.OS_PRODUCT_SHORT_NAME : void 0)?.trim();
|
|
8768
|
+
this.productName = (config.productName ?? envName ?? "ObjectOS").trim() || "ObjectOS";
|
|
8769
|
+
this.productShortName = (config.productShortName ?? envShort ?? this.productName).trim() || this.productName;
|
|
8573
8770
|
}
|
|
8574
8771
|
};
|
|
8575
8772
|
|
|
@@ -8859,9 +9056,15 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
8859
9056
|
const postHandler = async (c) => this.handleInstall(c, ctx);
|
|
8860
9057
|
const getHandler = async (c) => this.handleList(c);
|
|
8861
9058
|
const deleteHandler = async (c) => this.handleUninstall(c, ctx);
|
|
9059
|
+
const reseedHandler = async (c) => this.handleReseed(c, ctx);
|
|
9060
|
+
const purgeHandler = async (c) => this.handlePurge(c, ctx);
|
|
8862
9061
|
if (typeof rawApp.post === "function") rawApp.post(ROUTE_BASE, postHandler);
|
|
8863
9062
|
if (typeof rawApp.get === "function") rawApp.get(ROUTE_BASE, getHandler);
|
|
8864
9063
|
if (typeof rawApp.delete === "function") rawApp.delete(`${ROUTE_BASE}/:manifestId`, deleteHandler);
|
|
9064
|
+
if (typeof rawApp.post === "function") {
|
|
9065
|
+
rawApp.post(`${ROUTE_BASE}/:manifestId/reseed-sample-data`, reseedHandler);
|
|
9066
|
+
rawApp.post(`${ROUTE_BASE}/:manifestId/purge-sample-data`, purgeHandler);
|
|
9067
|
+
}
|
|
8865
9068
|
ctx.logger?.info?.(`[MarketplaceInstallLocal] mounted at ${ROUTE_BASE} (storage: ${this.storageDir})`);
|
|
8866
9069
|
});
|
|
8867
9070
|
};
|
|
@@ -8958,7 +9161,8 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
8958
9161
|
version,
|
|
8959
9162
|
manifest,
|
|
8960
9163
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8961
|
-
installedBy: userId
|
|
9164
|
+
installedBy: userId,
|
|
9165
|
+
withSampleData: false
|
|
8962
9166
|
};
|
|
8963
9167
|
try {
|
|
8964
9168
|
(0, import_node_fs3.mkdirSync)(this.storageDir, { recursive: true });
|
|
@@ -8985,6 +9189,13 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
8985
9189
|
ctx.logger?.warn?.(`[MarketplaceInstallLocal] syncSchemas failed for ${manifestId}: ${err?.message ?? err}`);
|
|
8986
9190
|
}
|
|
8987
9191
|
const seededSummary = await this.applySideEffects(ctx, manifest, { seedNow: true, c });
|
|
9192
|
+
if (seededSummary.seeded.mode === "inline" && (seededSummary.seeded.inserted ?? 0) + (seededSummary.seeded.updated ?? 0) > 0) {
|
|
9193
|
+
entry.withSampleData = true;
|
|
9194
|
+
try {
|
|
9195
|
+
(0, import_node_fs3.writeFileSync)((0, import_node_path6.join)(this.storageDir, safeFilename(manifestId)), JSON.stringify(entry, null, 2), "utf8");
|
|
9196
|
+
} catch {
|
|
9197
|
+
}
|
|
9198
|
+
}
|
|
8988
9199
|
return c.json({
|
|
8989
9200
|
success: true,
|
|
8990
9201
|
data: {
|
|
@@ -9011,7 +9222,8 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9011
9222
|
manifestId: e.manifestId,
|
|
9012
9223
|
version: e.version,
|
|
9013
9224
|
installedAt: e.installedAt,
|
|
9014
|
-
installedBy: e.installedBy
|
|
9225
|
+
installedBy: e.installedBy,
|
|
9226
|
+
withSampleData: e.withSampleData ?? false
|
|
9015
9227
|
})),
|
|
9016
9228
|
total: entries.length,
|
|
9017
9229
|
storageDir: this.storageDir
|
|
@@ -9076,6 +9288,145 @@ var MarketplaceInstallLocalPlugin = class {
|
|
|
9076
9288
|
* dev / single-tenant runtimes. Stricter checks can be layered on
|
|
9077
9289
|
* via a middleware in cloud-hosted multi-tenant deployments.
|
|
9078
9290
|
*/
|
|
9291
|
+
/**
|
|
9292
|
+
* POST /api/v1/marketplace/install-local/:manifestId/reseed-sample-data
|
|
9293
|
+
*
|
|
9294
|
+
* Re-runs SeedLoaderService against the cached manifest's `data` arrays.
|
|
9295
|
+
* Idempotent (upsert by id). Useful when:
|
|
9296
|
+
* • The user installed an app and skipped sample data
|
|
9297
|
+
* • A purge was undone
|
|
9298
|
+
* • The user wants a clean baseline back after editing demo rows
|
|
9299
|
+
*
|
|
9300
|
+
* Multi-tenant: requires an active organization on the session (same
|
|
9301
|
+
* rule as install seed path).
|
|
9302
|
+
*/
|
|
9303
|
+
this.handleReseed = async (c, ctx) => {
|
|
9304
|
+
const userId = await this.requireAuthenticatedUser(c, ctx);
|
|
9305
|
+
if (!userId) {
|
|
9306
|
+
return c.json({ success: false, error: { code: "unauthorized", message: "Authentication required." } }, 401);
|
|
9307
|
+
}
|
|
9308
|
+
const manifestId = String(c.req.param?.("manifestId") ?? c.req.params?.manifestId ?? "").trim();
|
|
9309
|
+
if (!manifestId) {
|
|
9310
|
+
return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
|
|
9311
|
+
}
|
|
9312
|
+
const file = (0, import_node_path6.join)(this.storageDir, safeFilename(manifestId));
|
|
9313
|
+
if (!(0, import_node_fs3.existsSync)(file)) {
|
|
9314
|
+
return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
|
|
9315
|
+
}
|
|
9316
|
+
let entry;
|
|
9317
|
+
try {
|
|
9318
|
+
entry = JSON.parse((0, import_node_fs3.readFileSync)(file, "utf8"));
|
|
9319
|
+
} catch (err) {
|
|
9320
|
+
return c.json({ success: false, error: { code: "storage_failed", message: `Failed to read manifest cache: ${err?.message ?? err}` } }, 500);
|
|
9321
|
+
}
|
|
9322
|
+
const summary = await this.applySideEffects(ctx, entry.manifest, { seedNow: true, c });
|
|
9323
|
+
if (summary.seeded.mode === "skipped") {
|
|
9324
|
+
return c.json({
|
|
9325
|
+
success: false,
|
|
9326
|
+
error: {
|
|
9327
|
+
code: "reseed_skipped",
|
|
9328
|
+
message: `Reseed did not run: ${summary.seeded.reason ?? "unknown reason"}`
|
|
9329
|
+
}
|
|
9330
|
+
}, 400);
|
|
9331
|
+
}
|
|
9332
|
+
try {
|
|
9333
|
+
entry.withSampleData = true;
|
|
9334
|
+
(0, import_node_fs3.writeFileSync)(file, JSON.stringify(entry, null, 2), "utf8");
|
|
9335
|
+
} catch {
|
|
9336
|
+
}
|
|
9337
|
+
return c.json({
|
|
9338
|
+
success: true,
|
|
9339
|
+
data: {
|
|
9340
|
+
manifestId,
|
|
9341
|
+
inserted: summary.seeded.inserted ?? 0,
|
|
9342
|
+
updated: summary.seeded.updated ?? 0,
|
|
9343
|
+
errors: summary.seeded.errors ?? 0,
|
|
9344
|
+
withSampleData: true
|
|
9345
|
+
}
|
|
9346
|
+
}, 200);
|
|
9347
|
+
};
|
|
9348
|
+
/**
|
|
9349
|
+
* POST /api/v1/marketplace/install-local/:manifestId/purge-sample-data
|
|
9350
|
+
*
|
|
9351
|
+
* Deletes every record whose id is declared in the cached manifest's
|
|
9352
|
+
* seed datasets. Uses the `driver` service directly to bypass ACL /
|
|
9353
|
+
* lifecycle hooks (same pattern as cloud purge). User-created records
|
|
9354
|
+
* are never touched — only ids declared in the package's bundled
|
|
9355
|
+
* datasets are removed. Already-deleted rows count as `skipped`.
|
|
9356
|
+
*/
|
|
9357
|
+
this.handlePurge = async (c, ctx) => {
|
|
9358
|
+
const userId = await this.requireAuthenticatedUser(c, ctx);
|
|
9359
|
+
if (!userId) {
|
|
9360
|
+
return c.json({ success: false, error: { code: "unauthorized", message: "Authentication required." } }, 401);
|
|
9361
|
+
}
|
|
9362
|
+
const manifestId = String(c.req.param?.("manifestId") ?? c.req.params?.manifestId ?? "").trim();
|
|
9363
|
+
if (!manifestId) {
|
|
9364
|
+
return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
|
|
9365
|
+
}
|
|
9366
|
+
const file = (0, import_node_path6.join)(this.storageDir, safeFilename(manifestId));
|
|
9367
|
+
if (!(0, import_node_fs3.existsSync)(file)) {
|
|
9368
|
+
return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
|
|
9369
|
+
}
|
|
9370
|
+
let entry;
|
|
9371
|
+
try {
|
|
9372
|
+
entry = JSON.parse((0, import_node_fs3.readFileSync)(file, "utf8"));
|
|
9373
|
+
} catch (err) {
|
|
9374
|
+
return c.json({ success: false, error: { code: "storage_failed", message: `Failed to read manifest cache: ${err?.message ?? err}` } }, 500);
|
|
9375
|
+
}
|
|
9376
|
+
const datasets = Array.isArray(entry.manifest?.data) ? entry.manifest.data.filter((d) => d && d.object && Array.isArray(d.records)) : [];
|
|
9377
|
+
if (datasets.length === 0) {
|
|
9378
|
+
return c.json({
|
|
9379
|
+
success: false,
|
|
9380
|
+
error: { code: "nothing_to_purge", message: "This package declares no seed datasets." }
|
|
9381
|
+
}, 400);
|
|
9382
|
+
}
|
|
9383
|
+
let driver;
|
|
9384
|
+
try {
|
|
9385
|
+
driver = ctx.getService("driver");
|
|
9386
|
+
} catch {
|
|
9387
|
+
}
|
|
9388
|
+
if (!driver || typeof driver.delete !== "function") {
|
|
9389
|
+
return c.json({
|
|
9390
|
+
success: false,
|
|
9391
|
+
error: { code: "driver_missing", message: "driver service unavailable \u2014 cannot purge." }
|
|
9392
|
+
}, 500);
|
|
9393
|
+
}
|
|
9394
|
+
let deleted = 0;
|
|
9395
|
+
let skipped = 0;
|
|
9396
|
+
let errors = 0;
|
|
9397
|
+
for (const ds of datasets) {
|
|
9398
|
+
const object = String(ds.object);
|
|
9399
|
+
for (const rec of ds.records) {
|
|
9400
|
+
const id = rec?.id;
|
|
9401
|
+
if (id === void 0 || id === null || id === "") {
|
|
9402
|
+
skipped++;
|
|
9403
|
+
continue;
|
|
9404
|
+
}
|
|
9405
|
+
try {
|
|
9406
|
+
const r = await driver.delete(object, id);
|
|
9407
|
+
if (r === false || r === 0 || r?.deleted === 0) skipped++;
|
|
9408
|
+
else deleted++;
|
|
9409
|
+
} catch (err) {
|
|
9410
|
+
const msg = String(err?.message ?? err);
|
|
9411
|
+
if (/not.?found|no row/i.test(msg)) skipped++;
|
|
9412
|
+
else {
|
|
9413
|
+
errors++;
|
|
9414
|
+
ctx.logger?.warn?.(`[MarketplaceInstallLocal] purge ${object}#${id}: ${msg}`);
|
|
9415
|
+
}
|
|
9416
|
+
}
|
|
9417
|
+
}
|
|
9418
|
+
}
|
|
9419
|
+
try {
|
|
9420
|
+
entry.withSampleData = false;
|
|
9421
|
+
(0, import_node_fs3.writeFileSync)(file, JSON.stringify(entry, null, 2), "utf8");
|
|
9422
|
+
} catch {
|
|
9423
|
+
}
|
|
9424
|
+
ctx.logger?.info?.(`[MarketplaceInstallLocal] purged ${manifestId}: deleted=${deleted} skipped=${skipped} errors=${errors}`);
|
|
9425
|
+
return c.json({
|
|
9426
|
+
success: true,
|
|
9427
|
+
data: { manifestId, deleted, skipped, errors, withSampleData: false }
|
|
9428
|
+
}, 200);
|
|
9429
|
+
};
|
|
9079
9430
|
/**
|
|
9080
9431
|
* Replicate the start-time side-effects that AppPlugin runs for
|
|
9081
9432
|
* statically-declared apps but the `manifest` service does NOT:
|