@itwin/core-backend 3.3.0-dev.7 → 3.3.0-dev.70

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.
Files changed (102) hide show
  1. package/CHANGELOG.md +38 -1
  2. package/lib/cjs/BackendHubAccess.d.ts +2 -7
  3. package/lib/cjs/BackendHubAccess.d.ts.map +1 -1
  4. package/lib/cjs/BackendHubAccess.js.map +1 -1
  5. package/lib/cjs/BisCoreSchema.d.ts +1 -1
  6. package/lib/cjs/BisCoreSchema.js +1 -1
  7. package/lib/cjs/BisCoreSchema.js.map +1 -1
  8. package/lib/cjs/BriefcaseManager.d.ts +10 -11
  9. package/lib/cjs/BriefcaseManager.d.ts.map +1 -1
  10. package/lib/cjs/BriefcaseManager.js +10 -11
  11. package/lib/cjs/BriefcaseManager.js.map +1 -1
  12. package/lib/cjs/CheckpointManager.d.ts +16 -4
  13. package/lib/cjs/CheckpointManager.d.ts.map +1 -1
  14. package/lib/cjs/CheckpointManager.js +103 -20
  15. package/lib/cjs/CheckpointManager.js.map +1 -1
  16. package/lib/cjs/ClassRegistry.js +6 -6
  17. package/lib/cjs/ClassRegistry.js.map +1 -1
  18. package/lib/cjs/CodeSpecs.d.ts +1 -1
  19. package/lib/cjs/CodeSpecs.js +1 -1
  20. package/lib/cjs/CodeSpecs.js.map +1 -1
  21. package/lib/cjs/DisplayStyle.d.ts +2 -2
  22. package/lib/cjs/DisplayStyle.d.ts.map +1 -1
  23. package/lib/cjs/DisplayStyle.js +14 -17
  24. package/lib/cjs/DisplayStyle.js.map +1 -1
  25. package/lib/cjs/Element.d.ts +36 -25
  26. package/lib/cjs/Element.d.ts.map +1 -1
  27. package/lib/cjs/Element.js +54 -39
  28. package/lib/cjs/Element.js.map +1 -1
  29. package/lib/cjs/GeoCoordConfig.d.ts +14 -0
  30. package/lib/cjs/GeoCoordConfig.d.ts.map +1 -0
  31. package/lib/cjs/GeoCoordConfig.js +73 -0
  32. package/lib/cjs/GeoCoordConfig.js.map +1 -0
  33. package/lib/cjs/HubMock.d.ts +99 -0
  34. package/lib/cjs/HubMock.d.ts.map +1 -0
  35. package/lib/cjs/HubMock.js +193 -0
  36. package/lib/cjs/HubMock.js.map +1 -0
  37. package/lib/cjs/IModelDb.d.ts +25 -15
  38. package/lib/cjs/IModelDb.d.ts.map +1 -1
  39. package/lib/cjs/IModelDb.js +43 -34
  40. package/lib/cjs/IModelDb.js.map +1 -1
  41. package/lib/cjs/IModelHost.d.ts +70 -32
  42. package/lib/cjs/IModelHost.d.ts.map +1 -1
  43. package/lib/cjs/IModelHost.js +65 -64
  44. package/lib/cjs/IModelHost.js.map +1 -1
  45. package/lib/cjs/IpcHost.d.ts +2 -2
  46. package/lib/cjs/IpcHost.d.ts.map +1 -1
  47. package/lib/cjs/IpcHost.js +1 -1
  48. package/lib/cjs/IpcHost.js.map +1 -1
  49. package/lib/cjs/LocalHub.d.ts +163 -0
  50. package/lib/cjs/LocalHub.d.ts.map +1 -0
  51. package/lib/cjs/LocalHub.js +588 -0
  52. package/lib/cjs/LocalHub.js.map +1 -0
  53. package/lib/cjs/LocalhostIpcHost.d.ts +2 -2
  54. package/lib/cjs/LocalhostIpcHost.d.ts.map +1 -1
  55. package/lib/cjs/LocalhostIpcHost.js +16 -0
  56. package/lib/cjs/LocalhostIpcHost.js.map +1 -1
  57. package/lib/cjs/RpcBackend.d.ts +2 -0
  58. package/lib/cjs/RpcBackend.d.ts.map +1 -1
  59. package/lib/cjs/RpcBackend.js +19 -2
  60. package/lib/cjs/RpcBackend.js.map +1 -1
  61. package/lib/cjs/SQLiteDb.d.ts +4 -3
  62. package/lib/cjs/SQLiteDb.d.ts.map +1 -1
  63. package/lib/cjs/SQLiteDb.js +5 -4
  64. package/lib/cjs/SQLiteDb.js.map +1 -1
  65. package/lib/cjs/SqliteStatement.d.ts +4 -0
  66. package/lib/cjs/SqliteStatement.d.ts.map +1 -1
  67. package/lib/cjs/SqliteStatement.js +4 -0
  68. package/lib/cjs/SqliteStatement.js.map +1 -1
  69. package/lib/cjs/ViewDefinition.d.ts +6 -6
  70. package/lib/cjs/ViewDefinition.d.ts.map +1 -1
  71. package/lib/cjs/ViewDefinition.js +20 -20
  72. package/lib/cjs/ViewDefinition.js.map +1 -1
  73. package/lib/cjs/assets/Settings/Schemas/Cloud.Schema.json +68 -0
  74. package/lib/cjs/assets/Settings/Schemas/Gcs.schema.json +22 -0
  75. package/lib/cjs/assets/Settings/Schemas/Workspace.Schema.json +53 -0
  76. package/lib/cjs/assets/Settings/backend.setting.json5 +139 -0
  77. package/lib/cjs/core-backend.d.ts +9 -7
  78. package/lib/cjs/core-backend.d.ts.map +1 -1
  79. package/lib/cjs/core-backend.js +9 -7
  80. package/lib/cjs/core-backend.js.map +1 -1
  81. package/lib/cjs/rpc-impl/RpcBriefcaseUtility.d.ts +1 -1
  82. package/lib/cjs/rpc-impl/RpcBriefcaseUtility.d.ts.map +1 -1
  83. package/lib/cjs/rpc-impl/RpcBriefcaseUtility.js +8 -12
  84. package/lib/cjs/rpc-impl/RpcBriefcaseUtility.js.map +1 -1
  85. package/lib/cjs/workspace/Settings.d.ts +8 -3
  86. package/lib/cjs/workspace/Settings.d.ts.map +1 -1
  87. package/lib/cjs/workspace/Settings.js +9 -0
  88. package/lib/cjs/workspace/Settings.js.map +1 -1
  89. package/lib/cjs/workspace/SettingsSchemas.d.ts +76 -0
  90. package/lib/cjs/workspace/SettingsSchemas.d.ts.map +1 -0
  91. package/lib/cjs/workspace/SettingsSchemas.js +192 -0
  92. package/lib/cjs/workspace/SettingsSchemas.js.map +1 -0
  93. package/lib/cjs/workspace/Workspace.d.ts +235 -140
  94. package/lib/cjs/workspace/Workspace.d.ts.map +1 -1
  95. package/lib/cjs/workspace/Workspace.js +262 -91
  96. package/lib/cjs/workspace/Workspace.js.map +1 -1
  97. package/package.json +23 -18
  98. package/lib/cjs/assets/Settings.schema.json +0 -25
  99. package/lib/cjs/workspace/SettingsSpecRegistry.d.ts +0 -68
  100. package/lib/cjs/workspace/SettingsSpecRegistry.d.ts.map +0 -1
  101. package/lib/cjs/workspace/SettingsSpecRegistry.js +0 -122
  102. package/lib/cjs/workspace/SettingsSpecRegistry.js.map +0 -1
@@ -7,89 +7,180 @@
7
7
  * @module Workspace
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.EditableWorkspaceDb = exports.ITwinWorkspaceDb = exports.ITwinWorkspaceContainer = exports.ITwinWorkspace = void 0;
10
+ exports.EditableWorkspaceDb = exports.ITwinWorkspaceDb = exports.ITwinWorkspaceContainer = exports.ITwinWorkspace = exports.WorkspaceSetting = void 0;
11
11
  const crypto_1 = require("crypto");
12
12
  const fs = require("fs-extra");
13
13
  const path_1 = require("path");
14
+ const semver = require("semver");
14
15
  const imodeljs_native_1 = require("@bentley/imodeljs-native");
15
16
  const core_bentley_1 = require("@itwin/core-bentley");
16
17
  const core_common_1 = require("@itwin/core-common");
18
+ const IModelHost_1 = require("../IModelHost");
17
19
  const IModelJsFs_1 = require("../IModelJsFs");
18
20
  const SQLiteDb_1 = require("../SQLiteDb");
19
- // cspell:ignore rowid
20
- /** The names of Settings used by Workspace
21
+ const Settings_1 = require("./Settings");
22
+ const SettingsSchemas_1 = require("./SettingsSchemas");
23
+ /* eslint-disable @typescript-eslint/naming-convention */
24
+ // cspell:ignore rowid primarykey
25
+ /** The Settings used by Workspace api
21
26
  * @beta
22
27
  */
23
- var WorkspaceSetting;
24
- (function (WorkspaceSetting) {
25
- WorkspaceSetting["ContainerAlias"] = "workspace/container/alias";
26
- })(WorkspaceSetting || (WorkspaceSetting = {}));
27
- const workspaceDbFileExt = "itwin-workspace";
28
+ exports.WorkspaceSetting = {
29
+ Accounts: "cloud/accounts",
30
+ Containers: "cloud/containers",
31
+ Databases: "workspace/databases",
32
+ };
28
33
  /** @internal */
29
34
  class ITwinWorkspace {
30
35
  constructor(settings, opts) {
31
36
  var _a;
32
37
  this._containers = new Map();
33
38
  this.settings = settings;
34
- this.containerDir = (_a = opts === null || opts === void 0 ? void 0 : opts.containerDir) !== null && _a !== void 0 ? _a : (0, path_1.join)(imodeljs_native_1.NativeLibrary.defaultLocalDir, "iTwin", "Workspace");
39
+ this.containerDir = (_a = opts === null || opts === void 0 ? void 0 : opts.containerDir) !== null && _a !== void 0 ? _a : (0, path_1.join)(IModelHost_1.IModelHost.cacheDir, "Workspace");
40
+ this._cloudCacheProps = opts === null || opts === void 0 ? void 0 : opts.cloudCacheProps;
41
+ let settingsFiles = opts === null || opts === void 0 ? void 0 : opts.settingsFiles;
42
+ if (settingsFiles) {
43
+ if (typeof settingsFiles === "string")
44
+ settingsFiles = [settingsFiles];
45
+ settingsFiles.forEach((file) => settings.addFile(file, Settings_1.SettingsPriority.application));
46
+ }
47
+ }
48
+ get cloudCache() {
49
+ var _a, _b, _c, _d, _e, _f;
50
+ if (undefined === this._cloudCache) {
51
+ const cacheProps = {
52
+ ...this._cloudCacheProps,
53
+ rootDir: (_b = (_a = this._cloudCacheProps) === null || _a === void 0 ? void 0 : _a.rootDir) !== null && _b !== void 0 ? _b : (0, path_1.join)(this.containerDir, "cloud"),
54
+ cacheSize: (_d = (_c = this._cloudCacheProps) === null || _c === void 0 ? void 0 : _c.cacheSize) !== null && _d !== void 0 ? _d : "20G",
55
+ name: (_f = (_e = this._cloudCacheProps) === null || _e === void 0 ? void 0 : _e.name) !== null && _f !== void 0 ? _f : "workspace",
56
+ };
57
+ IModelJsFs_1.IModelJsFs.recursiveMkDirSync(cacheProps.rootDir);
58
+ if (cacheProps.clearContents)
59
+ fs.emptyDirSync(cacheProps.rootDir);
60
+ try {
61
+ this._cloudCache = new IModelHost_1.IModelHost.platform.CloudCache(cacheProps);
62
+ }
63
+ catch (e) {
64
+ core_bentley_1.Logger.logError("workspace", `error creating workspace cloud cache in ${cacheProps.rootDir}`);
65
+ throw e;
66
+ }
67
+ }
68
+ return this._cloudCache;
35
69
  }
36
70
  addContainer(toAdd) {
37
71
  if (undefined !== this._containers.get(toAdd.id))
38
72
  throw new Error("container already exists in workspace");
39
73
  this._containers.set(toAdd.id, toAdd);
40
74
  }
41
- getContainer(props) {
42
- var _a;
43
- const id = this.resolveContainerId(props);
44
- if (undefined === id)
45
- throw new Error(`can't resolve workspace container name [${props.containerName}]`);
46
- return (_a = this._containers.get(id)) !== null && _a !== void 0 ? _a : new ITwinWorkspaceContainer(this, id);
75
+ findContainer(containerId) {
76
+ return this._containers.get(containerId);
47
77
  }
48
- async getWorkspaceDb(props) {
49
- return this.getContainer(props).getWorkspaceDb(props);
78
+ getContainer(props, account) {
79
+ var _a;
80
+ return (_a = this.findContainer(props.containerId)) !== null && _a !== void 0 ? _a : new ITwinWorkspaceContainer(this, props, account);
81
+ }
82
+ getWorkspaceDbFromProps(dbProps, containerProps, account) {
83
+ return this.getContainer(containerProps, account).getWorkspaceDb(dbProps);
84
+ }
85
+ getWorkspaceDb(dbAlias) {
86
+ const dbProps = this.resolveDatabase(dbAlias);
87
+ const containerProps = this.resolveContainer(dbProps.containerName);
88
+ const account = containerProps.accountName !== "" ? this.resolveAccount(containerProps.accountName) : undefined;
89
+ let container = this.findContainer(containerProps.containerId);
90
+ if (undefined === container) {
91
+ container = this.getContainer(containerProps, account);
92
+ }
93
+ return container === null || container === void 0 ? void 0 : container.getWorkspaceDb(dbProps);
50
94
  }
51
- async loadSettingsDictionary(settingRsc, priority) {
52
- const db = await this.getWorkspaceDb(settingRsc);
53
- const setting = db.getString(settingRsc.rscName);
95
+ loadSettingsDictionary(settingRsc, db, priority) {
96
+ const setting = db.getString(settingRsc);
54
97
  if (undefined === setting)
55
- throw new Error(`could not load setting resource ${settingRsc.rscName}`);
56
- this.settings.addJson(`${db.container.id}/${db.dbName}/${settingRsc.rscName}`, priority, setting);
98
+ throw new Error(`could not load setting resource ${settingRsc}`);
99
+ this.settings.addJson(`${db.container.id}/${db.dbName}/${settingRsc}`, priority, setting);
57
100
  }
58
101
  close() {
59
102
  this.settings.close();
60
103
  for (const [_id, container] of this._containers)
61
104
  container.close();
62
105
  this._containers.clear();
106
+ if (this._cloudCache) {
107
+ this._cloudCache.destroy();
108
+ this._cloudCache = undefined;
109
+ }
110
+ }
111
+ resolveAccount(accountName) {
112
+ const resolved = this.settings.resolveSetting(exports.WorkspaceSetting.Accounts, (val) => {
113
+ if (Array.isArray(val)) {
114
+ for (const entry of val) {
115
+ if (typeof entry === "object" && entry.name === accountName)
116
+ return SettingsSchemas_1.SettingsSchemas.validateArrayObject(entry, exports.WorkspaceSetting.Accounts, accountName);
117
+ }
118
+ }
119
+ return undefined; // keep going through all settings dictionaries
120
+ });
121
+ if (resolved === undefined)
122
+ throw new Error(`no setting "${exports.WorkspaceSetting.Accounts}" entry for "${accountName}"`);
123
+ return resolved;
124
+ }
125
+ resolveContainer(containerName) {
126
+ const resolved = this.settings.resolveSetting(exports.WorkspaceSetting.Containers, (val) => {
127
+ if (Array.isArray(val)) {
128
+ for (const entry of val) {
129
+ if (typeof entry === "object" && entry.name === containerName)
130
+ return SettingsSchemas_1.SettingsSchemas.validateArrayObject(entry, exports.WorkspaceSetting.Containers, containerName);
131
+ }
132
+ }
133
+ return undefined; // keep going through all settings dictionaries
134
+ });
135
+ if (resolved === undefined)
136
+ throw new Error(`no setting "${exports.WorkspaceSetting.Containers}" entry for "${containerName}"`);
137
+ return resolved;
63
138
  }
64
- resolveContainerId(props) {
65
- if (props.containerId)
66
- return props.containerId; // if the container id is supplied, just use it
67
- const id = this.settings.resolveSetting(WorkspaceSetting.ContainerAlias, (val) => {
139
+ resolveDatabase(databaseName) {
140
+ const resolved = this.settings.resolveSetting(exports.WorkspaceSetting.Databases, (val) => {
68
141
  if (Array.isArray(val)) {
69
142
  for (const entry of val) {
70
- if (typeof entry === "object" && entry.name === props.containerName && typeof entry.id === "string")
71
- return entry.id;
143
+ if (typeof entry === "object" && entry.name === databaseName)
144
+ return SettingsSchemas_1.SettingsSchemas.validateArrayObject(entry, exports.WorkspaceSetting.Databases, databaseName);
72
145
  }
73
146
  }
74
147
  return undefined; // keep going through all settings dictionaries
75
- }, props.containerName);
76
- if (undefined === id)
77
- throw new Error("Unable to resolve container id.");
78
- return id;
148
+ });
149
+ if (resolved === undefined)
150
+ throw new Error(`no setting "${exports.WorkspaceSetting.Databases}" entry for "${databaseName}"`);
151
+ return resolved;
79
152
  }
80
153
  }
81
154
  exports.ITwinWorkspace = ITwinWorkspace;
82
155
  /** @internal */
83
156
  class ITwinWorkspaceContainer {
84
- constructor(workspace, id) {
157
+ constructor(workspace, props, account) {
85
158
  this._wsDbs = new Map();
86
- ITwinWorkspaceContainer.validateContainerId(id);
159
+ ITwinWorkspaceContainer.validateContainerId(props.containerId);
87
160
  this.workspace = workspace;
88
- this.id = id;
161
+ this.id = props.containerId;
162
+ if ((account === null || account === void 0 ? void 0 : account.accessName) && account.storageType)
163
+ this.cloudContainer = new IModelHost_1.IModelHost.platform.CloudContainer({ accessToken: "", ...props, ...account });
89
164
  workspace.addContainer(this);
90
165
  this.filesDir = (0, path_1.join)(this.dirName, "Files");
166
+ const cloudContainer = this.cloudContainer;
167
+ if (undefined === cloudContainer)
168
+ return;
169
+ cloudContainer.connect(this.workspace.cloudCache);
170
+ if (props.syncOnConnect) {
171
+ try {
172
+ cloudContainer.checkForChanges();
173
+ }
174
+ catch (e) {
175
+ // must be offline
176
+ }
177
+ }
91
178
  }
92
179
  get dirName() { return (0, path_1.join)(this.workspace.containerDir, this.id); }
180
+ static noLeadingOrTrailingSpaces(name, msg) {
181
+ if (name.trim() !== name)
182
+ throw new Error(`${msg} [${name}] may not have leading or tailing spaces`);
183
+ }
93
184
  /** rules for ContainerIds (from Azure, see https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata)
94
185
  * - may only contain lower case letters, numbers or dashes
95
186
  * - may not start or end with with a dash nor have more than one dash in a row
@@ -99,79 +190,148 @@ class ITwinWorkspaceContainer {
99
190
  if (!/^(?=.{3,63}$)[a-z0-9]+(-[a-z0-9]+)*$/g.test(id))
100
191
  throw new Error(`invalid containerId: [${id}]`);
101
192
  }
193
+ static validateDbName(dbName) {
194
+ if (dbName === "" || dbName.length > 255 || /[#\.<>:"/\\"`'|?*\u0000-\u001F]/g.test(dbName) || /^(con|prn|aux|nul|com\d|lpt\d)$/i.test(dbName))
195
+ throw new Error(`invalid dbName: [${dbName}]`);
196
+ this.noLeadingOrTrailingSpaces(dbName, "dbName");
197
+ }
198
+ static validateVersion(version) {
199
+ var _a, _b;
200
+ version = version !== null && version !== void 0 ? version : "1.0.0";
201
+ if (version) {
202
+ const opts = { loose: true, includePrerelease: true };
203
+ // clean allows prerelease, so try it first. If that fails attempt to coerce it (coerce strips prerelease even if you say not to.)
204
+ const semVersion = (_a = semver.clean(version, opts)) !== null && _a !== void 0 ? _a : (_b = semver.coerce(version, opts)) === null || _b === void 0 ? void 0 : _b.version;
205
+ if (!semVersion)
206
+ throw new Error("invalid version specification");
207
+ version = semVersion;
208
+ }
209
+ return version;
210
+ }
211
+ static parseDbFileName(dbFileName) {
212
+ const parts = dbFileName.split(":");
213
+ return { dbName: parts[0], version: parts[1] };
214
+ }
215
+ static makeDbFileName(dbName, version) {
216
+ return `${dbName}:${this.validateVersion(version)}`;
217
+ }
218
+ static resolveCloudFileName(cloudContainer, props) {
219
+ var _a;
220
+ const dbName = props.dbName;
221
+ const dbs = cloudContainer.queryDatabases(`${dbName}*`); // get all databases that start with dbName
222
+ const versions = [];
223
+ for (const db of dbs) {
224
+ const thisDb = ITwinWorkspaceContainer.parseDbFileName(db);
225
+ if (thisDb.dbName === dbName && "string" === typeof thisDb.version && thisDb.version.length > 0)
226
+ versions.push(thisDb.version);
227
+ }
228
+ if (versions.length === 0)
229
+ versions[0] = "1.0.0";
230
+ const range = (_a = props.version) !== null && _a !== void 0 ? _a : "*";
231
+ try {
232
+ const version = semver.maxSatisfying(versions, range, { loose: true, includePrerelease: props.includePrerelease });
233
+ if (version)
234
+ return `${dbName}:${version}`;
235
+ }
236
+ catch (e) {
237
+ }
238
+ throw new Error(`No version of [${dbName}] available for "${range}"`);
239
+ }
240
+ /**
241
+ * Create a copy of an existing [[WorkspaceDb]] in a cloud container with a new version number.
242
+ * @param cloudContainer The attached cloud container holding the existing WorkspaceDb
243
+ * @param fromProps Properties that describe the source WorkspaceDb for the new version
244
+ * @param versionType The type of version increment to apply to the existing version.
245
+ * @note This requires that the cloudContainer is attached and the write lock on the container be held. The copy should be modified with
246
+ * new content before the write lock is released, and thereafter should never be modified again.
247
+ */
248
+ static async makeNewVersion(cloudContainer, fromProps, versionType) {
249
+ const oldName = this.resolveCloudFileName(cloudContainer, fromProps);
250
+ const oldDb = this.parseDbFileName(oldName);
251
+ const newVersion = semver.inc(oldDb.version, versionType);
252
+ if (!newVersion)
253
+ throw new Error("invalid version");
254
+ const newName = this.makeDbFileName(oldDb.dbName, newVersion);
255
+ await cloudContainer.copyDatabase(oldName, newName);
256
+ return { oldName, newName };
257
+ }
258
+ /**
259
+ * Convert a WorkspaceDb.Props specification into a DbFileName. For cloud-based containers, this resolves to the dbName, incorporating the appropriate
260
+ * version. For file-based containers, this returns a local filename of a WorkspaceDb file with the extension ".itwin-workspace"
261
+ */
262
+ resolveDbFileName(props) {
263
+ const cloudContainer = this.cloudContainer;
264
+ if (undefined === cloudContainer)
265
+ return (0, path_1.join)(this.dirName, `${props.dbName}.${ITwinWorkspaceDb.fileExt}`); // local file, versions not allowed
266
+ return ITwinWorkspaceContainer.resolveCloudFileName(cloudContainer, props);
267
+ }
102
268
  addWorkspaceDb(toAdd) {
103
269
  if (undefined !== this._wsDbs.get(toAdd.dbName))
104
- throw new Error("dbName already exists in workspace");
270
+ throw new Error(`workspaceDb ${toAdd.dbName} already exists in workspace`);
105
271
  this._wsDbs.set(toAdd.dbName, toAdd);
106
272
  }
107
- async getWorkspaceDb(props) {
273
+ getWorkspaceDb(props) {
108
274
  var _a;
109
- const db = (_a = this._wsDbs.get(props.dbName)) !== null && _a !== void 0 ? _a : new ITwinWorkspaceDb(props.dbName, this);
110
- if (!db.isOpen) {
111
- if (props.cloudProps)
112
- await imodeljs_native_1.CloudSqlite.downloadDb({ ...db, ...props.cloudProps, containerId: this.id });
275
+ const db = (_a = this._wsDbs.get(props.dbName)) !== null && _a !== void 0 ? _a : new ITwinWorkspaceDb(props, this);
276
+ if (!db.isOpen)
113
277
  db.open();
114
- }
115
278
  return db;
116
279
  }
117
280
  dropWorkspaceDb(toDrop) {
118
281
  const name = toDrop.dbName;
119
282
  const wsDb = this._wsDbs.get(name);
120
283
  if (wsDb === toDrop) {
121
- wsDb.close();
122
284
  this._wsDbs.delete(name);
285
+ wsDb.close();
123
286
  }
124
287
  }
125
288
  close() {
289
+ var _a;
126
290
  for (const [_name, db] of this._wsDbs)
127
291
  db.close();
128
292
  this._wsDbs.clear();
293
+ (_a = this.cloudContainer) === null || _a === void 0 ? void 0 : _a.disconnect();
129
294
  }
295
+ /** Delete all local files extracted by [[WorkspaceDb.getFile]] for this container. */
130
296
  purgeContainerFiles() {
131
297
  IModelJsFs_1.IModelJsFs.purgeDirSync(this.filesDir);
132
298
  }
133
299
  }
134
300
  exports.ITwinWorkspaceContainer = ITwinWorkspaceContainer;
135
301
  /**
136
- * A local file holding a WorkspaceDb.
302
+ * Implementation of WorkspaceDb
137
303
  * @beta
138
304
  */
139
305
  class ITwinWorkspaceDb {
140
- constructor(dbName, container) {
141
- this.sqliteDb = new SQLiteDb_1.SQLiteDb(); // eslint-disable-line @typescript-eslint/naming-convention
142
- this.onClosed = new core_bentley_1.BeEvent();
143
- ITwinWorkspaceDb.validateDbName(dbName);
144
- this.dbName = dbName;
306
+ constructor(props, container) {
307
+ /** the SQLiteDb for this WorkspaceDb*/
308
+ this.sqliteDb = new SQLiteDb_1.SQLiteDb();
309
+ /** called before db is closed */
310
+ this.onClose = new core_bentley_1.BeEvent();
311
+ ITwinWorkspaceContainer.validateDbName(props.dbName);
312
+ this.dbName = props.dbName;
145
313
  this.container = container;
146
- this.localFile = (0, path_1.join)(container.dirName, `${dbName}.${workspaceDbFileExt}`);
314
+ this.dbFileName = container.resolveDbFileName(props);
147
315
  container.addWorkspaceDb(this);
148
316
  }
149
- static noLeadingOrTrailingSpaces(name, msg) {
150
- if (name.trim() !== name)
151
- throw new Error(`${msg} [${name}] may not have leading or tailing spaces`);
152
- }
153
- static validateDbName(dbName) {
154
- if (dbName === "" || dbName.length > 255 || /[#\.<>:"/\\"`'|?*\u0000-\u001F]/g.test(dbName) || /^(con|prn|aux|nul|com\d|lpt\d)$/i.test(dbName))
155
- throw new Error(`invalid dbName: [${dbName}]`);
156
- this.noLeadingOrTrailingSpaces(dbName, "dbName");
157
- }
317
+ /** true if this WorkspaceDb is currently open */
158
318
  get isOpen() { return this.sqliteDb.isOpen; }
159
319
  queryFileResource(rscName) {
160
320
  const info = this.sqliteDb.nativeDb.queryEmbeddedFile(rscName);
161
321
  if (undefined === info)
162
322
  return undefined;
163
323
  // since resource names can contain illegal characters, path separators, etc., we make the local file name from its hash, in hex.
164
- let localFileName = (0, path_1.join)(this.container.filesDir, (0, crypto_1.createHash)("sha1").update(rscName).digest("hex"));
324
+ let localFileName = (0, path_1.join)(this.container.filesDir, (0, crypto_1.createHash)("sha1").update(this.dbFileName).update(rscName).digest("hex"));
165
325
  if (info.fileExt !== "") // since some applications may expect to see the extension, append it here if it was supplied.
166
326
  localFileName = `${localFileName}.${info.fileExt}`;
167
327
  return { localFileName, info };
168
328
  }
169
329
  open() {
170
- this.sqliteDb.openDb(this.localFile, core_bentley_1.OpenMode.Readonly);
330
+ this.sqliteDb.openDb(this.dbFileName, core_bentley_1.OpenMode.Readonly, this.container.cloudContainer);
171
331
  }
172
332
  close() {
173
333
  if (this.isOpen) {
174
- this.onClosed.raiseEvent();
334
+ this.onClose.raiseEvent();
175
335
  this.sqliteDb.closeDb();
176
336
  this.container.dropWorkspaceDb(this);
177
337
  }
@@ -183,7 +343,7 @@ class ITwinWorkspaceDb {
183
343
  });
184
344
  }
185
345
  /** Get a BlobIO reader for a blob WorkspaceResource.
186
- * @note when finished, caller must call `close` on the BlobIO.
346
+ * @note when finished, caller *must* call `close` on the BlobIO.
187
347
  */
188
348
  getBlobReader(rscName) {
189
349
  return this.sqliteDb.withSqliteStatement("SELECT rowid from blobs WHERE id=?", (stmt) => {
@@ -220,8 +380,14 @@ class ITwinWorkspaceDb {
220
380
  fs.chmodSync(localFileName, "0444"); // set file readonly
221
381
  return localFileName;
222
382
  }
383
+ prefetch(opts) {
384
+ const cloudContainer = this.container.cloudContainer;
385
+ return (cloudContainer !== undefined) ? new IModelHost_1.IModelHost.platform.CloudPrefetch(cloudContainer, this.dbFileName, opts) : undefined;
386
+ }
223
387
  }
224
388
  exports.ITwinWorkspaceDb = ITwinWorkspaceDb;
389
+ /** file extension for local WorkspaceDbs */
390
+ ITwinWorkspaceDb.fileExt = "itwin-workspace";
225
391
  /**
226
392
  * An editable [[WorkspaceDb]]. This is used by administrators for creating and modifying `WorkspaceDb`s.
227
393
  * For cloud-backed containers, the write token must be obtained before this class may be used. Only one user at at time
@@ -229,12 +395,8 @@ exports.ITwinWorkspaceDb = ITwinWorkspaceDb;
229
395
  * @beta
230
396
  */
231
397
  class EditableWorkspaceDb extends ITwinWorkspaceDb {
232
- constructor() {
233
- super(...arguments);
234
- this._isCloudOpen = false;
235
- }
236
398
  static validateResourceName(name) {
237
- ITwinWorkspaceDb.noLeadingOrTrailingSpaces(name, "resource name");
399
+ ITwinWorkspaceContainer.noLeadingOrTrailingSpaces(name, "resource name");
238
400
  if (name.length > 1024)
239
401
  throw new Error("resource name too long");
240
402
  }
@@ -244,14 +406,7 @@ class EditableWorkspaceDb extends ITwinWorkspaceDb {
244
406
  throw new Error("value is too large");
245
407
  }
246
408
  open() {
247
- this.sqliteDb.openDb(this.localFile, core_bentley_1.OpenMode.ReadWrite);
248
- }
249
- close() {
250
- if (this._isCloudOpen) {
251
- // this.db.nativeDb.flushCloudUpload(); TODO: add back when available
252
- this._isCloudOpen = false;
253
- }
254
- super.close();
409
+ this.sqliteDb.openDb(this.dbFileName, core_bentley_1.OpenMode.ReadWrite, this.container.cloudContainer);
255
410
  }
256
411
  getFileModifiedTime(localFileName) {
257
412
  return Math.round(fs.statSync(localFileName).mtimeMs);
@@ -261,24 +416,40 @@ class EditableWorkspaceDb extends ITwinWorkspaceDb {
261
416
  stmt.bindString(1, rscName);
262
417
  bind === null || bind === void 0 ? void 0 : bind(stmt);
263
418
  const rc = stmt.step();
264
- if (core_bentley_1.DbResult.BE_SQLITE_DONE !== rc)
265
- throw new core_common_1.IModelError(rc, "workspace write error");
419
+ if (core_bentley_1.DbResult.BE_SQLITE_DONE !== rc) {
420
+ if (core_bentley_1.DbResult.BE_SQLITE_CONSTRAINT_PRIMARYKEY === rc)
421
+ throw new core_common_1.IModelError(rc, `resource "${rscName}" already exists`);
422
+ throw new core_common_1.IModelError(rc, `workspace [${sql}]`);
423
+ }
266
424
  });
267
425
  this.sqliteDb.saveChanges();
268
426
  }
269
- /** Create a new, empty, EditableWorkspaceDb for importing Workspace resources. */
270
- create() {
271
- IModelJsFs_1.IModelJsFs.recursiveMkDirSync((0, path_1.dirname)(this.localFile));
272
- this.sqliteDb.createDb(this.localFile);
273
- this.sqliteDb.executeSQL("CREATE TABLE strings(id TEXT PRIMARY KEY NOT NULL,value TEXT)");
274
- this.sqliteDb.executeSQL("CREATE TABLE blobs(id TEXT PRIMARY KEY NOT NULL,value BLOB)");
275
- this.sqliteDb.saveChanges();
276
- }
277
- static async cloneVersion(oldVersion, newVersion, cloudProps) {
278
- return imodeljs_native_1.CloudSqlite.copyDb(oldVersion, newVersion, cloudProps);
427
+ /** @internal */
428
+ async createDb(version) {
429
+ if (!this.container.cloudContainer) {
430
+ EditableWorkspaceDb.createEmpty(this.dbFileName);
431
+ }
432
+ else {
433
+ // currently the only way to create a workspaceDb in a cloud container is to create a temporary workspaceDb and upload it.
434
+ const tempDbFile = (0, path_1.join)(IModelHost_1.KnownLocations.tmpdir, `empty.${ITwinWorkspaceDb.fileExt}`);
435
+ if (fs.existsSync(tempDbFile))
436
+ fs.rmSync(tempDbFile);
437
+ EditableWorkspaceDb.createEmpty(tempDbFile);
438
+ this.dbFileName = ITwinWorkspaceContainer.makeDbFileName(this.dbName, version);
439
+ await imodeljs_native_1.CloudSqlite.uploadDb(this.container.cloudContainer, { localFileName: tempDbFile, dbName: this.dbFileName });
440
+ fs.rmSync(tempDbFile);
441
+ }
442
+ this.open();
279
443
  }
280
- async upload(cloudProps) {
281
- return imodeljs_native_1.CloudSqlite.uploadDb({ ...cloudProps, ...this });
444
+ /** Create a new, empty, EditableWorkspaceDb for importing Workspace resources. */
445
+ static createEmpty(fileName) {
446
+ const db = new SQLiteDb_1.SQLiteDb();
447
+ IModelJsFs_1.IModelJsFs.recursiveMkDirSync((0, path_1.dirname)(fileName));
448
+ db.createDb(fileName);
449
+ db.executeSQL("CREATE TABLE strings(id TEXT PRIMARY KEY NOT NULL,value TEXT)");
450
+ db.executeSQL("CREATE TABLE blobs(id TEXT PRIMARY KEY NOT NULL,value BLOB)");
451
+ db.saveChanges();
452
+ db.closeDb();
282
453
  }
283
454
  /** Add a new string resource to this WorkspaceDb.
284
455
  * @param rscName The name of the string resource.
@@ -349,7 +520,7 @@ class EditableWorkspaceDb extends ITwinWorkspaceDb {
349
520
  fileExt = fileExt.slice(1);
350
521
  this.sqliteDb.nativeDb.embedFile({ name: rscName, localFileName, date: this.getFileModifiedTime(localFileName), fileExt });
351
522
  }
352
- /** Replace an existing file resource with the contents of a local file.
523
+ /** Replace an existing file resource with the contents of another local file.
353
524
  * @param rscName The name of the file resource.
354
525
  * @param localFileName The name of a local file to be read.
355
526
  * @throws if rscName does not exist