@itwin/core-backend 4.8.0-dev.3 → 4.8.0-dev.4

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 (98) hide show
  1. package/lib/cjs/BackendLoggerCategory.d.ts +3 -1
  2. package/lib/cjs/BackendLoggerCategory.d.ts.map +1 -1
  3. package/lib/cjs/BackendLoggerCategory.js +2 -0
  4. package/lib/cjs/BackendLoggerCategory.js.map +1 -1
  5. package/lib/cjs/BlobContainerService.d.ts +3 -3
  6. package/lib/cjs/BlobContainerService.d.ts.map +1 -1
  7. package/lib/cjs/BlobContainerService.js.map +1 -1
  8. package/lib/cjs/CloudSqlite.d.ts +21 -13
  9. package/lib/cjs/CloudSqlite.d.ts.map +1 -1
  10. package/lib/cjs/CloudSqlite.js +24 -6
  11. package/lib/cjs/CloudSqlite.js.map +1 -1
  12. package/lib/cjs/CodeService.d.ts +4 -4
  13. package/lib/cjs/CodeService.d.ts.map +1 -1
  14. package/lib/cjs/CodeService.js.map +1 -1
  15. package/lib/cjs/GeoCoordConfig.d.ts +10 -0
  16. package/lib/cjs/GeoCoordConfig.d.ts.map +1 -1
  17. package/lib/cjs/GeoCoordConfig.js +20 -23
  18. package/lib/cjs/GeoCoordConfig.js.map +1 -1
  19. package/lib/cjs/IModelDb.d.ts +14 -9
  20. package/lib/cjs/IModelDb.d.ts.map +1 -1
  21. package/lib/cjs/IModelDb.js +53 -17
  22. package/lib/cjs/IModelDb.js.map +1 -1
  23. package/lib/cjs/IModelHost.d.ts +8 -1
  24. package/lib/cjs/IModelHost.d.ts.map +1 -1
  25. package/lib/cjs/IModelHost.js +24 -15
  26. package/lib/cjs/IModelHost.js.map +1 -1
  27. package/lib/cjs/PropertyStore.d.ts +17 -7
  28. package/lib/cjs/PropertyStore.d.ts.map +1 -1
  29. package/lib/cjs/PropertyStore.js +11 -5
  30. package/lib/cjs/PropertyStore.js.map +1 -1
  31. package/lib/cjs/SQLiteDb.d.ts +2 -3
  32. package/lib/cjs/SQLiteDb.d.ts.map +1 -1
  33. package/lib/cjs/SQLiteDb.js +2 -3
  34. package/lib/cjs/SQLiteDb.js.map +1 -1
  35. package/lib/cjs/SchemaSync.d.ts +1 -1
  36. package/lib/cjs/SchemaSync.d.ts.map +1 -1
  37. package/lib/cjs/SchemaSync.js.map +1 -1
  38. package/lib/cjs/TextAnnotationGeometry.d.ts +4 -0
  39. package/lib/cjs/TextAnnotationGeometry.d.ts.map +1 -1
  40. package/lib/cjs/TextAnnotationGeometry.js +2 -3
  41. package/lib/cjs/TextAnnotationGeometry.js.map +1 -1
  42. package/lib/cjs/TextAnnotationLayout.d.ts +21 -13
  43. package/lib/cjs/TextAnnotationLayout.d.ts.map +1 -1
  44. package/lib/cjs/TextAnnotationLayout.js +119 -90
  45. package/lib/cjs/TextAnnotationLayout.js.map +1 -1
  46. package/lib/cjs/ViewStore.d.ts +16 -2
  47. package/lib/cjs/ViewStore.d.ts.map +1 -1
  48. package/lib/cjs/ViewStore.js +14 -1
  49. package/lib/cjs/ViewStore.js.map +1 -1
  50. package/lib/cjs/assets/Settings/Schemas/Base.Schema.json +33 -0
  51. package/lib/cjs/assets/Settings/Schemas/Gcs.schema.json +17 -21
  52. package/lib/cjs/assets/Settings/Schemas/Workspace.Schema.json +80 -38
  53. package/lib/cjs/assets/Settings/backend.setting.json5 +8 -119
  54. package/lib/cjs/core-backend.d.ts +2 -1
  55. package/lib/cjs/core-backend.d.ts.map +1 -1
  56. package/lib/cjs/core-backend.js +3 -2
  57. package/lib/cjs/core-backend.js.map +1 -1
  58. package/lib/cjs/internal/ImplementationProhibited.d.ts +44 -0
  59. package/lib/cjs/internal/ImplementationProhibited.d.ts.map +1 -0
  60. package/lib/cjs/internal/ImplementationProhibited.js +51 -0
  61. package/lib/cjs/internal/ImplementationProhibited.js.map +1 -0
  62. package/lib/cjs/internal/workspace/SettingsImpl.d.ts +43 -0
  63. package/lib/cjs/internal/workspace/SettingsImpl.d.ts.map +1 -0
  64. package/lib/cjs/internal/workspace/SettingsImpl.js +161 -0
  65. package/lib/cjs/internal/workspace/SettingsImpl.js.map +1 -0
  66. package/lib/cjs/internal/workspace/SettingsSchemasImpl.d.ts +6 -0
  67. package/lib/cjs/internal/workspace/SettingsSchemasImpl.d.ts.map +1 -0
  68. package/lib/cjs/internal/workspace/SettingsSchemasImpl.js +276 -0
  69. package/lib/cjs/internal/workspace/SettingsSchemasImpl.js.map +1 -0
  70. package/lib/cjs/internal/workspace/WorkspaceImpl.d.ts +41 -0
  71. package/lib/cjs/internal/workspace/WorkspaceImpl.d.ts.map +1 -0
  72. package/lib/cjs/internal/workspace/WorkspaceImpl.js +696 -0
  73. package/lib/cjs/internal/workspace/WorkspaceImpl.js.map +1 -0
  74. package/lib/cjs/internal/workspace/WorkspaceSqliteDb.d.ts +10 -0
  75. package/lib/cjs/internal/workspace/WorkspaceSqliteDb.d.ts.map +1 -0
  76. package/lib/cjs/internal/workspace/WorkspaceSqliteDb.js +41 -0
  77. package/lib/cjs/internal/workspace/WorkspaceSqliteDb.js.map +1 -0
  78. package/lib/cjs/rpc-impl/IModelReadRpcImpl.d.ts.map +1 -1
  79. package/lib/cjs/rpc-impl/IModelReadRpcImpl.js +1 -2
  80. package/lib/cjs/rpc-impl/IModelReadRpcImpl.js.map +1 -1
  81. package/lib/cjs/workspace/Settings.d.ts +167 -138
  82. package/lib/cjs/workspace/Settings.d.ts.map +1 -1
  83. package/lib/cjs/workspace/Settings.js +68 -138
  84. package/lib/cjs/workspace/Settings.js.map +1 -1
  85. package/lib/cjs/workspace/SettingsSchemas.d.ts +112 -48
  86. package/lib/cjs/workspace/SettingsSchemas.d.ts.map +1 -1
  87. package/lib/cjs/workspace/SettingsSchemas.js +1 -172
  88. package/lib/cjs/workspace/SettingsSchemas.js.map +1 -1
  89. package/lib/cjs/workspace/Workspace.d.ts +411 -277
  90. package/lib/cjs/workspace/Workspace.d.ts.map +1 -1
  91. package/lib/cjs/workspace/Workspace.js +76 -457
  92. package/lib/cjs/workspace/Workspace.js.map +1 -1
  93. package/lib/cjs/workspace/WorkspaceEditor.d.ts +245 -0
  94. package/lib/cjs/workspace/WorkspaceEditor.d.ts.map +1 -0
  95. package/lib/cjs/workspace/WorkspaceEditor.js +34 -0
  96. package/lib/cjs/workspace/WorkspaceEditor.js.map +1 -0
  97. package/package.json +12 -12
  98. package/lib/cjs/assets/Settings/Schemas/Cloud.Schema.json +0 -45
@@ -0,0 +1,696 @@
1
+ "use strict";
2
+ /*---------------------------------------------------------------------------------------------
3
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
4
+ * See LICENSE.md in the project root for license terms and full copyright notice.
5
+ *--------------------------------------------------------------------------------------------*/
6
+ /** @packageDocumentation
7
+ * @module Workspace
8
+ */
9
+ var _a, _b, _c, _d;
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.throwWorkspaceDbLoadErrors = exports.workspaceManifestProperty = exports.makeWorkspaceDbFileName = exports.parseWorkspaceDbFileName = exports.validateWorkspaceDbVersion = exports.validateWorkspaceContainerId = exports.validateWorkspaceDbName = exports.noLeadingOrTrailingSpaces = exports.constructWorkspaceEditor = exports.constructWorkspace = exports.constructWorkspaceDb = exports.workspaceDbFileExt = void 0;
12
+ const crypto_1 = require("crypto");
13
+ const fs = require("fs-extra");
14
+ const path_1 = require("path");
15
+ const semver = require("semver");
16
+ const core_bentley_1 = require("@itwin/core-bentley");
17
+ const core_common_1 = require("@itwin/core-common");
18
+ const CloudSqlite_1 = require("../../CloudSqlite");
19
+ const IModelHost_1 = require("../../IModelHost");
20
+ const IModelJsFs_1 = require("../../IModelJsFs");
21
+ const SQLiteDb_1 = require("../../SQLiteDb");
22
+ const Settings_1 = require("../../workspace/Settings");
23
+ const Workspace_1 = require("../../workspace/Workspace");
24
+ const WorkspaceEditor_1 = require("../../workspace/WorkspaceEditor");
25
+ const WorkspaceSqliteDb_1 = require("./WorkspaceSqliteDb");
26
+ const SettingsImpl_1 = require("./SettingsImpl");
27
+ const ImplementationProhibited_1 = require("../ImplementationProhibited");
28
+ function workspaceDbNameWithDefault(dbName) {
29
+ return dbName ?? "workspace-db";
30
+ }
31
+ /** file extension for local WorkspaceDbs */
32
+ exports.workspaceDbFileExt = "itwin-workspace";
33
+ function makeWorkspaceCloudCache(arg) {
34
+ const cache = CloudSqlite_1.CloudSqlite.CloudCaches.getCache(arg);
35
+ if (undefined === cache.workspaceContainers) // if we just created this container, add the map.
36
+ cache.workspaceContainers = new Map();
37
+ return cache;
38
+ }
39
+ function getContainerFullId(props) {
40
+ return `${props.baseUri}/${props.containerId}`;
41
+ }
42
+ function getWorkspaceCloudContainer(props, cache) {
43
+ const id = getContainerFullId(props);
44
+ let cloudContainer = cache.workspaceContainers.get(id);
45
+ if (undefined !== cloudContainer)
46
+ return cloudContainer;
47
+ cloudContainer = CloudSqlite_1.CloudSqlite.createCloudContainer(props);
48
+ cache.workspaceContainers.set(id, cloudContainer);
49
+ cloudContainer.connectCount = 0;
50
+ cloudContainer.sharedConnect = function () {
51
+ if (this.connectCount++ === 0) {
52
+ this.connect(cache);
53
+ return true;
54
+ }
55
+ return false;
56
+ };
57
+ cloudContainer.sharedDisconnect = function () {
58
+ if (--this.connectCount <= 0) {
59
+ this.disconnect();
60
+ cache.workspaceContainers.delete(id);
61
+ this.connectCount = 0;
62
+ }
63
+ };
64
+ return cloudContainer;
65
+ }
66
+ class WorkspaceContainerImpl {
67
+ get cloudContainer() {
68
+ return this._cloudContainer;
69
+ }
70
+ get dirName() { return (0, path_1.join)(this.workspace.containerDir, this.id); }
71
+ constructor(workspace, props) {
72
+ this[_a] = undefined;
73
+ this._wsDbs = new Map();
74
+ validateWorkspaceContainerId(props.containerId);
75
+ this.workspace = workspace;
76
+ this.id = props.containerId;
77
+ this.fromProps = props;
78
+ if (props.baseUri !== "")
79
+ this._cloudContainer = getWorkspaceCloudContainer(props, this.workspace.getCloudCache());
80
+ workspace.addContainer(this);
81
+ this.filesDir = (0, path_1.join)(this.dirName, "Files");
82
+ const cloudContainer = this.cloudContainer;
83
+ if (undefined === cloudContainer)
84
+ return;
85
+ // sharedConnect returns true if we just connected (if the container is shared, it may have already been connected)
86
+ if (cloudContainer.sharedConnect() && false !== props.syncOnConnect) {
87
+ try {
88
+ cloudContainer.checkForChanges();
89
+ }
90
+ catch (e) {
91
+ // must be offline
92
+ }
93
+ }
94
+ }
95
+ resolveDbFileName(props) {
96
+ const cloudContainer = this.cloudContainer;
97
+ if (undefined === cloudContainer)
98
+ return (0, path_1.join)(this.dirName, `${props.dbName}.${exports.workspaceDbFileExt}`); // local file, versions not allowed
99
+ const dbName = workspaceDbNameWithDefault(props.dbName);
100
+ const dbs = cloudContainer.queryDatabases(`${dbName}*`); // get all databases that start with dbName
101
+ const versions = [];
102
+ for (const db of dbs) {
103
+ const thisDb = parseWorkspaceDbFileName(db);
104
+ if (thisDb.dbName === dbName && "string" === typeof thisDb.version && thisDb.version.length > 0)
105
+ versions.push(thisDb.version);
106
+ }
107
+ if (versions.length === 0)
108
+ versions[0] = "1.0.0";
109
+ const range = props.version ?? "*";
110
+ try {
111
+ const version = semver.maxSatisfying(versions, range, { loose: true, includePrerelease: props.includePrerelease });
112
+ if (version)
113
+ return `${dbName}:${version}`;
114
+ }
115
+ catch (e) {
116
+ }
117
+ throwWorkspaceDbLoadError(`No version of '${dbName}' available for "${range}"`, props);
118
+ }
119
+ addWorkspaceDb(toAdd) {
120
+ if (undefined !== this._wsDbs.get(toAdd.dbName))
121
+ throw new Error(`workspaceDb '${toAdd.dbName}' already exists in workspace`);
122
+ this._wsDbs.set(toAdd.dbName, toAdd);
123
+ }
124
+ getWorkspaceDb(props) {
125
+ return this._wsDbs.get(workspaceDbNameWithDefault(props?.dbName)) ?? new WorkspaceDbImpl(props ?? {}, this);
126
+ }
127
+ closeWorkspaceDb(toDrop) {
128
+ const name = toDrop.dbName;
129
+ const wsDb = this._wsDbs.get(name);
130
+ if (wsDb === toDrop) {
131
+ this._wsDbs.delete(name);
132
+ wsDb.close();
133
+ }
134
+ }
135
+ close() {
136
+ for (const [_name, db] of this._wsDbs)
137
+ db.close();
138
+ this._wsDbs.clear();
139
+ this.cloudContainer?.sharedDisconnect();
140
+ }
141
+ }
142
+ _a = ImplementationProhibited_1.implementationProhibited;
143
+ /** Implementation of WorkspaceDb */
144
+ class WorkspaceDbImpl {
145
+ /** true if this WorkspaceDb is currently open */
146
+ get isOpen() { return this.sqliteDb.isOpen; }
147
+ get container() { return this._container; }
148
+ queryFileResource(rscName) {
149
+ const info = this.sqliteDb.nativeDb.queryEmbeddedFile(rscName);
150
+ if (undefined === info)
151
+ return undefined;
152
+ // since resource names can contain illegal characters, path separators, etc., we make the local file name from its hash, in hex.
153
+ let localFileName = (0, path_1.join)(this._container.filesDir, (0, crypto_1.createHash)("sha1").update(this.dbFileName).update(rscName).digest("hex"));
154
+ if (info.fileExt !== "") // since some applications may expect to see the extension, append it here if it was supplied.
155
+ localFileName = `${localFileName}.${info.fileExt}`;
156
+ return { localFileName, info };
157
+ }
158
+ constructor(props, container) {
159
+ this[_b] = undefined;
160
+ this.sqliteDb = new WorkspaceSqliteDb_1.WorkspaceSqliteDb();
161
+ this.onClose = new core_bentley_1.BeEvent();
162
+ this.dbName = workspaceDbNameWithDefault(props.dbName);
163
+ validateWorkspaceDbName(this.dbName);
164
+ this._container = container;
165
+ this.dbFileName = container.resolveDbFileName(props);
166
+ container.addWorkspaceDb(this);
167
+ if (true === props.prefetch)
168
+ this.prefetch();
169
+ }
170
+ open() {
171
+ this.sqliteDb.openDb(this.dbFileName, core_bentley_1.OpenMode.Readonly, this._container.cloudContainer);
172
+ }
173
+ close() {
174
+ if (this.isOpen) {
175
+ this.onClose.raiseEvent();
176
+ this.sqliteDb.closeDb();
177
+ this._container.closeWorkspaceDb(this);
178
+ }
179
+ }
180
+ get version() {
181
+ return parseWorkspaceDbFileName(this.dbFileName).version;
182
+ }
183
+ get manifest() {
184
+ return this._manifest ??= this.withOpenDb((db) => {
185
+ const manifestJson = db.nativeDb.queryFileProperty(exports.workspaceManifestProperty, true);
186
+ return manifestJson ? JSON.parse(manifestJson) : { workspaceName: this.dbName };
187
+ });
188
+ }
189
+ withOpenDb(operation) {
190
+ const done = this.isOpen ? () => { } : (this.open(), () => this.close());
191
+ try {
192
+ return operation(this.sqliteDb);
193
+ }
194
+ finally {
195
+ done();
196
+ }
197
+ }
198
+ getString(rscName) {
199
+ return this.withOpenDb((db) => {
200
+ return db.withSqliteStatement("SELECT value from strings WHERE id=?", (stmt) => {
201
+ stmt.bindString(1, rscName);
202
+ return core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step() ? stmt.getValueString(0) : undefined;
203
+ });
204
+ });
205
+ }
206
+ getBlobReader(rscName) {
207
+ return this.sqliteDb.withSqliteStatement("SELECT rowid from blobs WHERE id=?", (stmt) => {
208
+ stmt.bindString(1, rscName);
209
+ const blobReader = SQLiteDb_1.SQLiteDb.createBlobIO();
210
+ blobReader.open(this.sqliteDb.nativeDb, { tableName: "blobs", columnName: "value", row: stmt.getValueInteger(0) });
211
+ return blobReader;
212
+ });
213
+ }
214
+ getBlob(rscName) {
215
+ return this.withOpenDb((db) => {
216
+ return db.withSqliteStatement("SELECT value from blobs WHERE id=?", (stmt) => {
217
+ stmt.bindString(1, rscName);
218
+ return core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step() ? stmt.getValueBlob(0) : undefined;
219
+ });
220
+ });
221
+ }
222
+ getFile(rscName, targetFileName) {
223
+ return this.withOpenDb((db) => {
224
+ const file = this.queryFileResource(rscName);
225
+ if (!file)
226
+ return undefined;
227
+ const info = file.info;
228
+ const localFileName = targetFileName ?? file.localFileName;
229
+ // check whether the file is already up to date.
230
+ const stat = fs.existsSync(localFileName) && fs.statSync(localFileName);
231
+ if (stat && Math.round(stat.mtimeMs) === info.date && stat.size === info.size)
232
+ return localFileName; // yes, we're done
233
+ // extractEmbeddedFile fails if the file exists or if the directory does not exist
234
+ if (stat)
235
+ fs.removeSync(localFileName);
236
+ else
237
+ IModelJsFs_1.IModelJsFs.recursiveMkDirSync((0, path_1.dirname)(localFileName));
238
+ db.nativeDb.extractEmbeddedFile({ name: rscName, localFileName });
239
+ const date = new Date(info.date);
240
+ fs.utimesSync(localFileName, date, date); // set the last-modified date of the file to match date in container
241
+ fs.chmodSync(localFileName, "0444"); // set file readonly
242
+ return localFileName;
243
+ });
244
+ }
245
+ prefetch(opts) {
246
+ const cloudContainer = this._container.cloudContainer;
247
+ if (cloudContainer === undefined)
248
+ throw new Error("no cloud container to prefetch");
249
+ return CloudSqlite_1.CloudSqlite.startCloudPrefetch(cloudContainer, this.dbFileName, opts);
250
+ }
251
+ queryResources(args) {
252
+ const table = "blob" !== args.type ? "strings" : "blobs";
253
+ this.withOpenDb((db) => {
254
+ const where = undefined !== args.namePattern ? ` WHERE id ${args.nameCompare ?? "="} ?` : "";
255
+ db.withSqliteStatement(`SELECT id from ${table}${where}`, (stmt) => {
256
+ function* makeIterable() {
257
+ while (core_bentley_1.DbResult.BE_SQLITE_ROW === stmt.step()) {
258
+ yield stmt.getValueString(0);
259
+ }
260
+ }
261
+ if (undefined !== args.namePattern) {
262
+ stmt.bindString(1, args.namePattern);
263
+ }
264
+ args.callback(makeIterable());
265
+ });
266
+ });
267
+ }
268
+ }
269
+ _b = ImplementationProhibited_1.implementationProhibited;
270
+ /** Implementation of Workspace */
271
+ class WorkspaceImpl {
272
+ getCloudCache() {
273
+ return this._cloudCache ??= makeWorkspaceCloudCache({ cacheName: "Workspace", cacheSize: "20G" });
274
+ }
275
+ constructor(settings, opts) {
276
+ this[_c] = undefined;
277
+ this._containers = new Map();
278
+ this.settings = settings;
279
+ this.containerDir = opts?.containerDir ?? (0, path_1.join)(IModelHost_1.IModelHost.cacheDir, "Workspace");
280
+ let settingsFiles = opts?.settingsFiles;
281
+ if (settingsFiles) {
282
+ if (typeof settingsFiles === "string")
283
+ settingsFiles = [settingsFiles];
284
+ settingsFiles.forEach((file) => settings.addFile(file, Settings_1.SettingsPriority.application));
285
+ }
286
+ }
287
+ addContainer(toAdd) {
288
+ if (undefined !== this._containers.get(toAdd.id))
289
+ throw new Error("container already exists in workspace");
290
+ this._containers.set(toAdd.id, toAdd);
291
+ }
292
+ findContainer(containerId) {
293
+ return this._containers.get(containerId);
294
+ }
295
+ getContainer(props) {
296
+ return this.findContainer(props.containerId) ?? new WorkspaceContainerImpl(this, props);
297
+ }
298
+ async getContainerAsync(props) {
299
+ const accessToken = props.accessToken ?? ((props.baseUri === "") || props.isPublic) ? "" : await CloudSqlite_1.CloudSqlite.requestToken({ ...props, accessLevel: "read" });
300
+ return this.getContainer({ ...props, accessToken });
301
+ }
302
+ async getWorkspaceDb(props) {
303
+ let container = this.findContainer(props.containerId);
304
+ if (undefined === container) {
305
+ const accessToken = props.isPublic ? "" : await CloudSqlite_1.CloudSqlite.requestToken({ accessLevel: "read", ...props });
306
+ container = new WorkspaceContainerImpl(this, { ...props, accessToken });
307
+ }
308
+ return container.getWorkspaceDb(props);
309
+ }
310
+ async loadSettingsDictionary(props, problems) {
311
+ if (!Array.isArray(props))
312
+ props = [props];
313
+ for (const prop of props) {
314
+ const db = await this.getWorkspaceDb(prop);
315
+ db.open();
316
+ try {
317
+ const manifest = db.manifest;
318
+ const dictProps = { name: prop.resourceName, workspaceDb: db, priority: prop.priority };
319
+ // don't load if we already have this dictionary. Happens if the same WorkspaceDb is in more than one list
320
+ if (undefined === this.settings.getDictionary(dictProps)) {
321
+ const settingsJson = db.getString(prop.resourceName);
322
+ if (undefined === settingsJson)
323
+ throwWorkspaceDbLoadError(`could not load setting dictionary resource '${prop.resourceName}' from: '${manifest.workspaceName}'`, prop, db);
324
+ db.close(); // don't leave this db open in case we're going to find another dictionary in it recursively.
325
+ this.settings.addJson(dictProps, settingsJson);
326
+ const dict = this.settings.getDictionary(dictProps);
327
+ if (dict) {
328
+ Workspace_1.Workspace.onSettingsDictionaryLoadedFn({ dict, from: db });
329
+ // if the dictionary we just loaded has a "settingsWorkspaces" entry, load them too, recursively
330
+ const nested = dict.getSetting(Workspace_1.WorkspaceSettingNames.settingsWorkspaces);
331
+ if (nested !== undefined) {
332
+ IModelHost_1.IModelHost.settingsSchemas.validateSetting(nested, Workspace_1.WorkspaceSettingNames.settingsWorkspaces);
333
+ await this.loadSettingsDictionary(nested, problems);
334
+ }
335
+ }
336
+ }
337
+ }
338
+ catch (e) {
339
+ db.close();
340
+ problems?.push(e);
341
+ }
342
+ }
343
+ }
344
+ close() {
345
+ this.settings.close();
346
+ for (const [_id, container] of this._containers)
347
+ container.close();
348
+ this._containers.clear();
349
+ }
350
+ resolveWorkspaceDbSetting(settingName, filter) {
351
+ const settingDef = IModelHost_1.IModelHost.settingsSchemas.settingDefs.get(settingName);
352
+ const combine = settingDef?.combineArray === true;
353
+ filter = filter ?? (() => true);
354
+ const result = [];
355
+ for (const entry of this.settings.getSettingEntries(settingName)) {
356
+ for (const dbProp of entry.value) {
357
+ if (filter(dbProp, entry.dictionary)) {
358
+ result.push(dbProp);
359
+ }
360
+ }
361
+ if (!combine) {
362
+ break;
363
+ }
364
+ }
365
+ return result;
366
+ }
367
+ async getWorkspaceDbs(args) {
368
+ const dbList = (args.settingName !== undefined) ? this.resolveWorkspaceDbSetting(args.settingName, args.filter) : args.dbs;
369
+ const result = [];
370
+ const pushUnique = (wsDb) => {
371
+ for (const db of result) {
372
+ // if we already have this db, skip it. The test below also has to consider that we create a separate WorkspaceDb object for the same
373
+ // database from more than one Workspace (though then they must use a "shared" CloudContainer).
374
+ if (db === wsDb || ((db.container.cloudContainer === wsDb.container.cloudContainer) && (db.dbFileName === wsDb.dbFileName)))
375
+ return; // this db is redundant
376
+ }
377
+ result.push(wsDb);
378
+ };
379
+ for (const dbProps of dbList) {
380
+ try {
381
+ pushUnique(await this.getWorkspaceDb(dbProps));
382
+ }
383
+ catch (e) {
384
+ const loadErr = e;
385
+ loadErr.wsDbProps = dbProps;
386
+ args.problems?.push(loadErr);
387
+ }
388
+ }
389
+ return result;
390
+ }
391
+ }
392
+ _c = ImplementationProhibited_1.implementationProhibited;
393
+ const workspaceEditorName = "WorkspaceEditor"; // name of the cache for the editor workspace
394
+ class EditorWorkspaceImpl extends WorkspaceImpl {
395
+ getCloudCache() {
396
+ return this._cloudCache ??= makeWorkspaceCloudCache({ cacheName: workspaceEditorName, cacheSize: "20G" });
397
+ }
398
+ }
399
+ class EditorImpl {
400
+ constructor() {
401
+ this[_d] = undefined;
402
+ this.workspace = new EditorWorkspaceImpl(new SettingsImpl_1.SettingsImpl(), { containerDir: (0, path_1.join)(IModelHost_1.IModelHost.cacheDir, workspaceEditorName) });
403
+ }
404
+ async initializeContainer(args) {
405
+ class CloudAccess extends CloudSqlite_1.CloudSqlite.DbAccess {
406
+ static async initializeWorkspace(args) {
407
+ const props = await this.createBlobContainer({ scope: args.scope, metadata: { ...args.metadata, containerType: "workspace" } });
408
+ const dbFullName = makeWorkspaceDbFileName(workspaceDbNameWithDefault(args.dbName), "1.0.0");
409
+ await super._initializeDb({ ...args, props, dbName: dbFullName, dbType: WorkspaceSqliteDb_1.WorkspaceSqliteDb, blockSize: "4M" });
410
+ return props;
411
+ }
412
+ }
413
+ CloudAccess._cacheName = workspaceEditorName;
414
+ return CloudAccess.initializeWorkspace(args);
415
+ }
416
+ async createNewCloudContainer(args) {
417
+ const cloudContainer = await this.initializeContainer(args);
418
+ const userToken = await IModelHost_1.IModelHost.authorizationClient?.getAccessToken();
419
+ const accessToken = await CloudSqlite_1.CloudSqlite.requestToken({ ...cloudContainer, accessLevel: "write", userToken });
420
+ return this.getContainer({ accessToken, ...cloudContainer, writeable: true, description: args.metadata.description });
421
+ }
422
+ getContainer(props) {
423
+ return this.workspace.findContainer(props.containerId) ?? new EditorContainerImpl(this.workspace, props);
424
+ }
425
+ async getContainerAsync(props) {
426
+ const accessToken = props.accessToken ?? (props.baseUri === "") ? "" : await CloudSqlite_1.CloudSqlite.requestToken({ ...props, accessLevel: "write" });
427
+ return this.getContainer({ ...props, accessToken });
428
+ }
429
+ close() {
430
+ this.workspace.close();
431
+ }
432
+ }
433
+ _d = ImplementationProhibited_1.implementationProhibited;
434
+ class EditorContainerImpl extends WorkspaceContainerImpl {
435
+ get cloudContainer() {
436
+ return super.cloudContainer;
437
+ }
438
+ get cloudProps() {
439
+ const cloudContainer = this.cloudContainer;
440
+ if (undefined === cloudContainer)
441
+ return undefined;
442
+ return {
443
+ baseUri: cloudContainer.baseUri,
444
+ containerId: cloudContainer.containerId,
445
+ storageType: cloudContainer.storageType,
446
+ isPublic: cloudContainer.isPublic,
447
+ };
448
+ }
449
+ async createNewWorkspaceDbVersion(args) {
450
+ const cloudContainer = this.cloudContainer;
451
+ if (undefined === cloudContainer)
452
+ throw new Error("versions require cloud containers");
453
+ const oldName = this.resolveDbFileName(args.fromProps ?? {});
454
+ const oldDb = parseWorkspaceDbFileName(oldName);
455
+ const newVersion = semver.inc(oldDb.version, args.versionType, args.identifier);
456
+ if (!newVersion)
457
+ throwWorkspaceDbLoadError("invalid version", args.fromProps ?? {});
458
+ const newName = makeWorkspaceDbFileName(oldDb.dbName, newVersion);
459
+ await cloudContainer.copyDatabase(oldName, newName);
460
+ // return the old and new db names and versions
461
+ return { oldDb, newDb: { dbName: oldDb.dbName, version: newVersion } };
462
+ }
463
+ getWorkspaceDb(props) {
464
+ return this.getEditableDb(props);
465
+ }
466
+ getEditableDb(props) {
467
+ const db = this._wsDbs.get(workspaceDbNameWithDefault(props.dbName)) ?? new EditableDbImpl(props, this);
468
+ if (this.cloudContainer && this.cloudContainer.queryDatabase(db.dbFileName)?.state !== "copied")
469
+ throw new Error(`${db.dbFileName} has been published and is not editable. Make a new version first.`);
470
+ return db;
471
+ }
472
+ acquireWriteLock(user) {
473
+ if (this.cloudContainer) {
474
+ this.cloudContainer.acquireWriteLock(user);
475
+ this.cloudContainer.writeLockHeldBy = user;
476
+ }
477
+ }
478
+ releaseWriteLock() {
479
+ if (this.cloudContainer) {
480
+ this.cloudContainer.releaseWriteLock();
481
+ this.cloudContainer.writeLockHeldBy = undefined;
482
+ }
483
+ }
484
+ abandonChanges() {
485
+ if (this.cloudContainer) {
486
+ this.cloudContainer.abandonChanges();
487
+ this.cloudContainer.writeLockHeldBy = undefined;
488
+ }
489
+ }
490
+ async createDb(args) {
491
+ if (!this.cloudContainer) {
492
+ WorkspaceEditor_1.WorkspaceEditor.createEmptyDb({ localFileName: this.resolveDbFileName(args), manifest: args.manifest });
493
+ }
494
+ else {
495
+ // currently the only way to create a workspaceDb in a cloud container is to create a temporary workspaceDb and upload it.
496
+ const tempDbFile = (0, path_1.join)(IModelHost_1.KnownLocations.tmpdir, `empty.${exports.workspaceDbFileExt}`);
497
+ if (fs.existsSync(tempDbFile))
498
+ IModelJsFs_1.IModelJsFs.removeSync(tempDbFile);
499
+ WorkspaceEditor_1.WorkspaceEditor.createEmptyDb({ localFileName: tempDbFile, manifest: args.manifest });
500
+ await CloudSqlite_1.CloudSqlite.uploadDb(this.cloudContainer, { localFileName: tempDbFile, dbName: makeWorkspaceDbFileName(workspaceDbNameWithDefault(args.dbName), args.version) });
501
+ IModelJsFs_1.IModelJsFs.removeSync(tempDbFile);
502
+ }
503
+ return this.getWorkspaceDb(args);
504
+ }
505
+ }
506
+ class EditableDbImpl extends WorkspaceDbImpl {
507
+ get container() {
508
+ (0, core_bentley_1.assert)(this._container instanceof EditorContainerImpl);
509
+ return this._container;
510
+ }
511
+ static validateResourceName(name) {
512
+ if (name.trim() !== name) {
513
+ throw new Error("resource name may not have leading or trailing spaces");
514
+ }
515
+ if (name.length > 1024) {
516
+ throw new Error("resource name too long");
517
+ }
518
+ }
519
+ validateResourceSize(val) {
520
+ const len = typeof val === "string" ? val.length : val.byteLength;
521
+ if (len > (1024 * 1024 * 1024)) // one gigabyte
522
+ throw new Error("value is too large");
523
+ }
524
+ get cloudProps() {
525
+ const props = this._container.cloudProps;
526
+ if (props === undefined)
527
+ return undefined;
528
+ const parsed = parseWorkspaceDbFileName(this.dbFileName);
529
+ return { ...props, dbName: parsed.dbName, version: parsed.version };
530
+ }
531
+ open() {
532
+ this.sqliteDb.openDb(this.dbFileName, core_bentley_1.OpenMode.ReadWrite, this._container.cloudContainer);
533
+ }
534
+ close() {
535
+ if (this.isOpen) {
536
+ // whenever we close an EditableDb, update the name of the last editor in the manifest
537
+ const lastEditedBy = this._container.cloudContainer?.writeLockHeldBy;
538
+ if (lastEditedBy !== undefined)
539
+ this.updateManifest({ ...this.manifest, lastEditedBy });
540
+ // make sure all changes were saved before we close
541
+ this.sqliteDb.saveChanges();
542
+ }
543
+ super.close();
544
+ }
545
+ getFileModifiedTime(localFileName) {
546
+ return Math.round(fs.statSync(localFileName).mtimeMs);
547
+ }
548
+ performWriteSql(rscName, sql, bind) {
549
+ this.sqliteDb.withSqliteStatement(sql, (stmt) => {
550
+ stmt.bindString(1, rscName);
551
+ bind?.(stmt);
552
+ const rc = stmt.step();
553
+ if (core_bentley_1.DbResult.BE_SQLITE_DONE !== rc) {
554
+ if (core_bentley_1.DbResult.BE_SQLITE_CONSTRAINT_PRIMARYKEY === rc)
555
+ throw new core_common_1.IModelError(rc, `resource "${rscName}" already exists`);
556
+ throw new core_common_1.IModelError(rc, `workspace [${sql}]`);
557
+ }
558
+ });
559
+ this.sqliteDb.saveChanges();
560
+ }
561
+ updateManifest(manifest) {
562
+ this.sqliteDb.nativeDb.saveFileProperty(exports.workspaceManifestProperty, JSON.stringify(manifest));
563
+ this._manifest = undefined;
564
+ }
565
+ updateSettingsResource(settings, rscName) {
566
+ this.updateString(rscName ?? "settingsDictionary", JSON.stringify(settings));
567
+ }
568
+ addString(rscName, val) {
569
+ EditableDbImpl.validateResourceName(rscName);
570
+ this.validateResourceSize(val);
571
+ this.performWriteSql(rscName, "INSERT INTO strings(id,value) VALUES(?,?)", (stmt) => stmt.bindString(2, val));
572
+ }
573
+ updateString(rscName, val) {
574
+ this.validateResourceSize(val);
575
+ this.performWriteSql(rscName, "INSERT INTO strings(id,value) VALUES(?,?) ON CONFLICT(id) DO UPDATE SET value=excluded.value WHERE value!=excluded.value", (stmt) => stmt.bindString(2, val));
576
+ }
577
+ removeString(rscName) {
578
+ this.performWriteSql(rscName, "DELETE FROM strings WHERE id=?");
579
+ }
580
+ addBlob(rscName, val) {
581
+ EditableDbImpl.validateResourceName(rscName);
582
+ this.validateResourceSize(val);
583
+ this.performWriteSql(rscName, "INSERT INTO blobs(id,value) VALUES(?,?)", (stmt) => stmt.bindBlob(2, val));
584
+ }
585
+ updateBlob(rscName, val) {
586
+ this.validateResourceSize(val);
587
+ this.performWriteSql(rscName, "INSERT INTO blobs(id,value) VALUES(?,?) ON CONFLICT(id) DO UPDATE SET value=excluded.value WHERE value!=excluded.value", (stmt) => stmt.bindBlob(2, val));
588
+ }
589
+ getBlobWriter(rscName) {
590
+ return this.sqliteDb.withSqliteStatement("SELECT rowid from blobs WHERE id=?", (stmt) => {
591
+ stmt.bindString(1, rscName);
592
+ const blobWriter = SQLiteDb_1.SQLiteDb.createBlobIO();
593
+ blobWriter.open(this.sqliteDb.nativeDb, { tableName: "blobs", columnName: "value", row: stmt.getValueInteger(0), writeable: true });
594
+ return blobWriter;
595
+ });
596
+ }
597
+ removeBlob(rscName) {
598
+ this.performWriteSql(rscName, "DELETE FROM blobs WHERE id=?");
599
+ }
600
+ addFile(rscName, localFileName, fileExt) {
601
+ EditableDbImpl.validateResourceName(rscName);
602
+ fileExt = fileExt ?? (0, path_1.extname)(localFileName);
603
+ if (fileExt?.[0] === ".")
604
+ fileExt = fileExt.slice(1);
605
+ this.sqliteDb.nativeDb.embedFile({ name: rscName, localFileName, date: this.getFileModifiedTime(localFileName), fileExt });
606
+ }
607
+ updateFile(rscName, localFileName) {
608
+ this.queryFileResource(rscName); // throws if not present
609
+ this.sqliteDb.nativeDb.replaceEmbeddedFile({ name: rscName, localFileName, date: this.getFileModifiedTime(localFileName) });
610
+ }
611
+ removeFile(rscName) {
612
+ const file = this.queryFileResource(rscName);
613
+ if (undefined === file)
614
+ throw new Error(`file resource "${rscName}" does not exist`);
615
+ if (file && fs.existsSync(file.localFileName))
616
+ fs.unlinkSync(file.localFileName);
617
+ this.sqliteDb.nativeDb.removeEmbeddedFile(rscName);
618
+ }
619
+ }
620
+ function constructWorkspaceDb(props, container) {
621
+ return new WorkspaceDbImpl(props, container);
622
+ }
623
+ exports.constructWorkspaceDb = constructWorkspaceDb;
624
+ function constructWorkspace(settings, opts) {
625
+ return new WorkspaceImpl(settings, opts);
626
+ }
627
+ exports.constructWorkspace = constructWorkspace;
628
+ function constructWorkspaceEditor() {
629
+ return new EditorImpl();
630
+ }
631
+ exports.constructWorkspaceEditor = constructWorkspaceEditor;
632
+ function noLeadingOrTrailingSpaces(name, msg) {
633
+ if (name.trim() !== name)
634
+ throw new Error(`${msg} [${name}] may not have leading or trailing spaces`);
635
+ }
636
+ exports.noLeadingOrTrailingSpaces = noLeadingOrTrailingSpaces;
637
+ function validateWorkspaceDbName(dbName) {
638
+ if (dbName === "" || dbName.length > 255 || /[#\.<>:"/\\"`'|?*\u0000-\u001F]/g.test(dbName) || /^(con|prn|aux|nul|com\d|lpt\d)$/i.test(dbName))
639
+ throw new Error(`invalid dbName: [${dbName}]`);
640
+ noLeadingOrTrailingSpaces(dbName, "dbName");
641
+ }
642
+ exports.validateWorkspaceDbName = validateWorkspaceDbName;
643
+ /**
644
+ * Validate that a WorkspaceContainer.Id is valid.
645
+ * The rules for ContainerIds (from Azure, see https://docs.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata):
646
+ * - may only contain lower case letters, numbers or dashes
647
+ * - may not start or end with with a dash nor have more than one dash in a row
648
+ * - may not be shorter than 3 or longer than 63 characters
649
+ */
650
+ function validateWorkspaceContainerId(id) {
651
+ if (!/^(?=.{3,63}$)[a-z0-9]+(-[a-z0-9]+)*$/g.test(id))
652
+ throw new Error(`invalid containerId: [${id}]`);
653
+ }
654
+ exports.validateWorkspaceContainerId = validateWorkspaceContainerId;
655
+ function validateWorkspaceDbVersion(version) {
656
+ version = version ?? "1.0.0";
657
+ if (version) {
658
+ const opts = { loose: true, includePrerelease: true };
659
+ // clean allows prerelease, so try it first. If that fails attempt to coerce it (coerce strips prerelease even if you say not to.)
660
+ const semVersion = semver.clean(version, opts) ?? semver.coerce(version, opts)?.version;
661
+ if (!semVersion)
662
+ throw new Error("invalid version specification");
663
+ version = semVersion;
664
+ }
665
+ return version;
666
+ }
667
+ exports.validateWorkspaceDbVersion = validateWorkspaceDbVersion;
668
+ /**
669
+ * Parse the name stored in a WorkspaceContainer into the dbName and version number. A single WorkspaceContainer may hold
670
+ * many versions of the same WorkspaceDb. The name of the Db in the WorkspaceContainer is in the format "name:version". This
671
+ * function splits them into separate strings.
672
+ */
673
+ function parseWorkspaceDbFileName(dbFileName) {
674
+ const parts = dbFileName.split(":");
675
+ return { dbName: parts[0], version: parts[1] };
676
+ }
677
+ exports.parseWorkspaceDbFileName = parseWorkspaceDbFileName;
678
+ /** Create a dbName for a WorkspaceDb from its base name and version. This will be in the format "name:version" */
679
+ function makeWorkspaceDbFileName(dbName, version) {
680
+ return `${dbName}:${validateWorkspaceDbVersion(version)}`;
681
+ }
682
+ exports.makeWorkspaceDbFileName = makeWorkspaceDbFileName;
683
+ exports.workspaceManifestProperty = { namespace: "workspace", name: "manifest" };
684
+ function throwWorkspaceDbLoadError(msg, wsDbProps, db) {
685
+ const error = new Error(msg);
686
+ error.wsDbProps = wsDbProps;
687
+ error.wsDb = db;
688
+ throw error;
689
+ }
690
+ function throwWorkspaceDbLoadErrors(msg, errors) {
691
+ const error = new Error(msg);
692
+ error.wsLoadErrors = errors;
693
+ throw error;
694
+ }
695
+ exports.throwWorkspaceDbLoadErrors = throwWorkspaceDbLoadErrors;
696
+ //# sourceMappingURL=WorkspaceImpl.js.map