UNPKG

@itwin/core-backend

Version:
635 lines • 30 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Workspace */ Object.defineProperty(exports, "__esModule", { value: true }); exports.workspaceManifestProperty = exports.workspaceDbFileExt = void 0; exports.constructWorkspaceDb = constructWorkspaceDb; exports.constructWorkspace = constructWorkspace; exports.constructWorkspaceEditor = constructWorkspaceEditor; exports.validateWorkspaceContainerId = validateWorkspaceContainerId; exports.throwWorkspaceDbLoadErrors = throwWorkspaceDbLoadErrors; const crypto_1 = require("crypto"); const fs = require("fs-extra"); const path_1 = require("path"); const core_bentley_1 = require("@itwin/core-bentley"); const core_common_1 = require("@itwin/core-common"); const CloudSqlite_1 = require("../../CloudSqlite"); const IModelHost_1 = require("../../IModelHost"); const IModelJsFs_1 = require("../../IModelJsFs"); const SQLiteDb_1 = require("../../SQLiteDb"); const Settings_1 = require("../../workspace/Settings"); const Workspace_1 = require("../../workspace/Workspace"); const WorkspaceEditor_1 = require("../../workspace/WorkspaceEditor"); const WorkspaceSqliteDb_1 = require("./WorkspaceSqliteDb"); const SettingsImpl_1 = require("./SettingsImpl"); const Symbols_1 = require("../Symbols"); function workspaceDbNameWithDefault(dbName) { return dbName ?? "workspace-db"; } /** file extension for local WorkspaceDbs */ exports.workspaceDbFileExt = "itwin-workspace"; function makeWorkspaceCloudCache(arg) { const cache = CloudSqlite_1.CloudSqlite.CloudCaches.getCache(arg); if (undefined === cache.workspaceContainers) // if we just created this container, add the map. CloudSqlite_1.CloudSqlite.addHiddenProperty(cache, "workspaceContainers", new Map()); return cache; } function getContainerFullId(props) { return `${props.baseUri}/${props.containerId}`; } function getWorkspaceCloudContainer(props, cache) { const id = getContainerFullId(props); let cloudContainer = cache.workspaceContainers.get(id); if (undefined !== cloudContainer) return cloudContainer; cloudContainer = CloudSqlite_1.CloudSqlite.createCloudContainer(props); cache.workspaceContainers.set(id, cloudContainer); cloudContainer.connectCount = 0; CloudSqlite_1.CloudSqlite.addHiddenProperty(cloudContainer, "sharedConnect", function () { if (this.connectCount++ === 0) { this.connect(cache); return true; } return false; }); CloudSqlite_1.CloudSqlite.addHiddenProperty(cloudContainer, "sharedDisconnect", function () { if (--this.connectCount <= 0) { this.disconnect(); cache.workspaceContainers.delete(id); this.connectCount = 0; } }); return cloudContainer; } class WorkspaceContainerImpl { [Symbols_1._implementationProhibited] = undefined; workspace; filesDir; id; fromProps; _cloudContainer; get cloudContainer() { return this._cloudContainer; } _wsDbs = new Map(); get dirName() { return (0, path_1.join)(this.workspace.containerDir, this.id); } constructor(workspace, props) { validateWorkspaceContainerId(props.containerId); this.workspace = workspace; this.id = props.containerId; this.fromProps = props; if (props.baseUri !== "") this._cloudContainer = getWorkspaceCloudContainer(props, this.workspace.getCloudCache()); workspace.addContainer(this); this.filesDir = (0, path_1.join)(this.dirName, "Files"); const cloudContainer = this.cloudContainer; if (undefined === cloudContainer) return; // sharedConnect returns true if we just connected (if the container is shared, it may have already been connected) if (cloudContainer.sharedConnect() && false !== props.syncOnConnect) { try { cloudContainer.checkForChanges(); } catch { // must be offline } } } resolveDbFileName(props) { const container = this.cloudContainer; if (undefined === container) return (0, path_1.join)(this.dirName, `${props.dbName}.${exports.workspaceDbFileExt}`); // local file, versions not allowed return CloudSqlite_1.CloudSqlite.querySemverMatch({ ...props, container, dbName: workspaceDbNameWithDefault(props.dbName) }); } addWorkspaceDb(toAdd) { if (undefined !== this._wsDbs.get(toAdd.dbName)) core_common_1.WorkspaceError.throwError("already-exists", { message: `workspaceDb '${toAdd.dbName}' already exists in workspace` }); this._wsDbs.set(toAdd.dbName, toAdd); } getWorkspaceDb(props) { return this._wsDbs.get(workspaceDbNameWithDefault(props?.dbName)) ?? new WorkspaceDbImpl(props ?? {}, this); } closeWorkspaceDb(toDrop) { const name = toDrop.dbName; const wsDb = this._wsDbs.get(name); if (wsDb === toDrop) { this._wsDbs.delete(name); wsDb.close(); } } close() { for (const [_name, db] of this._wsDbs) db.close(); this._wsDbs.clear(); this.cloudContainer?.sharedDisconnect(); } } /** Implementation of WorkspaceDb */ class WorkspaceDbImpl { [Symbols_1._implementationProhibited] = undefined; sqliteDb = new WorkspaceSqliteDb_1.WorkspaceSqliteDb(); dbName; _container; onClose = new core_bentley_1.BeEvent(); dbFileName; _manifest; /** true if this WorkspaceDb is currently open */ get isOpen() { return this.sqliteDb.isOpen; } get container() { return this._container; } queryFileResource(rscName) { const info = this.sqliteDb[Symbols_1._nativeDb].queryEmbeddedFile(rscName); if (undefined === info) return undefined; // since resource names can contain illegal characters, path separators, etc., we make the local file name from its hash, in hex. let localFileName = (0, path_1.join)(this._container.filesDir, (0, crypto_1.createHash)("sha1").update(this.dbFileName).update(rscName).digest("hex")); if (info.fileExt !== "") // since some applications may expect to see the extension, append it here if it was supplied. localFileName = `${localFileName}.${info.fileExt}`; return { localFileName, info }; } constructor(props, container) { this.dbName = workspaceDbNameWithDefault(props.dbName); CloudSqlite_1.CloudSqlite.validateDbName(this.dbName); this._container = container; this.dbFileName = container.resolveDbFileName(props); container.addWorkspaceDb(this); if (true === props.prefetch) this.prefetch(); } open() { this.sqliteDb.openDb(this.dbFileName, core_bentley_1.OpenMode.Readonly, this._container.cloudContainer); } close() { if (this.isOpen) { this.onClose.raiseEvent(); this.sqliteDb.closeDb(); this._container.closeWorkspaceDb(this); } } get version() { const cloudContainer = this.container.cloudContainer; if (undefined === cloudContainer) return "1.0.0"; // local file, no versioning. return default return CloudSqlite_1.CloudSqlite.parseDbFileName(this.dbFileName).version; } get manifest() { return this._manifest ??= this.withOpenDb((db) => { const manifestJson = db[Symbols_1._nativeDb].queryFileProperty(exports.workspaceManifestProperty, true); return manifestJson ? JSON.parse(manifestJson) : { workspaceName: this.dbName }; }); } withOpenDb(operation) { const done = this.isOpen ? () => { } : (this.open(), () => this.close()); try { return operation(this.sqliteDb); } finally { done(); } } getString(rscName) { return this.withOpenDb((db) => { return db.withSqliteStatement("SELECT value from strings WHERE id=?", (stmt) => { stmt.bindString(1, rscName); return core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step() ? stmt.getValueString(0) : undefined; }); }); } getBlobReader(rscName) { return this.sqliteDb.withSqliteStatement("SELECT rowid from blobs WHERE id=?", (stmt) => { stmt.bindString(1, rscName); const blobReader = SQLiteDb_1.SQLiteDb.createBlobIO(); blobReader.open(this.sqliteDb[Symbols_1._nativeDb], { tableName: "blobs", columnName: "value", row: stmt.getValueInteger(0) }); return blobReader; }); } getBlob(rscName) { return this.withOpenDb((db) => { return db.withSqliteStatement("SELECT value from blobs WHERE id=?", (stmt) => { stmt.bindString(1, rscName); return core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step() ? stmt.getValueBlob(0) : undefined; }); }); } getFile(rscName, targetFileName) { return this.withOpenDb((db) => { const file = this.queryFileResource(rscName); if (!file) return undefined; const info = file.info; const localFileName = targetFileName ?? file.localFileName; // check whether the file is already up to date. const stat = fs.existsSync(localFileName) && fs.statSync(localFileName); if (stat && Math.round(stat.mtimeMs) === info.date && stat.size === info.size) return localFileName; // yes, we're done // extractEmbeddedFile fails if the file exists or if the directory does not exist if (stat) fs.removeSync(localFileName); else IModelJsFs_1.IModelJsFs.recursiveMkDirSync((0, path_1.dirname)(localFileName)); db[Symbols_1._nativeDb].extractEmbeddedFile({ name: rscName, localFileName }); const date = new Date(info.date); fs.utimesSync(localFileName, date, date); // set the last-modified date of the file to match date in container fs.chmodSync(localFileName, "0444"); // set file readonly return localFileName; }); } prefetch(opts) { const cloudContainer = this._container.cloudContainer; if (cloudContainer === undefined) core_common_1.WorkspaceError.throwError("no-cloud-container", { message: "no cloud container to prefetch" }); return CloudSqlite_1.CloudSqlite.startCloudPrefetch(cloudContainer, this.dbFileName, opts); } queryResources(args) { const table = "blob" !== args.type ? "strings" : "blobs"; this.withOpenDb((db) => { const where = undefined !== args.namePattern ? ` WHERE id ${args.nameCompare ?? "="} ?` : ""; db.withSqliteStatement(`SELECT id from ${table}${where}`, (stmt) => { function* makeIterable() { while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) { yield stmt.getValueString(0); } } if (undefined !== args.namePattern) { stmt.bindString(1, args.namePattern); } args.callback(makeIterable()); }); }); } } /** Implementation of Workspace */ class WorkspaceImpl { [Symbols_1._implementationProhibited] = undefined; _containers = new Map(); containerDir; settings; _cloudCache; getCloudCache() { return this._cloudCache ??= makeWorkspaceCloudCache({ cacheName: "Workspace", cacheSize: "20G" }); } constructor(settings, opts) { this.settings = settings; this.containerDir = opts?.containerDir ?? (0, path_1.join)(IModelHost_1.IModelHost.cacheDir, "Workspace"); let settingsFiles = opts?.settingsFiles; if (settingsFiles) { if (typeof settingsFiles === "string") settingsFiles = [settingsFiles]; settingsFiles.forEach((file) => settings.addFile(file, Settings_1.SettingsPriority.application)); } } addContainer(toAdd) { if (undefined !== this._containers.get(toAdd.id)) core_common_1.WorkspaceError.throwError("container-exists", { message: `container ${toAdd.id} already exists in workspace` }); this._containers.set(toAdd.id, toAdd); } findContainer(containerId) { return this._containers.get(containerId); } getContainer(props) { return this.findContainer(props.containerId) ?? new WorkspaceContainerImpl(this, props); } async getContainerAsync(props) { const accessToken = props.accessToken ?? ((props.baseUri === "") || props.isPublic) ? "" : await CloudSqlite_1.CloudSqlite.requestToken({ ...props, accessLevel: "read" }); return this.getContainer({ ...props, accessToken }); } async getWorkspaceDb(props) { let container = this.findContainer(props.containerId); if (undefined === container) { const accessToken = props.isPublic ? "" : await CloudSqlite_1.CloudSqlite.requestToken({ accessLevel: "read", ...props }); container = new WorkspaceContainerImpl(this, { ...props, accessToken }); } return container.getWorkspaceDb(props); } async loadSettingsDictionary(props, problems) { if (!Array.isArray(props)) props = [props]; for (const prop of props) { const db = await this.getWorkspaceDb(prop); db.open(); try { const manifest = db.manifest; const dictProps = { name: prop.resourceName, workspaceDb: db, priority: prop.priority }; // don't load if we already have this dictionary. Happens if the same WorkspaceDb is in more than one list if (undefined === this.settings.getDictionary(dictProps)) { const settingsJson = db.getString(prop.resourceName); if (undefined === settingsJson) throwWorkspaceDbLoadError(`could not load setting dictionary resource '${prop.resourceName}' from: '${manifest.workspaceName}'`, prop, db); db.close(); // don't leave this db open in case we're going to find another dictionary in it recursively. this.settings.addJson(dictProps, settingsJson); const dict = this.settings.getDictionary(dictProps); if (dict) { Workspace_1.Workspace.onSettingsDictionaryLoadedFn({ dict, from: db }); // if the dictionary we just loaded has a "settingsWorkspaces" entry, load them too, recursively const nested = dict.getSetting(Workspace_1.WorkspaceSettingNames.settingsWorkspaces); if (nested !== undefined) { IModelHost_1.IModelHost.settingsSchemas.validateSetting(nested, Workspace_1.WorkspaceSettingNames.settingsWorkspaces); await this.loadSettingsDictionary(nested, problems); } } } } catch (e) { db.close(); problems?.push(e); } } } close() { this.settings.close(); for (const [_id, container] of this._containers) container.close(); this._containers.clear(); } resolveWorkspaceDbSetting(settingName, filter) { const settingDef = IModelHost_1.IModelHost.settingsSchemas.settingDefs.get(settingName); const combine = settingDef?.combineArray === true; filter = filter ?? (() => true); const result = []; for (const entry of this.settings.getSettingEntries(settingName)) { for (const dbProp of entry.value) { if (filter(dbProp, entry.dictionary)) { result.push(dbProp); } } if (!combine) { break; } } return result; } async getWorkspaceDbs(args) { const dbList = (args.settingName !== undefined) ? this.resolveWorkspaceDbSetting(args.settingName, args.filter) : args.dbs; const result = []; const pushUnique = (wsDb) => { for (const db of result) { // if we already have this db, skip it. The test below also has to consider that we create a separate WorkspaceDb object for the same // database from more than one Workspace (though then they must use a "shared" CloudContainer). if (db === wsDb || ((db.container.cloudContainer === wsDb.container.cloudContainer) && (db.dbFileName === wsDb.dbFileName))) return; // this db is redundant } result.push(wsDb); }; for (const dbProps of dbList) { try { pushUnique(await this.getWorkspaceDb(dbProps)); } catch (e) { const loadErr = e; loadErr.wsDbProps = dbProps; args.problems?.push(loadErr); } } return result; } } const workspaceEditorName = "WorkspaceEditor"; // name of the cache for the editor workspace class EditorWorkspaceImpl extends WorkspaceImpl { getCloudCache() { return this._cloudCache ??= makeWorkspaceCloudCache({ cacheName: workspaceEditorName, cacheSize: "20G" }); } } class EditorImpl { [Symbols_1._implementationProhibited] = undefined; workspace = new EditorWorkspaceImpl(new SettingsImpl_1.SettingsImpl(), { containerDir: (0, path_1.join)(IModelHost_1.IModelHost.cacheDir, workspaceEditorName) }); async initializeContainer(args) { class CloudAccess extends CloudSqlite_1.CloudSqlite.DbAccess { static _cacheName = workspaceEditorName; static async initializeWorkspace(args) { const props = await this.createBlobContainer({ scope: args.scope, metadata: { ...args.metadata, containerType: "workspace" } }); const dbFullName = CloudSqlite_1.CloudSqlite.makeSemverName(workspaceDbNameWithDefault(args.dbName), "0.0.0"); await super._initializeDb({ ...args, props, dbName: dbFullName, dbType: WorkspaceSqliteDb_1.WorkspaceSqliteDb, blockSize: "4M" }); return props; } } return CloudAccess.initializeWorkspace(args); } async createNewCloudContainer(args) { const cloudContainer = await this.initializeContainer(args); const userToken = await IModelHost_1.IModelHost.authorizationClient?.getAccessToken(); const accessToken = await CloudSqlite_1.CloudSqlite.requestToken({ ...cloudContainer, accessLevel: "write", userToken }); return this.getContainer({ accessToken, ...cloudContainer, writeable: true, description: args.metadata.description }); } getContainer(props) { return this.workspace.findContainer(props.containerId) ?? new EditorContainerImpl(this.workspace, props); } async getContainerAsync(props) { const accessToken = props.accessToken ?? (props.baseUri === "") ? "" : await CloudSqlite_1.CloudSqlite.requestToken({ ...props, accessLevel: "write" }); return this.getContainer({ ...props, accessToken }); } close() { this.workspace.close(); } } class EditorContainerImpl extends WorkspaceContainerImpl { get cloudContainer() { return super.cloudContainer; } get cloudProps() { const cloudContainer = this.cloudContainer; if (undefined === cloudContainer) return undefined; return { baseUri: cloudContainer.baseUri, containerId: cloudContainer.containerId, storageType: cloudContainer.storageType, isPublic: cloudContainer.isPublic, }; } async createNewWorkspaceDbVersion(args) { const container = this.cloudContainer; if (undefined === container) core_common_1.WorkspaceError.throwError("no-cloud-container", { message: "versions require cloud containers" }); const fromDb = { ...args.fromProps, dbName: workspaceDbNameWithDefault(args.fromProps?.dbName) }; return CloudSqlite_1.CloudSqlite.createNewDbVersion(container, { ...args, fromDb }); } getWorkspaceDb(props) { return this.getEditableDb(props); } getEditableDb(props) { const db = this._wsDbs.get(workspaceDbNameWithDefault(props.dbName)) ?? new EditableDbImpl(props, this); if (this.cloudContainer && !CloudSqlite_1.CloudSqlite.isSemverEditable(db.dbFileName, this.cloudContainer)) { this._wsDbs.delete(workspaceDbNameWithDefault(props.dbName)); core_common_1.CloudSqliteError.throwError("already-published", { message: `${db.dbFileName} has been published and is not editable. Make a new version first.` }); } return db; } acquireWriteLock(user) { if (this.cloudContainer) { this.cloudContainer.acquireWriteLock(user); this.cloudContainer.writeLockHeldBy = user; } } releaseWriteLock() { if (this.cloudContainer) { this.cloudContainer.releaseWriteLock(); this.cloudContainer.writeLockHeldBy = undefined; } } abandonChanges() { if (this.cloudContainer) { this.cloudContainer.abandonChanges(); this.cloudContainer.writeLockHeldBy = undefined; } } async createDb(args) { if (!this.cloudContainer) { WorkspaceEditor_1.WorkspaceEditor.createEmptyDb({ localFileName: this.resolveDbFileName(args), manifest: args.manifest }); } else { // currently the only way to create a workspaceDb in a cloud container is to create a temporary workspaceDb and upload it. const tempDbFile = (0, path_1.join)(IModelHost_1.KnownLocations.tmpdir, `empty.${exports.workspaceDbFileExt}`); if (fs.existsSync(tempDbFile)) IModelJsFs_1.IModelJsFs.removeSync(tempDbFile); WorkspaceEditor_1.WorkspaceEditor.createEmptyDb({ localFileName: tempDbFile, manifest: args.manifest }); await CloudSqlite_1.CloudSqlite.uploadDb(this.cloudContainer, { localFileName: tempDbFile, dbName: CloudSqlite_1.CloudSqlite.makeSemverName(workspaceDbNameWithDefault(args.dbName)) }); IModelJsFs_1.IModelJsFs.removeSync(tempDbFile); } return this.getWorkspaceDb(args); } } class EditableDbImpl extends WorkspaceDbImpl { get container() { (0, core_bentley_1.assert)(this._container instanceof EditorContainerImpl); return this._container; } static validateResourceName(name) { if (name.trim() !== name) core_common_1.WorkspaceError.throwError("invalid-name", { message: "resource name may not have leading or trailing spaces" }); if (name.length > 1024) { core_common_1.WorkspaceError.throwError("invalid-name", { message: "resource name too long" }); } } validateResourceSize(val) { const len = typeof val === "string" ? val.length : val.byteLength; if (len > (1024 * 1024 * 1024)) // one gigabyte core_common_1.WorkspaceError.throwError("too-large", { message: "value is too large" }); } get cloudProps() { const props = this._container.cloudProps; if (props === undefined) return undefined; const parsed = CloudSqlite_1.CloudSqlite.parseDbFileName(this.dbFileName); return { ...props, dbName: parsed.dbName, version: parsed.version }; } open() { this.sqliteDb.openDb(this.dbFileName, core_bentley_1.OpenMode.ReadWrite, this._container.cloudContainer); } close() { if (this.isOpen) { // whenever we close an EditableDb, update the name of the last editor in the manifest const lastEditedBy = this._container.cloudContainer?.writeLockHeldBy; if (lastEditedBy !== undefined) this.updateManifest({ ...this.manifest, lastEditedBy }); // make sure all changes were saved before we close this.sqliteDb.saveChanges(); } super.close(); } getFileModifiedTime(localFileName) { return Math.round(fs.statSync(localFileName).mtimeMs); } performWriteSql(rscName, sql, bind) { this.sqliteDb.withSqliteStatement(sql, (stmt) => { stmt.bindString(1, rscName); bind?.(stmt); const rc = stmt.step(); if (core_bentley_1.DbResult.BE_SQLITE_DONE !== rc) { if (core_bentley_1.DbResult.BE_SQLITE_CONSTRAINT_PRIMARYKEY === rc) core_common_1.WorkspaceError.throwError("resource-exists", { message: `resource "${rscName}" already exists` }); core_common_1.WorkspaceError.throwError("write-error", { message: `workspace [${sql}], rc=${rc}` }); } }); this.sqliteDb.saveChanges(); } updateManifest(manifest) { this.sqliteDb[Symbols_1._nativeDb].saveFileProperty(exports.workspaceManifestProperty, JSON.stringify(manifest)); this._manifest = undefined; } updateSettingsResource(settings, rscName) { this.updateString(rscName ?? "settingsDictionary", JSON.stringify(settings)); } addString(rscName, val) { EditableDbImpl.validateResourceName(rscName); this.validateResourceSize(val); this.performWriteSql(rscName, "INSERT INTO strings(id,value) VALUES(?,?)", (stmt) => stmt.bindString(2, val)); } updateString(rscName, val) { this.validateResourceSize(val); this.performWriteSql(rscName, "INSERT INTO strings(id,value) VALUES(?,?) ON CONFLICT(id) DO UPDATE SET value=excluded.value WHERE value!=excluded.value", (stmt) => stmt.bindString(2, val)); } removeString(rscName) { this.performWriteSql(rscName, "DELETE FROM strings WHERE id=?"); } addBlob(rscName, val) { EditableDbImpl.validateResourceName(rscName); this.validateResourceSize(val); this.performWriteSql(rscName, "INSERT INTO blobs(id,value) VALUES(?,?)", (stmt) => stmt.bindBlob(2, val)); } updateBlob(rscName, val) { this.validateResourceSize(val); this.performWriteSql(rscName, "INSERT INTO blobs(id,value) VALUES(?,?) ON CONFLICT(id) DO UPDATE SET value=excluded.value WHERE value!=excluded.value", (stmt) => stmt.bindBlob(2, val)); } getBlobWriter(rscName) { return this.sqliteDb.withSqliteStatement("SELECT rowid from blobs WHERE id=?", (stmt) => { stmt.bindString(1, rscName); const blobWriter = SQLiteDb_1.SQLiteDb.createBlobIO(); blobWriter.open(this.sqliteDb[Symbols_1._nativeDb], { tableName: "blobs", columnName: "value", row: stmt.getValueInteger(0), writeable: true }); return blobWriter; }); } removeBlob(rscName) { this.performWriteSql(rscName, "DELETE FROM blobs WHERE id=?"); } addFile(rscName, localFileName, fileExt) { EditableDbImpl.validateResourceName(rscName); fileExt = fileExt ?? (0, path_1.extname)(localFileName); if (fileExt?.[0] === ".") fileExt = fileExt.slice(1); this.sqliteDb[Symbols_1._nativeDb].embedFile({ name: rscName, localFileName, date: this.getFileModifiedTime(localFileName), fileExt }); } updateFile(rscName, localFileName) { this.queryFileResource(rscName); // throws if not present this.sqliteDb[Symbols_1._nativeDb].replaceEmbeddedFile({ name: rscName, localFileName, date: this.getFileModifiedTime(localFileName) }); } removeFile(rscName) { const file = this.queryFileResource(rscName); if (undefined === file) core_common_1.WorkspaceError.throwError("does-not-exist", { message: `file resource "${rscName}" does not exist` }); if (file && fs.existsSync(file.localFileName)) fs.unlinkSync(file.localFileName); this.sqliteDb[Symbols_1._nativeDb].removeEmbeddedFile(rscName); } } function constructWorkspaceDb(props, container) { return new WorkspaceDbImpl(props, container); } function constructWorkspace(settings, opts) { return new WorkspaceImpl(settings, opts); } function constructWorkspaceEditor() { return new EditorImpl(); } /** * Validate that a WorkspaceContainer.Id is valid. * The rules for ContainerIds (from Azure, see https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata): * - may only contain lower case letters, numbers or dashes * - may not start or end with with a dash nor have more than one dash in a row * - may not be shorter than 3 or longer than 63 characters */ function validateWorkspaceContainerId(id) { if (!/^(?=.{3,63}$)[a-z0-9]+(-[a-z0-9]+)*$/g.test(id)) core_common_1.WorkspaceError.throwError("invalid-name", { message: `invalid containerId: [${id}]` }); } exports.workspaceManifestProperty = { namespace: "workspace", name: "manifest" }; function throwWorkspaceDbLoadError(message, wsDbProps, wsDb) { core_common_1.WorkspaceError.throwError("load-error", { message, wsDb, wsDbProps }); } function throwWorkspaceDbLoadErrors(message, wsLoadErrors) { core_common_1.WorkspaceError.throwError("load-errors", { message, wsLoadErrors }); } //# sourceMappingURL=WorkspaceImpl.js.map