@itwin/core-backend 3.3.0-dev.9 → 3.3.0

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