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