/** * @module cosmology/web-cosmology-workspace-service * * Web-specialized CosmologyWorkspaceService wrapper for @ibgib/web-gib. * Binds automatically to the active ContextModeService (metaspace + rootSpace) or persistent metaspace, * and seamlessly handles IndexedDB device delegate resolution for in-browser commitItems and two-phase sync. */ import { extractErrorMsg } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs"; import { getTjpAddr } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs"; import { MetaspaceService } from "@ibgib/core-gib/dist/witness/space/metaspace/metaspace-types.mjs"; import { IbGibSpaceAny } from "@ibgib/core-gib/dist/witness/space/space-base-v1.mjs"; import { CosmologyWorkspaceService, CosmologyWorkspaceServiceOptions, InitCosmosOpts, EnsureCosmosOpts, InitGalaxyOpts, InitGalaxyResult, EnsureGalaxyOpts, EnsureGalaxyResult, InitBranchOpts, InitBranchResult, EnsureBranchOpts, EnsureBranchResult, CommitItemsOpts, WorkspacePushOpts, WorkspacePullOpts, DirectCommitItemInput, } from "@ibgib/core-gib/dist/cosmology/workspace/index.mjs"; import { CosmosIbGib_V1, CosmosGalaxyEntry } from "@ibgib/core-gib/dist/cosmology/cosmos/cosmos-types.mjs"; import { B2tFSGalaxyIbGib_V1, B2tFSGalaxyData_V1 } from "@ibgib/core-gib/dist/cosmology/branching/b2tfs-galaxy/b2tfs-galaxy-types.mjs"; import { VcsInitResult, VcsAddResult, VcsCommitResult, VcsBranchResult, VcsCheckoutResult, VcsResetResult, VcsRestoreResult, VcsMergeResult, VcsLogResult, VcsStatusResult, VcsGenerateCommentResult, VcsPushResult, VcsInitOpts, VcsStatusOpts, VcsAddOpts, VcsCommitOpts, VcsBranchOpts, VcsCheckoutOpts, VcsResetOpts, VcsRestoreOpts, VcsMergeOpts, VcsLogOpts, } from "@ibgib/core-gib/dist/cosmology/branching/commands/index.mjs"; import { WorkspaceCmdOpts, WorkspaceCmdOptsSansIndex, WorkspaceCmdOptsSansIndexAndBranch, } from "@ibgib/core-gib/dist/cosmology/workspace/index.mjs"; import { KEYSTONE_VERB_AUTHOR, KEYSTONE_VERB_SYNC } from "@ibgib/core-gib/dist/keystone/keystone-constants.mjs"; import { KEYSTONE_ROLE_AUTHOR_DELEGATE } from "@ibgib/core-gib/dist/keystone/delegate/delegate-constants.mjs"; import { WebContextModeService } from "../context/context-mode-service.mjs"; import { getThisDeviceDelegate } from "../identity/device-delegate-helper.mjs"; import { getGlobalMetaspace_waitIfNeeded } from "../helpers.mjs"; export interface WebCosmologyWorkspaceServiceOptions { /** Explicit or injected ContextModeService. */ contextModeSvc?: WebContextModeService; /** Explicit metaspace override. */ metaspace?: MetaspaceService; /** Explicit local/root space override. */ localSpace?: IbGibSpaceAny; /** Optional default Cosmos name */ cosmosName?: string; /** Optional default Galaxy name */ galaxyName?: string; /** Optional default Branch name */ branchName?: string; /** Whether to acquire space lock during mutations (defaults to true) */ enableBranchLocking?: boolean; } export class WebCosmologyWorkspaceService { protected lc: string = `[WebCosmologyWorkspaceService]`; protected contextModeSvc?: WebContextModeService; protected explicitMetaspace?: MetaspaceService; protected explicitLocalSpace?: IbGibSpaceAny; protected defaultCosmosName?: string; protected defaultGalaxyName?: string; protected defaultBranchName?: string; protected enableBranchLocking?: boolean; constructor(opts: WebCosmologyWorkspaceServiceOptions = {}) { this.contextModeSvc = opts.contextModeSvc; this.explicitMetaspace = opts.metaspace; this.explicitLocalSpace = opts.localSpace; this.defaultCosmosName = opts.cosmosName; this.defaultGalaxyName = opts.galaxyName; this.defaultBranchName = opts.branchName; this.enableBranchLocking = opts.enableBranchLocking; } /** * Resolves the underlying CosmologyWorkspaceService bound to the active context mode * or explicit spaces. */ public async getUnderlyingService(): Promise { let metaspace = this.explicitMetaspace; let localSpace = this.explicitLocalSpace; if (!metaspace || !localSpace) { if (this.contextModeSvc && this.contextModeSvc.hasActiveContext) { const ctx = this.contextModeSvc.current; metaspace = metaspace || ctx.metaspace; localSpace = localSpace || ctx.rootSpace; } else if (this.contextModeSvc) { metaspace = metaspace || await this.contextModeSvc.getActiveMetaspace(); localSpace = localSpace || await metaspace.getLocalUserSpace({}); } else { metaspace = metaspace || await getGlobalMetaspace_waitIfNeeded(); localSpace = localSpace || await metaspace.getLocalUserSpace({}); } } if (!localSpace) { throw new Error(`${this.lc} localSpace could not be resolved. Ensure context mode is activated or localSpace is provided. (E: genuuid)`); } return new CosmologyWorkspaceService({ metaspace, localSpace, cosmosName: this.defaultCosmosName, galaxyName: this.defaultGalaxyName, branchName: this.defaultBranchName, enableBranchLocking: this.enableBranchLocking, }); } // Cosmos Methods public async initCosmos(opts: InitCosmosOpts): Promise { const svc = await this.getUnderlyingService(); return await svc.initCosmos(opts); } public async ensureCosmos(opts?: EnsureCosmosOpts): Promise { const svc = await this.getUnderlyingService(); return await svc.ensureCosmos(opts); } public async listCosmoses(): Promise { const svc = await this.getUnderlyingService(); return await svc.listCosmoses(); } // Galaxy Methods public async initGalaxy(opts: InitGalaxyOpts): Promise { const svc = await this.getUnderlyingService(); return await svc.initGalaxy(opts); } public async ensureGalaxy(opts?: EnsureGalaxyOpts): Promise { const svc = await this.getUnderlyingService(); return await svc.ensureGalaxy(opts); } public async listGalaxies(opts?: { cosmosName?: string }): Promise { const svc = await this.getUnderlyingService(); return await svc.listGalaxies(opts); } // Branch Methods public async initBranch(opts: InitBranchOpts): Promise { const svc = await this.getUnderlyingService(); return await svc.initBranch(opts); } public async ensureBranch(opts?: EnsureBranchOpts): Promise { const svc = await this.getUnderlyingService(); return await svc.ensureBranch(opts); } public async getGalaxyIbGib(): Promise { const svc = await this.getUnderlyingService(); return await svc.getGalaxyIbGib(); } public async getActiveBranchName(opts?: { galaxyIbGib?: B2tFSGalaxyIbGib_V1; targetDir?: string }): Promise { const svc = await this.getUnderlyingService(); return await svc.getActiveBranchName(opts); } public async getActiveBranchSpace(opts?: B2tFSGalaxyIbGib_V1 | { galaxyIbGib?: B2tFSGalaxyIbGib_V1; targetDir?: string; branchName?: string }): Promise { const svc = await this.getUnderlyingService(); return await svc.getActiveBranchSpace(opts); } /** * Programmatically commits file items directly into the Galaxy branch, * automatically resolving author delegate and primary identity from device context if omitted. */ public async commitItems(opts: CommitItemsOpts): Promise { const svc = await this.getUnderlyingService(); let primaryIdentityTjpAddr = opts.primaryIdentityTjpAddr; let deviceDelegate = opts.deviceDelegate; if (!primaryIdentityTjpAddr || !deviceDelegate) { const identity = this.contextModeSvc?.getPersistentIdentity()?.domainIdentity || (this.contextModeSvc?.hasActiveContext ? this.contextModeSvc.current.identity : undefined); if (identity) { primaryIdentityTjpAddr = primaryIdentityTjpAddr || getTjpAddr({ ibGib: identity }); if (!deviceDelegate) { const localSpace = await svc.getActiveBranchSpace(); const metaspace = (svc as any).metaspace; const delegateRes = await getThisDeviceDelegate({ domainIdentity: identity, verb: KEYSTONE_VERB_AUTHOR, role: KEYSTONE_ROLE_AUTHOR_DELEGATE, metaspace, space: localSpace, }); if (delegateRes) { deviceDelegate = { delegateKeystone: delegateRes.delegateKeystone, delegateSecret: delegateRes.delegateSecret, }; } } } } return await svc.commitItems({ ...opts, primaryIdentityTjpAddr, deviceDelegate, }); } /** * Pushes branch data to a remote Space-Gib server via Two-Phase Sync, * automatically resolving sync delegate credentials from IndexedDB if omitted. */ public async push(opts: WorkspacePushOpts): Promise { const svc = await this.getUnderlyingService(); let deviceIdentity = opts.deviceIdentity; let senderIdentityInfo = opts.senderIdentityInfo; let fnSenderSecret = opts.fnSenderSecret; if (!deviceIdentity && (!senderIdentityInfo || !fnSenderSecret)) { const identity = this.contextModeSvc?.getPersistentIdentity()?.domainIdentity || (this.contextModeSvc?.hasActiveContext ? this.contextModeSvc.current.identity : undefined); if (identity) { const localSpace = await svc.getActiveBranchSpace(); const metaspace = (svc as any).metaspace; const syncDelegateRes = await getThisDeviceDelegate({ domainIdentity: identity, verb: KEYSTONE_VERB_SYNC, metaspace, space: localSpace, }); if (syncDelegateRes) { deviceIdentity = { syncDelegateKeystone: syncDelegateRes.delegateKeystone, syncDelegateSecret: syncDelegateRes.delegateSecret, primaryTjpAddr: getTjpAddr({ ibGib: identity }), }; } } } return await svc.push({ ...opts, deviceIdentity, senderIdentityInfo, fnSenderSecret, }); } // Worktree & Forwarding Commands public async status(opts?: Partial): Promise { const svc = await this.getUnderlyingService(); return await svc.status(opts); } public async add(paths: string[] = [], opts?: Partial): Promise { const svc = await this.getUnderlyingService(); return await svc.add(paths, opts); } public async commit(opts: WorkspaceCmdOptsSansIndexAndBranch): Promise { const svc = await this.getUnderlyingService(); return await svc.commit(opts); } public async branch(opts: WorkspaceCmdOptsSansIndexAndBranch): Promise { const svc = await this.getUnderlyingService(); return await svc.branch(opts); } public async checkout(opts: WorkspaceCmdOptsSansIndex): Promise { const svc = await this.getUnderlyingService(); return await svc.checkout(opts); } public async reset(opts: WorkspaceCmdOpts): Promise { const svc = await this.getUnderlyingService(); return await svc.reset(opts); } public async restore(opts: Partial): Promise { const svc = await this.getUnderlyingService(); return await svc.restore(opts); } public async merge(opts: WorkspaceCmdOptsSansIndexAndBranch): Promise { const svc = await this.getUnderlyingService(); return await svc.merge(opts); } public async log(opts: WorkspaceCmdOpts): Promise { const svc = await this.getUnderlyingService(); return await svc.log(opts); } public async generateComment(opts?: { paths?: string[]; folderTopic?: string; targetDir?: string; galaxyIbGib?: B2tFSGalaxyIbGib_V1; }): Promise { const svc = await this.getUnderlyingService(); return await svc.generateComment(opts); } public async updateIndex(opts: { dataToUpdate?: Partial; targetSpace?: IbGibSpaceAny; }): Promise { const svc = await this.getUnderlyingService(); return await svc.updateIndex(opts); } }