@objectstack/objectql 7.8.0 → 7.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -2386,6 +2386,44 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
2386
2386
  }
2387
2387
  } catch {
2388
2388
  }
2389
+ if (request.previewDrafts) {
2390
+ try {
2391
+ const orgId = request.organizationId;
2392
+ const queryDrafts = async (oid) => {
2393
+ const whereClause = { type: request.type, state: "draft", organization_id: oid };
2394
+ if (packageId) whereClause.package_id = packageId;
2395
+ let rs = await this.engine.find("sys_metadata", { where: whereClause });
2396
+ if (!rs || rs.length === 0) {
2397
+ const alt = PLURAL_TO_SINGULAR3[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
2398
+ if (alt) {
2399
+ const altWhere = { type: alt, state: "draft", organization_id: oid };
2400
+ if (packageId) altWhere.package_id = packageId;
2401
+ rs = await this.engine.find("sys_metadata", { where: altWhere });
2402
+ }
2403
+ }
2404
+ return rs ?? [];
2405
+ };
2406
+ const draftRecords = [...await queryDrafts(null), ...orgId ? await queryDrafts(orgId) : []];
2407
+ if (draftRecords.length > 0) {
2408
+ const byName = /* @__PURE__ */ new Map();
2409
+ for (const existing of items) {
2410
+ const entry = existing;
2411
+ if (entry && typeof entry === "object" && "name" in entry) byName.set(entry.name, entry);
2412
+ }
2413
+ for (const record of draftRecords) {
2414
+ const data = typeof record.metadata === "string" ? JSON.parse(record.metadata) : record.metadata;
2415
+ if (data && typeof data === "object" && "name" in data) {
2416
+ const recPkg = record.package_id ?? void 0;
2417
+ if (recPkg && data._packageId === void 0) data._packageId = recPkg;
2418
+ data._draft = true;
2419
+ byName.set(data.name, data);
2420
+ }
2421
+ }
2422
+ items = Array.from(byName.values());
2423
+ }
2424
+ } catch {
2425
+ }
2426
+ }
2389
2427
  try {
2390
2428
  const services = this.getServicesRegistry?.();
2391
2429
  const metadataService = services?.get("metadata");
@@ -2444,6 +2482,34 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
2444
2482
  let item;
2445
2483
  const orgId = request.organizationId;
2446
2484
  const readState = request.state === "draft" ? "draft" : "active";
2485
+ if (request.previewDrafts && readState !== "draft") {
2486
+ try {
2487
+ const findDraft = async (oid) => {
2488
+ const rec = await this.engine.findOne("sys_metadata", {
2489
+ where: { type: request.type, name: request.name, state: "draft", organization_id: oid }
2490
+ });
2491
+ if (rec) return rec;
2492
+ const alt = PLURAL_TO_SINGULAR3[request.type] ?? SINGULAR_TO_PLURAL2[request.type];
2493
+ if (alt) {
2494
+ return await this.engine.findOne("sys_metadata", {
2495
+ where: { type: alt, name: request.name, state: "draft", organization_id: oid }
2496
+ });
2497
+ }
2498
+ return void 0;
2499
+ };
2500
+ const draftRec = (orgId ? await findDraft(orgId) : void 0) ?? await findDraft(null);
2501
+ if (draftRec) {
2502
+ const draftItem = typeof draftRec.metadata === "string" ? JSON.parse(draftRec.metadata) : draftRec.metadata;
2503
+ if (draftItem && typeof draftItem === "object") {
2504
+ const recPkg = draftRec.package_id ?? void 0;
2505
+ if (recPkg && draftItem._packageId === void 0) draftItem._packageId = recPkg;
2506
+ draftItem._draft = true;
2507
+ }
2508
+ return { type: request.type, name: request.name, item: decorateMetadataItem(request.type, draftItem) };
2509
+ }
2510
+ } catch {
2511
+ }
2512
+ }
2447
2513
  try {
2448
2514
  const findOverlay = async (oid) => {
2449
2515
  const where = {
@@ -3818,6 +3884,37 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3818
3884
  console.warn(`[Protocol] table sync failed for object '${name}': ${err?.message ?? err}`);
3819
3885
  }
3820
3886
  }
3887
+ /**
3888
+ * Inverse of {@link ensureObjectStorage}: drop an object's physical table.
3889
+ * DESTRUCTIVE — deletes the table and all its rows. Only invoked when a
3890
+ * delete explicitly opts into storage teardown (see {@link deleteMetaItem}'s
3891
+ * `dropStorage`), so publishing an object solely to preview it can be undone
3892
+ * without leaving an orphan table. Best-effort: a failure is logged, not
3893
+ * thrown — the metadata delete already succeeded, and a stray table is
3894
+ * reclaimed by the next sync/drop rather than blocking the delete.
3895
+ */
3896
+ async dropObjectStorage(type, name) {
3897
+ if (type !== "object" && type !== "objects") return;
3898
+ try {
3899
+ await this.engine.dropObjectSchema(name);
3900
+ } catch (err) {
3901
+ console.warn(`[Protocol] table drop failed for object '${name}': ${err?.message ?? err}`);
3902
+ }
3903
+ }
3904
+ /**
3905
+ * Guard for storage teardown on delete. Drops a physical table only when
3906
+ * the caller opted in AND it is safe: object types only (others have no
3907
+ * table), active state only (drafts were never materialised), and never a
3908
+ * `sys_`-prefixed platform table.
3909
+ */
3910
+ shouldDropStorage(type, name, dropStorage, state) {
3911
+ if (!dropStorage) return false;
3912
+ const singular = PLURAL_TO_SINGULAR3[type] ?? type;
3913
+ if (singular !== "object") return false;
3914
+ if (state !== "active") return false;
3915
+ if (name.startsWith("sys_")) return false;
3916
+ return true;
3917
+ }
3821
3918
  async saveMetaItem(request) {
3822
3919
  if (!request.item) {
3823
3920
  throw new Error("Item data is required");
@@ -4198,6 +4295,100 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4198
4295
  failed
4199
4296
  };
4200
4297
  }
4298
+ /**
4299
+ * Discard every pending DRAFT bound to a package — the NON-destructive
4300
+ * inverse of {@link publishPackageDrafts}. Drops only `state='draft'` rows
4301
+ * (via the per-item delete primitive), reverting the package to its last
4302
+ * published baseline; active/published metadata and physical tables are
4303
+ * left untouched.
4304
+ *
4305
+ * Use case: "I edited this app for a while and it turned out worse than
4306
+ * before — abandon all my changes." Routes through the sys_metadata path
4307
+ * (no metadata-service dependency, unlike `POST /packages/:id/revert`).
4308
+ */
4309
+ async discardPackageDrafts(request) {
4310
+ await this.ensureOverlayIndex();
4311
+ const orgId = request.organizationId ?? null;
4312
+ const repo = this.getOverlayRepo(orgId);
4313
+ const drafts = await repo.listDrafts({ packageId: request.packageId });
4314
+ const discarded = [];
4315
+ const failed = [];
4316
+ for (const d of drafts) {
4317
+ try {
4318
+ await this.deleteMetaItem({
4319
+ type: d.type,
4320
+ name: d.name,
4321
+ state: "draft",
4322
+ ...request.organizationId ? { organizationId: request.organizationId } : {},
4323
+ ...request.actor ? { actor: request.actor } : {}
4324
+ });
4325
+ discarded.push({ type: d.type, name: d.name });
4326
+ } catch (e) {
4327
+ failed.push({
4328
+ type: d.type,
4329
+ name: d.name,
4330
+ error: e?.message ?? "discard failed",
4331
+ ...e?.code ? { code: e.code } : {}
4332
+ });
4333
+ }
4334
+ }
4335
+ return {
4336
+ success: failed.length === 0 && discarded.length > 0,
4337
+ discardedCount: discarded.length,
4338
+ failedCount: failed.length,
4339
+ discarded,
4340
+ failed
4341
+ };
4342
+ }
4343
+ /**
4344
+ * Delete an ENTIRE package: every `sys_metadata` row bound to it (active
4345
+ * AND draft) and — by default — the physical table of each object it
4346
+ * defined. DESTRUCTIVE: removes the app and its data. Use case: "I don't
4347
+ * want this package anymore."
4348
+ *
4349
+ * Set `keepData: true` to remove the metadata but preserve object tables.
4350
+ * The `sys_`-table guard in {@link deleteMetaItem} still applies, so
4351
+ * platform storage is never dropped. Drafts are removed before active rows
4352
+ * so each object's table is torn down once. Per-item failures are collected
4353
+ * without aborting the rest.
4354
+ */
4355
+ async deletePackage(request) {
4356
+ const where = { package_id: request.packageId };
4357
+ if (request.organizationId) where.organization_id = request.organizationId;
4358
+ const rows = await this.engine.find("sys_metadata", { where });
4359
+ const dropStorage = request.keepData !== true;
4360
+ const ordered = [...rows].sort((a, b) => (a.state === "draft" ? 0 : 1) - (b.state === "draft" ? 0 : 1));
4361
+ const deleted = [];
4362
+ const failed = [];
4363
+ for (const row of ordered) {
4364
+ const state = row.state === "draft" ? "draft" : "active";
4365
+ try {
4366
+ await this.deleteMetaItem({
4367
+ type: row.type,
4368
+ name: row.name,
4369
+ state,
4370
+ ...row.organization_id ? { organizationId: row.organization_id } : {},
4371
+ ...request.actor ? { actor: request.actor } : {},
4372
+ ...dropStorage ? { dropStorage: true } : {}
4373
+ });
4374
+ deleted.push({ type: row.type, name: row.name, state });
4375
+ } catch (e) {
4376
+ failed.push({
4377
+ type: row.type,
4378
+ name: row.name,
4379
+ error: e?.message ?? "delete failed",
4380
+ ...e?.code ? { code: e.code } : {}
4381
+ });
4382
+ }
4383
+ }
4384
+ return {
4385
+ success: failed.length === 0 && deleted.length > 0,
4386
+ deletedCount: deleted.length,
4387
+ failedCount: failed.length,
4388
+ deleted,
4389
+ failed
4390
+ };
4391
+ }
4201
4392
  /**
4202
4393
  * Restore the body recorded at history `toVersion` as the new
4203
4394
  * live row. Writes a history event with `op='revert'`. 404
@@ -4430,6 +4621,9 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4430
4621
  } catch {
4431
4622
  }
4432
4623
  }
4624
+ if (this.shouldDropStorage(request.type, request.name, request.dropStorage, targetState)) {
4625
+ await this.dropObjectStorage(singularTypeForRepo, request.name);
4626
+ }
4433
4627
  await this.recordMetadataAudit({
4434
4628
  type: request.type,
4435
4629
  name: request.name,
@@ -4478,6 +4672,12 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4478
4672
  };
4479
4673
  }
4480
4674
  await this.engine.delete("sys_metadata", { where: { id: existing.id } });
4675
+ {
4676
+ const targetState = request.state === "draft" ? "draft" : "active";
4677
+ if (this.shouldDropStorage(request.type, request.name, request.dropStorage, targetState)) {
4678
+ await this.dropObjectStorage(PLURAL_TO_SINGULAR3[request.type] ?? request.type, request.name);
4679
+ }
4680
+ }
4481
4681
  if (this.environmentId === void 0) {
4482
4682
  try {
4483
4683
  const services = this.getServicesRegistry?.();
@@ -7579,6 +7779,22 @@ var _ObjectQL = class _ObjectQL {
7579
7779
  const tableName = StorageNameMapping.resolveTableName(obj);
7580
7780
  await driver.syncSchema(tableName, obj);
7581
7781
  }
7782
+ /**
7783
+ * Drop the physical storage (table/collection) backing an object — the
7784
+ * inverse of {@link syncObjectSchema}. DESTRUCTIVE: deletes all rows in the
7785
+ * table. Used by the protocol delete path when the caller explicitly opts
7786
+ * into storage teardown (e.g. discarding an object that was published only
7787
+ * to preview it). No-op when the object's driver does not expose `dropTable`.
7788
+ * Resolves the physical table name from the registered definition, falling
7789
+ * back to the bare name if the def was already removed.
7790
+ */
7791
+ async dropObjectSchema(objectName) {
7792
+ const obj = this._registry.getObject(objectName);
7793
+ const driver = this.getDriverForObject(objectName);
7794
+ if (!driver || typeof driver.dropTable !== "function") return;
7795
+ const tableName = StorageNameMapping.resolveTableName(obj ?? { name: objectName });
7796
+ await driver.dropTable(tableName);
7797
+ }
7582
7798
  /**
7583
7799
  * Get a registered driver by datasource name.
7584
7800
  * Alias matching @objectql/core datasource() API.