@objectstack/runtime 8.0.1 → 9.0.1
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 +54 -1839
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +69 -400
- package/dist/index.d.ts +69 -400
- package/dist/index.js +44 -1825
- package/dist/index.js.map +1 -1
- package/package.json +18 -18
package/dist/index.cjs
CHANGED
|
@@ -165,603 +165,13 @@ var init_driver_plugin = __esm({
|
|
|
165
165
|
// src/seed-loader.ts
|
|
166
166
|
var seed_loader_exports = {};
|
|
167
167
|
__export(seed_loader_exports, {
|
|
168
|
-
SeedLoaderService: () => SeedLoaderService
|
|
168
|
+
SeedLoaderService: () => import_objectql.SeedLoaderService
|
|
169
169
|
});
|
|
170
|
-
var
|
|
170
|
+
var import_objectql;
|
|
171
171
|
var init_seed_loader = __esm({
|
|
172
172
|
"src/seed-loader.ts"() {
|
|
173
173
|
"use strict";
|
|
174
|
-
|
|
175
|
-
import_formula = require("@objectstack/formula");
|
|
176
|
-
DEFAULT_EXTERNAL_ID_FIELD = "name";
|
|
177
|
-
_SeedLoaderService = class _SeedLoaderService {
|
|
178
|
-
constructor(engine, metadata, logger) {
|
|
179
|
-
this.engine = engine;
|
|
180
|
-
this.metadata = metadata;
|
|
181
|
-
this.logger = logger;
|
|
182
|
-
}
|
|
183
|
-
// ==========================================================================
|
|
184
|
-
// Public API
|
|
185
|
-
// ==========================================================================
|
|
186
|
-
async load(request) {
|
|
187
|
-
const startTime = Date.now();
|
|
188
|
-
const config = request.config;
|
|
189
|
-
const allErrors = [];
|
|
190
|
-
const allResults = [];
|
|
191
|
-
const datasets = this.filterByEnv(request.seeds, config.env);
|
|
192
|
-
if (datasets.length === 0) {
|
|
193
|
-
return this.buildEmptyResult(config, Date.now() - startTime);
|
|
194
|
-
}
|
|
195
|
-
const objectNames = datasets.map((d) => d.object);
|
|
196
|
-
const graph = await this.buildDependencyGraph(objectNames);
|
|
197
|
-
this.logger.info("[SeedLoader] Dependency graph built", {
|
|
198
|
-
objects: objectNames.length,
|
|
199
|
-
insertOrder: graph.insertOrder,
|
|
200
|
-
circularDeps: graph.circularDependencies.length
|
|
201
|
-
});
|
|
202
|
-
const orderedDatasets = this.orderDatasets(datasets, graph.insertOrder);
|
|
203
|
-
const refMap = this.buildReferenceMap(graph);
|
|
204
|
-
const insertedRecords = /* @__PURE__ */ new Map();
|
|
205
|
-
const deferredUpdates = [];
|
|
206
|
-
for (const dataset of orderedDatasets) {
|
|
207
|
-
const result = await this.loadDataset(
|
|
208
|
-
dataset,
|
|
209
|
-
config,
|
|
210
|
-
refMap,
|
|
211
|
-
insertedRecords,
|
|
212
|
-
deferredUpdates,
|
|
213
|
-
allErrors
|
|
214
|
-
);
|
|
215
|
-
allResults.push(result);
|
|
216
|
-
if (config.haltOnError && result.errored > 0) {
|
|
217
|
-
this.logger.warn("[SeedLoader] Halting on first error", { object: dataset.object });
|
|
218
|
-
break;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
if (config.multiPass && deferredUpdates.length > 0 && !config.dryRun) {
|
|
222
|
-
this.logger.info("[SeedLoader] Pass 2: resolving deferred references", {
|
|
223
|
-
count: deferredUpdates.length
|
|
224
|
-
});
|
|
225
|
-
await this.resolveDeferredUpdates(deferredUpdates, insertedRecords, allResults, allErrors, config.organizationId);
|
|
226
|
-
}
|
|
227
|
-
const durationMs = Date.now() - startTime;
|
|
228
|
-
return this.buildResult(config, graph, allResults, allErrors, durationMs);
|
|
229
|
-
}
|
|
230
|
-
async buildDependencyGraph(objectNames) {
|
|
231
|
-
const nodes = [];
|
|
232
|
-
const objectSet = new Set(objectNames);
|
|
233
|
-
for (const objectName of objectNames) {
|
|
234
|
-
const objDef = await this.metadata.getObject(objectName);
|
|
235
|
-
const dependsOn = [];
|
|
236
|
-
const references = [];
|
|
237
|
-
if (objDef && objDef.fields) {
|
|
238
|
-
const fields = objDef.fields;
|
|
239
|
-
for (const [fieldName, fieldDef] of Object.entries(fields)) {
|
|
240
|
-
if ((fieldDef.type === "lookup" || fieldDef.type === "master_detail") && fieldDef.reference) {
|
|
241
|
-
const targetObject = fieldDef.reference;
|
|
242
|
-
if (objectSet.has(targetObject) && !dependsOn.includes(targetObject)) {
|
|
243
|
-
dependsOn.push(targetObject);
|
|
244
|
-
}
|
|
245
|
-
references.push({
|
|
246
|
-
field: fieldName,
|
|
247
|
-
targetObject,
|
|
248
|
-
targetField: DEFAULT_EXTERNAL_ID_FIELD,
|
|
249
|
-
fieldType: fieldDef.type
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
nodes.push({ object: objectName, dependsOn, references });
|
|
255
|
-
}
|
|
256
|
-
const { insertOrder, circularDependencies } = this.topologicalSort(nodes);
|
|
257
|
-
return { nodes, insertOrder, circularDependencies };
|
|
258
|
-
}
|
|
259
|
-
async validate(datasets, config) {
|
|
260
|
-
const parsedConfig = import_data.SeedLoaderConfigSchema.parse({ ...config, dryRun: true });
|
|
261
|
-
return this.load({ seeds: datasets, config: parsedConfig });
|
|
262
|
-
}
|
|
263
|
-
// ==========================================================================
|
|
264
|
-
// Internal: Seed Loading
|
|
265
|
-
// ==========================================================================
|
|
266
|
-
async loadDataset(dataset, config, refMap, insertedRecords, deferredUpdates, allErrors) {
|
|
267
|
-
const objectName = dataset.object;
|
|
268
|
-
const mode = dataset.mode || config.defaultMode;
|
|
269
|
-
const externalId = dataset.externalId || "name";
|
|
270
|
-
let inserted = 0;
|
|
271
|
-
let updated = 0;
|
|
272
|
-
let skipped = 0;
|
|
273
|
-
let errored = 0;
|
|
274
|
-
let referencesResolved = 0;
|
|
275
|
-
let referencesDeferred = 0;
|
|
276
|
-
const errors = [];
|
|
277
|
-
if (!insertedRecords.has(objectName)) {
|
|
278
|
-
insertedRecords.set(objectName, /* @__PURE__ */ new Map());
|
|
279
|
-
}
|
|
280
|
-
let existingRecords;
|
|
281
|
-
if ((mode === "upsert" || mode === "update" || mode === "ignore") && !config.dryRun) {
|
|
282
|
-
existingRecords = await this.loadExistingRecords(
|
|
283
|
-
objectName,
|
|
284
|
-
externalId,
|
|
285
|
-
config.organizationId
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
const objectRefs = refMap.get(objectName) || [];
|
|
289
|
-
const seedNow = /* @__PURE__ */ new Date();
|
|
290
|
-
const seedIdentity = config.identity;
|
|
291
|
-
const baseEvalCtx = {
|
|
292
|
-
now: seedNow,
|
|
293
|
-
user: seedIdentity?.user,
|
|
294
|
-
// Fall back to the per-tenant organizationId so `os.org.id` resolves
|
|
295
|
-
// during per-org replay even without an explicit identity.org.
|
|
296
|
-
org: seedIdentity?.org ?? (config.organizationId ? { id: config.organizationId } : void 0),
|
|
297
|
-
env: config.env
|
|
298
|
-
};
|
|
299
|
-
for (let i = 0; i < dataset.records.length; i++) {
|
|
300
|
-
const seedResult = (0, import_formula.resolveSeedRecord)(
|
|
301
|
-
dataset.records[i],
|
|
302
|
-
baseEvalCtx
|
|
303
|
-
);
|
|
304
|
-
if (!seedResult.ok) {
|
|
305
|
-
errored++;
|
|
306
|
-
const error = {
|
|
307
|
-
sourceObject: objectName,
|
|
308
|
-
field: "(expression)",
|
|
309
|
-
targetObject: objectName,
|
|
310
|
-
targetField: "(expression)",
|
|
311
|
-
attemptedValue: dataset.records[i],
|
|
312
|
-
recordIndex: i,
|
|
313
|
-
message: `Cannot resolve dynamic seed values for ${objectName} record #${i}: ${seedResult.error.message}. Records using cel\`os.user.id\` / cel\`os.org.id\` require a seed identity \u2014 ensure a system/admin user exists before seeding (see SeedLoaderConfig.identity).`
|
|
314
|
-
};
|
|
315
|
-
errors.push(error);
|
|
316
|
-
allErrors.push(error);
|
|
317
|
-
this.logger.warn(`[SeedLoader] ${error.message}`);
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
const record = { ...seedResult.value };
|
|
321
|
-
if (config.organizationId && record["organization_id"] == null) {
|
|
322
|
-
record["organization_id"] = config.organizationId;
|
|
323
|
-
}
|
|
324
|
-
for (const ref of objectRefs) {
|
|
325
|
-
const fieldValue = record[ref.field];
|
|
326
|
-
if (fieldValue === void 0 || fieldValue === null) continue;
|
|
327
|
-
if (typeof fieldValue === "object") {
|
|
328
|
-
const wrapped = fieldValue.externalId;
|
|
329
|
-
const hint = wrapped !== void 0 ? ` Pass the natural key directly: ${ref.field}: ${JSON.stringify(wrapped)}.` : ` Pass the target's ${ref.targetField} value as a plain string.`;
|
|
330
|
-
const error = {
|
|
331
|
-
sourceObject: objectName,
|
|
332
|
-
field: ref.field,
|
|
333
|
-
targetObject: ref.targetObject,
|
|
334
|
-
targetField: ref.targetField,
|
|
335
|
-
attemptedValue: fieldValue,
|
|
336
|
-
recordIndex: i,
|
|
337
|
-
message: `Invalid reference for ${objectName}.${ref.field}: expected a ${ref.targetObject}.${ref.targetField} natural-key string but got an object.${hint}`
|
|
338
|
-
};
|
|
339
|
-
errors.push(error);
|
|
340
|
-
allErrors.push(error);
|
|
341
|
-
this.logger.warn(`[SeedLoader] ${error.message}`, { recordIndex: i });
|
|
342
|
-
record[ref.field] = null;
|
|
343
|
-
continue;
|
|
344
|
-
}
|
|
345
|
-
if (typeof fieldValue !== "string" || this.looksLikeInternalId(fieldValue)) continue;
|
|
346
|
-
const targetMap = insertedRecords.get(ref.targetObject);
|
|
347
|
-
const resolvedId = targetMap?.get(String(fieldValue));
|
|
348
|
-
if (resolvedId) {
|
|
349
|
-
record[ref.field] = resolvedId;
|
|
350
|
-
referencesResolved++;
|
|
351
|
-
} else if (!config.dryRun) {
|
|
352
|
-
const dbId = await this.resolveFromDatabase(ref.targetObject, ref.targetField, fieldValue, config.organizationId);
|
|
353
|
-
if (dbId) {
|
|
354
|
-
record[ref.field] = dbId;
|
|
355
|
-
referencesResolved++;
|
|
356
|
-
} else if (config.multiPass) {
|
|
357
|
-
record[ref.field] = null;
|
|
358
|
-
deferredUpdates.push({
|
|
359
|
-
objectName,
|
|
360
|
-
recordExternalId: String(record[externalId] ?? ""),
|
|
361
|
-
field: ref.field,
|
|
362
|
-
targetObject: ref.targetObject,
|
|
363
|
-
targetField: ref.targetField,
|
|
364
|
-
attemptedValue: fieldValue,
|
|
365
|
-
recordIndex: i
|
|
366
|
-
});
|
|
367
|
-
referencesDeferred++;
|
|
368
|
-
} else {
|
|
369
|
-
const error = {
|
|
370
|
-
sourceObject: objectName,
|
|
371
|
-
field: ref.field,
|
|
372
|
-
targetObject: ref.targetObject,
|
|
373
|
-
targetField: ref.targetField,
|
|
374
|
-
attemptedValue: fieldValue,
|
|
375
|
-
recordIndex: i,
|
|
376
|
-
message: `Cannot resolve reference: ${objectName}.${ref.field} = '${fieldValue}' \u2192 ${ref.targetObject}.${ref.targetField} not found`
|
|
377
|
-
};
|
|
378
|
-
errors.push(error);
|
|
379
|
-
allErrors.push(error);
|
|
380
|
-
}
|
|
381
|
-
} else {
|
|
382
|
-
const targetMap2 = insertedRecords.get(ref.targetObject);
|
|
383
|
-
if (!targetMap2?.has(String(fieldValue))) {
|
|
384
|
-
const error = {
|
|
385
|
-
sourceObject: objectName,
|
|
386
|
-
field: ref.field,
|
|
387
|
-
targetObject: ref.targetObject,
|
|
388
|
-
targetField: ref.targetField,
|
|
389
|
-
attemptedValue: fieldValue,
|
|
390
|
-
recordIndex: i,
|
|
391
|
-
message: `[dry-run] Reference may not resolve: ${objectName}.${ref.field} = '${fieldValue}' \u2192 ${ref.targetObject}.${ref.targetField}`
|
|
392
|
-
};
|
|
393
|
-
errors.push(error);
|
|
394
|
-
allErrors.push(error);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
if (!config.dryRun) {
|
|
399
|
-
try {
|
|
400
|
-
const result = await this.writeRecord(
|
|
401
|
-
objectName,
|
|
402
|
-
record,
|
|
403
|
-
mode,
|
|
404
|
-
externalId,
|
|
405
|
-
existingRecords
|
|
406
|
-
);
|
|
407
|
-
if (result.action === "inserted") inserted++;
|
|
408
|
-
else if (result.action === "updated") updated++;
|
|
409
|
-
else if (result.action === "skipped") skipped++;
|
|
410
|
-
const externalIdValue = String(record[externalId] ?? "");
|
|
411
|
-
const internalId = result.id;
|
|
412
|
-
if (externalIdValue && internalId) {
|
|
413
|
-
insertedRecords.get(objectName).set(externalIdValue, String(internalId));
|
|
414
|
-
}
|
|
415
|
-
} catch (err) {
|
|
416
|
-
errored++;
|
|
417
|
-
const error = {
|
|
418
|
-
sourceObject: objectName,
|
|
419
|
-
field: "(write)",
|
|
420
|
-
targetObject: objectName,
|
|
421
|
-
targetField: externalId,
|
|
422
|
-
attemptedValue: record[externalId] ?? null,
|
|
423
|
-
recordIndex: i,
|
|
424
|
-
message: `Failed to write ${objectName} record #${i} (${externalId}=${String(record[externalId] ?? "")}): ${err.message}`
|
|
425
|
-
};
|
|
426
|
-
errors.push(error);
|
|
427
|
-
allErrors.push(error);
|
|
428
|
-
this.logger.warn(`[SeedLoader] ${error.message}`, { recordIndex: i });
|
|
429
|
-
}
|
|
430
|
-
} else {
|
|
431
|
-
const externalIdValue = String(record[externalId] ?? "");
|
|
432
|
-
if (externalIdValue) {
|
|
433
|
-
insertedRecords.get(objectName).set(externalIdValue, `dry-run-id-${i}`);
|
|
434
|
-
}
|
|
435
|
-
inserted++;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
return {
|
|
439
|
-
object: objectName,
|
|
440
|
-
mode,
|
|
441
|
-
inserted,
|
|
442
|
-
updated,
|
|
443
|
-
skipped,
|
|
444
|
-
errored,
|
|
445
|
-
total: dataset.records.length,
|
|
446
|
-
referencesResolved,
|
|
447
|
-
referencesDeferred,
|
|
448
|
-
errors
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
// ==========================================================================
|
|
452
|
-
// Internal: Reference Resolution
|
|
453
|
-
// ==========================================================================
|
|
454
|
-
async resolveFromDatabase(targetObject, targetField, value, organizationId) {
|
|
455
|
-
try {
|
|
456
|
-
const where = { [targetField]: value };
|
|
457
|
-
if (organizationId) where.organization_id = organizationId;
|
|
458
|
-
const records = await this.engine.find(targetObject, {
|
|
459
|
-
where,
|
|
460
|
-
fields: ["id"],
|
|
461
|
-
limit: 1,
|
|
462
|
-
context: { isSystem: true }
|
|
463
|
-
});
|
|
464
|
-
if (records && records.length > 0) {
|
|
465
|
-
return String(records[0].id || records[0]._id);
|
|
466
|
-
}
|
|
467
|
-
} catch {
|
|
468
|
-
}
|
|
469
|
-
return null;
|
|
470
|
-
}
|
|
471
|
-
async resolveDeferredUpdates(deferredUpdates, insertedRecords, allResults, allErrors, organizationId) {
|
|
472
|
-
for (const deferred of deferredUpdates) {
|
|
473
|
-
const targetMap = insertedRecords.get(deferred.targetObject);
|
|
474
|
-
let resolvedId = targetMap?.get(String(deferred.attemptedValue));
|
|
475
|
-
if (!resolvedId) {
|
|
476
|
-
resolvedId = await this.resolveFromDatabase(
|
|
477
|
-
deferred.targetObject,
|
|
478
|
-
deferred.targetField,
|
|
479
|
-
deferred.attemptedValue,
|
|
480
|
-
organizationId
|
|
481
|
-
) ?? void 0;
|
|
482
|
-
}
|
|
483
|
-
if (resolvedId) {
|
|
484
|
-
const objectRecordMap = insertedRecords.get(deferred.objectName);
|
|
485
|
-
const recordId = objectRecordMap?.get(deferred.recordExternalId);
|
|
486
|
-
if (recordId) {
|
|
487
|
-
try {
|
|
488
|
-
await this.engine.update(deferred.objectName, {
|
|
489
|
-
id: recordId,
|
|
490
|
-
[deferred.field]: resolvedId
|
|
491
|
-
}, { context: { isSystem: true } });
|
|
492
|
-
const resultEntry = allResults.find((r) => r.object === deferred.objectName);
|
|
493
|
-
if (resultEntry) {
|
|
494
|
-
resultEntry.referencesResolved++;
|
|
495
|
-
resultEntry.referencesDeferred--;
|
|
496
|
-
}
|
|
497
|
-
} catch (err) {
|
|
498
|
-
this.logger.warn("[SeedLoader] Failed to resolve deferred reference", {
|
|
499
|
-
object: deferred.objectName,
|
|
500
|
-
field: deferred.field,
|
|
501
|
-
error: err.message
|
|
502
|
-
});
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
} else {
|
|
506
|
-
const error = {
|
|
507
|
-
sourceObject: deferred.objectName,
|
|
508
|
-
field: deferred.field,
|
|
509
|
-
targetObject: deferred.targetObject,
|
|
510
|
-
targetField: deferred.targetField,
|
|
511
|
-
attemptedValue: deferred.attemptedValue,
|
|
512
|
-
recordIndex: deferred.recordIndex,
|
|
513
|
-
message: `Deferred reference unresolved after pass 2: ${deferred.objectName}.${deferred.field} = '${deferred.attemptedValue}' \u2192 ${deferred.targetObject}.${deferred.targetField} not found`
|
|
514
|
-
};
|
|
515
|
-
const resultEntry = allResults.find((r) => r.object === deferred.objectName);
|
|
516
|
-
if (resultEntry) {
|
|
517
|
-
resultEntry.errors.push(error);
|
|
518
|
-
}
|
|
519
|
-
allErrors.push(error);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
async writeRecord(objectName, record, mode, externalId, existingRecords) {
|
|
524
|
-
const externalIdValue = record[externalId];
|
|
525
|
-
const existing = existingRecords?.get(String(externalIdValue ?? ""));
|
|
526
|
-
const opts = _SeedLoaderService.SEED_OPTIONS;
|
|
527
|
-
switch (mode) {
|
|
528
|
-
case "insert": {
|
|
529
|
-
const result = await this.engine.insert(objectName, record, opts);
|
|
530
|
-
return { action: "inserted", id: this.extractId(result) };
|
|
531
|
-
}
|
|
532
|
-
case "update": {
|
|
533
|
-
if (!existing) {
|
|
534
|
-
return { action: "skipped" };
|
|
535
|
-
}
|
|
536
|
-
const id = this.extractId(existing);
|
|
537
|
-
await this.engine.update(objectName, { ...record, id }, opts);
|
|
538
|
-
return { action: "updated", id };
|
|
539
|
-
}
|
|
540
|
-
case "upsert": {
|
|
541
|
-
if (existing) {
|
|
542
|
-
const id = this.extractId(existing);
|
|
543
|
-
await this.engine.update(objectName, { ...record, id }, opts);
|
|
544
|
-
return { action: "updated", id };
|
|
545
|
-
} else {
|
|
546
|
-
const result = await this.engine.insert(objectName, record, opts);
|
|
547
|
-
return { action: "inserted", id: this.extractId(result) };
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
case "ignore": {
|
|
551
|
-
if (existing) {
|
|
552
|
-
return { action: "skipped", id: this.extractId(existing) };
|
|
553
|
-
}
|
|
554
|
-
const result = await this.engine.insert(objectName, record, opts);
|
|
555
|
-
return { action: "inserted", id: this.extractId(result) };
|
|
556
|
-
}
|
|
557
|
-
case "replace": {
|
|
558
|
-
const result = await this.engine.insert(objectName, record, opts);
|
|
559
|
-
return { action: "inserted", id: this.extractId(result) };
|
|
560
|
-
}
|
|
561
|
-
default: {
|
|
562
|
-
const result = await this.engine.insert(objectName, record, opts);
|
|
563
|
-
return { action: "inserted", id: this.extractId(result) };
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
// ==========================================================================
|
|
568
|
-
// Internal: Dependency Graph
|
|
569
|
-
// ==========================================================================
|
|
570
|
-
/**
|
|
571
|
-
* Kahn's algorithm for topological sort with cycle detection.
|
|
572
|
-
*/
|
|
573
|
-
topologicalSort(nodes) {
|
|
574
|
-
const inDegree = /* @__PURE__ */ new Map();
|
|
575
|
-
const adjacency = /* @__PURE__ */ new Map();
|
|
576
|
-
const objectSet = new Set(nodes.map((n) => n.object));
|
|
577
|
-
for (const node of nodes) {
|
|
578
|
-
inDegree.set(node.object, 0);
|
|
579
|
-
adjacency.set(node.object, []);
|
|
580
|
-
}
|
|
581
|
-
for (const node of nodes) {
|
|
582
|
-
for (const dep of node.dependsOn) {
|
|
583
|
-
if (objectSet.has(dep) && dep !== node.object) {
|
|
584
|
-
adjacency.get(dep).push(node.object);
|
|
585
|
-
inDegree.set(node.object, (inDegree.get(node.object) || 0) + 1);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
const queue = [];
|
|
590
|
-
for (const [obj, degree] of inDegree) {
|
|
591
|
-
if (degree === 0) queue.push(obj);
|
|
592
|
-
}
|
|
593
|
-
const insertOrder = [];
|
|
594
|
-
while (queue.length > 0) {
|
|
595
|
-
const current = queue.shift();
|
|
596
|
-
insertOrder.push(current);
|
|
597
|
-
for (const neighbor of adjacency.get(current) || []) {
|
|
598
|
-
const newDegree = (inDegree.get(neighbor) || 0) - 1;
|
|
599
|
-
inDegree.set(neighbor, newDegree);
|
|
600
|
-
if (newDegree === 0) {
|
|
601
|
-
queue.push(neighbor);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
const circularDependencies = [];
|
|
606
|
-
const remaining = nodes.filter((n) => !insertOrder.includes(n.object));
|
|
607
|
-
if (remaining.length > 0) {
|
|
608
|
-
const cycles = this.findCycles(remaining);
|
|
609
|
-
circularDependencies.push(...cycles);
|
|
610
|
-
for (const node of remaining) {
|
|
611
|
-
if (!insertOrder.includes(node.object)) {
|
|
612
|
-
insertOrder.push(node.object);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
return { insertOrder, circularDependencies };
|
|
617
|
-
}
|
|
618
|
-
findCycles(nodes) {
|
|
619
|
-
const cycles = [];
|
|
620
|
-
const nodeMap = new Map(nodes.map((n) => [n.object, n]));
|
|
621
|
-
const visited = /* @__PURE__ */ new Set();
|
|
622
|
-
const inStack = /* @__PURE__ */ new Set();
|
|
623
|
-
const dfs = (current, path) => {
|
|
624
|
-
if (inStack.has(current)) {
|
|
625
|
-
const cycleStart = path.indexOf(current);
|
|
626
|
-
if (cycleStart !== -1) {
|
|
627
|
-
cycles.push([...path.slice(cycleStart), current]);
|
|
628
|
-
}
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
if (visited.has(current)) return;
|
|
632
|
-
visited.add(current);
|
|
633
|
-
inStack.add(current);
|
|
634
|
-
path.push(current);
|
|
635
|
-
const node = nodeMap.get(current);
|
|
636
|
-
if (node) {
|
|
637
|
-
for (const dep of node.dependsOn) {
|
|
638
|
-
if (nodeMap.has(dep)) {
|
|
639
|
-
dfs(dep, [...path]);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
inStack.delete(current);
|
|
644
|
-
};
|
|
645
|
-
for (const node of nodes) {
|
|
646
|
-
if (!visited.has(node.object)) {
|
|
647
|
-
dfs(node.object, []);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
return cycles;
|
|
651
|
-
}
|
|
652
|
-
// ==========================================================================
|
|
653
|
-
// Internal: Helpers
|
|
654
|
-
// ==========================================================================
|
|
655
|
-
filterByEnv(datasets, env) {
|
|
656
|
-
if (!env) return datasets;
|
|
657
|
-
return datasets.filter((d) => d.env.includes(env));
|
|
658
|
-
}
|
|
659
|
-
orderDatasets(datasets, insertOrder) {
|
|
660
|
-
const orderMap = new Map(insertOrder.map((name, i) => [name, i]));
|
|
661
|
-
return [...datasets].sort((a, b) => {
|
|
662
|
-
const orderA = orderMap.get(a.object) ?? Number.MAX_SAFE_INTEGER;
|
|
663
|
-
const orderB = orderMap.get(b.object) ?? Number.MAX_SAFE_INTEGER;
|
|
664
|
-
return orderA - orderB;
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
buildReferenceMap(graph) {
|
|
668
|
-
const map = /* @__PURE__ */ new Map();
|
|
669
|
-
for (const node of graph.nodes) {
|
|
670
|
-
if (node.references.length > 0) {
|
|
671
|
-
map.set(node.object, node.references);
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
return map;
|
|
675
|
-
}
|
|
676
|
-
async loadExistingRecords(objectName, externalId, organizationId) {
|
|
677
|
-
const map = /* @__PURE__ */ new Map();
|
|
678
|
-
try {
|
|
679
|
-
const findArgs = {
|
|
680
|
-
fields: ["id", externalId],
|
|
681
|
-
context: { isSystem: true }
|
|
682
|
-
};
|
|
683
|
-
if (organizationId) findArgs.where = { organization_id: organizationId };
|
|
684
|
-
const records = await this.engine.find(objectName, findArgs);
|
|
685
|
-
for (const record of records || []) {
|
|
686
|
-
const key = String(record[externalId] ?? "");
|
|
687
|
-
if (key) {
|
|
688
|
-
map.set(key, record);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
} catch {
|
|
692
|
-
}
|
|
693
|
-
return map;
|
|
694
|
-
}
|
|
695
|
-
looksLikeInternalId(value) {
|
|
696
|
-
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value)) {
|
|
697
|
-
return true;
|
|
698
|
-
}
|
|
699
|
-
if (/^[0-9a-f]{24}$/i.test(value)) {
|
|
700
|
-
return true;
|
|
701
|
-
}
|
|
702
|
-
return false;
|
|
703
|
-
}
|
|
704
|
-
extractId(record) {
|
|
705
|
-
if (!record) return void 0;
|
|
706
|
-
return String(record.id || record._id || "");
|
|
707
|
-
}
|
|
708
|
-
buildEmptyResult(config, durationMs) {
|
|
709
|
-
return {
|
|
710
|
-
success: true,
|
|
711
|
-
dryRun: config.dryRun,
|
|
712
|
-
dependencyGraph: { nodes: [], insertOrder: [], circularDependencies: [] },
|
|
713
|
-
results: [],
|
|
714
|
-
errors: [],
|
|
715
|
-
summary: {
|
|
716
|
-
objectsProcessed: 0,
|
|
717
|
-
totalRecords: 0,
|
|
718
|
-
totalInserted: 0,
|
|
719
|
-
totalUpdated: 0,
|
|
720
|
-
totalSkipped: 0,
|
|
721
|
-
totalErrored: 0,
|
|
722
|
-
totalReferencesResolved: 0,
|
|
723
|
-
totalReferencesDeferred: 0,
|
|
724
|
-
circularDependencyCount: 0,
|
|
725
|
-
durationMs
|
|
726
|
-
}
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
|
-
buildResult(config, graph, results, errors, durationMs) {
|
|
730
|
-
const summary = {
|
|
731
|
-
objectsProcessed: results.length,
|
|
732
|
-
totalRecords: results.reduce((sum, r) => sum + r.total, 0),
|
|
733
|
-
totalInserted: results.reduce((sum, r) => sum + r.inserted, 0),
|
|
734
|
-
totalUpdated: results.reduce((sum, r) => sum + r.updated, 0),
|
|
735
|
-
totalSkipped: results.reduce((sum, r) => sum + r.skipped, 0),
|
|
736
|
-
totalErrored: results.reduce((sum, r) => sum + r.errored, 0),
|
|
737
|
-
totalReferencesResolved: results.reduce((sum, r) => sum + r.referencesResolved, 0),
|
|
738
|
-
totalReferencesDeferred: results.reduce((sum, r) => sum + r.referencesDeferred, 0),
|
|
739
|
-
circularDependencyCount: graph.circularDependencies.length,
|
|
740
|
-
durationMs
|
|
741
|
-
};
|
|
742
|
-
const hasErrors = errors.length > 0 || summary.totalErrored > 0;
|
|
743
|
-
return {
|
|
744
|
-
success: !hasErrors,
|
|
745
|
-
dryRun: config.dryRun,
|
|
746
|
-
dependencyGraph: graph,
|
|
747
|
-
results,
|
|
748
|
-
errors,
|
|
749
|
-
summary
|
|
750
|
-
};
|
|
751
|
-
}
|
|
752
|
-
};
|
|
753
|
-
// ==========================================================================
|
|
754
|
-
// Internal: Write Operations
|
|
755
|
-
// ==========================================================================
|
|
756
|
-
/**
|
|
757
|
-
* Seed writes always run as a privileged system context. This bypasses
|
|
758
|
-
* RBAC checks (so seeds can target system tables like `sys_*`) and
|
|
759
|
-
* disables the SecurityPlugin's auto-injection of `organization_id` /
|
|
760
|
-
* `owner_id` — seeds either declare those fields explicitly per
|
|
761
|
-
* record, or are intentionally cross-tenant / global.
|
|
762
|
-
*/
|
|
763
|
-
_SeedLoaderService.SEED_OPTIONS = { context: { isSystem: true } };
|
|
764
|
-
SeedLoaderService = _SeedLoaderService;
|
|
174
|
+
import_objectql = require("@objectstack/objectql");
|
|
765
175
|
}
|
|
766
176
|
});
|
|
767
177
|
|
|
@@ -994,7 +404,7 @@ var init_quickjs_runner = __esm({
|
|
|
994
404
|
evalRes.value.dispose();
|
|
995
405
|
let pumps = 0;
|
|
996
406
|
while (pumps < 1e3) {
|
|
997
|
-
await new Promise((
|
|
407
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
998
408
|
const pending = runtime.executePendingJobs();
|
|
999
409
|
if (pending.error) {
|
|
1000
410
|
const err = vm.dump(pending.error);
|
|
@@ -1126,7 +536,7 @@ function hookBodyRunnerFactory(runner, opts) {
|
|
|
1126
536
|
return (hook) => {
|
|
1127
537
|
const raw = hook.body;
|
|
1128
538
|
if (!raw) return void 0;
|
|
1129
|
-
const parsed =
|
|
539
|
+
const parsed = import_data.HookBodySchema.safeParse(raw);
|
|
1130
540
|
if (!parsed.success) {
|
|
1131
541
|
opts.logger?.warn?.("[BodyRunner] invalid hook.body shape", {
|
|
1132
542
|
appId: opts.appId,
|
|
@@ -1163,7 +573,7 @@ function actionBodyRunnerFactory(runner, opts) {
|
|
|
1163
573
|
return (action) => {
|
|
1164
574
|
const raw = action.body;
|
|
1165
575
|
if (!raw) return void 0;
|
|
1166
|
-
const parsed =
|
|
576
|
+
const parsed = import_data.HookBodySchema.safeParse(raw);
|
|
1167
577
|
if (!parsed.success) {
|
|
1168
578
|
opts.logger?.warn?.("[BodyRunner] invalid action.body shape", {
|
|
1169
579
|
appId: opts.appId,
|
|
@@ -1294,11 +704,11 @@ function unwrapProxyToPlain(v) {
|
|
|
1294
704
|
return void 0;
|
|
1295
705
|
}
|
|
1296
706
|
}
|
|
1297
|
-
var
|
|
707
|
+
var import_data;
|
|
1298
708
|
var init_body_runner = __esm({
|
|
1299
709
|
"src/sandbox/body-runner.ts"() {
|
|
1300
710
|
"use strict";
|
|
1301
|
-
|
|
711
|
+
import_data = require("@objectstack/spec/data");
|
|
1302
712
|
}
|
|
1303
713
|
});
|
|
1304
714
|
|
|
@@ -1677,7 +1087,7 @@ var init_app_plugin = __esm({
|
|
|
1677
1087
|
if (!Array.isArray(datasetsNow) || datasetsNow.length === 0) {
|
|
1678
1088
|
return { inserted: 0, updated: 0, errors: [] };
|
|
1679
1089
|
}
|
|
1680
|
-
const seedLoader = new SeedLoaderService(ql, md, loggerRef);
|
|
1090
|
+
const seedLoader = new import_objectql.SeedLoaderService(ql, md, loggerRef);
|
|
1681
1091
|
const { SeedLoaderRequestSchema } = await import("@objectstack/spec/data");
|
|
1682
1092
|
const request = SeedLoaderRequestSchema.parse({
|
|
1683
1093
|
seeds: datasetsNow,
|
|
@@ -1713,7 +1123,7 @@ var init_app_plugin = __esm({
|
|
|
1713
1123
|
try {
|
|
1714
1124
|
const metadata = ctx.getService("metadata");
|
|
1715
1125
|
if (metadata) {
|
|
1716
|
-
const seedLoader = new SeedLoaderService(ql, metadata, ctx.logger);
|
|
1126
|
+
const seedLoader = new import_objectql.SeedLoaderService(ql, metadata, ctx.logger);
|
|
1717
1127
|
const { SeedLoaderRequestSchema } = await import("@objectstack/spec/data");
|
|
1718
1128
|
const request = SeedLoaderRequestSchema.parse({
|
|
1719
1129
|
seeds: normalizedDatasets,
|
|
@@ -1774,8 +1184,8 @@ var init_app_plugin = __esm({
|
|
|
1774
1184
|
}
|
|
1775
1185
|
})();
|
|
1776
1186
|
let timer;
|
|
1777
|
-
const budget = new Promise((
|
|
1778
|
-
timer = setTimeout(() =>
|
|
1187
|
+
const budget = new Promise((resolve) => {
|
|
1188
|
+
timer = setTimeout(() => resolve("budget"), seedBudgetMs);
|
|
1779
1189
|
});
|
|
1780
1190
|
const winner = await Promise.race([seedPromise.then(() => "done"), budget]);
|
|
1781
1191
|
if (timer) clearTimeout(timer);
|
|
@@ -2194,7 +1604,6 @@ var init_standalone_stack = __esm({
|
|
|
2194
1604
|
var index_exports = {};
|
|
2195
1605
|
__export(index_exports, {
|
|
2196
1606
|
AppPlugin: () => AppPlugin,
|
|
2197
|
-
DEFAULT_CLOUD_URL: () => DEFAULT_CLOUD_URL,
|
|
2198
1607
|
DEFAULT_RATE_LIMITS: () => DEFAULT_RATE_LIMITS,
|
|
2199
1608
|
DriverPlugin: () => DriverPlugin,
|
|
2200
1609
|
ExternalValidationPlugin: () => ExternalValidationPlugin,
|
|
@@ -2202,8 +1611,6 @@ __export(index_exports, {
|
|
|
2202
1611
|
HttpServer: () => HttpServer,
|
|
2203
1612
|
InMemoryErrorReporter: () => import_observability2.InMemoryErrorReporter,
|
|
2204
1613
|
InMemoryMetricsRegistry: () => import_observability.InMemoryMetricsRegistry,
|
|
2205
|
-
MarketplaceInstallLocalPlugin: () => MarketplaceInstallLocalPlugin,
|
|
2206
|
-
MarketplaceProxyPlugin: () => MarketplaceProxyPlugin,
|
|
2207
1614
|
MiddlewareManager: () => MiddlewareManager,
|
|
2208
1615
|
NoopErrorReporter: () => import_observability2.NoopErrorReporter,
|
|
2209
1616
|
NoopMetricsRegistry: () => import_observability.NoopMetricsRegistry,
|
|
@@ -2218,12 +1625,11 @@ __export(index_exports, {
|
|
|
2218
1625
|
RouteGroupBuilder: () => import_rest.RouteGroupBuilder,
|
|
2219
1626
|
RouteManager: () => import_rest.RouteManager,
|
|
2220
1627
|
Runtime: () => Runtime,
|
|
2221
|
-
RuntimeConfigPlugin: () => RuntimeConfigPlugin,
|
|
2222
1628
|
SYSTEM_ENVIRONMENT_ID: () => SYSTEM_ENVIRONMENT_ID,
|
|
2223
1629
|
SandboxError: () => SandboxError,
|
|
2224
|
-
SeedLoaderService: () => SeedLoaderService,
|
|
1630
|
+
SeedLoaderService: () => import_objectql.SeedLoaderService,
|
|
2225
1631
|
UnimplementedScriptRunner: () => UnimplementedScriptRunner,
|
|
2226
|
-
_resetEnvDeprecationWarnings: () =>
|
|
1632
|
+
_resetEnvDeprecationWarnings: () => import_types3._resetEnvDeprecationWarnings,
|
|
2227
1633
|
actionBodyRunnerFactory: () => actionBodyRunnerFactory,
|
|
2228
1634
|
buildSecurityHeaders: () => buildSecurityHeaders,
|
|
2229
1635
|
collectBundleActions: () => collectBundleActions,
|
|
@@ -2244,8 +1650,7 @@ __export(index_exports, {
|
|
|
2244
1650
|
mergeRuntimeModule: () => mergeRuntimeModule,
|
|
2245
1651
|
parseTraceparent: () => parseTraceparent,
|
|
2246
1652
|
readArtifactSource: () => readArtifactSource,
|
|
2247
|
-
readEnvWithDeprecation: () =>
|
|
2248
|
-
resolveCloudUrl: () => resolveCloudUrl,
|
|
1653
|
+
readEnvWithDeprecation: () => import_types3.readEnvWithDeprecation,
|
|
2249
1654
|
resolveDefaultArtifactPath: () => resolveDefaultArtifactPath,
|
|
2250
1655
|
resolveErrorReporter: () => resolveErrorReporter,
|
|
2251
1656
|
resolveMetrics: () => resolveMetrics,
|
|
@@ -2716,7 +2121,13 @@ function randomUUID() {
|
|
|
2716
2121
|
});
|
|
2717
2122
|
}
|
|
2718
2123
|
var _HttpDispatcher = class _HttpDispatcher {
|
|
2719
|
-
|
|
2124
|
+
/**
|
|
2125
|
+
* @param _envRegistryIgnored — RETIRED (ADR-0006 Phase 5). Environment
|
|
2126
|
+
* resolution moved behind the host's {@link KernelResolver}; the
|
|
2127
|
+
* positional parameter is kept so existing 3-arg callers keep compiling,
|
|
2128
|
+
* but its value is ignored.
|
|
2129
|
+
*/
|
|
2130
|
+
constructor(kernel, _envRegistryIgnored, options) {
|
|
2720
2131
|
/**
|
|
2721
2132
|
* In-memory cache of positive membership checks, keyed by
|
|
2722
2133
|
* `${environmentId}:${userId}`. Entries expire 60 seconds after insertion
|
|
@@ -2733,9 +2144,8 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
2733
2144
|
return void 0;
|
|
2734
2145
|
}
|
|
2735
2146
|
};
|
|
2736
|
-
this.envRegistry = envRegistry ?? resolveService("env-registry");
|
|
2737
2147
|
this.enforceMembership = options?.enforceProjectMembership ?? true;
|
|
2738
|
-
this.
|
|
2148
|
+
this.kernelResolver = options?.kernelResolver ?? resolveService("kernel-resolver");
|
|
2739
2149
|
this.scopeManager = options?.scopeManager ?? resolveService("scope-manager");
|
|
2740
2150
|
}
|
|
2741
2151
|
resolveDefaultProject() {
|
|
@@ -3123,130 +2533,20 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3123
2533
|
return candidate;
|
|
3124
2534
|
}
|
|
3125
2535
|
/**
|
|
3126
|
-
*
|
|
3127
|
-
*
|
|
3128
|
-
* Precedence:
|
|
3129
|
-
* 0. URL path matches `/environments/:environmentId/...` OR request.params.environmentId set by router
|
|
3130
|
-
* → envRegistry.resolveById(id)
|
|
3131
|
-
* 1. request.headers.host → envRegistry.resolveByHostname(host)
|
|
3132
|
-
* 2. request.headers['x-environment-id'] → envRegistry.resolveById(id)
|
|
3133
|
-
* 3. session.activeEnvironmentId → envRegistry.resolveById(id)
|
|
3134
|
-
* 4. session.activeOrganizationId → find default project → envRegistry.resolveById(id)
|
|
3135
|
-
* 5. single-environment default (registered by `createSingleEnvironmentPlugin`)
|
|
3136
|
-
* → envRegistry.resolveById(defaultProject.environmentId). Lets bare
|
|
3137
|
-
* `/api/v1/data/...` URLs resolve to the lone project in
|
|
3138
|
-
* `cloudUrl: 'local'` deployments.
|
|
2536
|
+
* Attach the dispatcher's parsing hints for the host's
|
|
2537
|
+
* {@link KernelResolver} (ADR-0006 Phase 5).
|
|
3139
2538
|
*
|
|
3140
|
-
*
|
|
3141
|
-
*
|
|
2539
|
+
* Environment RESOLUTION (hostname / x-environment-id / session /
|
|
2540
|
+
* org-default / single-env-default → environment + driver) is owned by
|
|
2541
|
+
* the host's resolver — the dispatcher no longer touches an environment
|
|
2542
|
+
* registry. What stays here is pure URL parsing (the dispatcher's own
|
|
2543
|
+
* routing convention): the scoped-path environment-id candidate and the
|
|
2544
|
+
* cleaned route path, both UNVALIDATED.
|
|
3142
2545
|
*/
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
}
|
|
3148
|
-
if (!this.envRegistry) {
|
|
3149
|
-
return;
|
|
3150
|
-
}
|
|
3151
|
-
const headers = context.request?.headers;
|
|
3152
|
-
const getHeader = (name) => {
|
|
3153
|
-
if (!headers) return void 0;
|
|
3154
|
-
const h = headers;
|
|
3155
|
-
if (typeof h.get === "function") {
|
|
3156
|
-
const v = h.get(name);
|
|
3157
|
-
return v == null ? void 0 : String(v);
|
|
3158
|
-
}
|
|
3159
|
-
const lower = name.toLowerCase();
|
|
3160
|
-
for (const k of Object.keys(h)) {
|
|
3161
|
-
if (k.toLowerCase() === lower) {
|
|
3162
|
-
const v = h[k];
|
|
3163
|
-
return Array.isArray(v) ? v[0] : v == null ? void 0 : String(v);
|
|
3164
|
-
}
|
|
3165
|
-
}
|
|
3166
|
-
return void 0;
|
|
3167
|
-
};
|
|
3168
|
-
try {
|
|
3169
|
-
const urlEnvironmentId = this.extractEnvironmentIdFromPath(path) ?? context.request?.params?.environmentId;
|
|
3170
|
-
if (urlEnvironmentId) {
|
|
3171
|
-
const driver = await this.envRegistry.resolveById(urlEnvironmentId);
|
|
3172
|
-
if (driver) {
|
|
3173
|
-
context.environmentId = urlEnvironmentId;
|
|
3174
|
-
context.dataDriver = driver;
|
|
3175
|
-
return;
|
|
3176
|
-
}
|
|
3177
|
-
}
|
|
3178
|
-
const host = getHeader("host");
|
|
3179
|
-
if (host) {
|
|
3180
|
-
const hostname = host.split(":")[0];
|
|
3181
|
-
const result = await this.envRegistry.resolveByHostname(hostname);
|
|
3182
|
-
if (result) {
|
|
3183
|
-
context.environmentId = result.environmentId;
|
|
3184
|
-
context.dataDriver = result.driver;
|
|
3185
|
-
return;
|
|
3186
|
-
}
|
|
3187
|
-
}
|
|
3188
|
-
const envIdHeader = getHeader("x-environment-id");
|
|
3189
|
-
if (envIdHeader) {
|
|
3190
|
-
const driver = await this.envRegistry.resolveById(envIdHeader);
|
|
3191
|
-
if (driver) {
|
|
3192
|
-
context.environmentId = envIdHeader;
|
|
3193
|
-
context.dataDriver = driver;
|
|
3194
|
-
return;
|
|
3195
|
-
}
|
|
3196
|
-
}
|
|
3197
|
-
try {
|
|
3198
|
-
const authService = await this.getService(import_system2.CoreServiceName.enum.auth);
|
|
3199
|
-
const sessionData = await authService?.api?.getSession?.({
|
|
3200
|
-
headers: context.request?.headers
|
|
3201
|
-
});
|
|
3202
|
-
const activeEnvironmentId = sessionData?.session?.activeEnvironmentId ?? sessionData?.session?.activeEnvironmentId;
|
|
3203
|
-
if (activeEnvironmentId) {
|
|
3204
|
-
const driver = await this.envRegistry.resolveById(activeEnvironmentId);
|
|
3205
|
-
if (driver) {
|
|
3206
|
-
context.environmentId = activeEnvironmentId;
|
|
3207
|
-
context.dataDriver = driver;
|
|
3208
|
-
return;
|
|
3209
|
-
}
|
|
3210
|
-
}
|
|
3211
|
-
const activeOrganizationId = sessionData?.session?.activeOrganizationId;
|
|
3212
|
-
if (activeOrganizationId) {
|
|
3213
|
-
const qlService = await this.getObjectQLService();
|
|
3214
|
-
const ql = qlService ?? await this.resolveService("objectql");
|
|
3215
|
-
if (ql) {
|
|
3216
|
-
let rows = await ql.find("sys_environment", {
|
|
3217
|
-
where: {
|
|
3218
|
-
organization_id: activeOrganizationId,
|
|
3219
|
-
is_default: true
|
|
3220
|
-
},
|
|
3221
|
-
limit: 1
|
|
3222
|
-
});
|
|
3223
|
-
if (rows && rows.value) rows = rows.value;
|
|
3224
|
-
if (Array.isArray(rows) && rows[0]) {
|
|
3225
|
-
const defaultEnv = rows[0];
|
|
3226
|
-
const driver = await this.envRegistry.resolveById(defaultEnv.id);
|
|
3227
|
-
if (driver) {
|
|
3228
|
-
context.environmentId = defaultEnv.id;
|
|
3229
|
-
context.dataDriver = driver;
|
|
3230
|
-
return;
|
|
3231
|
-
}
|
|
3232
|
-
}
|
|
3233
|
-
}
|
|
3234
|
-
}
|
|
3235
|
-
} catch (sessionError) {
|
|
3236
|
-
console.debug("[HttpDispatcher] Session resolution failed:", sessionError);
|
|
3237
|
-
}
|
|
3238
|
-
if (this.defaultProject?.environmentId || this.resolveDefaultProject()) {
|
|
3239
|
-
const def = this.defaultProject;
|
|
3240
|
-
const driver = await this.envRegistry.resolveById(def.environmentId);
|
|
3241
|
-
if (driver) {
|
|
3242
|
-
context.environmentId = def.environmentId;
|
|
3243
|
-
context.dataDriver = driver;
|
|
3244
|
-
return;
|
|
3245
|
-
}
|
|
3246
|
-
}
|
|
3247
|
-
} catch (error) {
|
|
3248
|
-
console.error("[HttpDispatcher] Environment resolution failed:", error);
|
|
3249
|
-
}
|
|
2546
|
+
prepareResolverHints(context, path) {
|
|
2547
|
+
context.routePath = path;
|
|
2548
|
+
const urlEnvironmentId = this.extractEnvironmentIdFromPath(path) ?? context.request?.params?.environmentId;
|
|
2549
|
+
if (urlEnvironmentId) context.urlEnvironmentId = String(urlEnvironmentId);
|
|
3250
2550
|
}
|
|
3251
2551
|
/**
|
|
3252
2552
|
* Check whether the authenticated user is a member of
|
|
@@ -3769,7 +3069,7 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
3769
3069
|
if (!objectName) {
|
|
3770
3070
|
return { handled: true, response: this.error("Object name required", 400) };
|
|
3771
3071
|
}
|
|
3772
|
-
if (!_context.dataDriver && this.
|
|
3072
|
+
if (!_context.dataDriver && this.kernelResolver) {
|
|
3773
3073
|
return {
|
|
3774
3074
|
handled: true,
|
|
3775
3075
|
response: this.error("Project not resolved. Please specify X-Environment-Id header or ensure hostname maps to a project.", 428)
|
|
@@ -4048,17 +3348,19 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4048
3348
|
...organizationId ? { organizationId } : {},
|
|
4049
3349
|
...body?.actor ? { actor: body.actor } : {}
|
|
4050
3350
|
});
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
3351
|
+
if (result?.seedApplied === void 0) {
|
|
3352
|
+
try {
|
|
3353
|
+
const seedNames = (result?.published ?? []).filter((p) => p?.type === "seed").map((p) => p.name);
|
|
3354
|
+
if (seedNames.length > 0) {
|
|
3355
|
+
result.seedApplied = await this.applyPublishedSeeds(
|
|
3356
|
+
seedNames,
|
|
3357
|
+
organizationId,
|
|
3358
|
+
_context
|
|
3359
|
+
);
|
|
3360
|
+
}
|
|
3361
|
+
} catch (e) {
|
|
3362
|
+
result.seedApplied = { success: false, error: e?.message ?? "seed apply failed" };
|
|
4059
3363
|
}
|
|
4060
|
-
} catch (e) {
|
|
4061
|
-
result.seedApplied = { success: false, error: e?.message ?? "seed apply failed" };
|
|
4062
3364
|
}
|
|
4063
3365
|
return { handled: true, response: this.success(result) };
|
|
4064
3366
|
} catch (e) {
|
|
@@ -4668,9 +3970,9 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4668
3970
|
if (def?.environmentId) _context.environmentId = def.environmentId;
|
|
4669
3971
|
}
|
|
4670
3972
|
let projectQl = null;
|
|
4671
|
-
if (this.
|
|
3973
|
+
if (this.kernelResolver && _context.environmentId && _context.environmentId !== "platform") {
|
|
4672
3974
|
try {
|
|
4673
|
-
const projectKernel = await this.
|
|
3975
|
+
const projectKernel = await this.kernelResolver.resolveKernel(_context, this.defaultKernel);
|
|
4674
3976
|
if (projectKernel) {
|
|
4675
3977
|
this.kernel = projectKernel;
|
|
4676
3978
|
if (typeof projectKernel.getServiceAsync === "function") {
|
|
@@ -4843,9 +4145,9 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4843
4145
|
*/
|
|
4844
4146
|
async dispatch(method, path, body, query, context, prefix) {
|
|
4845
4147
|
let cleanPath = path.replace(/\/$/, "");
|
|
4846
|
-
|
|
4847
|
-
if (this.
|
|
4848
|
-
this.kernel = await this.
|
|
4148
|
+
this.prepareResolverHints(context, cleanPath);
|
|
4149
|
+
if (this.kernelResolver) {
|
|
4150
|
+
this.kernel = await this.kernelResolver.resolveKernel(context, this.defaultKernel) ?? this.defaultKernel;
|
|
4849
4151
|
} else {
|
|
4850
4152
|
this.kernel = this.defaultKernel;
|
|
4851
4153
|
}
|
|
@@ -6271,1088 +5573,6 @@ var MiddlewareManager = class {
|
|
|
6271
5573
|
// src/index.ts
|
|
6272
5574
|
init_load_artifact_bundle();
|
|
6273
5575
|
|
|
6274
|
-
// src/cloud/cloud-url.ts
|
|
6275
|
-
var DEFAULT_CLOUD_URL = "https://cloud.objectos.ai";
|
|
6276
|
-
function resolveCloudUrl(explicit) {
|
|
6277
|
-
const raw = (explicit ?? process.env.OS_CLOUD_URL ?? "").trim();
|
|
6278
|
-
const lower = raw.toLowerCase();
|
|
6279
|
-
if (lower === "off" || lower === "none" || lower === "local" || lower === "disabled") {
|
|
6280
|
-
return "";
|
|
6281
|
-
}
|
|
6282
|
-
const picked = raw || DEFAULT_CLOUD_URL;
|
|
6283
|
-
return picked.replace(/\/+$/, "");
|
|
6284
|
-
}
|
|
6285
|
-
|
|
6286
|
-
// src/cloud/marketplace-public-url.ts
|
|
6287
|
-
function resolveMarketplacePublicBaseUrl(explicit) {
|
|
6288
|
-
const raw = (explicit ?? process.env.OS_MARKETPLACE_PUBLIC_BASE_URL ?? "").trim();
|
|
6289
|
-
const lower = raw.toLowerCase();
|
|
6290
|
-
if (!raw || lower === "off" || lower === "none" || lower === "disabled" || lower === "false") {
|
|
6291
|
-
return "";
|
|
6292
|
-
}
|
|
6293
|
-
return raw.replace(/\/+$/, "");
|
|
6294
|
-
}
|
|
6295
|
-
function publicMarketplaceKeyForApiPath(pathname) {
|
|
6296
|
-
const prefix = "/api/v1/marketplace/packages";
|
|
6297
|
-
if (pathname === prefix) return "packages.json";
|
|
6298
|
-
if (!pathname.startsWith(`${prefix}/`)) return null;
|
|
6299
|
-
const tail = pathname.slice(prefix.length + 1);
|
|
6300
|
-
if (!tail) return null;
|
|
6301
|
-
const parts = tail.split("/");
|
|
6302
|
-
if (parts.length === 1) {
|
|
6303
|
-
const id = decodeURIComponent(parts[0] ?? "");
|
|
6304
|
-
if (!id) return null;
|
|
6305
|
-
return `packages/${encodeURIComponent(id)}.json`;
|
|
6306
|
-
}
|
|
6307
|
-
if (parts.length === 4 && parts[1] === "versions" && parts[3] === "manifest") {
|
|
6308
|
-
const id = decodeURIComponent(parts[0] ?? "");
|
|
6309
|
-
const versionId = decodeURIComponent(parts[2] ?? "");
|
|
6310
|
-
if (!id || !versionId) return null;
|
|
6311
|
-
return `packages/${encodeURIComponent(id)}/versions/${encodeURIComponent(versionId)}/manifest.json`;
|
|
6312
|
-
}
|
|
6313
|
-
return null;
|
|
6314
|
-
}
|
|
6315
|
-
|
|
6316
|
-
// src/cloud/marketplace-proxy-plugin.ts
|
|
6317
|
-
var MARKETPLACE_PREFIX = "/api/v1/marketplace";
|
|
6318
|
-
var DEFAULT_LRU_MAX = 200;
|
|
6319
|
-
var LIST_TTL_MS = 30 * 60 * 1e3;
|
|
6320
|
-
var PACKAGE_TTL_MS = 2 * 60 * 60 * 1e3;
|
|
6321
|
-
var VERSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
6322
|
-
function ttlForPath(pathname) {
|
|
6323
|
-
if (/\/packages\/[^/]+\/versions\//.test(pathname)) return VERSION_TTL_MS;
|
|
6324
|
-
if (/\/packages\/[^/]+/.test(pathname)) return PACKAGE_TTL_MS;
|
|
6325
|
-
return LIST_TTL_MS;
|
|
6326
|
-
}
|
|
6327
|
-
var LruTtlCache = class {
|
|
6328
|
-
constructor(max) {
|
|
6329
|
-
this.max = max;
|
|
6330
|
-
this.map = /* @__PURE__ */ new Map();
|
|
6331
|
-
}
|
|
6332
|
-
get(key) {
|
|
6333
|
-
const entry = this.map.get(key);
|
|
6334
|
-
if (!entry) return void 0;
|
|
6335
|
-
this.map.delete(key);
|
|
6336
|
-
this.map.set(key, entry);
|
|
6337
|
-
return entry;
|
|
6338
|
-
}
|
|
6339
|
-
set(key, entry) {
|
|
6340
|
-
if (this.map.has(key)) this.map.delete(key);
|
|
6341
|
-
this.map.set(key, entry);
|
|
6342
|
-
while (this.map.size > this.max) {
|
|
6343
|
-
const oldest = this.map.keys().next().value;
|
|
6344
|
-
if (oldest === void 0) break;
|
|
6345
|
-
this.map.delete(oldest);
|
|
6346
|
-
}
|
|
6347
|
-
}
|
|
6348
|
-
clear() {
|
|
6349
|
-
this.map.clear();
|
|
6350
|
-
}
|
|
6351
|
-
};
|
|
6352
|
-
var MarketplaceProxyPlugin = class _MarketplaceProxyPlugin {
|
|
6353
|
-
constructor(config = {}) {
|
|
6354
|
-
this.name = "com.objectstack.runtime.marketplace-proxy";
|
|
6355
|
-
this.version = "1.1.0";
|
|
6356
|
-
this.init = async (_ctx) => {
|
|
6357
|
-
};
|
|
6358
|
-
this.start = async (ctx) => {
|
|
6359
|
-
ctx.hook("kernel:ready", async () => {
|
|
6360
|
-
let httpServer;
|
|
6361
|
-
try {
|
|
6362
|
-
httpServer = ctx.getService("http-server");
|
|
6363
|
-
} catch {
|
|
6364
|
-
ctx.logger?.warn?.("[MarketplaceProxyPlugin] http-server not available \u2014 marketplace routes not mounted");
|
|
6365
|
-
return;
|
|
6366
|
-
}
|
|
6367
|
-
if (!httpServer || typeof httpServer.getRawApp !== "function") {
|
|
6368
|
-
ctx.logger?.warn?.("[MarketplaceProxyPlugin] http-server missing getRawApp() \u2014 marketplace routes not mounted");
|
|
6369
|
-
return;
|
|
6370
|
-
}
|
|
6371
|
-
const rawApp = httpServer.getRawApp();
|
|
6372
|
-
const cloudUrl = this.cloudUrl;
|
|
6373
|
-
const publicBaseUrl = this.publicBaseUrl;
|
|
6374
|
-
const cache = this.cache;
|
|
6375
|
-
if (publicBaseUrl) {
|
|
6376
|
-
ctx.logger?.info?.(`[MarketplaceProxyPlugin] public R2 fast-path enabled \u2192 ${publicBaseUrl}`);
|
|
6377
|
-
}
|
|
6378
|
-
const handler = async (c, next) => {
|
|
6379
|
-
if (!cloudUrl) {
|
|
6380
|
-
return c.json({
|
|
6381
|
-
success: false,
|
|
6382
|
-
error: {
|
|
6383
|
-
code: "marketplace_unavailable",
|
|
6384
|
-
message: "No control-plane URL configured for this runtime (OS_CLOUD_URL)."
|
|
6385
|
-
}
|
|
6386
|
-
}, 503);
|
|
6387
|
-
}
|
|
6388
|
-
try {
|
|
6389
|
-
const incomingUrl = new URL(c.req.url);
|
|
6390
|
-
if (incomingUrl.pathname.startsWith(`${MARKETPLACE_PREFIX}/install-local`)) {
|
|
6391
|
-
return next();
|
|
6392
|
-
}
|
|
6393
|
-
const method = String(c.req.method ?? "GET").toUpperCase();
|
|
6394
|
-
if (publicBaseUrl && (method === "GET" || method === "HEAD")) {
|
|
6395
|
-
const r2Resp = await tryPublicMarketplaceFetch(
|
|
6396
|
-
publicBaseUrl,
|
|
6397
|
-
incomingUrl,
|
|
6398
|
-
method,
|
|
6399
|
-
c.req.header("accept"),
|
|
6400
|
-
ctx.logger
|
|
6401
|
-
);
|
|
6402
|
-
if (r2Resp) return r2Resp;
|
|
6403
|
-
}
|
|
6404
|
-
const target = `${cloudUrl}${incomingUrl.pathname}${incomingUrl.search}`;
|
|
6405
|
-
if (method !== "GET" && method !== "HEAD") {
|
|
6406
|
-
return next();
|
|
6407
|
-
}
|
|
6408
|
-
const accept = c.req.header("accept") ?? "application/json";
|
|
6409
|
-
const acceptLang = c.req.header("accept-language") ?? "";
|
|
6410
|
-
const cacheKey = `${incomingUrl.pathname}${incomingUrl.search}|al=${acceptLang}|a=${accept}`;
|
|
6411
|
-
const reqCacheCtl = (c.req.header("cache-control") ?? "").toLowerCase();
|
|
6412
|
-
const bypass = !cache || reqCacheCtl.includes("no-cache") || reqCacheCtl.includes("no-store");
|
|
6413
|
-
const now = Date.now();
|
|
6414
|
-
if (cache && !bypass) {
|
|
6415
|
-
const hit = cache.get(cacheKey);
|
|
6416
|
-
if (hit && hit.expiresAt > now) {
|
|
6417
|
-
return buildCachedResponse(hit, method, "HIT");
|
|
6418
|
-
}
|
|
6419
|
-
if (hit) {
|
|
6420
|
-
const revalHeaders = {
|
|
6421
|
-
"Accept": accept,
|
|
6422
|
-
"User-Agent": `objectos-marketplace-proxy/${_MarketplaceProxyPlugin.prototype.version ?? "1.0.0"}`
|
|
6423
|
-
};
|
|
6424
|
-
if (acceptLang) revalHeaders["Accept-Language"] = acceptLang;
|
|
6425
|
-
if (hit.etag) revalHeaders["If-None-Match"] = hit.etag;
|
|
6426
|
-
if (hit.lastModified) revalHeaders["If-Modified-Since"] = hit.lastModified;
|
|
6427
|
-
const revalResp = await fetch(target, { method: "GET", headers: revalHeaders });
|
|
6428
|
-
if (revalResp.status === 304) {
|
|
6429
|
-
hit.expiresAt = now + hit.ttlMs;
|
|
6430
|
-
const newEtag = revalResp.headers.get("etag");
|
|
6431
|
-
const newLm = revalResp.headers.get("last-modified");
|
|
6432
|
-
if (newEtag) hit.etag = newEtag;
|
|
6433
|
-
if (newLm) hit.lastModified = newLm;
|
|
6434
|
-
cache.set(cacheKey, hit);
|
|
6435
|
-
return buildCachedResponse(hit, method, "REVALIDATED");
|
|
6436
|
-
}
|
|
6437
|
-
return await consumeAndMaybeCache(revalResp, cacheKey, incomingUrl.pathname, method, cache);
|
|
6438
|
-
}
|
|
6439
|
-
}
|
|
6440
|
-
const reqHeaders = {
|
|
6441
|
-
// Strip the inbound Host header — fetch will set
|
|
6442
|
-
// it to the cloud host. Forward only the
|
|
6443
|
-
// identifying headers cloud might log.
|
|
6444
|
-
"Accept": accept,
|
|
6445
|
-
"User-Agent": `objectos-marketplace-proxy/${_MarketplaceProxyPlugin.prototype.version ?? "1.0.0"}`
|
|
6446
|
-
};
|
|
6447
|
-
if (acceptLang) reqHeaders["Accept-Language"] = acceptLang;
|
|
6448
|
-
const resp = await fetch(target, { method: "GET", headers: reqHeaders });
|
|
6449
|
-
if (bypass || !cache) {
|
|
6450
|
-
return await passthroughResponse(resp, method, bypass ? "BYPASS" : "MISS");
|
|
6451
|
-
}
|
|
6452
|
-
return await consumeAndMaybeCache(resp, cacheKey, incomingUrl.pathname, method, cache);
|
|
6453
|
-
} catch (err) {
|
|
6454
|
-
const errObj = err instanceof Error ? err : new Error(err?.message ?? String(err));
|
|
6455
|
-
ctx.logger?.error?.("[MarketplaceProxyPlugin] proxy failed", errObj);
|
|
6456
|
-
return c.json({
|
|
6457
|
-
success: false,
|
|
6458
|
-
error: {
|
|
6459
|
-
code: "marketplace_proxy_failed",
|
|
6460
|
-
message: err?.message ?? String(err)
|
|
6461
|
-
}
|
|
6462
|
-
}, 502);
|
|
6463
|
-
}
|
|
6464
|
-
};
|
|
6465
|
-
if (typeof rawApp.all === "function") {
|
|
6466
|
-
rawApp.all(`${MARKETPLACE_PREFIX}/*`, handler);
|
|
6467
|
-
} else {
|
|
6468
|
-
for (const m of ["get", "head"]) {
|
|
6469
|
-
try {
|
|
6470
|
-
rawApp[m]?.(`${MARKETPLACE_PREFIX}/*`, handler);
|
|
6471
|
-
} catch {
|
|
6472
|
-
}
|
|
6473
|
-
}
|
|
6474
|
-
}
|
|
6475
|
-
ctx.logger?.info?.(`[MarketplaceProxyPlugin] mounted at ${MARKETPLACE_PREFIX}/* \u2192 ${cloudUrl || "(unconfigured)"} (cache=${this.cache ? "on" : "off"})`);
|
|
6476
|
-
});
|
|
6477
|
-
};
|
|
6478
|
-
this.cloudUrl = resolveCloudUrl(config.controlPlaneUrl);
|
|
6479
|
-
this.publicBaseUrl = resolveMarketplacePublicBaseUrl(config.publicMarketplaceBaseUrl);
|
|
6480
|
-
const envFlag = (process.env.OS_MARKETPLACE_CACHE ?? "").trim().toLowerCase();
|
|
6481
|
-
const envDisabled = ["off", "false", "0", "no", "disable", "disabled"].includes(envFlag);
|
|
6482
|
-
const disabled = config.cacheDisabled ?? envDisabled;
|
|
6483
|
-
this.cache = disabled ? null : new LruTtlCache(Math.max(8, config.cacheMaxEntries ?? DEFAULT_LRU_MAX));
|
|
6484
|
-
}
|
|
6485
|
-
};
|
|
6486
|
-
async function tryPublicMarketplaceFetch(publicBaseUrl, incomingUrl, method, acceptHeader, logger) {
|
|
6487
|
-
const key = publicMarketplaceKeyForApiPath(incomingUrl.pathname);
|
|
6488
|
-
if (!key) return null;
|
|
6489
|
-
const target = `${publicBaseUrl}/${key}`;
|
|
6490
|
-
let resp;
|
|
6491
|
-
try {
|
|
6492
|
-
resp = await fetch(target, {
|
|
6493
|
-
method: "GET",
|
|
6494
|
-
headers: {
|
|
6495
|
-
"Accept": acceptHeader || "application/json",
|
|
6496
|
-
"User-Agent": `objectos-marketplace-proxy/public-r2`
|
|
6497
|
-
}
|
|
6498
|
-
});
|
|
6499
|
-
} catch (err) {
|
|
6500
|
-
logger?.warn?.(`[MarketplaceProxyPlugin] public R2 fetch failed (${target}): ${err?.message ?? err}`);
|
|
6501
|
-
return null;
|
|
6502
|
-
}
|
|
6503
|
-
if (resp.status === 404) return null;
|
|
6504
|
-
if (!resp.ok) {
|
|
6505
|
-
logger?.warn?.(`[MarketplaceProxyPlugin] public R2 ${target} returned ${resp.status} \u2014 falling back to cloud`);
|
|
6506
|
-
return null;
|
|
6507
|
-
}
|
|
6508
|
-
const isList = key === "packages.json";
|
|
6509
|
-
const hasFilters = isList && (incomingUrl.searchParams.has("q") || incomingUrl.searchParams.has("category") || incomingUrl.searchParams.has("limit") || incomingUrl.searchParams.has("offset"));
|
|
6510
|
-
if (!hasFilters) {
|
|
6511
|
-
const headers2 = new Headers();
|
|
6512
|
-
const ct = resp.headers.get("content-type") ?? "application/json; charset=utf-8";
|
|
6513
|
-
headers2.set("content-type", ct);
|
|
6514
|
-
const cc = resp.headers.get("cache-control");
|
|
6515
|
-
if (cc) headers2.set("cache-control", cc);
|
|
6516
|
-
const etag = resp.headers.get("etag");
|
|
6517
|
-
if (etag) headers2.set("etag", etag);
|
|
6518
|
-
headers2.set("x-cache", "PUBLIC-R2");
|
|
6519
|
-
const body2 = method === "HEAD" ? null : resp.body;
|
|
6520
|
-
return new Response(body2, { status: 200, headers: headers2 });
|
|
6521
|
-
}
|
|
6522
|
-
let snapshot;
|
|
6523
|
-
try {
|
|
6524
|
-
snapshot = await resp.json();
|
|
6525
|
-
} catch (err) {
|
|
6526
|
-
logger?.warn?.(`[MarketplaceProxyPlugin] public R2 list snapshot parse failed: ${err?.message ?? err}`);
|
|
6527
|
-
return null;
|
|
6528
|
-
}
|
|
6529
|
-
const items = Array.isArray(snapshot?.data?.items) ? snapshot.data.items : [];
|
|
6530
|
-
const q = (incomingUrl.searchParams.get("q") ?? "").trim().toLowerCase();
|
|
6531
|
-
const category = (incomingUrl.searchParams.get("category") ?? "").trim();
|
|
6532
|
-
const limit = Math.min(Math.max(Number(incomingUrl.searchParams.get("limit") ?? 50), 1), 100);
|
|
6533
|
-
const offset = Math.max(Number(incomingUrl.searchParams.get("offset") ?? 0), 0);
|
|
6534
|
-
let filtered = items;
|
|
6535
|
-
if (q) {
|
|
6536
|
-
filtered = filtered.filter((r) => {
|
|
6537
|
-
const dn = String(r?.display_name ?? "").toLowerCase();
|
|
6538
|
-
const mid = String(r?.manifest_id ?? "").toLowerCase();
|
|
6539
|
-
return dn.includes(q) || mid.includes(q);
|
|
6540
|
-
});
|
|
6541
|
-
}
|
|
6542
|
-
if (category) {
|
|
6543
|
-
filtered = filtered.filter((r) => String(r?.category ?? "") === category);
|
|
6544
|
-
}
|
|
6545
|
-
const total = filtered.length;
|
|
6546
|
-
const page = filtered.slice(offset, offset + limit);
|
|
6547
|
-
const body = JSON.stringify({ success: true, data: { items: page, total, limit, offset } });
|
|
6548
|
-
const headers = new Headers({
|
|
6549
|
-
"content-type": "application/json; charset=utf-8",
|
|
6550
|
-
"cache-control": "public, max-age=30",
|
|
6551
|
-
"x-cache": "PUBLIC-R2-FILTERED"
|
|
6552
|
-
});
|
|
6553
|
-
return new Response(method === "HEAD" ? null : body, { status: 200, headers });
|
|
6554
|
-
}
|
|
6555
|
-
var PASSTHROUGH_HEADERS = ["content-type", "cache-control", "etag", "last-modified", "vary"];
|
|
6556
|
-
function collectHeaders(src) {
|
|
6557
|
-
const out = {};
|
|
6558
|
-
for (const h of PASSTHROUGH_HEADERS) {
|
|
6559
|
-
const v = src.headers.get(h);
|
|
6560
|
-
if (v) out[h] = v;
|
|
6561
|
-
}
|
|
6562
|
-
return out;
|
|
6563
|
-
}
|
|
6564
|
-
function buildCachedResponse(entry, method, xCache) {
|
|
6565
|
-
const headers = new Headers(entry.headers);
|
|
6566
|
-
headers.set("X-Cache", xCache);
|
|
6567
|
-
const ageSec = Math.max(0, Math.floor((entry.expiresAt - entry.ttlMs - Date.now()) / -1e3));
|
|
6568
|
-
headers.set("Age", String(Math.max(0, ageSec)));
|
|
6569
|
-
const body = method === "HEAD" ? null : entry.body;
|
|
6570
|
-
return new Response(body, { status: entry.status, headers });
|
|
6571
|
-
}
|
|
6572
|
-
async function passthroughResponse(resp, method, xCache) {
|
|
6573
|
-
const headers = new Headers(collectHeaders(resp));
|
|
6574
|
-
headers.set("X-Cache", xCache);
|
|
6575
|
-
if (method === "HEAD") {
|
|
6576
|
-
try {
|
|
6577
|
-
await resp.arrayBuffer();
|
|
6578
|
-
} catch {
|
|
6579
|
-
}
|
|
6580
|
-
return new Response(null, { status: resp.status, headers });
|
|
6581
|
-
}
|
|
6582
|
-
const body = await resp.arrayBuffer();
|
|
6583
|
-
return new Response(body, { status: resp.status, headers });
|
|
6584
|
-
}
|
|
6585
|
-
async function consumeAndMaybeCache(resp, key, pathname, method, cache) {
|
|
6586
|
-
const body = await resp.arrayBuffer();
|
|
6587
|
-
const headers = collectHeaders(resp);
|
|
6588
|
-
if (resp.status >= 200 && resp.status < 300) {
|
|
6589
|
-
const ttlMs = ttlForPath(pathname);
|
|
6590
|
-
const entry = {
|
|
6591
|
-
status: resp.status,
|
|
6592
|
-
body,
|
|
6593
|
-
headers,
|
|
6594
|
-
etag: resp.headers.get("etag") ?? void 0,
|
|
6595
|
-
lastModified: resp.headers.get("last-modified") ?? void 0,
|
|
6596
|
-
expiresAt: Date.now() + ttlMs,
|
|
6597
|
-
ttlMs
|
|
6598
|
-
};
|
|
6599
|
-
cache.set(key, entry);
|
|
6600
|
-
}
|
|
6601
|
-
const respHeaders = new Headers(headers);
|
|
6602
|
-
respHeaders.set("X-Cache", "MISS");
|
|
6603
|
-
const outBody = method === "HEAD" ? null : body;
|
|
6604
|
-
return new Response(outBody, { status: resp.status, headers: respHeaders });
|
|
6605
|
-
}
|
|
6606
|
-
|
|
6607
|
-
// src/cloud/marketplace-install-local-plugin.ts
|
|
6608
|
-
var import_node_fs4 = require("fs");
|
|
6609
|
-
var import_node_path5 = require("path");
|
|
6610
|
-
var import_types3 = require("@objectstack/types");
|
|
6611
|
-
var ROUTE_BASE = "/api/v1/marketplace/install-local";
|
|
6612
|
-
var DEFAULT_DIR = ".objectstack/installed-packages";
|
|
6613
|
-
function safeFilename(manifestId) {
|
|
6614
|
-
return manifestId.replace(/[^a-zA-Z0-9._-]/g, "_") + ".json";
|
|
6615
|
-
}
|
|
6616
|
-
var MarketplaceInstallLocalPlugin = class {
|
|
6617
|
-
constructor(config = {}) {
|
|
6618
|
-
this.name = "com.objectstack.runtime.marketplace-install-local";
|
|
6619
|
-
this.version = "1.0.0";
|
|
6620
|
-
this.init = async (_ctx) => {
|
|
6621
|
-
};
|
|
6622
|
-
this.start = async (ctx) => {
|
|
6623
|
-
ctx.hook("kernel:ready", async () => {
|
|
6624
|
-
await this.rehydrate(ctx);
|
|
6625
|
-
let httpServer;
|
|
6626
|
-
try {
|
|
6627
|
-
httpServer = ctx.getService("http-server");
|
|
6628
|
-
} catch {
|
|
6629
|
-
ctx.logger?.warn?.("[MarketplaceInstallLocal] http-server not available \u2014 install endpoints not mounted");
|
|
6630
|
-
return;
|
|
6631
|
-
}
|
|
6632
|
-
if (!httpServer || typeof httpServer.getRawApp !== "function") {
|
|
6633
|
-
ctx.logger?.warn?.("[MarketplaceInstallLocal] http-server missing getRawApp() \u2014 install endpoints not mounted");
|
|
6634
|
-
return;
|
|
6635
|
-
}
|
|
6636
|
-
const rawApp = httpServer.getRawApp();
|
|
6637
|
-
const postHandler = async (c) => this.handleInstall(c, ctx);
|
|
6638
|
-
const getHandler = async (c) => this.handleList(c);
|
|
6639
|
-
const deleteHandler = async (c) => this.handleUninstall(c, ctx);
|
|
6640
|
-
const reseedHandler = async (c) => this.handleReseed(c, ctx);
|
|
6641
|
-
const purgeHandler = async (c) => this.handlePurge(c, ctx);
|
|
6642
|
-
if (typeof rawApp.post === "function") rawApp.post(ROUTE_BASE, postHandler);
|
|
6643
|
-
if (typeof rawApp.get === "function") rawApp.get(ROUTE_BASE, getHandler);
|
|
6644
|
-
if (typeof rawApp.delete === "function") rawApp.delete(`${ROUTE_BASE}/:manifestId`, deleteHandler);
|
|
6645
|
-
if (typeof rawApp.post === "function") {
|
|
6646
|
-
rawApp.post(`${ROUTE_BASE}/:manifestId/reseed-sample-data`, reseedHandler);
|
|
6647
|
-
rawApp.post(`${ROUTE_BASE}/:manifestId/purge-sample-data`, purgeHandler);
|
|
6648
|
-
}
|
|
6649
|
-
ctx.logger?.info?.(`[MarketplaceInstallLocal] mounted at ${ROUTE_BASE} (storage: ${this.storageDir})`);
|
|
6650
|
-
});
|
|
6651
|
-
};
|
|
6652
|
-
/**
|
|
6653
|
-
* Re-register every cached manifest with the kernel's manifest service.
|
|
6654
|
-
* Safe to call on a kernel that already has the same manifest_id (the
|
|
6655
|
-
* underlying ObjectQL registry overwrites by id, but we still warn so
|
|
6656
|
-
* a developer can spot the dev-time clash between their config.ts and
|
|
6657
|
-
* a marketplace package).
|
|
6658
|
-
*/
|
|
6659
|
-
this.rehydrate = async (ctx) => {
|
|
6660
|
-
const entries = this.readAll();
|
|
6661
|
-
if (entries.length === 0) return;
|
|
6662
|
-
let manifestService = null;
|
|
6663
|
-
try {
|
|
6664
|
-
manifestService = ctx.getService("manifest");
|
|
6665
|
-
} catch {
|
|
6666
|
-
ctx.logger?.warn?.("[MarketplaceInstallLocal] no `manifest` service \u2014 rehydrate skipped");
|
|
6667
|
-
return;
|
|
6668
|
-
}
|
|
6669
|
-
for (const entry of entries) {
|
|
6670
|
-
try {
|
|
6671
|
-
manifestService.register(entry.manifest);
|
|
6672
|
-
try {
|
|
6673
|
-
const ql = ctx.getService("objectql");
|
|
6674
|
-
if (ql && typeof ql.syncSchemas === "function") await ql.syncSchemas();
|
|
6675
|
-
} catch {
|
|
6676
|
-
}
|
|
6677
|
-
await this.applySideEffects(ctx, entry.manifest, { seedNow: false });
|
|
6678
|
-
ctx.logger?.info?.(`[MarketplaceInstallLocal] rehydrated ${entry.manifestId}@${entry.version}`);
|
|
6679
|
-
} catch (err) {
|
|
6680
|
-
ctx.logger?.error?.(`[MarketplaceInstallLocal] rehydrate failed for ${entry.manifestId}`, err instanceof Error ? err : new Error(String(err)));
|
|
6681
|
-
}
|
|
6682
|
-
}
|
|
6683
|
-
};
|
|
6684
|
-
this.handleInstall = async (c, ctx) => {
|
|
6685
|
-
const userId = await this.requireAuthenticatedUser(c, ctx);
|
|
6686
|
-
if (!userId) {
|
|
6687
|
-
return c.json({ success: false, error: { code: "unauthorized", message: "Authentication required to install packages." } }, 401);
|
|
6688
|
-
}
|
|
6689
|
-
let body = {};
|
|
6690
|
-
try {
|
|
6691
|
-
body = await c.req.json();
|
|
6692
|
-
} catch {
|
|
6693
|
-
}
|
|
6694
|
-
const inlineManifest = body?.manifest && typeof body.manifest === "object" ? body.manifest : null;
|
|
6695
|
-
let manifest;
|
|
6696
|
-
let resolvedVersionId;
|
|
6697
|
-
let version;
|
|
6698
|
-
let packageId;
|
|
6699
|
-
if (inlineManifest) {
|
|
6700
|
-
manifest = inlineManifest;
|
|
6701
|
-
packageId = String(manifest.id ?? manifest.name ?? "").trim();
|
|
6702
|
-
version = String(manifest.version ?? "unknown");
|
|
6703
|
-
resolvedVersionId = String(body?.versionId ?? version);
|
|
6704
|
-
if (!packageId) {
|
|
6705
|
-
return c.json({ success: false, error: { code: "invalid_manifest", message: 'Inline manifest must have an "id" or "name".' } }, 400);
|
|
6706
|
-
}
|
|
6707
|
-
} else {
|
|
6708
|
-
if (!this.cloudUrl) {
|
|
6709
|
-
return c.json({ success: false, error: { code: "marketplace_unavailable", message: "OS_CLOUD_URL not configured." } }, 503);
|
|
6710
|
-
}
|
|
6711
|
-
packageId = String(body?.packageId ?? "").trim();
|
|
6712
|
-
const versionId = String(body?.versionId ?? "latest").trim() || "latest";
|
|
6713
|
-
if (!packageId) {
|
|
6714
|
-
return c.json({ success: false, error: { code: "bad_request", message: "packageId is required." } }, 400);
|
|
6715
|
-
}
|
|
6716
|
-
let payload;
|
|
6717
|
-
const publicBase = resolveMarketplacePublicBaseUrl();
|
|
6718
|
-
const fetchAttempts = [];
|
|
6719
|
-
if (publicBase) {
|
|
6720
|
-
fetchAttempts.push({
|
|
6721
|
-
label: "public-r2",
|
|
6722
|
-
url: `${publicBase}/packages/${encodeURIComponent(packageId)}/versions/${encodeURIComponent(versionId)}/manifest.json`
|
|
6723
|
-
});
|
|
6724
|
-
}
|
|
6725
|
-
fetchAttempts.push({
|
|
6726
|
-
label: "cloud",
|
|
6727
|
-
url: `${this.cloudUrl}/api/v1/marketplace/packages/${encodeURIComponent(packageId)}/versions/${encodeURIComponent(versionId)}/manifest`
|
|
6728
|
-
});
|
|
6729
|
-
let lastErrStatus = 0;
|
|
6730
|
-
let lastErrText = "";
|
|
6731
|
-
for (const attempt of fetchAttempts) {
|
|
6732
|
-
try {
|
|
6733
|
-
const resp = await fetch(attempt.url, { headers: { "Accept": "application/json" } });
|
|
6734
|
-
if (!resp.ok) {
|
|
6735
|
-
lastErrStatus = resp.status;
|
|
6736
|
-
lastErrText = (await resp.text().catch(() => "")).slice(0, 200);
|
|
6737
|
-
if (attempt.label === "public-r2" && resp.status === 404) {
|
|
6738
|
-
ctx.logger?.info?.(`[MarketplaceInstallLocal] public-r2 miss for ${packageId}@${versionId}, falling back to cloud`);
|
|
6739
|
-
continue;
|
|
6740
|
-
}
|
|
6741
|
-
if (attempt.label === "public-r2" && resp.status >= 500) {
|
|
6742
|
-
ctx.logger?.warn?.(`[MarketplaceInstallLocal] public-r2 ${resp.status}, falling back to cloud`);
|
|
6743
|
-
continue;
|
|
6744
|
-
}
|
|
6745
|
-
break;
|
|
6746
|
-
}
|
|
6747
|
-
payload = await resp.json();
|
|
6748
|
-
lastErrStatus = 0;
|
|
6749
|
-
break;
|
|
6750
|
-
} catch (err) {
|
|
6751
|
-
if (attempt.label === "public-r2") {
|
|
6752
|
-
ctx.logger?.warn?.(`[MarketplaceInstallLocal] public-r2 fetch error: ${err?.message ?? err}, falling back to cloud`);
|
|
6753
|
-
continue;
|
|
6754
|
-
}
|
|
6755
|
-
return c.json({
|
|
6756
|
-
success: false,
|
|
6757
|
-
error: { code: "cloud_fetch_failed", message: err?.message ?? String(err) }
|
|
6758
|
-
}, 502);
|
|
6759
|
-
}
|
|
6760
|
-
}
|
|
6761
|
-
if (!payload) {
|
|
6762
|
-
return c.json({
|
|
6763
|
-
success: false,
|
|
6764
|
-
error: { code: "cloud_fetch_failed", message: `Cloud returned ${lastErrStatus}: ${lastErrText}` }
|
|
6765
|
-
}, lastErrStatus === 404 ? 404 : 502);
|
|
6766
|
-
}
|
|
6767
|
-
const data = payload?.data ?? payload;
|
|
6768
|
-
manifest = data?.manifest;
|
|
6769
|
-
resolvedVersionId = String(data?.version_id ?? versionId);
|
|
6770
|
-
version = String(data?.version ?? "unknown");
|
|
6771
|
-
}
|
|
6772
|
-
const manifestId = String(manifest?.id ?? manifest?.name ?? "");
|
|
6773
|
-
if (!manifest || !manifestId) {
|
|
6774
|
-
return c.json({ success: false, error: { code: "invalid_manifest", message: "Invalid manifest payload." } }, inlineManifest ? 400 : 502);
|
|
6775
|
-
}
|
|
6776
|
-
const conflict = this.findConflict(ctx, manifestId);
|
|
6777
|
-
if (conflict === "user-code") {
|
|
6778
|
-
return c.json({
|
|
6779
|
-
success: false,
|
|
6780
|
-
error: {
|
|
6781
|
-
code: "manifest_conflict",
|
|
6782
|
-
message: `manifest_id "${manifestId}" is already defined by this runtime's local code. Refusing to overwrite. Uninstall the local definition first.`
|
|
6783
|
-
}
|
|
6784
|
-
}, 409);
|
|
6785
|
-
}
|
|
6786
|
-
try {
|
|
6787
|
-
const manifestService = ctx.getService("manifest");
|
|
6788
|
-
manifestService.register(manifest);
|
|
6789
|
-
} catch (err) {
|
|
6790
|
-
if (inlineManifest) {
|
|
6791
|
-
return c.json({
|
|
6792
|
-
success: false,
|
|
6793
|
-
error: { code: "register_failed", message: `Failed to register imported manifest: ${err?.message ?? err}` }
|
|
6794
|
-
}, 422);
|
|
6795
|
-
}
|
|
6796
|
-
ctx.logger?.warn?.(`[MarketplaceInstallLocal] hot-register failed for ${manifestId} (will load on next restart): ${err?.message ?? err}`);
|
|
6797
|
-
}
|
|
6798
|
-
const entry = {
|
|
6799
|
-
packageId,
|
|
6800
|
-
versionId: resolvedVersionId,
|
|
6801
|
-
manifestId,
|
|
6802
|
-
version,
|
|
6803
|
-
manifest,
|
|
6804
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6805
|
-
installedBy: userId,
|
|
6806
|
-
withSampleData: false
|
|
6807
|
-
};
|
|
6808
|
-
try {
|
|
6809
|
-
(0, import_node_fs4.mkdirSync)(this.storageDir, { recursive: true });
|
|
6810
|
-
(0, import_node_fs4.writeFileSync)((0, import_node_path5.join)(this.storageDir, safeFilename(manifestId)), JSON.stringify(entry, null, 2), "utf8");
|
|
6811
|
-
} catch (err) {
|
|
6812
|
-
return c.json({
|
|
6813
|
-
success: false,
|
|
6814
|
-
error: { code: "storage_failed", message: `Failed to persist manifest: ${err?.message ?? err}` }
|
|
6815
|
-
}, 500);
|
|
6816
|
-
}
|
|
6817
|
-
try {
|
|
6818
|
-
const ql = ctx.getService("objectql");
|
|
6819
|
-
if (ql && typeof ql.syncSchemas === "function") {
|
|
6820
|
-
await ql.syncSchemas();
|
|
6821
|
-
ctx.logger?.info?.(`[MarketplaceInstallLocal] syncSchemas() ran after registering ${manifestId}`);
|
|
6822
|
-
}
|
|
6823
|
-
} catch (err) {
|
|
6824
|
-
ctx.logger?.warn?.(`[MarketplaceInstallLocal] syncSchemas failed for ${manifestId}: ${err?.message ?? err}`);
|
|
6825
|
-
}
|
|
6826
|
-
const seededSummary = await this.applySideEffects(ctx, manifest, { seedNow: true, c });
|
|
6827
|
-
if (seededSummary.seeded.mode === "inline" && (seededSummary.seeded.inserted ?? 0) + (seededSummary.seeded.updated ?? 0) > 0) {
|
|
6828
|
-
entry.withSampleData = true;
|
|
6829
|
-
try {
|
|
6830
|
-
(0, import_node_fs4.writeFileSync)((0, import_node_path5.join)(this.storageDir, safeFilename(manifestId)), JSON.stringify(entry, null, 2), "utf8");
|
|
6831
|
-
} catch {
|
|
6832
|
-
}
|
|
6833
|
-
}
|
|
6834
|
-
return c.json({
|
|
6835
|
-
success: true,
|
|
6836
|
-
data: {
|
|
6837
|
-
manifestId,
|
|
6838
|
-
version,
|
|
6839
|
-
versionId: resolvedVersionId,
|
|
6840
|
-
installedAt: entry.installedAt,
|
|
6841
|
-
hotLoaded: true,
|
|
6842
|
-
upgradedFrom: conflict === "marketplace" ? "previous-marketplace-version" : null,
|
|
6843
|
-
translationsLoaded: seededSummary.translationsLoaded,
|
|
6844
|
-
seeded: seededSummary.seeded,
|
|
6845
|
-
note: "App is now available in this runtime. Refresh the console to see it in the app switcher."
|
|
6846
|
-
}
|
|
6847
|
-
}, 200);
|
|
6848
|
-
};
|
|
6849
|
-
this.handleList = async (c) => {
|
|
6850
|
-
const entries = this.readAll();
|
|
6851
|
-
return c.json({
|
|
6852
|
-
success: true,
|
|
6853
|
-
data: {
|
|
6854
|
-
items: entries.map((e) => ({
|
|
6855
|
-
packageId: e.packageId,
|
|
6856
|
-
versionId: e.versionId,
|
|
6857
|
-
manifestId: e.manifestId,
|
|
6858
|
-
version: e.version,
|
|
6859
|
-
installedAt: e.installedAt,
|
|
6860
|
-
installedBy: e.installedBy,
|
|
6861
|
-
withSampleData: e.withSampleData ?? false
|
|
6862
|
-
})),
|
|
6863
|
-
total: entries.length,
|
|
6864
|
-
storageDir: this.storageDir
|
|
6865
|
-
}
|
|
6866
|
-
}, 200);
|
|
6867
|
-
};
|
|
6868
|
-
this.handleUninstall = async (c, ctx) => {
|
|
6869
|
-
const userId = await this.requireAuthenticatedUser(c, ctx);
|
|
6870
|
-
if (!userId) {
|
|
6871
|
-
return c.json({ success: false, error: { code: "unauthorized", message: "Authentication required." } }, 401);
|
|
6872
|
-
}
|
|
6873
|
-
const manifestId = String(c.req.param?.("manifestId") ?? c.req.params?.manifestId ?? "").trim();
|
|
6874
|
-
if (!manifestId) {
|
|
6875
|
-
return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
|
|
6876
|
-
}
|
|
6877
|
-
const file = (0, import_node_path5.join)(this.storageDir, safeFilename(manifestId));
|
|
6878
|
-
if (!(0, import_node_fs4.existsSync)(file)) {
|
|
6879
|
-
return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
|
|
6880
|
-
}
|
|
6881
|
-
try {
|
|
6882
|
-
(0, import_node_fs4.unlinkSync)(file);
|
|
6883
|
-
} catch (err) {
|
|
6884
|
-
return c.json({ success: false, error: { code: "storage_failed", message: err?.message ?? String(err) } }, 500);
|
|
6885
|
-
}
|
|
6886
|
-
ctx.logger?.info?.(`[MarketplaceInstallLocal] uninstalled ${manifestId} (cached manifest removed; restart runtime to unload from running kernel)`);
|
|
6887
|
-
return c.json({
|
|
6888
|
-
success: true,
|
|
6889
|
-
data: {
|
|
6890
|
-
manifestId,
|
|
6891
|
-
note: "Cached manifest removed. The app remains loaded in the running kernel until the next restart (the kernel API does not support unregistering apps in-place)."
|
|
6892
|
-
}
|
|
6893
|
-
}, 200);
|
|
6894
|
-
};
|
|
6895
|
-
/**
|
|
6896
|
-
* Detect whether `manifestId` is already known to the kernel and classify
|
|
6897
|
-
* the source so we can refuse vs upgrade gracefully.
|
|
6898
|
-
*
|
|
6899
|
-
* 'none' — fresh install
|
|
6900
|
-
* 'marketplace' — previously installed by this plugin (allow upgrade)
|
|
6901
|
-
* 'user-code' — defined by AppPlugin from objectstack.config.ts
|
|
6902
|
-
* (refuse to avoid silently overwriting authored code)
|
|
6903
|
-
*/
|
|
6904
|
-
this.findConflict = (ctx, manifestId) => {
|
|
6905
|
-
if ((0, import_node_fs4.existsSync)((0, import_node_path5.join)(this.storageDir, safeFilename(manifestId)))) {
|
|
6906
|
-
return "marketplace";
|
|
6907
|
-
}
|
|
6908
|
-
try {
|
|
6909
|
-
const ql = ctx.getService("objectql");
|
|
6910
|
-
const packages = ql?.registry?.getAllPackages?.() ?? [];
|
|
6911
|
-
const hit = packages.find(
|
|
6912
|
-
(p) => (p?.manifest?.id ?? p?.id ?? p?.manifest?.name) === manifestId
|
|
6913
|
-
);
|
|
6914
|
-
if (hit) return "user-code";
|
|
6915
|
-
} catch {
|
|
6916
|
-
}
|
|
6917
|
-
return "none";
|
|
6918
|
-
};
|
|
6919
|
-
/**
|
|
6920
|
-
* Pull a userId out of the request's better-auth session, if any.
|
|
6921
|
-
* Returns null when there is no signed-in user. v1 does not check
|
|
6922
|
-
* admin role — UI gating + the auth requirement is sufficient for
|
|
6923
|
-
* dev / single-tenant runtimes. Stricter checks can be layered on
|
|
6924
|
-
* via a middleware in cloud-hosted multi-tenant deployments.
|
|
6925
|
-
*/
|
|
6926
|
-
/**
|
|
6927
|
-
* POST /api/v1/marketplace/install-local/:manifestId/reseed-sample-data
|
|
6928
|
-
*
|
|
6929
|
-
* Re-runs SeedLoaderService against the cached manifest's `data` arrays.
|
|
6930
|
-
* Idempotent (upsert by id). Useful when:
|
|
6931
|
-
* • The user installed an app and skipped sample data
|
|
6932
|
-
* • A purge was undone
|
|
6933
|
-
* • The user wants a clean baseline back after editing demo rows
|
|
6934
|
-
*
|
|
6935
|
-
* Multi-tenant: requires an active organization on the session (same
|
|
6936
|
-
* rule as install seed path).
|
|
6937
|
-
*/
|
|
6938
|
-
this.handleReseed = async (c, ctx) => {
|
|
6939
|
-
const userId = await this.requireAuthenticatedUser(c, ctx);
|
|
6940
|
-
if (!userId) {
|
|
6941
|
-
return c.json({ success: false, error: { code: "unauthorized", message: "Authentication required." } }, 401);
|
|
6942
|
-
}
|
|
6943
|
-
const manifestId = String(c.req.param?.("manifestId") ?? c.req.params?.manifestId ?? "").trim();
|
|
6944
|
-
if (!manifestId) {
|
|
6945
|
-
return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
|
|
6946
|
-
}
|
|
6947
|
-
const file = (0, import_node_path5.join)(this.storageDir, safeFilename(manifestId));
|
|
6948
|
-
if (!(0, import_node_fs4.existsSync)(file)) {
|
|
6949
|
-
return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
|
|
6950
|
-
}
|
|
6951
|
-
let entry;
|
|
6952
|
-
try {
|
|
6953
|
-
entry = JSON.parse((0, import_node_fs4.readFileSync)(file, "utf8"));
|
|
6954
|
-
} catch (err) {
|
|
6955
|
-
return c.json({ success: false, error: { code: "storage_failed", message: `Failed to read manifest cache: ${err?.message ?? err}` } }, 500);
|
|
6956
|
-
}
|
|
6957
|
-
const summary = await this.applySideEffects(ctx, entry.manifest, { seedNow: true, c });
|
|
6958
|
-
if (summary.seeded.mode === "skipped") {
|
|
6959
|
-
return c.json({
|
|
6960
|
-
success: false,
|
|
6961
|
-
error: {
|
|
6962
|
-
code: "reseed_skipped",
|
|
6963
|
-
message: `Reseed did not run: ${summary.seeded.reason ?? "unknown reason"}`
|
|
6964
|
-
}
|
|
6965
|
-
}, 400);
|
|
6966
|
-
}
|
|
6967
|
-
try {
|
|
6968
|
-
entry.withSampleData = true;
|
|
6969
|
-
(0, import_node_fs4.writeFileSync)(file, JSON.stringify(entry, null, 2), "utf8");
|
|
6970
|
-
} catch {
|
|
6971
|
-
}
|
|
6972
|
-
return c.json({
|
|
6973
|
-
success: true,
|
|
6974
|
-
data: {
|
|
6975
|
-
manifestId,
|
|
6976
|
-
inserted: summary.seeded.inserted ?? 0,
|
|
6977
|
-
updated: summary.seeded.updated ?? 0,
|
|
6978
|
-
errors: summary.seeded.errors ?? 0,
|
|
6979
|
-
withSampleData: true
|
|
6980
|
-
}
|
|
6981
|
-
}, 200);
|
|
6982
|
-
};
|
|
6983
|
-
/**
|
|
6984
|
-
* POST /api/v1/marketplace/install-local/:manifestId/purge-sample-data
|
|
6985
|
-
*
|
|
6986
|
-
* Deletes every record whose id is declared in the cached manifest's
|
|
6987
|
-
* seed datasets. Uses the `driver` service directly to bypass ACL /
|
|
6988
|
-
* lifecycle hooks (same pattern as cloud purge). User-created records
|
|
6989
|
-
* are never touched — only ids declared in the package's bundled
|
|
6990
|
-
* datasets are removed. Already-deleted rows count as `skipped`.
|
|
6991
|
-
*/
|
|
6992
|
-
this.handlePurge = async (c, ctx) => {
|
|
6993
|
-
const userId = await this.requireAuthenticatedUser(c, ctx);
|
|
6994
|
-
if (!userId) {
|
|
6995
|
-
return c.json({ success: false, error: { code: "unauthorized", message: "Authentication required." } }, 401);
|
|
6996
|
-
}
|
|
6997
|
-
const manifestId = String(c.req.param?.("manifestId") ?? c.req.params?.manifestId ?? "").trim();
|
|
6998
|
-
if (!manifestId) {
|
|
6999
|
-
return c.json({ success: false, error: { code: "bad_request", message: "manifestId path param required." } }, 400);
|
|
7000
|
-
}
|
|
7001
|
-
const file = (0, import_node_path5.join)(this.storageDir, safeFilename(manifestId));
|
|
7002
|
-
if (!(0, import_node_fs4.existsSync)(file)) {
|
|
7003
|
-
return c.json({ success: false, error: { code: "not_found", message: `No marketplace install for ${manifestId}.` } }, 404);
|
|
7004
|
-
}
|
|
7005
|
-
let entry;
|
|
7006
|
-
try {
|
|
7007
|
-
entry = JSON.parse((0, import_node_fs4.readFileSync)(file, "utf8"));
|
|
7008
|
-
} catch (err) {
|
|
7009
|
-
return c.json({ success: false, error: { code: "storage_failed", message: `Failed to read manifest cache: ${err?.message ?? err}` } }, 500);
|
|
7010
|
-
}
|
|
7011
|
-
const datasets = Array.isArray(entry.manifest?.data) ? entry.manifest.data.filter((d) => d && d.object && Array.isArray(d.records)) : [];
|
|
7012
|
-
if (datasets.length === 0) {
|
|
7013
|
-
return c.json({
|
|
7014
|
-
success: false,
|
|
7015
|
-
error: { code: "nothing_to_purge", message: "This package declares no seed datasets." }
|
|
7016
|
-
}, 400);
|
|
7017
|
-
}
|
|
7018
|
-
let driver;
|
|
7019
|
-
try {
|
|
7020
|
-
driver = ctx.getService("driver");
|
|
7021
|
-
} catch {
|
|
7022
|
-
}
|
|
7023
|
-
if (!driver || typeof driver.delete !== "function") {
|
|
7024
|
-
return c.json({
|
|
7025
|
-
success: false,
|
|
7026
|
-
error: { code: "driver_missing", message: "driver service unavailable \u2014 cannot purge." }
|
|
7027
|
-
}, 500);
|
|
7028
|
-
}
|
|
7029
|
-
let deleted = 0;
|
|
7030
|
-
let skipped = 0;
|
|
7031
|
-
let errors = 0;
|
|
7032
|
-
for (const ds of datasets) {
|
|
7033
|
-
const object = String(ds.object);
|
|
7034
|
-
for (const rec of ds.records) {
|
|
7035
|
-
const id = rec?.id;
|
|
7036
|
-
if (id === void 0 || id === null || id === "") {
|
|
7037
|
-
skipped++;
|
|
7038
|
-
continue;
|
|
7039
|
-
}
|
|
7040
|
-
try {
|
|
7041
|
-
const r = await driver.delete(object, id);
|
|
7042
|
-
if (r === false || r === 0 || r?.deleted === 0) skipped++;
|
|
7043
|
-
else deleted++;
|
|
7044
|
-
} catch (err) {
|
|
7045
|
-
const msg = String(err?.message ?? err);
|
|
7046
|
-
if (/not.?found|no row/i.test(msg)) skipped++;
|
|
7047
|
-
else {
|
|
7048
|
-
errors++;
|
|
7049
|
-
ctx.logger?.warn?.(`[MarketplaceInstallLocal] purge ${object}#${id}: ${msg}`);
|
|
7050
|
-
}
|
|
7051
|
-
}
|
|
7052
|
-
}
|
|
7053
|
-
}
|
|
7054
|
-
try {
|
|
7055
|
-
entry.withSampleData = false;
|
|
7056
|
-
(0, import_node_fs4.writeFileSync)(file, JSON.stringify(entry, null, 2), "utf8");
|
|
7057
|
-
} catch {
|
|
7058
|
-
}
|
|
7059
|
-
ctx.logger?.info?.(`[MarketplaceInstallLocal] purged ${manifestId}: deleted=${deleted} skipped=${skipped} errors=${errors}`);
|
|
7060
|
-
return c.json({
|
|
7061
|
-
success: true,
|
|
7062
|
-
data: { manifestId, deleted, skipped, errors, withSampleData: false }
|
|
7063
|
-
}, 200);
|
|
7064
|
-
};
|
|
7065
|
-
/**
|
|
7066
|
-
* Replicate the start-time side-effects that AppPlugin runs for
|
|
7067
|
-
* statically-declared apps but the `manifest` service does NOT:
|
|
7068
|
-
*
|
|
7069
|
-
* 1. Load `manifest.translations` (array of `Record<locale, data>`)
|
|
7070
|
-
* into the i18n service — auto-creating an in-memory fallback if
|
|
7071
|
-
* none is registered, matching AppPlugin's behaviour.
|
|
7072
|
-
*
|
|
7073
|
-
* 2. Merge `manifest.data` (an array of seed datasets) into the
|
|
7074
|
-
* kernel's `seed-datasets` service so SecurityPlugin's per-org
|
|
7075
|
-
* replay middleware picks them up on every future
|
|
7076
|
-
* sys_organization insert.
|
|
7077
|
-
*
|
|
7078
|
-
* 3. When `seedNow=true`, also run the seed immediately so the user
|
|
7079
|
-
* sees demo data without having to create a new org:
|
|
7080
|
-
* • single-tenant: run SeedLoaderService inline (mirrors
|
|
7081
|
-
* AppPlugin single-tenant branch)
|
|
7082
|
-
* • multi-tenant: invoke `seed-replayer` for the caller's
|
|
7083
|
-
* active org (resolved from the request session)
|
|
7084
|
-
*
|
|
7085
|
-
* Errors are logged but never thrown — install succeeds even if
|
|
7086
|
-
* post-register side-effects partially fail (the manifest itself is
|
|
7087
|
-
* already registered + cached). Returns a small summary for the
|
|
7088
|
-
* response envelope.
|
|
7089
|
-
*/
|
|
7090
|
-
this.applySideEffects = async (ctx, manifest, opts) => {
|
|
7091
|
-
const appId = String(manifest?.id ?? "unknown");
|
|
7092
|
-
let translationsLoaded = 0;
|
|
7093
|
-
let seedSummary = { mode: "skipped", reason: "no-datasets" };
|
|
7094
|
-
try {
|
|
7095
|
-
const bundles = [];
|
|
7096
|
-
if (Array.isArray(manifest?.translations)) bundles.push(...manifest.translations);
|
|
7097
|
-
if (Array.isArray(manifest?.i18n)) bundles.push(...manifest.i18n);
|
|
7098
|
-
if (bundles.length > 0) {
|
|
7099
|
-
let i18nService;
|
|
7100
|
-
try {
|
|
7101
|
-
i18nService = ctx.getService("i18n");
|
|
7102
|
-
} catch {
|
|
7103
|
-
}
|
|
7104
|
-
if (!i18nService) {
|
|
7105
|
-
try {
|
|
7106
|
-
const mod = await import("@objectstack/core");
|
|
7107
|
-
const createMemoryI18n = mod.createMemoryI18n;
|
|
7108
|
-
if (typeof createMemoryI18n === "function") {
|
|
7109
|
-
i18nService = createMemoryI18n();
|
|
7110
|
-
ctx.registerService?.("i18n", i18nService);
|
|
7111
|
-
ctx.logger?.info?.(`[MarketplaceInstallLocal] auto-registered in-memory i18n fallback for "${appId}"`);
|
|
7112
|
-
}
|
|
7113
|
-
} catch {
|
|
7114
|
-
}
|
|
7115
|
-
}
|
|
7116
|
-
if (i18nService?.loadTranslations) {
|
|
7117
|
-
for (const bundle of bundles) {
|
|
7118
|
-
for (const [locale, data] of Object.entries(bundle)) {
|
|
7119
|
-
if (data && typeof data === "object") {
|
|
7120
|
-
try {
|
|
7121
|
-
i18nService.loadTranslations(locale, data);
|
|
7122
|
-
translationsLoaded++;
|
|
7123
|
-
} catch (err) {
|
|
7124
|
-
ctx.logger?.warn?.(`[MarketplaceInstallLocal] failed to load ${appId} translations for ${locale}: ${err?.message ?? err}`);
|
|
7125
|
-
}
|
|
7126
|
-
}
|
|
7127
|
-
}
|
|
7128
|
-
}
|
|
7129
|
-
ctx.logger?.info?.(`[MarketplaceInstallLocal] loaded ${translationsLoaded} locale bundle(s) for ${appId}`);
|
|
7130
|
-
}
|
|
7131
|
-
}
|
|
7132
|
-
} catch (err) {
|
|
7133
|
-
ctx.logger?.warn?.(`[MarketplaceInstallLocal] i18n side-effect failed for ${appId}: ${err?.message ?? err}`);
|
|
7134
|
-
}
|
|
7135
|
-
const datasets = Array.isArray(manifest?.data) ? manifest.data.filter((d) => d && d.object && Array.isArray(d.records)) : [];
|
|
7136
|
-
if (datasets.length > 0) {
|
|
7137
|
-
try {
|
|
7138
|
-
const kernel = ctx.kernel;
|
|
7139
|
-
let existing = [];
|
|
7140
|
-
try {
|
|
7141
|
-
const v = kernel?.getService?.("seed-datasets");
|
|
7142
|
-
if (Array.isArray(v)) existing = v;
|
|
7143
|
-
} catch {
|
|
7144
|
-
}
|
|
7145
|
-
const merged = [...existing, ...datasets];
|
|
7146
|
-
if (kernel?.registerService) kernel.registerService("seed-datasets", merged);
|
|
7147
|
-
else ctx.registerService?.("seed-datasets", merged);
|
|
7148
|
-
ctx.logger?.info?.(`[MarketplaceInstallLocal] merged ${datasets.length} seed dataset(s) into kernel (total: ${merged.length})`);
|
|
7149
|
-
} catch (err) {
|
|
7150
|
-
ctx.logger?.warn?.(`[MarketplaceInstallLocal] failed to merge seed-datasets: ${err?.message ?? err}`);
|
|
7151
|
-
}
|
|
7152
|
-
}
|
|
7153
|
-
if (opts.seedNow && datasets.length > 0) {
|
|
7154
|
-
const multiTenant = String((0, import_types3.readEnvWithDeprecation)("OS_MULTI_ORG_ENABLED", "OS_MULTI_TENANT") ?? "false").toLowerCase() !== "false";
|
|
7155
|
-
try {
|
|
7156
|
-
const ql = ctx.getService("objectql");
|
|
7157
|
-
let metadata;
|
|
7158
|
-
try {
|
|
7159
|
-
metadata = ctx.getService("metadata");
|
|
7160
|
-
} catch {
|
|
7161
|
-
}
|
|
7162
|
-
if (!ql || !metadata) {
|
|
7163
|
-
seedSummary = { mode: "skipped", reason: "objectql-or-metadata-missing" };
|
|
7164
|
-
} else {
|
|
7165
|
-
let organizationId;
|
|
7166
|
-
if (multiTenant) {
|
|
7167
|
-
const resolved = await this.resolveActiveOrgId(opts.c, ctx);
|
|
7168
|
-
if (resolved) organizationId = resolved;
|
|
7169
|
-
else {
|
|
7170
|
-
seedSummary = { mode: "skipped", reason: "multi-tenant-no-active-org" };
|
|
7171
|
-
ctx.logger?.warn?.("[MarketplaceInstallLocal] multi-tenant: no active org on request \u2014 data not seeded");
|
|
7172
|
-
}
|
|
7173
|
-
}
|
|
7174
|
-
if (!multiTenant || organizationId) {
|
|
7175
|
-
const [{ SeedLoaderService: SeedLoaderService2 }, { SeedLoaderRequestSchema }] = await Promise.all([
|
|
7176
|
-
Promise.resolve().then(() => (init_seed_loader(), seed_loader_exports)),
|
|
7177
|
-
import("@objectstack/spec/data")
|
|
7178
|
-
]);
|
|
7179
|
-
const seedLoader = new SeedLoaderService2(ql, metadata, ctx.logger);
|
|
7180
|
-
const request = SeedLoaderRequestSchema.parse({
|
|
7181
|
-
datasets,
|
|
7182
|
-
config: {
|
|
7183
|
-
defaultMode: "upsert",
|
|
7184
|
-
multiPass: true,
|
|
7185
|
-
...organizationId ? { organizationId } : {}
|
|
7186
|
-
}
|
|
7187
|
-
});
|
|
7188
|
-
const result = await seedLoader.load(request);
|
|
7189
|
-
seedSummary = {
|
|
7190
|
-
mode: "inline",
|
|
7191
|
-
inserted: result.summary.totalInserted,
|
|
7192
|
-
updated: result.summary.totalUpdated,
|
|
7193
|
-
errors: result.errors.length
|
|
7194
|
-
};
|
|
7195
|
-
ctx.logger?.info?.(`[MarketplaceInstallLocal] inline seed for ${appId}${organizationId ? ` (org=${organizationId})` : ""}: inserted=${seedSummary.inserted} updated=${seedSummary.updated} errors=${seedSummary.errors}`);
|
|
7196
|
-
}
|
|
7197
|
-
}
|
|
7198
|
-
} catch (err) {
|
|
7199
|
-
seedSummary = { mode: "skipped", reason: `seed-error: ${err?.message ?? err}` };
|
|
7200
|
-
ctx.logger?.warn?.(`[MarketplaceInstallLocal] seed run failed for ${appId}: ${err?.message ?? err}`);
|
|
7201
|
-
}
|
|
7202
|
-
}
|
|
7203
|
-
return { translationsLoaded, seeded: seedSummary };
|
|
7204
|
-
};
|
|
7205
|
-
/**
|
|
7206
|
-
* Best-effort active-org resolution. Reads the better-auth session
|
|
7207
|
-
* (same path as requireAuthenticatedUser) and returns
|
|
7208
|
-
* `session.activeOrganizationId`, falling back to the user's first
|
|
7209
|
-
* org membership.
|
|
7210
|
-
*/
|
|
7211
|
-
this.resolveActiveOrgId = async (c, ctx) => {
|
|
7212
|
-
if (!c?.req?.raw?.headers) return null;
|
|
7213
|
-
try {
|
|
7214
|
-
const authService = ctx.getService("auth");
|
|
7215
|
-
let api = authService?.api;
|
|
7216
|
-
if (!api && typeof authService?.getApi === "function") api = await authService.getApi();
|
|
7217
|
-
if (!api?.getSession) return null;
|
|
7218
|
-
const session = await api.getSession({ headers: c.req.raw.headers });
|
|
7219
|
-
const direct = session?.session?.activeOrganizationId ?? session?.activeOrganizationId ?? null;
|
|
7220
|
-
if (direct) return String(direct);
|
|
7221
|
-
const userId = session?.user?.id;
|
|
7222
|
-
if (!userId) return null;
|
|
7223
|
-
try {
|
|
7224
|
-
const ql = ctx.getService("objectql");
|
|
7225
|
-
if (ql?.find) {
|
|
7226
|
-
const rows = await ql.find("sys_organization_member", { where: { user_id: userId }, limit: 1, context: { isSystem: true } });
|
|
7227
|
-
const row = Array.isArray(rows) ? rows[0] : rows?.items?.[0] ?? null;
|
|
7228
|
-
return row?.organization_id ? String(row.organization_id) : null;
|
|
7229
|
-
}
|
|
7230
|
-
} catch {
|
|
7231
|
-
}
|
|
7232
|
-
} catch {
|
|
7233
|
-
}
|
|
7234
|
-
return null;
|
|
7235
|
-
};
|
|
7236
|
-
this.requireAuthenticatedUser = async (c, ctx) => {
|
|
7237
|
-
try {
|
|
7238
|
-
const authService = ctx.getService("auth");
|
|
7239
|
-
let api = authService?.api;
|
|
7240
|
-
if (!api && typeof authService?.getApi === "function") {
|
|
7241
|
-
api = await authService.getApi();
|
|
7242
|
-
}
|
|
7243
|
-
if (api?.getSession && c?.req?.raw?.headers) {
|
|
7244
|
-
const session = await api.getSession({ headers: c.req.raw.headers });
|
|
7245
|
-
const userId = session?.user?.id ?? null;
|
|
7246
|
-
if (userId) return String(userId);
|
|
7247
|
-
}
|
|
7248
|
-
} catch {
|
|
7249
|
-
}
|
|
7250
|
-
const xUserId = c?.req?.header?.("x-user-id");
|
|
7251
|
-
if (xUserId) return String(xUserId);
|
|
7252
|
-
return null;
|
|
7253
|
-
};
|
|
7254
|
-
this.readAll = () => {
|
|
7255
|
-
if (!(0, import_node_fs4.existsSync)(this.storageDir)) return [];
|
|
7256
|
-
const out = [];
|
|
7257
|
-
for (const name of (0, import_node_fs4.readdirSync)(this.storageDir)) {
|
|
7258
|
-
if (!name.endsWith(".json")) continue;
|
|
7259
|
-
try {
|
|
7260
|
-
const raw = (0, import_node_fs4.readFileSync)((0, import_node_path5.join)(this.storageDir, name), "utf8");
|
|
7261
|
-
out.push(JSON.parse(raw));
|
|
7262
|
-
} catch {
|
|
7263
|
-
}
|
|
7264
|
-
}
|
|
7265
|
-
return out;
|
|
7266
|
-
};
|
|
7267
|
-
this.cloudUrl = resolveCloudUrl(config.controlPlaneUrl);
|
|
7268
|
-
this.storageDir = config.storageDir ? (0, import_node_path5.resolve)(config.storageDir) : (0, import_node_path5.resolve)(process.cwd(), DEFAULT_DIR);
|
|
7269
|
-
}
|
|
7270
|
-
};
|
|
7271
|
-
|
|
7272
|
-
// src/cloud/runtime-config-plugin.ts
|
|
7273
|
-
var RuntimeConfigPlugin = class {
|
|
7274
|
-
constructor(config = {}) {
|
|
7275
|
-
this.name = "com.objectstack.runtime.runtime-config";
|
|
7276
|
-
this.version = "1.0.0";
|
|
7277
|
-
this.init = async (_ctx) => {
|
|
7278
|
-
};
|
|
7279
|
-
this.start = async (ctx) => {
|
|
7280
|
-
ctx.hook("kernel:ready", async () => {
|
|
7281
|
-
let httpServer;
|
|
7282
|
-
try {
|
|
7283
|
-
httpServer = ctx.getService("http-server");
|
|
7284
|
-
} catch {
|
|
7285
|
-
ctx.logger?.warn?.("[RuntimeConfigPlugin] http-server not available \u2014 runtime/config not mounted");
|
|
7286
|
-
return;
|
|
7287
|
-
}
|
|
7288
|
-
if (!httpServer || typeof httpServer.getRawApp !== "function") {
|
|
7289
|
-
ctx.logger?.warn?.("[RuntimeConfigPlugin] http-server missing getRawApp() \u2014 runtime/config not mounted");
|
|
7290
|
-
return;
|
|
7291
|
-
}
|
|
7292
|
-
const rawApp = httpServer.getRawApp();
|
|
7293
|
-
const features = {
|
|
7294
|
-
installLocal: this.installLocal,
|
|
7295
|
-
marketplace: true,
|
|
7296
|
-
aiStudio: this.aiStudio
|
|
7297
|
-
};
|
|
7298
|
-
let envRegistry = null;
|
|
7299
|
-
try {
|
|
7300
|
-
envRegistry = ctx.getService("env-registry");
|
|
7301
|
-
} catch {
|
|
7302
|
-
}
|
|
7303
|
-
const handler = async (c) => {
|
|
7304
|
-
const rawHost = c.req.header("host") ?? "";
|
|
7305
|
-
const host = rawHost.split(":")[0].toLowerCase().trim();
|
|
7306
|
-
let defaultEnvironmentId;
|
|
7307
|
-
let defaultOrgId;
|
|
7308
|
-
let resolvedSingleEnv = this.singleEnvironment;
|
|
7309
|
-
const resolveFn = typeof envRegistry?.resolveByHostname === "function" ? envRegistry.resolveByHostname.bind(envRegistry) : typeof envRegistry?.resolveHostname === "function" ? envRegistry.resolveHostname.bind(envRegistry) : null;
|
|
7310
|
-
if (resolveFn && host) {
|
|
7311
|
-
try {
|
|
7312
|
-
const resolved = await resolveFn(host);
|
|
7313
|
-
if (resolved?.environmentId) {
|
|
7314
|
-
defaultEnvironmentId = String(resolved.environmentId);
|
|
7315
|
-
const orgId = resolved.organizationId ?? resolved.organization_id;
|
|
7316
|
-
if (orgId) defaultOrgId = String(orgId);
|
|
7317
|
-
resolvedSingleEnv = true;
|
|
7318
|
-
}
|
|
7319
|
-
} catch {
|
|
7320
|
-
}
|
|
7321
|
-
}
|
|
7322
|
-
return c.json({
|
|
7323
|
-
cloudUrl: this.cloudUrl,
|
|
7324
|
-
singleEnvironment: resolvedSingleEnv,
|
|
7325
|
-
defaultOrgId,
|
|
7326
|
-
defaultEnvironmentId,
|
|
7327
|
-
features,
|
|
7328
|
-
branding: {
|
|
7329
|
-
productName: this.productName,
|
|
7330
|
-
productShortName: this.productShortName
|
|
7331
|
-
}
|
|
7332
|
-
});
|
|
7333
|
-
};
|
|
7334
|
-
rawApp.get("/api/v1/runtime/config", handler);
|
|
7335
|
-
rawApp.get("/api/v1/studio/runtime-config", handler);
|
|
7336
|
-
ctx.logger?.info?.("[RuntimeConfigPlugin] mounted /api/v1/runtime/config", {
|
|
7337
|
-
cloudUrl: this.cloudUrl || "(empty)",
|
|
7338
|
-
installLocal: this.installLocal,
|
|
7339
|
-
perHostEnvResolution: !!envRegistry
|
|
7340
|
-
});
|
|
7341
|
-
});
|
|
7342
|
-
};
|
|
7343
|
-
this.destroy = async () => {
|
|
7344
|
-
};
|
|
7345
|
-
this.cloudUrl = config.controlPlaneUrl === "" ? "" : resolveCloudUrl(config.controlPlaneUrl) ?? "";
|
|
7346
|
-
this.installLocal = !!config.installLocal;
|
|
7347
|
-
this.aiStudio = config.aiStudio !== false;
|
|
7348
|
-
this.singleEnvironment = !!config.singleEnvironment;
|
|
7349
|
-
const envName = (typeof process !== "undefined" ? process.env?.OS_PRODUCT_NAME : void 0)?.trim();
|
|
7350
|
-
const envShort = (typeof process !== "undefined" ? process.env?.OS_PRODUCT_SHORT_NAME : void 0)?.trim();
|
|
7351
|
-
this.productName = (config.productName ?? envName ?? "ObjectOS").trim() || "ObjectOS";
|
|
7352
|
-
this.productShortName = (config.productShortName ?? envShort ?? this.productName).trim() || this.productName;
|
|
7353
|
-
}
|
|
7354
|
-
};
|
|
7355
|
-
|
|
7356
5576
|
// src/sandbox/script-runner.ts
|
|
7357
5577
|
var UnimplementedScriptRunner = class {
|
|
7358
5578
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -7378,11 +5598,10 @@ init_body_runner();
|
|
|
7378
5598
|
// src/index.ts
|
|
7379
5599
|
var import_rest = require("@objectstack/rest");
|
|
7380
5600
|
__reExport(index_exports, require("@objectstack/core"), module.exports);
|
|
7381
|
-
var
|
|
5601
|
+
var import_types3 = require("@objectstack/types");
|
|
7382
5602
|
// Annotate the CommonJS export names for ESM import in node:
|
|
7383
5603
|
0 && (module.exports = {
|
|
7384
5604
|
AppPlugin,
|
|
7385
|
-
DEFAULT_CLOUD_URL,
|
|
7386
5605
|
DEFAULT_RATE_LIMITS,
|
|
7387
5606
|
DriverPlugin,
|
|
7388
5607
|
ExternalValidationPlugin,
|
|
@@ -7390,8 +5609,6 @@ var import_types4 = require("@objectstack/types");
|
|
|
7390
5609
|
HttpServer,
|
|
7391
5610
|
InMemoryErrorReporter,
|
|
7392
5611
|
InMemoryMetricsRegistry,
|
|
7393
|
-
MarketplaceInstallLocalPlugin,
|
|
7394
|
-
MarketplaceProxyPlugin,
|
|
7395
5612
|
MiddlewareManager,
|
|
7396
5613
|
NoopErrorReporter,
|
|
7397
5614
|
NoopMetricsRegistry,
|
|
@@ -7406,7 +5623,6 @@ var import_types4 = require("@objectstack/types");
|
|
|
7406
5623
|
RouteGroupBuilder,
|
|
7407
5624
|
RouteManager,
|
|
7408
5625
|
Runtime,
|
|
7409
|
-
RuntimeConfigPlugin,
|
|
7410
5626
|
SYSTEM_ENVIRONMENT_ID,
|
|
7411
5627
|
SandboxError,
|
|
7412
5628
|
SeedLoaderService,
|
|
@@ -7433,7 +5649,6 @@ var import_types4 = require("@objectstack/types");
|
|
|
7433
5649
|
parseTraceparent,
|
|
7434
5650
|
readArtifactSource,
|
|
7435
5651
|
readEnvWithDeprecation,
|
|
7436
|
-
resolveCloudUrl,
|
|
7437
5652
|
resolveDefaultArtifactPath,
|
|
7438
5653
|
resolveErrorReporter,
|
|
7439
5654
|
resolveMetrics,
|