@objectstack/objectql 7.7.0 → 7.8.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
@@ -1219,6 +1219,30 @@ var SysMetadataRepository = class {
1219
1219
  yield header;
1220
1220
  }
1221
1221
  }
1222
+ /**
1223
+ * List pending DRAFT rows (ADR-0033) for this org, optionally narrowed by
1224
+ * `type` and/or `packageId`. Unlike {@link list} (which is hard-scoped to
1225
+ * `state='active'`), this reads `state='draft'` so the console can surface
1226
+ * what an AI authored but a human hasn't published yet. Returns a light
1227
+ * header projection (no body) suitable for a "pending changes" list.
1228
+ */
1229
+ async listDrafts(filter) {
1230
+ this.assertOpen();
1231
+ const where = {
1232
+ organization_id: this.organizationId,
1233
+ state: "draft"
1234
+ };
1235
+ if (filter?.type) where.type = filter.type;
1236
+ if (filter?.packageId) where.package_id = filter.packageId;
1237
+ const rows = await this.engine.find("sys_metadata", { where });
1238
+ return rows.map((row) => ({
1239
+ type: row.type,
1240
+ name: row.name,
1241
+ packageId: row.package_id ?? null,
1242
+ updatedAt: row.updated_at ?? row.created_at ?? null,
1243
+ updatedBy: row.updated_by ?? row.created_by ?? null
1244
+ }));
1245
+ }
1222
1246
  /**
1223
1247
  * Yield every history event for `(org, type?, name?)` from the
1224
1248
  * durable log, ordered by per-(type,name) `version` ascending. When
@@ -1733,6 +1757,13 @@ function resolveOverlaySchema(type, _item) {
1733
1757
  const singular = PLURAL_TO_SINGULAR3[type] ?? type;
1734
1758
  return getMetadataTypeSchema2(singular) ?? null;
1735
1759
  }
1760
+ function normalizeViewMetadata(type, item, saveName) {
1761
+ const singular = PLURAL_TO_SINGULAR3[type] ?? type;
1762
+ if (singular !== "view") return item;
1763
+ if (!item || typeof item !== "object" || Array.isArray(item)) return item;
1764
+ const it = item;
1765
+ return it.name ? it : { ...it, name: saveName };
1766
+ }
1736
1767
  function mergeArtifactProtection(item, artifactItem) {
1737
1768
  if (item === void 0 || item === null) return item;
1738
1769
  if (artifactItem === void 0 || artifactItem === null) return item;
@@ -3769,6 +3800,24 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3769
3800
  );
3770
3801
  }
3771
3802
  }
3803
+ /**
3804
+ * Ensure a just-PUBLISHED object's physical table exists so it is usable
3805
+ * for data CRUD immediately — without a server restart. Registering the
3806
+ * object (above) only updates the in-memory registry; the table is created
3807
+ * by the driver's schema sync, which otherwise only runs at boot. Without
3808
+ * this, inserting into a freshly-published object fails with "no such
3809
+ * table" (surfaced as `object_not_found`) until the next restart.
3810
+ * Best-effort + non-fatal: drivers without DDL (or read-only datasources)
3811
+ * simply no-op, and a sync failure must not abort the publish.
3812
+ */
3813
+ async ensureObjectStorage(type, name) {
3814
+ if (type !== "object" && type !== "objects") return;
3815
+ try {
3816
+ await this.engine.syncObjectSchema(name);
3817
+ } catch (err) {
3818
+ console.warn(`[Protocol] table sync failed for object '${name}': ${err?.message ?? err}`);
3819
+ }
3820
+ }
3772
3821
  async saveMetaItem(request) {
3773
3822
  if (!request.item) {
3774
3823
  throw new Error("Item data is required");
@@ -3842,6 +3891,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3842
3891
  throw err;
3843
3892
  }
3844
3893
  }
3894
+ request.item = normalizeViewMetadata(request.type, request.item, request.name);
3845
3895
  {
3846
3896
  const schema = resolveOverlaySchema(request.type, request.item);
3847
3897
  if (schema) {
@@ -3896,6 +3946,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
3896
3946
  });
3897
3947
  if (mode === "publish") {
3898
3948
  this.applyObjectRegistryMutation(request);
3949
+ await this.ensureObjectStorage(request.type, request.name);
3899
3950
  }
3900
3951
  await this.recordMetadataAudit({
3901
3952
  type: request.type,
@@ -4066,6 +4117,7 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4066
4117
  name: request.name,
4067
4118
  item: result.item.body
4068
4119
  });
4120
+ await this.ensureObjectStorage(request.type, request.name);
4069
4121
  return {
4070
4122
  success: true,
4071
4123
  version: result.version,
@@ -4086,6 +4138,66 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4086
4138
  throw err;
4087
4139
  }
4088
4140
  }
4141
+ /**
4142
+ * List pending DRAFT metadata (ADR-0033) for the org, optionally narrowed
4143
+ * by `packageId` and/or `type`. The list reads of `getMetaItems` only see
4144
+ * the ACTIVE registry; this exposes what an AI authored but a human hasn't
4145
+ * published yet, so the console can show a "pending changes" surface and a
4146
+ * just-built app package isn't displayed as empty. No body is returned.
4147
+ */
4148
+ async listDrafts(request) {
4149
+ await this.ensureOverlayIndex();
4150
+ const orgId = request?.organizationId ?? null;
4151
+ const repo = this.getOverlayRepo(orgId);
4152
+ const drafts = await repo.listDrafts({
4153
+ ...request?.type ? { type: PLURAL_TO_SINGULAR3[request.type] ?? request.type } : {},
4154
+ ...request?.packageId ? { packageId: request.packageId } : {}
4155
+ });
4156
+ return { drafts };
4157
+ }
4158
+ /**
4159
+ * Publish every pending DRAFT bound to a package in one shot (ADR-0033) —
4160
+ * the "publish whole app" action. Promotes each draft→active by reusing the
4161
+ * per-item {@link publishMetaItem} primitive (which runs the overridable /
4162
+ * lock guards and refreshes the runtime registry), so this needs NO
4163
+ * `metadata` service (unlike `MetadataService.publishPackage`, which reads
4164
+ * the in-memory registry and 503s when that service is absent). Per-item
4165
+ * failures are collected and do NOT abort the rest.
4166
+ */
4167
+ async publishPackageDrafts(request) {
4168
+ await this.ensureOverlayIndex();
4169
+ const orgId = request.organizationId ?? null;
4170
+ const repo = this.getOverlayRepo(orgId);
4171
+ const drafts = await repo.listDrafts({ packageId: request.packageId });
4172
+ const published = [];
4173
+ const failed = [];
4174
+ for (const d of drafts) {
4175
+ try {
4176
+ const r = await this.publishMetaItem({
4177
+ type: d.type,
4178
+ name: d.name,
4179
+ ...request.organizationId ? { organizationId: request.organizationId } : {},
4180
+ ...request.actor ? { actor: request.actor } : {},
4181
+ message: `publish app package '${request.packageId}'`
4182
+ });
4183
+ published.push({ type: d.type, name: d.name, version: r.version });
4184
+ } catch (e) {
4185
+ failed.push({
4186
+ type: d.type,
4187
+ name: d.name,
4188
+ error: e?.message ?? "publish failed",
4189
+ ...e?.code ? { code: e.code } : {}
4190
+ });
4191
+ }
4192
+ }
4193
+ return {
4194
+ success: failed.length === 0 && published.length > 0,
4195
+ publishedCount: published.length,
4196
+ failedCount: failed.length,
4197
+ published,
4198
+ failed
4199
+ };
4200
+ }
4089
4201
  /**
4090
4202
  * Restore the body recorded at history `toVersion` as the new
4091
4203
  * live row. Writes a history event with `op='revert'`. 404
@@ -4623,6 +4735,39 @@ var _ObjectStackProtocolImplementation = class _ObjectStackProtocolImplementatio
4623
4735
  const unsubscribed = await svc.unsubscribe(request.object, request.recordId, "current_user");
4624
4736
  return { success: true, data: { object: request.object, recordId: request.recordId, unsubscribed } };
4625
4737
  }
4738
+ /**
4739
+ * Install a package from a manifest — the single canonical write primitive
4740
+ * for the package subsystem (ADR-0033 consolidation).
4741
+ *
4742
+ * It writes BOTH stores that the runtime keeps for packages, so a package
4743
+ * surfaces consistently no matter which read path is used:
4744
+ * 1. the in-memory `SchemaRegistry` (what the dispatcher's
4745
+ * `/api/v1/packages` list/detail and `getMetaItems({type:'package'})`
4746
+ * read — i.e. what Studio's package selector shows), and
4747
+ * 2. the durable `sys_packages` table via the optional `package` service
4748
+ * (so the package survives a restart; that service re-hydrates these
4749
+ * rows back into the registry on boot).
4750
+ *
4751
+ * The DB write is best-effort and non-fatal: when the `package` service is
4752
+ * absent (e.g. the `marketplace` capability is off) the package is still
4753
+ * registered in-memory and visible for the lifetime of the process.
4754
+ */
4755
+ async installPackage(request) {
4756
+ const manifest = request.manifest;
4757
+ const pkg = this.engine.registry.installPackage(manifest, request.settings);
4758
+ try {
4759
+ const services = this.getServicesRegistry?.();
4760
+ const pkgSvc = services?.get("package");
4761
+ if (pkgSvc?.publish && manifest?.version) {
4762
+ await pkgSvc.publish({ manifest, metadata: {} });
4763
+ }
4764
+ } catch (e) {
4765
+ console.warn(
4766
+ `[protocol.installPackage] sys_packages persist skipped for '${manifest?.id}': ${e?.message}`
4767
+ );
4768
+ }
4769
+ return { package: pkg, message: `Installed package: ${manifest?.id}` };
4770
+ }
4626
4771
  };
4627
4772
  /**
4628
4773
  * Metadata types that are customer-overridable via {@link saveMetaItem}/
@@ -7417,6 +7562,23 @@ var _ObjectQL = class _ObjectQL {
7417
7562
  }
7418
7563
  }
7419
7564
  }
7565
+ /**
7566
+ * Sync a SINGLE object's physical storage (create/alter its table) on
7567
+ * demand. Boot-time {@link syncSchemas} runs once at startup, so an object
7568
+ * that becomes live at runtime (e.g. publishing a drafted object) has a
7569
+ * registry entry but no table — data CRUD then fails with "no such table"
7570
+ * until the next restart. Calling this right after the object is registered
7571
+ * makes it immediately usable. Idempotent: the SQL driver only creates the
7572
+ * table when absent (and alters to add new columns).
7573
+ */
7574
+ async syncObjectSchema(objectName) {
7575
+ const obj = this._registry.getObject(objectName);
7576
+ if (!obj) return;
7577
+ const driver = this.getDriverForObject(objectName);
7578
+ if (!driver || typeof driver.syncSchema !== "function") return;
7579
+ const tableName = StorageNameMapping.resolveTableName(obj);
7580
+ await driver.syncSchema(tableName, obj);
7581
+ }
7420
7582
  /**
7421
7583
  * Get a registered driver by datasource name.
7422
7584
  * Alias matching @objectql/core datasource() API.