@itwin/core-backend 3.3.0-dev.6 → 3.3.0-dev.62

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