@milaboratories/pl-middle-layer 1.58.4 → 1.59.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.
- package/dist/index.d.ts +2 -2
- package/dist/middle_layer/middle_layer.cjs +71 -47
- package/dist/middle_layer/middle_layer.cjs.map +1 -1
- package/dist/middle_layer/middle_layer.d.ts +18 -18
- package/dist/middle_layer/middle_layer.d.ts.map +1 -1
- package/dist/middle_layer/middle_layer.js +72 -48
- package/dist/middle_layer/middle_layer.js.map +1 -1
- package/dist/middle_layer/project.cjs +8 -9
- package/dist/middle_layer/project.cjs.map +1 -1
- package/dist/middle_layer/project.d.ts +6 -5
- package/dist/middle_layer/project.d.ts.map +1 -1
- package/dist/middle_layer/project.js +9 -10
- package/dist/middle_layer/project.js.map +1 -1
- package/dist/middle_layer/project_list.cjs +3 -3
- package/dist/middle_layer/project_list.cjs.map +1 -1
- package/dist/middle_layer/project_list.d.ts +1 -1
- package/dist/middle_layer/project_list.js +4 -4
- package/dist/middle_layer/project_list.js.map +1 -1
- package/dist/middle_layer/project_overview.cjs.map +1 -1
- package/dist/middle_layer/project_overview.js.map +1 -1
- package/dist/middle_layer/util.cjs.map +1 -1
- package/dist/middle_layer/util.js.map +1 -1
- package/dist/model/index.d.ts +2 -2
- package/dist/model/project_helper.cjs.map +1 -1
- package/dist/model/project_helper.d.ts +3 -3
- package/dist/model/project_helper.d.ts.map +1 -1
- package/dist/model/project_helper.js.map +1 -1
- package/dist/model/project_model.cjs.map +1 -1
- package/dist/model/project_model.d.ts +6 -5
- package/dist/model/project_model.d.ts.map +1 -1
- package/dist/model/project_model.js.map +1 -1
- package/dist/model/template_spec.d.ts +2 -2
- package/dist/model/template_spec.d.ts.map +1 -1
- package/dist/mutator/migration.cjs +1 -1
- package/dist/mutator/migration.cjs.map +1 -1
- package/dist/mutator/migration.js +2 -2
- package/dist/mutator/migration.js.map +1 -1
- package/dist/mutator/project.cjs +6 -6
- package/dist/mutator/project.cjs.map +1 -1
- package/dist/mutator/project.d.ts +3 -3
- package/dist/mutator/project.d.ts.map +1 -1
- package/dist/mutator/project.js +7 -7
- package/dist/mutator/project.js.map +1 -1
- package/dist/mutator/template/template_cache.cjs +7 -7
- package/dist/mutator/template/template_cache.cjs.map +1 -1
- package/dist/mutator/template/template_cache.d.ts +8 -8
- package/dist/mutator/template/template_cache.d.ts.map +1 -1
- package/dist/mutator/template/template_cache.js +8 -8
- package/dist/mutator/template/template_cache.js.map +1 -1
- package/dist/network_check/template.cjs +5 -5
- package/dist/network_check/template.cjs.map +1 -1
- package/dist/network_check/template.js +6 -6
- package/dist/network_check/template.js.map +1 -1
- package/dist/pool/data.cjs +2 -1
- package/dist/pool/data.cjs.map +1 -1
- package/dist/pool/data.d.ts +1 -1
- package/dist/pool/data.d.ts.map +1 -1
- package/dist/pool/data.js +3 -2
- package/dist/pool/data.js.map +1 -1
- package/dist/pool/driver.cjs +3 -1
- package/dist/pool/driver.cjs.map +1 -1
- package/dist/pool/driver.d.ts.map +1 -1
- package/dist/pool/driver.js +3 -1
- package/dist/pool/driver.js.map +1 -1
- package/package.json +16 -16
- package/src/index.ts +1 -1
- package/src/middle_layer/middle_layer.ts +99 -61
- package/src/middle_layer/project.ts +14 -13
- package/src/middle_layer/project_list.ts +10 -8
- package/src/middle_layer/project_overview.ts +2 -2
- package/src/middle_layer/render.test.ts +9 -9
- package/src/middle_layer/util.ts +2 -2
- package/src/model/index.ts +1 -1
- package/src/model/project_helper.ts +2 -2
- package/src/model/project_model.ts +7 -4
- package/src/model/template_spec.ts +2 -2
- package/src/mutator/block-pack/block_pack.test.ts +7 -2
- package/src/mutator/migration.ts +7 -7
- package/src/mutator/project.ts +20 -19
- package/src/mutator/template/template_cache.test.ts +6 -6
- package/src/mutator/template/template_cache.ts +24 -21
- package/src/mutator/template/template_render.test.ts +7 -7
- package/src/network_check/template.ts +8 -8
- package/src/pool/data.ts +6 -4
- package/src/pool/driver.ts +3 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { V2RegistryProvider } from "./block_registry/registry-v2-provider.js";
|
|
2
2
|
import { BlockPackRegistry, getDevV1PacketMtime, getDevV2PacketMtime } from "./block_registry/registry.js";
|
|
3
3
|
import { CentralBlockRegistry, V1CentralDevSnapshotRegistry, V1CentralRegistry } from "./block_registry/well_known_registries.js";
|
|
4
|
+
import { BlockArgsAuthorKeyPrefix, ProjectCreatedTimestamp, ProjectField, ProjectId, ProjectLastModifiedTimestamp, ProjectListEntry, ProjectMetaKey, ProjectResourceType, ProjectStructureAuthorKey, ProjectStructureKey, SchemaVersionCurrent, SchemaVersionKey } from "./model/project_model.js";
|
|
4
5
|
import { CachedTemplate, ExplicitTemplate, PreparedTemplate, TemplateFromFile, TemplateFromRegistry, TemplateSpecAny, TemplateSpecPrepared } from "./model/template_spec.js";
|
|
5
6
|
import { BlockPackExplicit, BlockPackSpecAny, BlockPackSpecPrepared, FrontendFromFolder, FrontendFromFolderData, FrontendFromFolderResourceType, FrontendFromUrl, FrontendFromUrlData, FrontendFromUrlResourceType, FrontendSpec } from "./model/block_pack_spec.js";
|
|
6
|
-
import { BlockArgsAuthorKeyPrefix, ProjectCreatedTimestamp, ProjectField, ProjectLastModifiedTimestamp, ProjectListEntry, ProjectMetaKey, ProjectResourceType, ProjectStructureAuthorKey, ProjectStructureKey, SchemaVersionCurrent, SchemaVersionKey } from "./model/project_model.js";
|
|
7
7
|
import { Project } from "./middle_layer/project.js";
|
|
8
8
|
import { DefaultDriverKitOpsPaths, DefaultDriverKitOpsSettings, DefaultMiddleLayerOpsPaths, DefaultMiddleLayerOpsSettings, DriverKitOps, DriverKitOpsConstructor, DriverKitOpsPaths, DriverKitOpsSettings, MiddleLayerDebugOptions, MiddleLayerOps, MiddleLayerOpsConstructor, MiddleLayerOpsPaths, MiddleLayerOpsSettings } from "./middle_layer/ops.js";
|
|
9
9
|
import { MiddleLayerDriverKit, initDriverKit } from "./middle_layer/driver_kit.js";
|
|
@@ -22,4 +22,4 @@ export * from "@platforma-sdk/model";
|
|
|
22
22
|
export * from "@milaboratories/pl-model-middle-layer";
|
|
23
23
|
export * from "@milaboratories/pl-deployments";
|
|
24
24
|
export * from "@milaboratories/pl-client";
|
|
25
|
-
export { ACCESS_COUNT_KEY, ACCESS_KEY_PREFIX, BlockArgsAuthorKeyPrefix, BlockPackExplicit, BlockPackRegistry, BlockPackSpecAny, BlockPackSpecPrepared, CachedTemplate, CentralBlockRegistry, CheckNetworkOpts, DefaultDriverKitOpsPaths, DefaultDriverKitOpsSettings, DefaultMiddleLayerOpsPaths, DefaultMiddleLayerOpsSettings, DriverKitOps, DriverKitOpsConstructor, DriverKitOpsPaths, DriverKitOpsSettings, ExplicitTemplate, type FieldType, FrontendFromFolder, FrontendFromFolderData, FrontendFromFolderResourceType, FrontendFromUrl, FrontendFromUrlData, FrontendFromUrlResourceType, FrontendSpec, GC_ACCESS_THRESHOLD, GC_MAX_ENTRIES, type InternalLsDriver, MiddleLayer, MiddleLayerDebugOptions, MiddleLayerDriverKit, MiddleLayerOps, MiddleLayerOpsConstructor, MiddleLayerOpsPaths, MiddleLayerOpsSettings, PreparedTemplate, Project, ProjectCreatedTimestamp, ProjectField, ProjectLastModifiedTimestamp, type ProjectListEntry, ProjectMetaKey, ProjectResourceType, ProjectStructureAuthorKey, ProjectStructureKey, ProjectsField, type ResourceType, SchemaVersionCurrent, SchemaVersionKey, TemplateCacheFieldName, TemplateCacheStat, TemplateCacheType, TemplateFromFile, TemplateFromRegistry, TemplateSpecAny, TemplateSpecPrepared, TengoTemplateGet, TengoTemplateGetRegistry, TengoTemplateGetTemplate, TengoTemplateGetTemplateURI, TengoTemplatePack, TengoTemplatePackConvert, TengoTemplatePackConvertTemplate, TengoTemplatePackConvertTemplatePack, V1CentralDevSnapshotRegistry, V1CentralRegistry, V2RegistryProvider, cacheBlockPackTemplate, checkNetwork, createRenderTemplate, deriveGlobalPObjectId, deriveLocalPObjectId, dropTemplateCache, duplicateProject, flattenTemplateTree, getDevV1PacketMtime, getDevV2PacketMtime, getOrCreateTemplateCache, initDriverKit, initNetworkCheck, invalidateTemplateCacheId, loadTemplate, loadTemplateCached, parseFinalPObjectCollection, prepareTemplateSpec, runGc };
|
|
25
|
+
export { ACCESS_COUNT_KEY, ACCESS_KEY_PREFIX, BlockArgsAuthorKeyPrefix, BlockPackExplicit, BlockPackRegistry, BlockPackSpecAny, BlockPackSpecPrepared, CachedTemplate, CentralBlockRegistry, CheckNetworkOpts, DefaultDriverKitOpsPaths, DefaultDriverKitOpsSettings, DefaultMiddleLayerOpsPaths, DefaultMiddleLayerOpsSettings, DriverKitOps, DriverKitOpsConstructor, DriverKitOpsPaths, DriverKitOpsSettings, ExplicitTemplate, type FieldType, FrontendFromFolder, FrontendFromFolderData, FrontendFromFolderResourceType, FrontendFromUrl, FrontendFromUrlData, FrontendFromUrlResourceType, FrontendSpec, GC_ACCESS_THRESHOLD, GC_MAX_ENTRIES, type InternalLsDriver, MiddleLayer, MiddleLayerDebugOptions, MiddleLayerDriverKit, MiddleLayerOps, MiddleLayerOpsConstructor, MiddleLayerOpsPaths, MiddleLayerOpsSettings, PreparedTemplate, Project, ProjectCreatedTimestamp, ProjectField, type ProjectId, ProjectLastModifiedTimestamp, type ProjectListEntry, ProjectMetaKey, ProjectResourceType, ProjectStructureAuthorKey, ProjectStructureKey, ProjectsField, type ResourceType, SchemaVersionCurrent, SchemaVersionKey, TemplateCacheFieldName, TemplateCacheStat, TemplateCacheType, TemplateFromFile, TemplateFromRegistry, TemplateSpecAny, TemplateSpecPrepared, TengoTemplateGet, TengoTemplateGetRegistry, TengoTemplateGetTemplate, TengoTemplateGetTemplateURI, TengoTemplatePack, TengoTemplatePackConvert, TengoTemplatePackConvertTemplate, TengoTemplatePackConvertTemplatePack, V1CentralDevSnapshotRegistry, V1CentralRegistry, V2RegistryProvider, cacheBlockPackTemplate, checkNetwork, createRenderTemplate, deriveGlobalPObjectId, deriveLocalPObjectId, dropTemplateCache, duplicateProject, flattenTemplateTree, getDevV1PacketMtime, getDevV2PacketMtime, getOrCreateTemplateCache, initDriverKit, initNetworkCheck, invalidateTemplateCacheId, loadTemplate, loadTemplateCached, parseFinalPObjectCollection, prepareTemplateSpec, runGc };
|
|
@@ -16,6 +16,7 @@ let _platforma_sdk_model = require("@platforma-sdk/model");
|
|
|
16
16
|
let undici = require("undici");
|
|
17
17
|
let _milaboratories_ts_helpers = require("@milaboratories/ts-helpers");
|
|
18
18
|
let _milaboratories_pl_client = require("@milaboratories/pl-client");
|
|
19
|
+
let lru_cache = require("lru-cache");
|
|
19
20
|
let _milaboratories_computable = require("@milaboratories/computable");
|
|
20
21
|
let node_crypto = require("node:crypto");
|
|
21
22
|
let quickjs_emscripten = require("quickjs-emscripten");
|
|
@@ -71,19 +72,40 @@ var MiddleLayer = class MiddleLayer {
|
|
|
71
72
|
get serviceRegistry() {
|
|
72
73
|
return this.env.serviceRegistry;
|
|
73
74
|
}
|
|
75
|
+
projectIdCache = new lru_cache.LRUCache({ max: 1024 });
|
|
76
|
+
/** Resolves a ProjectId to a signed SignedResourceId.
|
|
77
|
+
* Uses LRU cache with TX-scan fallback. */
|
|
78
|
+
async resolveProjectId(projectId) {
|
|
79
|
+
const cached = this.projectIdCache.get(projectId);
|
|
80
|
+
if (cached !== void 0) return cached;
|
|
81
|
+
const rid = await this.pl.withReadTx("ResolveProjectId", async (tx) => {
|
|
82
|
+
const data = await tx.getResourceData(this.projectListResourceId, true);
|
|
83
|
+
for (const f of data.fields) {
|
|
84
|
+
if ((0, _milaboratories_pl_client.isNullSignedResourceId)(f.value)) continue;
|
|
85
|
+
if ((0, _milaboratories_pl_client.resourceIdToString)(f.value) === projectId) return f.value;
|
|
86
|
+
}
|
|
87
|
+
throw new Error(`Project ${projectId} not found in project list.`);
|
|
88
|
+
});
|
|
89
|
+
this.projectIdCache.set(projectId, rid);
|
|
90
|
+
return rid;
|
|
91
|
+
}
|
|
74
92
|
/** Creates a project with initial state and adds it to project list. */
|
|
75
|
-
async createProject(meta
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
93
|
+
async createProject(meta) {
|
|
94
|
+
let prj;
|
|
95
|
+
await this.pl.withWriteTx("MLCreateProject", async (tx) => {
|
|
96
|
+
prj = await require_project.createProject(tx, meta);
|
|
97
|
+
tx.createField((0, _milaboratories_pl_client.field)(this.projectListResourceId, (0, node_crypto.randomUUID)()), "Dynamic", prj);
|
|
79
98
|
await tx.commit();
|
|
80
|
-
return await (0, _milaboratories_pl_client.toGlobalResourceId)(prj);
|
|
81
99
|
});
|
|
82
100
|
await this.projectListTree.refreshState();
|
|
83
|
-
|
|
101
|
+
const signedRid = await prj.globalId;
|
|
102
|
+
const projectId = (0, _milaboratories_pl_client.resourceIdToString)(signedRid);
|
|
103
|
+
this.projectIdCache.set(projectId, signedRid);
|
|
104
|
+
return projectId;
|
|
84
105
|
}
|
|
85
106
|
/** Updates project metadata */
|
|
86
|
-
async setProjectMeta(
|
|
107
|
+
async setProjectMeta(id, meta, author) {
|
|
108
|
+
const rid = await this.resolveProjectId(id);
|
|
87
109
|
await require_project.withProjectAuthored(this.env.projectHelper, this.pl, rid, author, (prj) => {
|
|
88
110
|
prj.setMeta(meta);
|
|
89
111
|
}, { name: "setProjectMeta" });
|
|
@@ -93,69 +115,71 @@ var MiddleLayer = class MiddleLayer {
|
|
|
93
115
|
* destruction of all attached objects, like files, analysis results etc. */
|
|
94
116
|
async deleteProject(id) {
|
|
95
117
|
await this.pl.withWriteTx("MLRemoveProject", async (tx) => {
|
|
96
|
-
tx.
|
|
118
|
+
const data = await tx.getResourceData(this.projectListResourceId, true);
|
|
119
|
+
let fieldName;
|
|
120
|
+
for (const f of data.fields) {
|
|
121
|
+
if ((0, _milaboratories_pl_client.isNullSignedResourceId)(f.value)) continue;
|
|
122
|
+
if ((0, _milaboratories_pl_client.resourceIdToString)(f.value) === id) {
|
|
123
|
+
fieldName = f.name;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (fieldName === void 0) throw new Error(`Project ${id} not found in project list.`);
|
|
128
|
+
tx.removeField((0, _milaboratories_pl_client.field)(this.projectListResourceId, fieldName));
|
|
97
129
|
await tx.commit();
|
|
98
130
|
});
|
|
131
|
+
this.projectIdCache.delete(id);
|
|
99
132
|
await this.projectListTree.refreshState();
|
|
100
133
|
}
|
|
101
134
|
/**
|
|
102
135
|
* Duplicates an existing project and adds the copy to this user's project list.
|
|
103
136
|
*
|
|
104
|
-
* @param
|
|
137
|
+
* @param srcProjectId - project id of the project to duplicate
|
|
105
138
|
* @param rename - optional function that receives the source label and all existing
|
|
106
139
|
* project labels (read within the same transaction), and returns the label for the copy
|
|
107
|
-
* @param id - optional id for the new project list entry (defaults to random UUID)
|
|
108
140
|
*/
|
|
109
|
-
async duplicateProject(
|
|
110
|
-
const
|
|
141
|
+
async duplicateProject(srcProjectId, rename) {
|
|
142
|
+
const sourceRid = await this.resolveProjectId(srcProjectId);
|
|
143
|
+
const newPrj = await this.pl.withWriteTx("MLDuplicateProject", async (tx) => {
|
|
111
144
|
const sourceMeta = await tx.getKValueJson(sourceRid, require_project_model.ProjectMetaKey);
|
|
112
|
-
const projectRids = (await tx.getResourceData(this.projectListResourceId, true)).fields.map((f) => f.value).filter(_milaboratories_pl_client.
|
|
145
|
+
const projectRids = (await tx.getResourceData(this.projectListResourceId, true)).fields.map((f) => f.value).filter(_milaboratories_pl_client.isNotNullSignedResourceId);
|
|
113
146
|
const existingLabels = (await Promise.all(projectRids.map((rid) => tx.getKValueJson(rid, require_project_model.ProjectMetaKey)))).map((m) => m.label);
|
|
114
147
|
const newPrj = await require_project.duplicateProject(tx, sourceRid, { label: rename ? rename(sourceMeta.label, existingLabels) : sourceMeta.label });
|
|
115
|
-
tx.createField((0, _milaboratories_pl_client.field)(this.projectListResourceId,
|
|
148
|
+
tx.createField((0, _milaboratories_pl_client.field)(this.projectListResourceId, (0, node_crypto.randomUUID)()), "Dynamic", newPrj);
|
|
116
149
|
await tx.commit();
|
|
117
|
-
return
|
|
150
|
+
return newPrj;
|
|
118
151
|
});
|
|
119
152
|
await this.projectListTree.refreshState();
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return await this.pl.withReadTx("Project id to resource id", async (tx) => {
|
|
125
|
-
const rid = (await tx.getField((0, _milaboratories_pl_client.field)(this.projectListResourceId, id))).value;
|
|
126
|
-
if ((0, _milaboratories_pl_client.isNullResourceId)(rid)) throw new Error("Unexpected project list structure.");
|
|
127
|
-
return rid;
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
async ensureProjectRid(id) {
|
|
131
|
-
if (typeof id === "string") return await this.projectIdToResourceId(id);
|
|
132
|
-
else return id;
|
|
153
|
+
const signedRid = await newPrj.globalId;
|
|
154
|
+
const newProjectId = (0, _milaboratories_pl_client.resourceIdToString)(signedRid);
|
|
155
|
+
this.projectIdCache.set(newProjectId, signedRid);
|
|
156
|
+
return newProjectId;
|
|
133
157
|
}
|
|
158
|
+
openedProjects = /* @__PURE__ */ new Map();
|
|
134
159
|
/** Opens a project, and starts corresponding project maintenance loop. */
|
|
135
160
|
async openProject(id) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
this.
|
|
139
|
-
this.openedProjectsList.setValue([...this.
|
|
161
|
+
if (this.openedProjects.has(id)) throw new Error(`Project ${id} already opened`);
|
|
162
|
+
const rid = await this.resolveProjectId(id);
|
|
163
|
+
this.openedProjects.set(id, await require_project$1.Project.init(this.env, id, rid));
|
|
164
|
+
this.openedProjectsList.setValue([...this.openedProjects.keys()]);
|
|
140
165
|
}
|
|
141
166
|
/** Closes the project, and deallocate all corresponding resources. */
|
|
142
|
-
async closeProject(
|
|
143
|
-
const prj = this.
|
|
144
|
-
if (prj === void 0) throw new Error(`Project ${
|
|
145
|
-
this.
|
|
167
|
+
async closeProject(id) {
|
|
168
|
+
const prj = this.openedProjects.get(id);
|
|
169
|
+
if (prj === void 0) throw new Error(`Project ${id} not found among opened projects`);
|
|
170
|
+
this.openedProjects.delete(id);
|
|
146
171
|
await prj.destroy();
|
|
147
|
-
this.openedProjectsList.setValue([...this.
|
|
172
|
+
this.openedProjectsList.setValue([...this.openedProjects.keys()]);
|
|
148
173
|
}
|
|
149
|
-
/** Returns a project access object for opened project
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (prj === void 0) throw new Error(`Project ${rid} not found among opened projects`);
|
|
174
|
+
/** Returns a project access object for an opened project. */
|
|
175
|
+
getOpenedProject(id) {
|
|
176
|
+
const prj = this.openedProjects.get(id);
|
|
177
|
+
if (prj === void 0) throw new Error(`Project ${id} not found among opened projects`);
|
|
154
178
|
return prj;
|
|
155
179
|
}
|
|
156
|
-
/** Returns true if project with given
|
|
157
|
-
isProjectOpened(
|
|
158
|
-
return this.
|
|
180
|
+
/** Returns true if project with given id is currently opened. */
|
|
181
|
+
isProjectOpened(id) {
|
|
182
|
+
return this.openedProjects.has(id);
|
|
159
183
|
}
|
|
160
184
|
/**
|
|
161
185
|
* Deallocates all runtime resources consumed by this object and awaits
|
|
@@ -163,7 +187,7 @@ var MiddleLayer = class MiddleLayer {
|
|
|
163
187
|
* them.
|
|
164
188
|
*/
|
|
165
189
|
async close() {
|
|
166
|
-
await Promise.all([...this.
|
|
190
|
+
await Promise.all([...this.openedProjects.values()].map((prj) => prj.destroy()));
|
|
167
191
|
await this.projectListTree.terminate();
|
|
168
192
|
await this.env.dispose();
|
|
169
193
|
await this.pl.close();
|
|
@@ -194,7 +218,7 @@ var MiddleLayer = class MiddleLayer {
|
|
|
194
218
|
const projectsField = (0, _milaboratories_pl_client.field)(tx.clientRoot, require_project_list.ProjectsField);
|
|
195
219
|
tx.createField(projectsField, "Dynamic");
|
|
196
220
|
const projectsFieldData = await tx.getField(projectsField);
|
|
197
|
-
if ((0, _milaboratories_pl_client.
|
|
221
|
+
if ((0, _milaboratories_pl_client.isNullSignedResourceId)(projectsFieldData.value)) {
|
|
198
222
|
const projects = tx.createEphemeral(require_project_list.ProjectsResourceType);
|
|
199
223
|
tx.lock(projects);
|
|
200
224
|
tx.setField(projectsField, projects);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middle_layer.cjs","names":["createProject","withProjectAuthored","ProjectMetaKey","isNotNullResourceId","duplicateProject","Project","HmacSha256Signer","DefaultMiddleLayerOpsSettings","DefaultMiddleLayerOpsPaths","getDebugFlags","ProjectsField","ProjectsResourceType","initDriverKit","RetryAgent","V2RegistryProvider","BlockPackPreparer","RuntimeCapabilities","REQUIRES_PFRAMES_VERSION","createModelServiceRegistry","BlockEventDispatcher","BlockUpdateWatcher","ProjectHelper","WatchableValue","createProjectList"],"sources":["../../src/middle_layer/middle_layer.ts"],"sourcesContent":["import type { PlClient, ResourceId } from \"@milaboratories/pl-client\";\nimport {\n field,\n isNotNullResourceId,\n isNullResourceId,\n toGlobalResourceId,\n} from \"@milaboratories/pl-client\";\nimport { createProjectList, ProjectsField, ProjectsResourceType } from \"./project_list\";\nimport { createProject, duplicateProject, withProjectAuthored } from \"../mutator/project\";\nimport { ProjectMetaKey } from \"../model/project_model\";\nimport type { SynchronizedTreeState } from \"@milaboratories/pl-tree\";\nimport { BlockPackPreparer } from \"../mutator/block-pack/block_pack\";\nimport type { MiLogger, Signer } from \"@milaboratories/ts-helpers\";\nimport { BlockEventDispatcher } from \"@milaboratories/ts-helpers\";\nimport { HmacSha256Signer } from \"@milaboratories/ts-helpers\";\nimport type { ComputableStableDefined } from \"@milaboratories/computable\";\nimport { WatchableValue } from \"@milaboratories/computable\";\nimport { Project } from \"./project\";\nimport type { MiddleLayerOps, MiddleLayerOpsConstructor } from \"./ops\";\nimport { DefaultMiddleLayerOpsPaths, DefaultMiddleLayerOpsSettings } from \"./ops\";\nimport { randomUUID } from \"node:crypto\";\nimport type { ProjectListEntry } from \"../model\";\nimport type {\n AuthorMarker,\n ProjectMeta,\n BlockPlatform,\n} from \"@milaboratories/pl-model-middle-layer\";\nimport { BlockUpdateWatcher } from \"../block_registry/watcher\";\nimport type { QuickJSWASMModule } from \"quickjs-emscripten\";\nimport { getQuickJS } from \"quickjs-emscripten\";\nimport type { MiddleLayerDriverKit } from \"./driver_kit\";\nimport { initDriverKit } from \"./driver_kit\";\nimport type { BlockCodeFeatureFlags, DriverKit, SupportedRequirement } from \"@platforma-sdk/model\";\nimport { RuntimeCapabilities } from \"@platforma-sdk/model\";\nimport {\n type ModelServiceRegistry,\n registerServiceCapabilities,\n REQUIRES_PFRAMES_VERSION,\n} from \"@milaboratories/pl-model-common\";\nimport { createModelServiceRegistry } from \"../service_factories\";\nimport type { DownloadUrlDriver } from \"@milaboratories/pl-drivers\";\nimport { V2RegistryProvider } from \"../block_registry\";\nimport type { Dispatcher } from \"undici\";\nimport { RetryAgent } from \"undici\";\nimport { getDebugFlags } from \"../debug\";\nimport { ProjectHelper } from \"../model/project_helper\";\n\nexport interface MiddleLayerEnvironment {\n dispose(): Promise<void>;\n readonly pl: PlClient;\n readonly runtimeCapabilities: RuntimeCapabilities;\n readonly logger: MiLogger;\n readonly blockEventDispatcher: BlockEventDispatcher;\n readonly httpDispatcher: Dispatcher;\n readonly retryHttpDispatcher: Dispatcher;\n readonly signer: Signer;\n readonly ops: MiddleLayerOps;\n readonly bpPreparer: BlockPackPreparer;\n readonly frontendDownloadDriver: DownloadUrlDriver;\n readonly blockUpdateWatcher: BlockUpdateWatcher;\n readonly quickJs: QuickJSWASMModule;\n readonly driverKit: MiddleLayerDriverKit;\n readonly serviceRegistry: ModelServiceRegistry;\n readonly projectHelper: ProjectHelper;\n}\n\n/**\n * Main access object to work with pl from UI.\n *\n * It implements an abstraction layer of projects and blocks.\n *\n * As a main entry point inside the pl, this object uses a resource attached\n * via the {@link ProjectsField} to the pl client's root, this resource\n * contains project list.\n *\n * Read about alternative roots, if isolated project lists (working environments)\n * are required.\n * */\nexport class MiddleLayer {\n public readonly pl: PlClient;\n\n /** Contains a reactive list of projects along with their meta information. */\n public readonly projectList: ComputableStableDefined<ProjectListEntry[]>;\n\n private constructor(\n private readonly env: MiddleLayerEnvironment,\n public readonly driverKit: DriverKit,\n public readonly signer: Signer,\n private readonly projectListResourceId: ResourceId,\n private readonly openedProjectsList: WatchableValue<ResourceId[]>,\n private readonly projectListTree: SynchronizedTreeState,\n public readonly blockRegistryProvider: V2RegistryProvider,\n projectList: ComputableStableDefined<ProjectListEntry[]>,\n ) {\n this.projectList = projectList;\n this.pl = this.env.pl;\n }\n\n /**\n * Get the OS where backend is running.\n * For old backend versions returns undefined.\n */\n public get serverPlatform(): BlockPlatform | undefined {\n return this.pl.serverInfo.platform as BlockPlatform | undefined;\n }\n\n /** Adds a runtime capability to the middle layer. */\n public addRuntimeCapability(\n requirement: SupportedRequirement,\n value: number | boolean = true,\n ): void {\n this.env.runtimeCapabilities.addSupportedRequirement(requirement, value);\n }\n\n /** Checks if the given block feature flags are compatible with the runtime capabilities. */\n public checkBlockCompatibility(featureFlags: BlockCodeFeatureFlags | undefined): boolean {\n return this.env.runtimeCapabilities.checkCompatibility(featureFlags);\n }\n\n /** Returns extended API driver kit used internally by middle layer. */\n public get internalDriverKit(): MiddleLayerDriverKit {\n return this.env.driverKit;\n }\n\n /** Returns the service registry for service introspection. */\n public get serviceRegistry(): ModelServiceRegistry {\n return this.env.serviceRegistry;\n }\n\n //\n // Project List Manipulation\n //\n\n /** Creates a project with initial state and adds it to project list. */\n public async createProject(meta: ProjectMeta, id: string = randomUUID()): Promise<ResourceId> {\n const resource = await this.pl.withWriteTx(\"MLCreateProject\", async (tx) => {\n const prj = await createProject(tx, meta);\n tx.createField(field(this.projectListResourceId, id), \"Dynamic\", prj);\n await tx.commit();\n return await toGlobalResourceId(prj);\n });\n await this.projectListTree.refreshState();\n return resource;\n }\n\n /** Updates project metadata */\n public async setProjectMeta(\n rid: ResourceId,\n meta: ProjectMeta,\n author?: AuthorMarker,\n ): Promise<void> {\n await withProjectAuthored(\n this.env.projectHelper,\n this.pl,\n rid,\n author,\n (prj) => {\n prj.setMeta(meta);\n },\n { name: \"setProjectMeta\" },\n );\n await this.projectListTree.refreshState();\n }\n\n /** Permanently deletes project from the project list, this will result in\n * destruction of all attached objects, like files, analysis results etc. */\n public async deleteProject(id: string): Promise<void> {\n await this.pl.withWriteTx(\"MLRemoveProject\", async (tx) => {\n tx.removeField(field(this.projectListResourceId, id));\n await tx.commit();\n });\n await this.projectListTree.refreshState();\n }\n\n /**\n * Duplicates an existing project and adds the copy to this user's project list.\n *\n * @param sourceRid - resource id of the project to duplicate\n * @param rename - optional function that receives the source label and all existing\n * project labels (read within the same transaction), and returns the label for the copy\n * @param id - optional id for the new project list entry (defaults to random UUID)\n */\n public async duplicateProject(\n sourceRid: ResourceId,\n rename?: (previousLabel: string, existingLabels: string[]) => string,\n id: string = randomUUID(),\n ): Promise<ResourceId> {\n const resource = await this.pl.withWriteTx(\"MLDuplicateProject\", async (tx) => {\n // Read source project meta\n const sourceMeta = await tx.getKValueJson<ProjectMeta>(sourceRid, ProjectMetaKey);\n\n // Read all existing project labels from the project list (parallel reads)\n const projectListData = await tx.getResourceData(this.projectListResourceId, true);\n const projectRids = projectListData.fields.map((f) => f.value).filter(isNotNullResourceId);\n const existingLabels = (\n await Promise.all(\n projectRids.map((rid) => tx.getKValueJson<ProjectMeta>(rid, ProjectMetaKey)),\n )\n ).map((m) => m.label);\n\n // Compute new label\n const newLabel = rename ? rename(sourceMeta.label, existingLabels) : sourceMeta.label;\n\n // Create the duplicate\n const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });\n\n // Attach to project list\n tx.createField(field(this.projectListResourceId, id), \"Dynamic\", newPrj);\n await tx.commit();\n return await toGlobalResourceId(newPrj);\n });\n await this.projectListTree.refreshState();\n return resource;\n }\n\n //\n // Projects\n //\n\n private readonly openedProjectsByRid = new Map<ResourceId, Project>();\n\n private async projectIdToResourceId(id: string): Promise<ResourceId> {\n return await this.pl.withReadTx(\"Project id to resource id\", async (tx) => {\n const rid = (await tx.getField(field(this.projectListResourceId, id))).value;\n if (isNullResourceId(rid)) throw new Error(\"Unexpected project list structure.\");\n return rid;\n });\n }\n\n private async ensureProjectRid(id: ResourceId | string): Promise<ResourceId> {\n if (typeof id === \"string\") return await this.projectIdToResourceId(id);\n else return id;\n }\n\n /** Opens a project, and starts corresponding project maintenance loop. */\n public async openProject(id: ResourceId | string) {\n const rid = await this.ensureProjectRid(id);\n if (this.openedProjectsByRid.has(rid)) throw new Error(`Project ${rid} already opened`);\n this.openedProjectsByRid.set(rid, await Project.init(this.env, rid));\n this.openedProjectsList.setValue([...this.openedProjectsByRid.keys()]);\n }\n\n /** Closes the project, and deallocate all corresponding resources. */\n public async closeProject(rid: ResourceId): Promise<void> {\n const prj = this.openedProjectsByRid.get(rid);\n if (prj === undefined) throw new Error(`Project ${rid} not found among opened projects`);\n this.openedProjectsByRid.delete(rid);\n await prj.destroy();\n this.openedProjectsList.setValue([...this.openedProjectsByRid.keys()]);\n }\n\n /** Returns a project access object for opened project, for the given project\n * resource id. */\n public getOpenedProject(rid: ResourceId): Project {\n const prj = this.openedProjectsByRid.get(rid);\n if (prj === undefined) throw new Error(`Project ${rid} not found among opened projects`);\n return prj;\n }\n\n /** Returns true if project with given resource id is currently opened. */\n public isProjectOpened(rid: ResourceId): boolean {\n return this.openedProjectsByRid.has(rid);\n }\n\n /**\n * Deallocates all runtime resources consumed by this object and awaits\n * actual termination of event loops and other processes associated with\n * them.\n */\n public async close() {\n await Promise.all([...this.openedProjectsByRid.values()].map((prj) => prj.destroy()));\n // this.env.quickJs;\n await this.projectListTree.terminate();\n await this.env.dispose();\n await this.pl.close();\n }\n\n /** @deprecated */\n public async closeAndAwaitTermination() {\n await this.close();\n }\n\n /** Generates sufficiently random string to be used as local secret for the\n * middle layer */\n public static generateLocalSecret(): string {\n return HmacSha256Signer.generateSecret();\n }\n\n /** Returns a block event dispatcher, which can be used to listen to block events. */\n public get blockEventDispatcher(): BlockEventDispatcher {\n return this.env.blockEventDispatcher;\n }\n\n /** Initialize middle layer */\n public static async init(\n pl: PlClient,\n workdir: string,\n _ops: MiddleLayerOpsConstructor,\n ): Promise<MiddleLayer> {\n const ops: MiddleLayerOps = {\n ...DefaultMiddleLayerOpsSettings,\n ...DefaultMiddleLayerOpsPaths(workdir),\n ..._ops,\n };\n\n // overriding debug options from environment variables\n ops.defaultTreeOptions.logStat = getDebugFlags().logTreeStats;\n ops.debugOps.dumpInitialTreeState = getDebugFlags().dumpInitialTreeState;\n\n const projects = await pl.withWriteTx(\"MLInitialization\", async (tx) => {\n const projectsField = field(tx.clientRoot, ProjectsField);\n tx.createField(projectsField, \"Dynamic\");\n const projectsFieldData = await tx.getField(projectsField);\n if (isNullResourceId(projectsFieldData.value)) {\n const projects = tx.createEphemeral(ProjectsResourceType);\n tx.lock(projects);\n\n tx.setField(projectsField, projects);\n\n await tx.commit();\n\n return await projects.globalId;\n } else {\n return projectsFieldData.value;\n }\n });\n\n const logger = ops.logger;\n\n const driverKit = await initDriverKit(pl, workdir, ops.frontendDownloadPath, ops);\n\n // passed to components having no own retry logic\n const retryHttpDispatcher = new RetryAgent(pl.httpDispatcher);\n\n const v2RegistryProvider = new V2RegistryProvider(retryHttpDispatcher);\n\n const bpPreparer = new BlockPackPreparer(\n v2RegistryProvider,\n driverKit.signer,\n retryHttpDispatcher,\n );\n\n const quickJs = await getQuickJS();\n\n const runtimeCapabilities = new RuntimeCapabilities();\n // add runtime capabilities of model here\n runtimeCapabilities.addSupportedRequirement(\"requiresModelAPIVersion\", 1);\n runtimeCapabilities.addSupportedRequirement(\"requiresModelAPIVersion\", 2);\n runtimeCapabilities.addSupportedRequirement(\"requiresCreatePTable\", 2);\n runtimeCapabilities.addSupportedRequirement(\"requiresPFramesVersion\", REQUIRES_PFRAMES_VERSION);\n registerServiceCapabilities((flag, value) =>\n runtimeCapabilities.addSupportedRequirement(flag, value),\n );\n // runtime capabilities of the desktop are to be added by the desktop app / test framework\n\n const serviceRegistry = createModelServiceRegistry({ logger });\n\n const env: MiddleLayerEnvironment = {\n pl,\n blockEventDispatcher: new BlockEventDispatcher(),\n signer: driverKit.signer,\n logger,\n httpDispatcher: pl.httpDispatcher,\n retryHttpDispatcher,\n ops,\n bpPreparer,\n frontendDownloadDriver: driverKit.frontendDriver,\n driverKit,\n blockUpdateWatcher: new BlockUpdateWatcher(v2RegistryProvider, logger, {\n minDelay: ops.devBlockUpdateRecheckInterval,\n http: retryHttpDispatcher,\n preferredUpdateChannel: ops.preferredUpdateChannel,\n }),\n runtimeCapabilities,\n serviceRegistry,\n quickJs,\n projectHelper: new ProjectHelper(quickJs, logger),\n dispose: async () => {\n await serviceRegistry.dispose();\n await retryHttpDispatcher.destroy();\n await driverKit.dispose();\n },\n };\n\n const openedProjects = new WatchableValue<ResourceId[]>([]);\n const projectListTC = await createProjectList(pl, projects, openedProjects, env);\n\n return new MiddleLayer(\n env,\n driverKit,\n driverKit.signer,\n projects,\n openedProjects,\n projectListTC.tree,\n v2RegistryProvider,\n projectListTC.computable,\n );\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"middle_layer.cjs","names":["LRUCache","createProject","withProjectAuthored","ProjectMetaKey","isNotNullSignedResourceId","duplicateProject","Project","HmacSha256Signer","DefaultMiddleLayerOpsSettings","DefaultMiddleLayerOpsPaths","getDebugFlags","ProjectsField","ProjectsResourceType","initDriverKit","RetryAgent","V2RegistryProvider","BlockPackPreparer","RuntimeCapabilities","REQUIRES_PFRAMES_VERSION","createModelServiceRegistry","BlockEventDispatcher","BlockUpdateWatcher","ProjectHelper","WatchableValue","createProjectList"],"sources":["../../src/middle_layer/middle_layer.ts"],"sourcesContent":["import type { PlClient, SignedResourceId, ResourceRef } from \"@milaboratories/pl-client\";\nimport {\n field,\n isNotNullSignedResourceId,\n isNullSignedResourceId,\n resourceIdToString,\n} from \"@milaboratories/pl-client\";\nimport { LRUCache } from \"lru-cache\";\nimport { createProjectList, ProjectsField, ProjectsResourceType } from \"./project_list\";\nimport { createProject, duplicateProject, withProjectAuthored } from \"../mutator/project\";\nimport { ProjectMetaKey } from \"../model/project_model\";\nimport type { ProjectId } from \"../model/project_model\";\nimport type { SynchronizedTreeState } from \"@milaboratories/pl-tree\";\nimport { BlockPackPreparer } from \"../mutator/block-pack/block_pack\";\nimport type { MiLogger, Signer } from \"@milaboratories/ts-helpers\";\nimport { BlockEventDispatcher } from \"@milaboratories/ts-helpers\";\nimport { HmacSha256Signer } from \"@milaboratories/ts-helpers\";\nimport type { ComputableStableDefined } from \"@milaboratories/computable\";\nimport { WatchableValue } from \"@milaboratories/computable\";\nimport { Project } from \"./project\";\nimport type { MiddleLayerOps, MiddleLayerOpsConstructor } from \"./ops\";\nimport { DefaultMiddleLayerOpsPaths, DefaultMiddleLayerOpsSettings } from \"./ops\";\nimport { randomUUID } from \"node:crypto\";\nimport type { ProjectListEntry } from \"../model\";\nimport type {\n AuthorMarker,\n ProjectMeta,\n BlockPlatform,\n} from \"@milaboratories/pl-model-middle-layer\";\nimport { BlockUpdateWatcher } from \"../block_registry/watcher\";\nimport type { QuickJSWASMModule } from \"quickjs-emscripten\";\nimport { getQuickJS } from \"quickjs-emscripten\";\nimport type { MiddleLayerDriverKit } from \"./driver_kit\";\nimport { initDriverKit } from \"./driver_kit\";\nimport type { BlockCodeFeatureFlags, DriverKit, SupportedRequirement } from \"@platforma-sdk/model\";\nimport { RuntimeCapabilities } from \"@platforma-sdk/model\";\nimport {\n type ModelServiceRegistry,\n registerServiceCapabilities,\n REQUIRES_PFRAMES_VERSION,\n} from \"@milaboratories/pl-model-common\";\nimport { createModelServiceRegistry } from \"../service_factories\";\nimport type { DownloadUrlDriver } from \"@milaboratories/pl-drivers\";\nimport { V2RegistryProvider } from \"../block_registry\";\nimport type { Dispatcher } from \"undici\";\nimport { RetryAgent } from \"undici\";\nimport { getDebugFlags } from \"../debug\";\nimport { ProjectHelper } from \"../model/project_helper\";\n\nexport interface MiddleLayerEnvironment {\n dispose(): Promise<void>;\n readonly pl: PlClient;\n readonly runtimeCapabilities: RuntimeCapabilities;\n readonly logger: MiLogger;\n readonly blockEventDispatcher: BlockEventDispatcher;\n readonly httpDispatcher: Dispatcher;\n readonly retryHttpDispatcher: Dispatcher;\n readonly signer: Signer;\n readonly ops: MiddleLayerOps;\n readonly bpPreparer: BlockPackPreparer;\n readonly frontendDownloadDriver: DownloadUrlDriver;\n readonly blockUpdateWatcher: BlockUpdateWatcher;\n readonly quickJs: QuickJSWASMModule;\n readonly driverKit: MiddleLayerDriverKit;\n readonly serviceRegistry: ModelServiceRegistry;\n readonly projectHelper: ProjectHelper;\n}\n\n/**\n * Main access object to work with pl from UI.\n *\n * It implements an abstraction layer of projects and blocks.\n *\n * As a main entry point inside the pl, this object uses a resource attached\n * via the {@link ProjectsField} to the pl client's root, this resource\n * contains project list.\n *\n * Read about alternative roots, if isolated project lists (working environments)\n * are required.\n * */\nexport class MiddleLayer {\n public readonly pl: PlClient;\n\n /** Contains a reactive list of projects along with their meta information. */\n public readonly projectList: ComputableStableDefined<ProjectListEntry[]>;\n\n private constructor(\n private readonly env: MiddleLayerEnvironment,\n public readonly driverKit: DriverKit,\n public readonly signer: Signer,\n private readonly projectListResourceId: SignedResourceId,\n private readonly openedProjectsList: WatchableValue<ProjectId[]>,\n private readonly projectListTree: SynchronizedTreeState,\n public readonly blockRegistryProvider: V2RegistryProvider,\n projectList: ComputableStableDefined<ProjectListEntry[]>,\n ) {\n this.projectList = projectList;\n this.pl = this.env.pl;\n }\n\n /**\n * Get the OS where backend is running.\n * For old backend versions returns undefined.\n */\n public get serverPlatform(): BlockPlatform | undefined {\n return this.pl.serverInfo.platform as BlockPlatform | undefined;\n }\n\n /** Adds a runtime capability to the middle layer. */\n public addRuntimeCapability(\n requirement: SupportedRequirement,\n value: number | boolean = true,\n ): void {\n this.env.runtimeCapabilities.addSupportedRequirement(requirement, value);\n }\n\n /** Checks if the given block feature flags are compatible with the runtime capabilities. */\n public checkBlockCompatibility(featureFlags: BlockCodeFeatureFlags | undefined): boolean {\n return this.env.runtimeCapabilities.checkCompatibility(featureFlags);\n }\n\n /** Returns extended API driver kit used internally by middle layer. */\n public get internalDriverKit(): MiddleLayerDriverKit {\n return this.env.driverKit;\n }\n\n /** Returns the service registry for service introspection. */\n public get serviceRegistry(): ModelServiceRegistry {\n return this.env.serviceRegistry;\n }\n\n //\n // ProjectId ↔ SignedResourceId resolution\n //\n\n private readonly projectIdCache = new LRUCache<ProjectId, SignedResourceId>({ max: 1024 });\n\n /** Resolves a ProjectId to a signed SignedResourceId.\n * Uses LRU cache with TX-scan fallback. */\n private async resolveProjectId(projectId: ProjectId): Promise<SignedResourceId> {\n const cached = this.projectIdCache.get(projectId);\n if (cached !== undefined) return cached;\n\n // Cache miss — scan project list fields to find the matching resource\n const rid = await this.pl.withReadTx(\"ResolveProjectId\", async (tx) => {\n const data = await tx.getResourceData(this.projectListResourceId, true);\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n if (resourceIdToString(f.value) === (projectId as string)) return f.value;\n }\n throw new Error(`Project ${projectId} not found in project list.`);\n });\n\n this.projectIdCache.set(projectId, rid);\n return rid;\n }\n\n //\n // Project List Manipulation\n //\n\n /** Creates a project with initial state and adds it to project list. */\n public async createProject(meta: ProjectMeta): Promise<ProjectId> {\n let prj: ResourceRef;\n await this.pl.withWriteTx(\"MLCreateProject\", async (tx) => {\n prj = await createProject(tx, meta);\n tx.createField(field(this.projectListResourceId, randomUUID()), \"Dynamic\", prj);\n await tx.commit();\n });\n await this.projectListTree.refreshState();\n\n const signedRid = await prj!.globalId;\n const projectId = resourceIdToString(signedRid) as ProjectId;\n this.projectIdCache.set(projectId, signedRid);\n return projectId;\n }\n\n /** Updates project metadata */\n public async setProjectMeta(\n id: ProjectId,\n meta: ProjectMeta,\n author?: AuthorMarker,\n ): Promise<void> {\n const rid = await this.resolveProjectId(id);\n await withProjectAuthored(\n this.env.projectHelper,\n this.pl,\n rid,\n author,\n (prj) => {\n prj.setMeta(meta);\n },\n { name: \"setProjectMeta\" },\n );\n await this.projectListTree.refreshState();\n }\n\n /** Permanently deletes project from the project list, this will result in\n * destruction of all attached objects, like files, analysis results etc. */\n public async deleteProject(id: ProjectId): Promise<void> {\n await this.pl.withWriteTx(\"MLRemoveProject\", async (tx) => {\n const data = await tx.getResourceData(this.projectListResourceId, true);\n let fieldName: string | undefined;\n for (const f of data.fields) {\n if (isNullSignedResourceId(f.value)) continue;\n if (resourceIdToString(f.value) === (id as string)) {\n fieldName = f.name;\n break;\n }\n }\n if (fieldName === undefined) throw new Error(`Project ${id} not found in project list.`);\n tx.removeField(field(this.projectListResourceId, fieldName));\n await tx.commit();\n });\n this.projectIdCache.delete(id);\n await this.projectListTree.refreshState();\n }\n\n /**\n * Duplicates an existing project and adds the copy to this user's project list.\n *\n * @param srcProjectId - project id of the project to duplicate\n * @param rename - optional function that receives the source label and all existing\n * project labels (read within the same transaction), and returns the label for the copy\n */\n public async duplicateProject(\n srcProjectId: ProjectId,\n rename?: (previousLabel: string, existingLabels: string[]) => string,\n ): Promise<ProjectId> {\n const sourceRid = await this.resolveProjectId(srcProjectId);\n\n const newPrj: ResourceRef = await this.pl.withWriteTx(\"MLDuplicateProject\", async (tx) => {\n // Read source project meta\n const sourceMeta = await tx.getKValueJson<ProjectMeta>(sourceRid, ProjectMetaKey);\n\n // Read all existing project labels from the project list (parallel reads)\n const projectListData = await tx.getResourceData(this.projectListResourceId, true);\n const projectRids = projectListData.fields\n .map((f) => f.value)\n .filter(isNotNullSignedResourceId);\n const existingLabels = (\n await Promise.all(\n projectRids.map((rid) => tx.getKValueJson<ProjectMeta>(rid, ProjectMetaKey)),\n )\n ).map((m) => m.label);\n\n // Compute new label\n const newLabel = rename ? rename(sourceMeta.label, existingLabels) : sourceMeta.label;\n\n // Create the duplicate\n const newPrj = await duplicateProject(tx, sourceRid, { label: newLabel });\n\n // Attach to project list with a random UUID field name\n tx.createField(field(this.projectListResourceId, randomUUID()), \"Dynamic\", newPrj);\n await tx.commit();\n\n return newPrj;\n });\n\n await this.projectListTree.refreshState();\n\n const signedRid = await newPrj.globalId;\n const newProjectId = resourceIdToString(signedRid) as ProjectId;\n this.projectIdCache.set(newProjectId, signedRid);\n return newProjectId;\n }\n\n //\n // Projects\n //\n\n private readonly openedProjects = new Map<ProjectId, Project>();\n\n /** Opens a project, and starts corresponding project maintenance loop. */\n public async openProject(id: ProjectId): Promise<void> {\n if (this.openedProjects.has(id)) throw new Error(`Project ${id} already opened`);\n const rid = await this.resolveProjectId(id);\n this.openedProjects.set(id, await Project.init(this.env, id, rid));\n this.openedProjectsList.setValue([...this.openedProjects.keys()]);\n }\n\n /** Closes the project, and deallocate all corresponding resources. */\n public async closeProject(id: ProjectId): Promise<void> {\n const prj = this.openedProjects.get(id);\n if (prj === undefined) throw new Error(`Project ${id} not found among opened projects`);\n this.openedProjects.delete(id);\n await prj.destroy();\n this.openedProjectsList.setValue([...this.openedProjects.keys()]);\n }\n\n /** Returns a project access object for an opened project. */\n public getOpenedProject(id: ProjectId): Project {\n const prj = this.openedProjects.get(id);\n if (prj === undefined) throw new Error(`Project ${id} not found among opened projects`);\n return prj;\n }\n\n /** Returns true if project with given id is currently opened. */\n public isProjectOpened(id: ProjectId): boolean {\n return this.openedProjects.has(id);\n }\n\n /**\n * Deallocates all runtime resources consumed by this object and awaits\n * actual termination of event loops and other processes associated with\n * them.\n */\n public async close() {\n await Promise.all([...this.openedProjects.values()].map((prj) => prj.destroy()));\n // this.env.quickJs;\n await this.projectListTree.terminate();\n await this.env.dispose();\n await this.pl.close();\n }\n\n /** @deprecated */\n public async closeAndAwaitTermination() {\n await this.close();\n }\n\n /** Generates sufficiently random string to be used as local secret for the\n * middle layer */\n public static generateLocalSecret(): string {\n return HmacSha256Signer.generateSecret();\n }\n\n /** Returns a block event dispatcher, which can be used to listen to block events. */\n public get blockEventDispatcher(): BlockEventDispatcher {\n return this.env.blockEventDispatcher;\n }\n\n /** Initialize middle layer */\n public static async init(\n pl: PlClient,\n workdir: string,\n _ops: MiddleLayerOpsConstructor,\n ): Promise<MiddleLayer> {\n const ops: MiddleLayerOps = {\n ...DefaultMiddleLayerOpsSettings,\n ...DefaultMiddleLayerOpsPaths(workdir),\n ..._ops,\n };\n\n // overriding debug options from environment variables\n ops.defaultTreeOptions.logStat = getDebugFlags().logTreeStats;\n ops.debugOps.dumpInitialTreeState = getDebugFlags().dumpInitialTreeState;\n\n const projects = await pl.withWriteTx(\"MLInitialization\", async (tx) => {\n const projectsField = field(tx.clientRoot, ProjectsField);\n tx.createField(projectsField, \"Dynamic\");\n const projectsFieldData = await tx.getField(projectsField);\n if (isNullSignedResourceId(projectsFieldData.value)) {\n const projects = tx.createEphemeral(ProjectsResourceType);\n tx.lock(projects);\n\n tx.setField(projectsField, projects);\n\n await tx.commit();\n\n return await projects.globalId;\n } else {\n return projectsFieldData.value;\n }\n });\n\n const logger = ops.logger;\n\n const driverKit = await initDriverKit(pl, workdir, ops.frontendDownloadPath, ops);\n\n // passed to components having no own retry logic\n const retryHttpDispatcher = new RetryAgent(pl.httpDispatcher);\n\n const v2RegistryProvider = new V2RegistryProvider(retryHttpDispatcher);\n\n const bpPreparer = new BlockPackPreparer(\n v2RegistryProvider,\n driverKit.signer,\n retryHttpDispatcher,\n );\n\n const quickJs = await getQuickJS();\n\n const runtimeCapabilities = new RuntimeCapabilities();\n // add runtime capabilities of model here\n runtimeCapabilities.addSupportedRequirement(\"requiresModelAPIVersion\", 1);\n runtimeCapabilities.addSupportedRequirement(\"requiresModelAPIVersion\", 2);\n runtimeCapabilities.addSupportedRequirement(\"requiresCreatePTable\", 2);\n runtimeCapabilities.addSupportedRequirement(\"requiresPFramesVersion\", REQUIRES_PFRAMES_VERSION);\n registerServiceCapabilities((flag, value) =>\n runtimeCapabilities.addSupportedRequirement(flag, value),\n );\n // runtime capabilities of the desktop are to be added by the desktop app / test framework\n\n const serviceRegistry = createModelServiceRegistry({ logger });\n\n const env: MiddleLayerEnvironment = {\n pl,\n blockEventDispatcher: new BlockEventDispatcher(),\n signer: driverKit.signer,\n logger,\n httpDispatcher: pl.httpDispatcher,\n retryHttpDispatcher,\n ops,\n bpPreparer,\n frontendDownloadDriver: driverKit.frontendDriver,\n driverKit,\n blockUpdateWatcher: new BlockUpdateWatcher(v2RegistryProvider, logger, {\n minDelay: ops.devBlockUpdateRecheckInterval,\n http: retryHttpDispatcher,\n preferredUpdateChannel: ops.preferredUpdateChannel,\n }),\n runtimeCapabilities,\n serviceRegistry,\n quickJs,\n projectHelper: new ProjectHelper(quickJs, logger),\n dispose: async () => {\n await serviceRegistry.dispose();\n await retryHttpDispatcher.destroy();\n await driverKit.dispose();\n },\n };\n\n const openedProjects = new WatchableValue<ProjectId[]>([]);\n const projectListTC = await createProjectList(pl, projects, openedProjects, env);\n\n return new MiddleLayer(\n env,\n driverKit,\n driverKit.signer,\n projects,\n openedProjects,\n projectListTC.tree,\n v2RegistryProvider,\n projectListTC.computable,\n );\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { V2RegistryProvider } from "../block_registry/registry-v2-provider.js";
|
|
2
|
-
import { ProjectListEntry as ProjectListEntry$1 } from "../model/project_model.js";
|
|
2
|
+
import { ProjectId, ProjectListEntry as ProjectListEntry$1 } from "../model/project_model.js";
|
|
3
3
|
import { BlockPackPreparer } from "../mutator/block-pack/block_pack.js";
|
|
4
4
|
import { Project } from "./project.js";
|
|
5
5
|
import { MiddleLayerOps, MiddleLayerOpsConstructor } from "./ops.js";
|
|
@@ -10,7 +10,7 @@ import { BlockCodeFeatureFlags, DriverKit, RuntimeCapabilities, SupportedRequire
|
|
|
10
10
|
import { AuthorMarker, BlockPlatform, ProjectMeta } from "@milaboratories/pl-model-middle-layer";
|
|
11
11
|
import { Dispatcher } from "undici";
|
|
12
12
|
import { BlockEventDispatcher, MiLogger, Signer } from "@milaboratories/ts-helpers";
|
|
13
|
-
import { PlClient
|
|
13
|
+
import { PlClient } from "@milaboratories/pl-client";
|
|
14
14
|
import { ComputableStableDefined } from "@milaboratories/computable";
|
|
15
15
|
import { DownloadUrlDriver } from "@milaboratories/pl-drivers";
|
|
16
16
|
import { QuickJSWASMModule } from "quickjs-emscripten";
|
|
@@ -72,34 +72,34 @@ declare class MiddleLayer {
|
|
|
72
72
|
get internalDriverKit(): MiddleLayerDriverKit;
|
|
73
73
|
/** Returns the service registry for service introspection. */
|
|
74
74
|
get serviceRegistry(): ModelServiceRegistry;
|
|
75
|
+
private readonly projectIdCache;
|
|
76
|
+
/** Resolves a ProjectId to a signed SignedResourceId.
|
|
77
|
+
* Uses LRU cache with TX-scan fallback. */
|
|
78
|
+
private resolveProjectId;
|
|
75
79
|
/** Creates a project with initial state and adds it to project list. */
|
|
76
|
-
createProject(meta: ProjectMeta
|
|
80
|
+
createProject(meta: ProjectMeta): Promise<ProjectId>;
|
|
77
81
|
/** Updates project metadata */
|
|
78
|
-
setProjectMeta(
|
|
82
|
+
setProjectMeta(id: ProjectId, meta: ProjectMeta, author?: AuthorMarker): Promise<void>;
|
|
79
83
|
/** Permanently deletes project from the project list, this will result in
|
|
80
84
|
* destruction of all attached objects, like files, analysis results etc. */
|
|
81
|
-
deleteProject(id:
|
|
85
|
+
deleteProject(id: ProjectId): Promise<void>;
|
|
82
86
|
/**
|
|
83
87
|
* Duplicates an existing project and adds the copy to this user's project list.
|
|
84
88
|
*
|
|
85
|
-
* @param
|
|
89
|
+
* @param srcProjectId - project id of the project to duplicate
|
|
86
90
|
* @param rename - optional function that receives the source label and all existing
|
|
87
91
|
* project labels (read within the same transaction), and returns the label for the copy
|
|
88
|
-
* @param id - optional id for the new project list entry (defaults to random UUID)
|
|
89
92
|
*/
|
|
90
|
-
duplicateProject(
|
|
91
|
-
private readonly
|
|
92
|
-
private projectIdToResourceId;
|
|
93
|
-
private ensureProjectRid;
|
|
93
|
+
duplicateProject(srcProjectId: ProjectId, rename?: (previousLabel: string, existingLabels: string[]) => string): Promise<ProjectId>;
|
|
94
|
+
private readonly openedProjects;
|
|
94
95
|
/** Opens a project, and starts corresponding project maintenance loop. */
|
|
95
|
-
openProject(id:
|
|
96
|
+
openProject(id: ProjectId): Promise<void>;
|
|
96
97
|
/** Closes the project, and deallocate all corresponding resources. */
|
|
97
|
-
closeProject(
|
|
98
|
-
/** Returns a project access object for opened project
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
isProjectOpened(rid: ResourceId): boolean;
|
|
98
|
+
closeProject(id: ProjectId): Promise<void>;
|
|
99
|
+
/** Returns a project access object for an opened project. */
|
|
100
|
+
getOpenedProject(id: ProjectId): Project;
|
|
101
|
+
/** Returns true if project with given id is currently opened. */
|
|
102
|
+
isProjectOpened(id: ProjectId): boolean;
|
|
103
103
|
/**
|
|
104
104
|
* Deallocates all runtime resources consumed by this object and awaits
|
|
105
105
|
* actual termination of event loops and other processes associated with
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middle_layer.d.ts","names":[],"sources":["../../src/middle_layer/middle_layer.ts"],"mappings":";;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"middle_layer.d.ts","names":[],"sources":["../../src/middle_layer/middle_layer.ts"],"mappings":";;;;;;;;;;;;;;;;;;;UAiDiB,sBAAA;EACf,OAAA,IAAW,OAAA;EAAA,SACF,EAAA,EAAI,QAAA;EAAA,SACJ,mBAAA,EAAqB,mBAAA;EAAA,SACrB,MAAA,EAAQ,QAAA;EAAA,SACR,oBAAA,EAAsB,oBAAA;EAAA,SACtB,cAAA,EAAgB,UAAA;EAAA,SAChB,mBAAA,EAAqB,UAAA;EAAA,SACrB,MAAA,EAAQ,MAAA;EAAA,SACR,GAAA,EAAK,cAAA;EAAA,SACL,UAAA,EAAY,iBAAA;EAAA,SACZ,sBAAA,EAAwB,iBAAA;EAAA,SACxB,kBAAA,EAAoB,kBAAA;EAAA,SACpB,OAAA,EAAS,iBAAA;EAAA,SACT,SAAA,EAAW,oBAAA;EAAA,SACX,eAAA,EAAiB,oBAAA;EAAA,SACjB,aAAA,EAAe,aAAA;AAAA;;;;;;;;;;;;;cAeb,WAAA;EAAA,iBAOQ,GAAA;EAAA,SACD,SAAA,EAAW,SAAA;EAAA,SACX,MAAA,EAAQ,MAAA;EAAA,iBACP,qBAAA;EAAA,iBACA,kBAAA;EAAA,iBACA,eAAA;EAAA,SACD,qBAAA,EAAuB,kBAAA;EAAA,SAZzB,EAAA,EAAI,QAAA;EAvBN;EAAA,SA0BE,WAAA,EAAa,uBAAA,CAAwB,kBAAA;EAAA,QAE9C,WAAA,CAAA;EA1BE;;;;EAAA,IA4CE,cAAA,CAAA,GAAkB,aAAA;EA1CX;EA+CX,oBAAA,CACL,WAAA,EAAa,oBAAA,EACb,KAAA;EAhDkB;EAsDb,uBAAA,CAAwB,YAAA,EAAc,qBAAA;EArDnB;EAAA,IA0Df,iBAAA,CAAA,GAAqB,oBAAA;EAzDR;EAAA,IA8Db,eAAA,CAAA,GAAmB,oBAAA;EAAA,iBAQb,cAAA;EAvDN;;EAAA,QA2DG,gBAAA;EAnDe;EA0EhB,aAAA,CAAc,IAAA,EAAM,WAAA,GAAc,OAAA,CAAQ,SAAA;EArEd;EAqF5B,cAAA,CACX,EAAA,EAAI,SAAA,EACJ,IAAA,EAAM,WAAA,EACN,MAAA,GAAS,YAAA,GACR,OAAA;EAlGkD;;EAmHxC,aAAA,CAAc,EAAA,EAAI,SAAA,GAAY,OAAA;EAzF5B;;;;;;;EAmHF,gBAAA,CACX,YAAA,EAAc,SAAA,EACd,MAAA,IAAU,aAAA,UAAuB,cAAA,wBAChC,OAAA,CAAQ,SAAA;EAAA,iBA2CM,cAAA;EA1FN;EA6FE,WAAA,CAAY,EAAA,EAAI,SAAA,GAAY,OAAA;EA3EV;EAmFlB,YAAA,CAAa,EAAA,EAAI,SAAA,GAAY,OAAA;EAxD1B;EAiET,gBAAA,CAAiB,EAAA,EAAI,SAAA,GAAY,OAAA;EA/DrC;EAsEI,eAAA,CAAgB,EAAA,EAAI,SAAA;EAxBc;;;;;EAiC5B,KAAA,CAAA,GAAK,OAAA;EAAA;EASL,wBAAA,CAAA,GAAwB,OAAA;EAWF;;EAAA,OALrB,mBAAA,CAAA;EAcH;EAAA,IATA,oBAAA,CAAA,GAAwB,oBAAA;EASzB;EAAA,OAJU,IAAA,CAClB,EAAA,EAAI,QAAA,EACJ,OAAA,UACA,IAAA,EAAM,yBAAA,GACL,OAAA,CAAQ,WAAA;AAAA"}
|
|
@@ -14,7 +14,8 @@ import { ProjectHelper } from "../model/project_helper.js";
|
|
|
14
14
|
import { RuntimeCapabilities } from "@platforma-sdk/model";
|
|
15
15
|
import { RetryAgent } from "undici";
|
|
16
16
|
import { BlockEventDispatcher, HmacSha256Signer } from "@milaboratories/ts-helpers";
|
|
17
|
-
import { field,
|
|
17
|
+
import { field, isNotNullSignedResourceId, isNullSignedResourceId, resourceIdToString } from "@milaboratories/pl-client";
|
|
18
|
+
import { LRUCache } from "lru-cache";
|
|
18
19
|
import { WatchableValue } from "@milaboratories/computable";
|
|
19
20
|
import { randomUUID } from "node:crypto";
|
|
20
21
|
import { getQuickJS } from "quickjs-emscripten";
|
|
@@ -70,19 +71,40 @@ var MiddleLayer = class MiddleLayer {
|
|
|
70
71
|
get serviceRegistry() {
|
|
71
72
|
return this.env.serviceRegistry;
|
|
72
73
|
}
|
|
74
|
+
projectIdCache = new LRUCache({ max: 1024 });
|
|
75
|
+
/** Resolves a ProjectId to a signed SignedResourceId.
|
|
76
|
+
* Uses LRU cache with TX-scan fallback. */
|
|
77
|
+
async resolveProjectId(projectId) {
|
|
78
|
+
const cached = this.projectIdCache.get(projectId);
|
|
79
|
+
if (cached !== void 0) return cached;
|
|
80
|
+
const rid = await this.pl.withReadTx("ResolveProjectId", async (tx) => {
|
|
81
|
+
const data = await tx.getResourceData(this.projectListResourceId, true);
|
|
82
|
+
for (const f of data.fields) {
|
|
83
|
+
if (isNullSignedResourceId(f.value)) continue;
|
|
84
|
+
if (resourceIdToString(f.value) === projectId) return f.value;
|
|
85
|
+
}
|
|
86
|
+
throw new Error(`Project ${projectId} not found in project list.`);
|
|
87
|
+
});
|
|
88
|
+
this.projectIdCache.set(projectId, rid);
|
|
89
|
+
return rid;
|
|
90
|
+
}
|
|
73
91
|
/** Creates a project with initial state and adds it to project list. */
|
|
74
|
-
async createProject(meta
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
92
|
+
async createProject(meta) {
|
|
93
|
+
let prj;
|
|
94
|
+
await this.pl.withWriteTx("MLCreateProject", async (tx) => {
|
|
95
|
+
prj = await createProject(tx, meta);
|
|
96
|
+
tx.createField(field(this.projectListResourceId, randomUUID()), "Dynamic", prj);
|
|
78
97
|
await tx.commit();
|
|
79
|
-
return await toGlobalResourceId(prj);
|
|
80
98
|
});
|
|
81
99
|
await this.projectListTree.refreshState();
|
|
82
|
-
|
|
100
|
+
const signedRid = await prj.globalId;
|
|
101
|
+
const projectId = resourceIdToString(signedRid);
|
|
102
|
+
this.projectIdCache.set(projectId, signedRid);
|
|
103
|
+
return projectId;
|
|
83
104
|
}
|
|
84
105
|
/** Updates project metadata */
|
|
85
|
-
async setProjectMeta(
|
|
106
|
+
async setProjectMeta(id, meta, author) {
|
|
107
|
+
const rid = await this.resolveProjectId(id);
|
|
86
108
|
await withProjectAuthored(this.env.projectHelper, this.pl, rid, author, (prj) => {
|
|
87
109
|
prj.setMeta(meta);
|
|
88
110
|
}, { name: "setProjectMeta" });
|
|
@@ -92,69 +114,71 @@ var MiddleLayer = class MiddleLayer {
|
|
|
92
114
|
* destruction of all attached objects, like files, analysis results etc. */
|
|
93
115
|
async deleteProject(id) {
|
|
94
116
|
await this.pl.withWriteTx("MLRemoveProject", async (tx) => {
|
|
95
|
-
tx.
|
|
117
|
+
const data = await tx.getResourceData(this.projectListResourceId, true);
|
|
118
|
+
let fieldName;
|
|
119
|
+
for (const f of data.fields) {
|
|
120
|
+
if (isNullSignedResourceId(f.value)) continue;
|
|
121
|
+
if (resourceIdToString(f.value) === id) {
|
|
122
|
+
fieldName = f.name;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (fieldName === void 0) throw new Error(`Project ${id} not found in project list.`);
|
|
127
|
+
tx.removeField(field(this.projectListResourceId, fieldName));
|
|
96
128
|
await tx.commit();
|
|
97
129
|
});
|
|
130
|
+
this.projectIdCache.delete(id);
|
|
98
131
|
await this.projectListTree.refreshState();
|
|
99
132
|
}
|
|
100
133
|
/**
|
|
101
134
|
* Duplicates an existing project and adds the copy to this user's project list.
|
|
102
135
|
*
|
|
103
|
-
* @param
|
|
136
|
+
* @param srcProjectId - project id of the project to duplicate
|
|
104
137
|
* @param rename - optional function that receives the source label and all existing
|
|
105
138
|
* project labels (read within the same transaction), and returns the label for the copy
|
|
106
|
-
* @param id - optional id for the new project list entry (defaults to random UUID)
|
|
107
139
|
*/
|
|
108
|
-
async duplicateProject(
|
|
109
|
-
const
|
|
140
|
+
async duplicateProject(srcProjectId, rename) {
|
|
141
|
+
const sourceRid = await this.resolveProjectId(srcProjectId);
|
|
142
|
+
const newPrj = await this.pl.withWriteTx("MLDuplicateProject", async (tx) => {
|
|
110
143
|
const sourceMeta = await tx.getKValueJson(sourceRid, ProjectMetaKey);
|
|
111
|
-
const projectRids = (await tx.getResourceData(this.projectListResourceId, true)).fields.map((f) => f.value).filter(
|
|
144
|
+
const projectRids = (await tx.getResourceData(this.projectListResourceId, true)).fields.map((f) => f.value).filter(isNotNullSignedResourceId);
|
|
112
145
|
const existingLabels = (await Promise.all(projectRids.map((rid) => tx.getKValueJson(rid, ProjectMetaKey)))).map((m) => m.label);
|
|
113
146
|
const newPrj = await duplicateProject(tx, sourceRid, { label: rename ? rename(sourceMeta.label, existingLabels) : sourceMeta.label });
|
|
114
|
-
tx.createField(field(this.projectListResourceId,
|
|
147
|
+
tx.createField(field(this.projectListResourceId, randomUUID()), "Dynamic", newPrj);
|
|
115
148
|
await tx.commit();
|
|
116
|
-
return
|
|
149
|
+
return newPrj;
|
|
117
150
|
});
|
|
118
151
|
await this.projectListTree.refreshState();
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return await this.pl.withReadTx("Project id to resource id", async (tx) => {
|
|
124
|
-
const rid = (await tx.getField(field(this.projectListResourceId, id))).value;
|
|
125
|
-
if (isNullResourceId(rid)) throw new Error("Unexpected project list structure.");
|
|
126
|
-
return rid;
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
async ensureProjectRid(id) {
|
|
130
|
-
if (typeof id === "string") return await this.projectIdToResourceId(id);
|
|
131
|
-
else return id;
|
|
152
|
+
const signedRid = await newPrj.globalId;
|
|
153
|
+
const newProjectId = resourceIdToString(signedRid);
|
|
154
|
+
this.projectIdCache.set(newProjectId, signedRid);
|
|
155
|
+
return newProjectId;
|
|
132
156
|
}
|
|
157
|
+
openedProjects = /* @__PURE__ */ new Map();
|
|
133
158
|
/** Opens a project, and starts corresponding project maintenance loop. */
|
|
134
159
|
async openProject(id) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
this.
|
|
138
|
-
this.openedProjectsList.setValue([...this.
|
|
160
|
+
if (this.openedProjects.has(id)) throw new Error(`Project ${id} already opened`);
|
|
161
|
+
const rid = await this.resolveProjectId(id);
|
|
162
|
+
this.openedProjects.set(id, await Project.init(this.env, id, rid));
|
|
163
|
+
this.openedProjectsList.setValue([...this.openedProjects.keys()]);
|
|
139
164
|
}
|
|
140
165
|
/** Closes the project, and deallocate all corresponding resources. */
|
|
141
|
-
async closeProject(
|
|
142
|
-
const prj = this.
|
|
143
|
-
if (prj === void 0) throw new Error(`Project ${
|
|
144
|
-
this.
|
|
166
|
+
async closeProject(id) {
|
|
167
|
+
const prj = this.openedProjects.get(id);
|
|
168
|
+
if (prj === void 0) throw new Error(`Project ${id} not found among opened projects`);
|
|
169
|
+
this.openedProjects.delete(id);
|
|
145
170
|
await prj.destroy();
|
|
146
|
-
this.openedProjectsList.setValue([...this.
|
|
171
|
+
this.openedProjectsList.setValue([...this.openedProjects.keys()]);
|
|
147
172
|
}
|
|
148
|
-
/** Returns a project access object for opened project
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (prj === void 0) throw new Error(`Project ${rid} not found among opened projects`);
|
|
173
|
+
/** Returns a project access object for an opened project. */
|
|
174
|
+
getOpenedProject(id) {
|
|
175
|
+
const prj = this.openedProjects.get(id);
|
|
176
|
+
if (prj === void 0) throw new Error(`Project ${id} not found among opened projects`);
|
|
153
177
|
return prj;
|
|
154
178
|
}
|
|
155
|
-
/** Returns true if project with given
|
|
156
|
-
isProjectOpened(
|
|
157
|
-
return this.
|
|
179
|
+
/** Returns true if project with given id is currently opened. */
|
|
180
|
+
isProjectOpened(id) {
|
|
181
|
+
return this.openedProjects.has(id);
|
|
158
182
|
}
|
|
159
183
|
/**
|
|
160
184
|
* Deallocates all runtime resources consumed by this object and awaits
|
|
@@ -162,7 +186,7 @@ var MiddleLayer = class MiddleLayer {
|
|
|
162
186
|
* them.
|
|
163
187
|
*/
|
|
164
188
|
async close() {
|
|
165
|
-
await Promise.all([...this.
|
|
189
|
+
await Promise.all([...this.openedProjects.values()].map((prj) => prj.destroy()));
|
|
166
190
|
await this.projectListTree.terminate();
|
|
167
191
|
await this.env.dispose();
|
|
168
192
|
await this.pl.close();
|
|
@@ -193,7 +217,7 @@ var MiddleLayer = class MiddleLayer {
|
|
|
193
217
|
const projectsField = field(tx.clientRoot, ProjectsField);
|
|
194
218
|
tx.createField(projectsField, "Dynamic");
|
|
195
219
|
const projectsFieldData = await tx.getField(projectsField);
|
|
196
|
-
if (
|
|
220
|
+
if (isNullSignedResourceId(projectsFieldData.value)) {
|
|
197
221
|
const projects = tx.createEphemeral(ProjectsResourceType);
|
|
198
222
|
tx.lock(projects);
|
|
199
223
|
tx.setField(projectsField, projects);
|