@terraforge/core 0.0.19 → 0.0.20

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.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { S3Client } from "@aws-sdk/client-s3";
2
1
  import { DynamoDB } from "@aws-sdk/client-dynamodb";
2
+ import { S3Client } from "@aws-sdk/client-s3";
3
3
  import { UUID } from "node:crypto";
4
4
  import { AwsCredentialIdentity, AwsCredentialIdentityProvider } from "@aws-sdk/types";
5
5
 
@@ -208,6 +208,20 @@ type StateBackend = {
208
208
  delete(urn: URN): Promise<void>;
209
209
  };
210
210
  //#endregion
211
+ //#region src/backend/activity-log.d.ts
212
+ type LogProps = {
213
+ action: 'deploy' | 'delete';
214
+ filters?: string[];
215
+ };
216
+ type Log = LogProps & {
217
+ user?: string;
218
+ date?: number;
219
+ };
220
+ type ActivityLogBackend = {
221
+ log(urn: URN, log: LogProps): Promise<void>;
222
+ tail(urn: URN): Promise<Log[]>;
223
+ };
224
+ //#endregion
211
225
  //#region src/provider.d.ts
212
226
  type CreateProps<T = State> = {
213
227
  type: string;
@@ -319,6 +333,7 @@ type WorkSpaceOptions = {
319
333
  backend: {
320
334
  state: StateBackend;
321
335
  lock: LockBackend;
336
+ activityLog?: ActivityLogBackend;
322
337
  };
323
338
  hooks?: Hooks;
324
339
  };
@@ -367,6 +382,19 @@ declare class AppError extends Error {
367
382
  declare class ResourceNotFound extends Error {}
368
383
  declare class ResourceAlreadyExists extends Error {}
369
384
  //#endregion
385
+ //#region src/backend/memory/activity-log.d.ts
386
+ type Props$4 = {
387
+ user?: string;
388
+ };
389
+ declare class MemoryActivityLogBackend implements ActivityLogBackend {
390
+ private props;
391
+ protected groups: Map<`urn:${string}`, Log[]>;
392
+ constructor(props?: Props$4);
393
+ log(urn: URN, log: LogProps): Promise<void>;
394
+ private getLogGroup;
395
+ tail(urn: URN, limit?: number): Promise<Log[]>;
396
+ }
397
+ //#endregion
370
398
  //#region src/backend/memory/state.d.ts
371
399
  declare class MemoryStateBackend implements StateBackend {
372
400
  protected states: Map<`urn:${string}`, AppState>;
@@ -385,6 +413,20 @@ declare class MemoryLockBackend implements LockBackend {
385
413
  clear(): void;
386
414
  }
387
415
  //#endregion
416
+ //#region src/backend/file/activity-log.d.ts
417
+ type Props$3 = {
418
+ user?: string;
419
+ dir: string;
420
+ };
421
+ declare class FileActivityLogBackend implements ActivityLogBackend {
422
+ private props;
423
+ constructor(props: Props$3);
424
+ private logFile;
425
+ private mkdir;
426
+ log(urn: URN, log: LogProps): Promise<void>;
427
+ tail(urn: URN, limit?: number): Promise<Log[]>;
428
+ }
429
+ //#endregion
388
430
  //#region src/backend/file/state.d.ts
389
431
  declare class FileStateBackend implements StateBackend {
390
432
  private props;
@@ -411,23 +453,23 @@ declare class FileLockBackend implements LockBackend {
411
453
  lock(urn: URN): Promise<() => Promise<void>>;
412
454
  }
413
455
  //#endregion
414
- //#region src/backend/aws/s3-state.d.ts
415
- type Props$1 = {
456
+ //#region src/backend/aws/dynamodb-activity-log.d.ts
457
+ type Props$2 = {
416
458
  credentials: AwsCredentialIdentity | AwsCredentialIdentityProvider;
417
459
  region: string;
418
- bucket: string;
460
+ tableName: string;
461
+ user?: string;
419
462
  };
420
- declare class S3StateBackend implements StateBackend {
463
+ declare class DynamoDBActivityLogBackend implements ActivityLogBackend {
421
464
  private props;
422
- protected client: S3Client;
423
- constructor(props: Props$1);
424
- get(urn: URN): Promise<any>;
425
- update(urn: URN, state: AppState): Promise<void>;
426
- delete(urn: URN): Promise<void>;
465
+ protected client: DynamoDB;
466
+ constructor(props: Props$2);
467
+ log(urn: URN, log: LogProps): Promise<void>;
468
+ tail(urn: URN, limit?: number): Promise<Log[]>;
427
469
  }
428
470
  //#endregion
429
471
  //#region src/backend/aws/dynamodb-lock.d.ts
430
- type Props = {
472
+ type Props$1 = {
431
473
  credentials: AwsCredentialIdentity | AwsCredentialIdentityProvider;
432
474
  region: string;
433
475
  tableName: string;
@@ -435,12 +477,27 @@ type Props = {
435
477
  declare class DynamoLockBackend implements LockBackend {
436
478
  private props;
437
479
  protected client: DynamoDB;
438
- constructor(props: Props);
480
+ constructor(props: Props$1);
439
481
  insecureReleaseLock(urn: URN): Promise<void>;
440
482
  locked(urn: URN): Promise<boolean>;
441
483
  lock(urn: URN): Promise<() => Promise<void>>;
442
484
  }
443
485
  //#endregion
486
+ //#region src/backend/aws/s3-state.d.ts
487
+ type Props = {
488
+ credentials: AwsCredentialIdentity | AwsCredentialIdentityProvider;
489
+ region: string;
490
+ bucket: string;
491
+ };
492
+ declare class S3StateBackend implements StateBackend {
493
+ private props;
494
+ protected client: S3Client;
495
+ constructor(props: Props);
496
+ get(urn: URN): Promise<any>;
497
+ update(urn: URN, state: AppState): Promise<void>;
498
+ delete(urn: URN): Promise<void>;
499
+ }
500
+ //#endregion
444
501
  //#region src/helpers.d.ts
445
502
  declare const file: (path: string, encoding?: BufferEncoding) => Future<string>;
446
503
  declare const hash: (path: string, algo?: string) => Future<string>;
@@ -471,4 +528,4 @@ type CustomResourceProvider = Partial<{
471
528
  }>;
472
529
  declare const createCustomProvider: (providerId: string, resourceProviders: Record<string, CustomResourceProvider>) => Provider;
473
530
  //#endregion
474
- export { App, AppError, type Config, type CreateProps, type CustomResourceProvider, type DataSource, type DataSourceFunction, type DataSourceMeta, type DeleteProps, DynamoLockBackend, FileLockBackend, FileStateBackend, Future, type GetDataProps, type GetProps, Group, type Input, LockBackend, MemoryLockBackend, MemoryStateBackend, type Meta, type Node, type OptionalInput, type OptionalOutput, Output, type PlanProps, type ProcedureOptions, type Provider, type Resource, ResourceAlreadyExists, type ResourceClass, type ResourceConfig, ResourceError, type ResourceMeta, ResourceNotFound, type ResourceStatus, type ResourceStatusInfo, S3StateBackend, Stack, type StackStatusInfo, type State, StateBackend, type Tag, type URN, type UpdateProps, WorkSpace, type WorkSpaceOptions, createCustomProvider, createCustomResourceClass, createDebugger, createMeta, deferredOutput, enableDebug, findInputDeps, getMeta, isDataSource, isNode, isResource, nodeMetaSymbol, output, resolveInputs };
531
+ export { ActivityLogBackend, App, AppError, type Config, type CreateProps, type CustomResourceProvider, type DataSource, type DataSourceFunction, type DataSourceMeta, type DeleteProps, DynamoDBActivityLogBackend, DynamoLockBackend, FileActivityLogBackend, FileLockBackend, FileStateBackend, Future, type GetDataProps, type GetProps, Group, type Input, LockBackend, Log, LogProps, MemoryActivityLogBackend, MemoryLockBackend, MemoryStateBackend, type Meta, type Node, type OptionalInput, type OptionalOutput, Output, type PlanProps, type ProcedureOptions, type Provider, type Resource, ResourceAlreadyExists, type ResourceClass, type ResourceConfig, ResourceError, type ResourceMeta, ResourceNotFound, type ResourceStatus, type ResourceStatusInfo, S3StateBackend, Stack, type StackStatusInfo, type State, StateBackend, type Tag, type URN, type UpdateProps, WorkSpace, type WorkSpaceOptions, createCustomProvider, createCustomResourceClass, createDebugger, createMeta, deferredOutput, enableDebug, findInputDeps, getMeta, isDataSource, isNode, isResource, nodeMetaSymbol, output, resolveInputs };
package/dist/index.mjs CHANGED
@@ -4,12 +4,12 @@ import { DirectedGraph } from "graphology";
4
4
  import { topologicalGenerations, willCreateCycle } from "graphology-dag";
5
5
  import { v5 } from "uuid";
6
6
  import { get } from "get-wild";
7
- import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
7
+ import { appendFile, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
8
8
  import { join } from "node:path";
9
9
  import { lock } from "proper-lockfile";
10
- import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
11
10
  import { DynamoDB } from "@aws-sdk/client-dynamodb";
12
11
  import { marshall, unmarshall } from "@aws-sdk/util-dynamodb";
12
+ import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client, S3ServiceException } from "@aws-sdk/client-s3";
13
13
  import { createHash } from "node:crypto";
14
14
 
15
15
  //#region src/node.ts
@@ -605,6 +605,10 @@ const deleteResource = async (appToken, urn, state, opt) => {
605
605
  //#endregion
606
606
  //#region src/workspace/procedure/delete-app.ts
607
607
  const deleteApp = async (app, opt) => {
608
+ await opt.backend.activityLog?.log(app.urn, {
609
+ action: "delete",
610
+ filters: opt.filters
611
+ });
608
612
  const latestState = await opt.backend.state.get(app.urn);
609
613
  if (!latestState) throw new AppError(app.name, [], `App already deleted: ${app.name}`);
610
614
  const appState = migrateAppState(latestState);
@@ -863,6 +867,10 @@ const updateResource = async (resource, appToken, priorInputState, priorOutputSt
863
867
  const debug$1 = createDebugger("Deploy App");
864
868
  const deployApp = async (app, opt) => {
865
869
  debug$1(app.name, "start");
870
+ await opt.backend.activityLog?.log(app.urn, {
871
+ action: "deploy",
872
+ filters: opt.filters
873
+ });
866
874
  const appState = migrateAppState(await opt.backend.state.get(app.urn) ?? {
867
875
  name: app.name,
868
876
  stacks: {}
@@ -1304,6 +1312,29 @@ var WorkSpace = class {
1304
1312
  }
1305
1313
  };
1306
1314
 
1315
+ //#endregion
1316
+ //#region src/backend/memory/activity-log.ts
1317
+ var MemoryActivityLogBackend = class {
1318
+ groups = /* @__PURE__ */ new Map();
1319
+ constructor(props = {}) {
1320
+ this.props = props;
1321
+ }
1322
+ async log(urn, log) {
1323
+ this.getLogGroup(urn).push({
1324
+ user: this.props.user,
1325
+ date: Date.now(),
1326
+ ...log
1327
+ });
1328
+ }
1329
+ getLogGroup(urn) {
1330
+ if (!this.groups.has(urn)) this.groups.set(urn, []);
1331
+ return this.groups.get(urn);
1332
+ }
1333
+ async tail(urn, limit = 10) {
1334
+ return this.getLogGroup(urn).slice(-limit);
1335
+ }
1336
+ };
1337
+
1307
1338
  //#endregion
1308
1339
  //#region src/backend/memory/state.ts
1309
1340
  var MemoryStateBackend = class {
@@ -1345,6 +1376,34 @@ var MemoryLockBackend = class {
1345
1376
  }
1346
1377
  };
1347
1378
 
1379
+ //#endregion
1380
+ //#region src/backend/file/activity-log.ts
1381
+ var FileActivityLogBackend = class {
1382
+ constructor(props) {
1383
+ this.props = props;
1384
+ }
1385
+ logFile(urn) {
1386
+ return join(this.props.dir, `${urn}.log.jsonl`);
1387
+ }
1388
+ async mkdir() {
1389
+ await mkdir(this.props.dir, { recursive: true });
1390
+ }
1391
+ async log(urn, log) {
1392
+ const json = JSON.stringify({
1393
+ user: this.props.user,
1394
+ date: Date.now(),
1395
+ ...log
1396
+ });
1397
+ await this.mkdir();
1398
+ await appendFile(this.logFile(urn), `${json}\n`);
1399
+ }
1400
+ async tail(urn, limit = 10) {
1401
+ const file$1 = this.logFile(urn);
1402
+ if (!(await stat(file$1)).isFile()) return [];
1403
+ return (await readFile(file$1, "utf8")).split("\n").filter(Boolean).slice(-limit).map((line) => JSON.parse(line));
1404
+ }
1405
+ };
1406
+
1348
1407
  //#endregion
1349
1408
  //#region src/backend/file/state.ts
1350
1409
  const debug = createDebugger("State");
@@ -1353,7 +1412,7 @@ var FileStateBackend = class {
1353
1412
  this.props = props;
1354
1413
  }
1355
1414
  stateFile(urn) {
1356
- return join(this.props.dir, `${urn}.json`);
1415
+ return join(this.props.dir, `${urn}.state.json`);
1357
1416
  }
1358
1417
  async mkdir() {
1359
1418
  await mkdir(this.props.dir, { recursive: true });
@@ -1405,40 +1464,35 @@ var FileLockBackend = class {
1405
1464
  };
1406
1465
 
1407
1466
  //#endregion
1408
- //#region src/backend/aws/s3-state.ts
1409
- var S3StateBackend = class {
1467
+ //#region src/backend/aws/dynamodb-activity-log.ts
1468
+ var DynamoDBActivityLogBackend = class {
1410
1469
  client;
1411
1470
  constructor(props) {
1412
1471
  this.props = props;
1413
- this.client = new S3Client(props);
1414
- }
1415
- async get(urn) {
1416
- let result;
1417
- try {
1418
- result = await this.client.send(new GetObjectCommand({
1419
- Bucket: this.props.bucket,
1420
- Key: `${urn}.state`
1421
- }));
1422
- } catch (error) {
1423
- if (error instanceof S3ServiceException && error.name === "NoSuchKey") return;
1424
- throw error;
1425
- }
1426
- if (!result.Body) return;
1427
- const body = await result.Body.transformToString("utf8");
1428
- return JSON.parse(body);
1472
+ this.client = new DynamoDB(props);
1429
1473
  }
1430
- async update(urn, state) {
1431
- await this.client.send(new PutObjectCommand({
1432
- Bucket: this.props.bucket,
1433
- Key: `${urn}.state`,
1434
- Body: JSON.stringify(state)
1435
- }));
1474
+ async log(urn, log) {
1475
+ await this.client.putItem({
1476
+ TableName: this.props.tableName,
1477
+ Item: marshall({
1478
+ urn,
1479
+ user: this.props.user,
1480
+ date: Date.now(),
1481
+ ...log
1482
+ })
1483
+ });
1436
1484
  }
1437
- async delete(urn) {
1438
- await this.client.send(new DeleteObjectCommand({
1439
- Bucket: this.props.bucket,
1440
- Key: `${urn}.state`
1441
- }));
1485
+ async tail(urn, limit = 10) {
1486
+ return (await this.client.query({
1487
+ TableName: this.props.tableName,
1488
+ KeyConditionExpression: "#urn = :urn",
1489
+ ExpressionAttributeNames: { "#urn": "urn" },
1490
+ ExpressionAttributeValues: { ":urn": marshall(urn) },
1491
+ ScanIndexForward: false,
1492
+ Limit: limit
1493
+ })).Items?.map((item) => {
1494
+ return unmarshall(item);
1495
+ }) ?? [];
1442
1496
  }
1443
1497
  };
1444
1498
 
@@ -1489,6 +1543,44 @@ var DynamoLockBackend = class {
1489
1543
  }
1490
1544
  };
1491
1545
 
1546
+ //#endregion
1547
+ //#region src/backend/aws/s3-state.ts
1548
+ var S3StateBackend = class {
1549
+ client;
1550
+ constructor(props) {
1551
+ this.props = props;
1552
+ this.client = new S3Client(props);
1553
+ }
1554
+ async get(urn) {
1555
+ let result;
1556
+ try {
1557
+ result = await this.client.send(new GetObjectCommand({
1558
+ Bucket: this.props.bucket,
1559
+ Key: `${urn}.state`
1560
+ }));
1561
+ } catch (error) {
1562
+ if (error instanceof S3ServiceException && error.name === "NoSuchKey") return;
1563
+ throw error;
1564
+ }
1565
+ if (!result.Body) return;
1566
+ const body = await result.Body.transformToString("utf8");
1567
+ return JSON.parse(body);
1568
+ }
1569
+ async update(urn, state) {
1570
+ await this.client.send(new PutObjectCommand({
1571
+ Bucket: this.props.bucket,
1572
+ Key: `${urn}.state`,
1573
+ Body: JSON.stringify(state)
1574
+ }));
1575
+ }
1576
+ async delete(urn) {
1577
+ await this.client.send(new DeleteObjectCommand({
1578
+ Bucket: this.props.bucket,
1579
+ Key: `${urn}.state`
1580
+ }));
1581
+ }
1582
+ };
1583
+
1492
1584
  //#endregion
1493
1585
  //#region src/helpers.ts
1494
1586
  const file = (path, encoding = "utf8") => {
@@ -1599,4 +1691,4 @@ const createCustomProvider = (providerId, resourceProviders) => {
1599
1691
  };
1600
1692
 
1601
1693
  //#endregion
1602
- export { App, AppError, DynamoLockBackend, FileLockBackend, FileStateBackend, Future, Group, MemoryLockBackend, MemoryStateBackend, Output, ResourceAlreadyExists, ResourceError, ResourceNotFound, S3StateBackend, Stack, WorkSpace, createCustomProvider, createCustomResourceClass, createDebugger, createMeta, deferredOutput, enableDebug, findInputDeps, getMeta, isDataSource, isNode, isResource, nodeMetaSymbol, output, resolveInputs };
1694
+ export { App, AppError, DynamoDBActivityLogBackend, DynamoLockBackend, FileActivityLogBackend, FileLockBackend, FileStateBackend, Future, Group, MemoryActivityLogBackend, MemoryLockBackend, MemoryStateBackend, Output, ResourceAlreadyExists, ResourceError, ResourceNotFound, S3StateBackend, Stack, WorkSpace, createCustomProvider, createCustomResourceClass, createDebugger, createMeta, deferredOutput, enableDebug, findInputDeps, getMeta, isDataSource, isNode, isResource, nodeMetaSymbol, output, resolveInputs };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terraforge/core",
3
- "version": "0.0.19",
3
+ "version": "0.0.20",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",