@highstate/backend 0.4.6 → 0.5.1

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 CHANGED
@@ -63,6 +63,7 @@ declare class LocalPulumiHost {
63
63
  private passwords;
64
64
  hasPassword(projectId: string): boolean;
65
65
  setPassword(projectId: string, password: string): void;
66
+ removePassword(projectId: string): void;
66
67
  private getPassword;
67
68
  tryUnlockStack(stack: Stack, error: unknown): Promise<boolean>;
68
69
  static create(logger: Logger): LocalPulumiHost;
@@ -209,6 +210,12 @@ interface ProjectBackend {
209
210
  * @param projectId The ID of the project.
210
211
  */
211
212
  getProject(projectId: string): Promise<ProjectModel>;
213
+ /**
214
+ * Create an empty project.
215
+ *
216
+ * @param projectId The ID of the project.
217
+ */
218
+ createProject(projectId: string): Promise<void>;
212
219
  /**
213
220
  * Create the instance of the project.
214
221
  *
package/dist/index.mjs CHANGED
@@ -10,7 +10,7 @@ import Watcher from 'watcher';
10
10
  import { resolve } from 'import-meta-resolve';
11
11
  import { readdir, readFile, writeFile, mkdir } from 'node:fs/promises';
12
12
  import { getInstanceId, isUnitModel, parseInstanceId } from '@highstate/contract';
13
- import { h as hubModelSchema, i as instanceModelSchema, c as createInputResolver, a as createInputHashResolver, b as createInstanceState, d as instanceTerminalSchema, e as instancePageSchema, f as instanceFileSchema, g as instanceStatusFieldSchema, j as instanceTriggerSchema, k as compositeInstanceSchema, p as projectOperationSchema, l as instanceStateSchema, m as isFinalOperationStatus, t as terminalSessionSchema, n as applyPartialInstanceState, o as createInstanceStateFrontendPatch } from './terminal-C1HuyJ6e.mjs';
13
+ import { h as hubModelSchema, i as instanceModelSchema, c as createInputResolver, a as createInputHashResolver, b as createInstanceState, d as instanceTerminalSchema, e as instancePageSchema, f as instanceFileSchema, g as instanceStatusFieldSchema, j as instanceTriggerSchema, k as compositeInstanceSchema, p as projectOperationSchema, l as instanceStateSchema, m as isFinalOperationStatus, t as terminalSessionSchema, n as applyPartialInstanceState, o as createInstanceStateFrontendPatch } from './terminal-CkVAFPZo.mjs';
14
14
  import { sha256 } from 'crypto-hash';
15
15
  import 'ajv';
16
16
  import { Readable, PassThrough } from 'node:stream';
@@ -162,6 +162,9 @@ class LocalPulumiHost {
162
162
  setPassword(projectId, password) {
163
163
  this.passwords.set(projectId, password);
164
164
  }
165
+ removePassword(projectId) {
166
+ this.passwords.delete(projectId);
167
+ }
165
168
  getPassword(projectId) {
166
169
  return this.sharedPassword || this.passwords.get(projectId) || "";
167
170
  }
@@ -253,7 +256,7 @@ class LocalSecretBackend {
253
256
  projectId,
254
257
  () => this.projectPath,
255
258
  async (stack) => {
256
- this.logger.debug("checking password", { projectId });
259
+ this.logger.debug({ projectId }, "checking password");
257
260
  await stack.info(true);
258
261
  }
259
262
  );
@@ -261,6 +264,8 @@ class LocalSecretBackend {
261
264
  } catch (error) {
262
265
  if (error instanceof Error) {
263
266
  if (error.message.includes("incorrect passphrase")) {
267
+ this.logger.debug({ projectId, error }, "incorrect passphrase");
268
+ this.pulumiProjectHost.removePassword(projectId);
264
269
  return false;
265
270
  }
266
271
  }
@@ -274,7 +279,7 @@ class LocalSecretBackend {
274
279
  projectId,
275
280
  () => this.projectPath,
276
281
  async (stack) => {
277
- this.logger.debug("getting secrets", { projectId, instanceId });
282
+ this.logger.debug({ projectId, instanceId }, "getting secrets");
278
283
  const config = await stack.getAllConfig();
279
284
  const prefix = this.getPrefix(projectId, instanceId);
280
285
  const secrets = pickBy(config, (_, key) => key.startsWith(prefix));
@@ -290,7 +295,7 @@ class LocalSecretBackend {
290
295
  projectId,
291
296
  () => this.projectPath,
292
297
  async (stack) => {
293
- this.logger.debug("setting secrets", { projectId, instanceId });
298
+ this.logger.debug({ projectId, instanceId }, "setting secrets");
294
299
  const componentSecrets = mapValues(
295
300
  mapKeys(values, (key) => `${this.getPrefix(projectId, instanceId)}${key}`),
296
301
  (value) => ({
@@ -305,7 +310,7 @@ class LocalSecretBackend {
305
310
  );
306
311
  }
307
312
  getPrefix(projectId, instanceId) {
308
- instanceId = instanceId.replace(/:/g, ".");
313
+ instanceId = instanceId.replace(/:/g, "_");
309
314
  return `${this.projectName}:${projectId}/${instanceId}/`;
310
315
  }
311
316
  static async create(config, pulumiProjectHost, logger) {
@@ -484,6 +489,15 @@ class LocalProjectBackend {
484
489
  throw new Error("Failed to get project instances", { cause: error });
485
490
  }
486
491
  }
492
+ async createProject(projectId) {
493
+ try {
494
+ await this.withProject(projectId, () => {
495
+ return { instances: {}, hubs: {} };
496
+ });
497
+ } catch (error) {
498
+ throw new Error("Failed to create project", { cause: error });
499
+ }
500
+ }
487
501
  async createInstance(projectId, instance) {
488
502
  try {
489
503
  return await this.withProject(projectId, (project) => {
@@ -1209,7 +1223,7 @@ class LocalRunnerBackend {
1209
1223
  return this.pulumiProjectHost.runEmpty(
1210
1224
  options.projectId,
1211
1225
  options.instanceType,
1212
- options.instanceName,
1226
+ LocalRunnerBackend.getStackName(options),
1213
1227
  async (stack) => {
1214
1228
  const info = await stack.info();
1215
1229
  const instanceId = getInstanceId(options.instanceType, options.instanceName);
@@ -1238,7 +1252,7 @@ class LocalRunnerBackend {
1238
1252
  return this.pulumiProjectHost.runEmpty(
1239
1253
  options.projectId,
1240
1254
  options.instanceType,
1241
- options.instanceName,
1255
+ LocalRunnerBackend.getStackName(options),
1242
1256
  async (stack) => {
1243
1257
  const outputs = await stack.outputs();
1244
1258
  if (!outputs["$terminals"]) {
@@ -1257,7 +1271,7 @@ class LocalRunnerBackend {
1257
1271
  return this.pulumiProjectHost.runEmpty(
1258
1272
  options.projectId,
1259
1273
  options.instanceType,
1260
- options.instanceName,
1274
+ LocalRunnerBackend.getStackName(options),
1261
1275
  async (stack) => {
1262
1276
  const outputs = await stack.outputs();
1263
1277
  if (!outputs["$pages"]) {
@@ -1276,7 +1290,7 @@ class LocalRunnerBackend {
1276
1290
  return this.pulumiProjectHost.runEmpty(
1277
1291
  options.projectId,
1278
1292
  options.instanceType,
1279
- options.instanceName,
1293
+ LocalRunnerBackend.getStackName(options),
1280
1294
  async (stack) => {
1281
1295
  const outputs = await stack.outputs();
1282
1296
  if (!outputs["$files"]) {
@@ -1313,7 +1327,7 @@ class LocalRunnerBackend {
1313
1327
  await this.pulumiProjectHost.runLocal(
1314
1328
  options.projectId,
1315
1329
  options.instanceType,
1316
- options.instanceName,
1330
+ LocalRunnerBackend.getStackName(options),
1317
1331
  () => this.resolveProjectPath(options.source),
1318
1332
  async (stack) => {
1319
1333
  await stack.setAllConfig(configMap);
@@ -1400,7 +1414,7 @@ class LocalRunnerBackend {
1400
1414
  await this.pulumiProjectHost.runEmpty(
1401
1415
  options.projectId,
1402
1416
  options.instanceType,
1403
- options.instanceName,
1417
+ LocalRunnerBackend.getStackName(options),
1404
1418
  async (stack) => {
1405
1419
  const summary = await stack.workspace.stack();
1406
1420
  let currentResourceCount = summary?.resourceCount ?? 0;
@@ -1470,7 +1484,7 @@ class LocalRunnerBackend {
1470
1484
  await this.pulumiProjectHost.runEmpty(
1471
1485
  options.projectId,
1472
1486
  options.instanceType,
1473
- options.instanceName,
1487
+ LocalRunnerBackend.getStackName(options),
1474
1488
  async (stack) => {
1475
1489
  const summary = await stack.workspace.stack();
1476
1490
  let currentResourceCount = 0;
@@ -1622,6 +1636,9 @@ class LocalRunnerBackend {
1622
1636
  await ensureDependencyInstalled(packageName);
1623
1637
  return true;
1624
1638
  }
1639
+ static getStackName(options) {
1640
+ return `${options.projectId}_${options.instanceName}`;
1641
+ }
1625
1642
  static async create(config, pulumiProjectHost) {
1626
1643
  let sourceBasePath = config.HIGHSTATE_BACKEND_RUNNER_LOCAL_SOURCE_BASE_PATH;
1627
1644
  if (!sourceBasePath) {
@@ -2071,6 +2088,9 @@ class RuntimeOperation {
2071
2088
  childrenStateMap = /* @__PURE__ */ new Map();
2072
2089
  dependentStateMap = /* @__PURE__ */ new Map();
2073
2090
  library;
2091
+ inputHashLock = new BetterLock();
2092
+ inputHashPromiseCache = /* @__PURE__ */ new Map();
2093
+ inputHashNodes = /* @__PURE__ */ new Map();
2074
2094
  resolveInputHash;
2075
2095
  async operateSafe() {
2076
2096
  try {
@@ -2156,9 +2176,8 @@ class RuntimeOperation {
2156
2176
  this.tryAddStateToParent(state);
2157
2177
  this.addDependentState(state);
2158
2178
  }
2159
- const inputHashNodes = /* @__PURE__ */ new Map();
2160
2179
  for (const instance of allInstances) {
2161
- inputHashNodes.set(instance.id, {
2180
+ this.inputHashNodes.set(instance.id, {
2162
2181
  instance,
2163
2182
  resolvedInputs: this.resolvedInstanceInputs.get(instance.id),
2164
2183
  state: this.stateMap.get(instance.id),
@@ -2167,8 +2186,9 @@ class RuntimeOperation {
2167
2186
  });
2168
2187
  }
2169
2188
  this.resolveInputHash = createInputHashResolver(
2170
- inputHashNodes,
2171
- this.logger.child({ resolver: "input-hash-resolver" })
2189
+ this.inputHashNodes,
2190
+ this.logger.child({ resolver: "input-hash-resolver" }),
2191
+ { promiseCache: this.inputHashPromiseCache }
2172
2192
  );
2173
2193
  if (this.operation.type === "update") {
2174
2194
  await this.extendWithNotCreatedDependencies();
@@ -2325,7 +2345,17 @@ class RuntimeOperation {
2325
2345
  finalStatuses: ["created", "error"]
2326
2346
  });
2327
2347
  await this.watchStateStream(stream);
2328
- const inputHash = await this.resolveInputHash(instance.id);
2348
+ const inputHash = await this.inputHashLock.acquire(async () => {
2349
+ this.inputHashNodes.set(instance.id, {
2350
+ instance,
2351
+ resolvedInputs: this.resolvedInstanceInputs.get(instance.id),
2352
+ state: this.stateMap.get(instance.id),
2353
+ sourceHash: void 0
2354
+ // TODO: implement source hash
2355
+ });
2356
+ this.inputHashPromiseCache.clear();
2357
+ return await this.resolveInputHash(instance.id);
2358
+ });
2329
2359
  this.updateInstanceState({ id: instance.id, inputHash });
2330
2360
  logger.debug("input hash after update", { inputHash });
2331
2361
  logger.info("unit updated");
@@ -11,8 +11,8 @@ type GraphResolverOptions<TNode, TOutput> = {
11
11
  };
12
12
  interface GraphResolverBackend<TOutput> {
13
13
  promiseCache: Map<string, Promise<TOutput>>;
14
- setOutput(id: string, value: TOutput): void;
15
- setDependencies(id: string, dependencies: string[]): void;
14
+ setOutput?(id: string, value: TOutput): void;
15
+ setDependencies?(id: string, dependencies: string[]): void;
16
16
  }
17
17
  type GraphResolverFactory<TNode, TOutput> = (nodes: ReadonlyMap<string, TNode>, logger: Logger, backend?: GraphResolverBackend<TOutput>) => GraphResolver<TOutput>;
18
18
  type GraphResolver<TOutput> = (id: string) => Promise<TOutput>;
@@ -1,5 +1,5 @@
1
- import { q as defineGraphResolver } from '../terminal-C1HuyJ6e.mjs';
2
- export { J as CircularDependencyError, n as applyPartialInstanceState, k as compositeInstanceSchema, K as createDefaultGraphResolverBackend, a as createInputHashResolver, c as createInputResolver, b as createInstanceState, o as createInstanceStateFrontendPatch, O as getMatchedInjectionInstanceInputs, L as getResolvedHubInputs, N as getResolvedInjectionInstanceInputs, M as getResolvedInstanceInputs, u as hubInstanceInputSchema, w as hubModelPatchSchema, h as hubModelSchema, y as instanceFileMetaSchema, f as instanceFileSchema, s as instanceInputSchema, v as instanceModelPatchSchema, i as instanceModelSchema, z as instancePageBlockSchema, A as instancePageMetaSchema, e as instancePageSchema, F as instanceStatePatchSchema, l as instanceStateSchema, g as instanceStatusFieldSchema, x as instanceStatusSchema, C as instanceTerminalFileSchema, B as instanceTerminalMetaSchema, d as instanceTerminalSchema, E as instanceTriggerInvocationSchema, j as instanceTriggerSchema, D as instanceTriggerSpecSchema, m as isFinalOperationStatus, H as operationStatusSchema, G as operationTypeSchema, r as positionSchema, I as projectOperationRequestSchema, p as projectOperationSchema, t as terminalSessionSchema } from '../terminal-C1HuyJ6e.mjs';
1
+ import { q as defineGraphResolver } from '../terminal-CkVAFPZo.mjs';
2
+ export { J as CircularDependencyError, n as applyPartialInstanceState, k as compositeInstanceSchema, K as createDefaultGraphResolverBackend, a as createInputHashResolver, c as createInputResolver, b as createInstanceState, o as createInstanceStateFrontendPatch, O as getMatchedInjectionInstanceInputs, L as getResolvedHubInputs, N as getResolvedInjectionInstanceInputs, M as getResolvedInstanceInputs, u as hubInstanceInputSchema, w as hubModelPatchSchema, h as hubModelSchema, y as instanceFileMetaSchema, f as instanceFileSchema, s as instanceInputSchema, v as instanceModelPatchSchema, i as instanceModelSchema, z as instancePageBlockSchema, A as instancePageMetaSchema, e as instancePageSchema, F as instanceStatePatchSchema, l as instanceStateSchema, g as instanceStatusFieldSchema, x as instanceStatusSchema, C as instanceTerminalFileSchema, B as instanceTerminalMetaSchema, d as instanceTerminalSchema, E as instanceTriggerInvocationSchema, j as instanceTriggerSchema, D as instanceTriggerSpecSchema, m as isFinalOperationStatus, H as operationStatusSchema, G as operationTypeSchema, r as positionSchema, I as projectOperationRequestSchema, p as projectOperationSchema, t as terminalSessionSchema } from '../terminal-CkVAFPZo.mjs';
3
3
  import { Ajv } from 'ajv';
4
4
  import 'zod';
5
5
  import 'remeda';
@@ -259,11 +259,7 @@ class CircularDependencyError extends Error {
259
259
  function createDefaultGraphResolverBackend() {
260
260
  const promiseCache = /* @__PURE__ */ new Map();
261
261
  return {
262
- promiseCache,
263
- setOutput() {
264
- },
265
- setDependencies() {
266
- }
262
+ promiseCache
267
263
  };
268
264
  }
269
265
  function defineGraphResolver(options) {
@@ -284,7 +280,7 @@ function defineGraphResolver(options) {
284
280
  }
285
281
  const resolve = async () => {
286
282
  const dependencies = unique(options.getNodeDependencies(item));
287
- backend.setDependencies(itemId, dependencies);
283
+ backend.setDependencies?.(itemId, dependencies);
288
284
  logger.trace({ itemId, dependencies }, "resolving item dependencies");
289
285
  const resolvedDependencies = /* @__PURE__ */ new Map();
290
286
  const newChain = [...dependencyChain, itemId];
@@ -298,7 +294,7 @@ function defineGraphResolver(options) {
298
294
  };
299
295
  const promise = resolve().then((result) => {
300
296
  if (backend.promiseCache.get(itemId) === promise) {
301
- backend.setOutput(itemId, result);
297
+ backend.setOutput?.(itemId, result);
302
298
  }
303
299
  return result;
304
300
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@highstate/backend",
3
- "version": "0.4.6",
3
+ "version": "0.5.1",
4
4
  "type": "module",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -27,7 +27,7 @@
27
27
  "build": "pkgroll --tsconfig=tsconfig.build.json"
28
28
  },
29
29
  "dependencies": {
30
- "@highstate/contract": "^0.4.6",
30
+ "@highstate/contract": "^0.5.1",
31
31
  "@types/node": "^22.10.1",
32
32
  "ajv": "^8.17.1",
33
33
  "better-lock": "^3.2.0",
@@ -65,5 +65,5 @@
65
65
  "rollup": "^4.28.1",
66
66
  "typescript": "^5.7.2"
67
67
  },
68
- "gitHead": "dbb1d8125884cfe3a9d95df2e0710333c01c7edf"
68
+ "gitHead": "4ee64a0eebcb655746fa591ebf8e82199cf9e5d5"
69
69
  }