@le-space/node 0.1.15 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.d.ts +41 -0
  2. package/index.js +190 -10
  3. package/package.json +4 -4
package/index.d.ts CHANGED
@@ -160,6 +160,26 @@ interface JsonFetchLikeResponse {
160
160
  }
161
161
  type JsonFetchLike = (url: string, init?: RequestInit) => Promise<JsonFetchLikeResponse>;
162
162
 
163
+ declare function forgetAlephMessages(args: {
164
+ sender: string;
165
+ hashes?: string[];
166
+ aggregates?: string[];
167
+ reason?: string;
168
+ signer: MessageSigner;
169
+ hasher: MessageHasher;
170
+ fetch: JsonFetchLike;
171
+ channel?: string;
172
+ apiHost?: string;
173
+ sync?: boolean;
174
+ now?: number;
175
+ }): Promise<{
176
+ sender: string;
177
+ itemHash: string;
178
+ response: _shared_aleph_shared_types.AlephBroadcastResponse;
179
+ httpStatus: number;
180
+ status: _shared_aleph_shared_types.MessageStatus;
181
+ }>;
182
+
163
183
  interface RetentionRecord {
164
184
  instance_item_hash: string;
165
185
  rootfs_item_hash: string;
@@ -197,6 +217,9 @@ declare function retainSuccessfulDeployments(args: {
197
217
  retainedRecords: RetentionRecord[];
198
218
  prunedRecords: RetentionRecord[];
199
219
  forgetHashes: string[];
220
+ forgottenHashes: string[];
221
+ outstandingForgetHashes: string[];
222
+ forgetStatuses: Record<string, string>;
200
223
  forgetResult: {
201
224
  sender: string;
202
225
  itemHash: string;
@@ -204,6 +227,24 @@ declare function retainSuccessfulDeployments(args: {
204
227
  httpStatus: number;
205
228
  status: _shared_aleph_shared_types.MessageStatus;
206
229
  } | null;
230
+ followUpForgetResults: {
231
+ hash: string;
232
+ result: Awaited<ReturnType<typeof forgetAlephMessages>>;
233
+ }[];
234
+ forgetStageResults: {
235
+ stage: "instances" | "dependents";
236
+ hashes: string[];
237
+ skipped?: boolean;
238
+ skippedReason?: string;
239
+ forgottenHashes: string[];
240
+ outstandingForgetHashes: string[];
241
+ statuses: Record<string, string>;
242
+ forgetResult: Awaited<ReturnType<typeof forgetAlephMessages>> | null;
243
+ followUpForgetResults: Array<{
244
+ hash: string;
245
+ result: Awaited<ReturnType<typeof forgetAlephMessages>>;
246
+ }>;
247
+ }[];
207
248
  }>;
208
249
 
209
250
  declare function buildScaffoldDeployResult(env?: NodeJS.ProcessEnv): DeployOutputResult;
package/index.js CHANGED
@@ -896,6 +896,26 @@ function retentionRecordId(record) {
896
896
  function hashesFromRetentionRecord(record) {
897
897
  return [record.instance_item_hash, record.rootfs_item_hash, record.site_item_hash].filter(Boolean);
898
898
  }
899
+ function uniqueHashes(hashes) {
900
+ return [...new Set(hashes.filter(Boolean))];
901
+ }
902
+ function dependentHashesFromRetentionRecord(record) {
903
+ return [record.rootfs_item_hash, record.site_item_hash].filter(Boolean);
904
+ }
905
+ function splitForgetStages(args) {
906
+ const instanceForgetHashes = uniqueHashes(args.prunedRecords.map((record) => record.instance_item_hash)).filter(
907
+ (hash) => !args.retainedHashes.has(hash)
908
+ );
909
+ const dependentForgetHashes = uniqueHashes([
910
+ ...args.prunedRecords.flatMap(dependentHashesFromRetentionRecord),
911
+ ...args.extraForgetHashes
912
+ ]).filter((hash) => !args.retainedHashes.has(hash) && !instanceForgetHashes.includes(hash));
913
+ return {
914
+ instanceForgetHashes,
915
+ dependentForgetHashes,
916
+ orderedForgetHashes: [...instanceForgetHashes, ...dependentForgetHashes]
917
+ };
918
+ }
899
919
  async function fetchAggregateKey(args) {
900
920
  const requestUrl = new URL(`/api/v0/aggregates/${args.address}.json`, args.apiHost ?? "https://api2.aleph.im");
901
921
  requestUrl.searchParams.set("keys", args.key);
@@ -948,6 +968,98 @@ async function publishAggregateKey(args) {
948
968
  httpStatus
949
969
  };
950
970
  }
971
+ async function fetchMessageStatus(args) {
972
+ const requestUrl = new URL(`/api/v0/messages/${args.hash}`, args.apiHost ?? "https://api2.aleph.im");
973
+ const response = await args.fetch(requestUrl.toString(), { cache: "no-cache" });
974
+ if (response.status === 404) return "missing";
975
+ if (!response.ok) return "unknown";
976
+ const payload = await response.json();
977
+ return asString3(payload?.status) ?? "unknown";
978
+ }
979
+ async function classifyForgetHashes(args) {
980
+ const statuses = {};
981
+ for (const hash of args.hashes) {
982
+ statuses[hash] = await fetchMessageStatus({
983
+ hash,
984
+ fetch: args.fetch,
985
+ apiHost: args.apiHost
986
+ });
987
+ }
988
+ const forgottenHashes = args.hashes.filter((hash) => statuses[hash] === "forgotten");
989
+ const outstandingForgetHashes = args.hashes.filter((hash) => statuses[hash] !== "forgotten");
990
+ return {
991
+ forgottenHashes,
992
+ outstandingForgetHashes,
993
+ statuses
994
+ };
995
+ }
996
+ async function executeForgetStage(args) {
997
+ const stageHashes = uniqueHashes(args.hashes);
998
+ let forgetResult = null;
999
+ const followUpForgetResults = [];
1000
+ if (stageHashes.length === 0) {
1001
+ return {
1002
+ hashes: stageHashes,
1003
+ forgottenHashes: [],
1004
+ outstandingForgetHashes: [],
1005
+ statuses: {},
1006
+ forgetResult,
1007
+ followUpForgetResults
1008
+ };
1009
+ }
1010
+ let classified = await classifyForgetHashes({
1011
+ hashes: stageHashes,
1012
+ fetch: args.fetch,
1013
+ apiHost: args.apiHost
1014
+ });
1015
+ if (classified.outstandingForgetHashes.length > 0) {
1016
+ forgetResult = await forgetAlephMessages({
1017
+ sender: args.sender,
1018
+ hashes: classified.outstandingForgetHashes,
1019
+ reason: args.reason,
1020
+ signer: args.signer,
1021
+ hasher: args.hasher,
1022
+ fetch: args.fetch,
1023
+ channel: args.channel,
1024
+ apiHost: args.apiHost,
1025
+ now: args.now
1026
+ });
1027
+ classified = await classifyForgetHashes({
1028
+ hashes: stageHashes,
1029
+ fetch: args.fetch,
1030
+ apiHost: args.apiHost
1031
+ });
1032
+ if (classified.outstandingForgetHashes.length > 0) {
1033
+ for (const hash of classified.outstandingForgetHashes) {
1034
+ const result = await forgetAlephMessages({
1035
+ sender: args.sender,
1036
+ hashes: [hash],
1037
+ reason: args.reason,
1038
+ signer: args.signer,
1039
+ hasher: args.hasher,
1040
+ fetch: args.fetch,
1041
+ channel: args.channel,
1042
+ apiHost: args.apiHost,
1043
+ now: args.now
1044
+ });
1045
+ followUpForgetResults.push({ hash, result });
1046
+ }
1047
+ classified = await classifyForgetHashes({
1048
+ hashes: stageHashes,
1049
+ fetch: args.fetch,
1050
+ apiHost: args.apiHost
1051
+ });
1052
+ }
1053
+ }
1054
+ return {
1055
+ hashes: stageHashes,
1056
+ forgottenHashes: classified.forgottenHashes,
1057
+ outstandingForgetHashes: classified.outstandingForgetHashes,
1058
+ statuses: classified.statuses,
1059
+ forgetResult,
1060
+ followUpForgetResults
1061
+ };
1062
+ }
951
1063
  async function retainSuccessfulDeployments(args) {
952
1064
  const keepCount = Math.max(0, Number.parseInt(String(args.keepCount ?? 0), 10) || 0);
953
1065
  const aggregateKey = asString3(args.aggregateKey) ?? SUCCESSFUL_DEPLOYMENTS_AGGREGATE_KEY;
@@ -974,10 +1086,13 @@ async function retainSuccessfulDeployments(args) {
974
1086
  const retainedRecords = keepCount > 0 ? uniqueRecords.slice(0, keepCount) : [];
975
1087
  const prunedRecords = keepCount > 0 ? uniqueRecords.slice(keepCount) : uniqueRecords;
976
1088
  const retainedHashes = new Set(retainedRecords.flatMap(hashesFromRetentionRecord));
977
- const extraForgetHashes = [...new Set((args.extraForgetHashes ?? []).filter(Boolean))];
978
- const forgetHashes = [.../* @__PURE__ */ new Set([...prunedRecords.flatMap(hashesFromRetentionRecord), ...extraForgetHashes])].filter(
979
- (hash) => !retainedHashes.has(hash)
980
- );
1089
+ const extraForgetHashes = uniqueHashes(args.extraForgetHashes ?? []);
1090
+ const { instanceForgetHashes, dependentForgetHashes, orderedForgetHashes } = splitForgetStages({
1091
+ prunedRecords,
1092
+ extraForgetHashes,
1093
+ retainedHashes
1094
+ });
1095
+ const forgetHashes = orderedForgetHashes;
981
1096
  const aggregateContent = {
982
1097
  keep: keepCount,
983
1098
  updated_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -994,12 +1109,18 @@ async function retainSuccessfulDeployments(args) {
994
1109
  apiHost: args.apiHost,
995
1110
  now: args.now
996
1111
  });
1112
+ const forgetReason = args.reason ?? `Prune successful deployments beyond retention limit ${keepCount}`;
1113
+ const forgetStageResults = [];
1114
+ let forgottenHashes = [];
1115
+ let outstandingForgetHashes = [];
1116
+ let forgetStatuses = {};
997
1117
  let forgetResult = null;
998
- if (forgetHashes.length > 0) {
999
- forgetResult = await forgetAlephMessages({
1118
+ const followUpForgetResults = [];
1119
+ if (instanceForgetHashes.length > 0) {
1120
+ const instanceStage = await executeForgetStage({
1000
1121
  sender: args.sender,
1001
- hashes: forgetHashes,
1002
- reason: args.reason ?? `Prune successful deployments beyond retention limit ${keepCount}`,
1122
+ hashes: instanceForgetHashes,
1123
+ reason: forgetReason,
1003
1124
  signer: args.signer,
1004
1125
  hasher: args.hasher,
1005
1126
  fetch: args.fetch,
@@ -1007,7 +1128,60 @@ async function retainSuccessfulDeployments(args) {
1007
1128
  apiHost: args.apiHost,
1008
1129
  now: args.now
1009
1130
  });
1131
+ forgetStageResults.push({ stage: "instances", ...instanceStage });
1132
+ forgottenHashes.push(...instanceStage.forgottenHashes);
1133
+ outstandingForgetHashes.push(...instanceStage.outstandingForgetHashes);
1134
+ forgetStatuses = { ...forgetStatuses, ...instanceStage.statuses };
1135
+ if (!forgetResult && instanceStage.forgetResult) {
1136
+ forgetResult = instanceStage.forgetResult;
1137
+ }
1138
+ followUpForgetResults.push(...instanceStage.followUpForgetResults);
1139
+ }
1140
+ if (dependentForgetHashes.length > 0) {
1141
+ if (outstandingForgetHashes.length === 0) {
1142
+ const dependentStage = await executeForgetStage({
1143
+ sender: args.sender,
1144
+ hashes: dependentForgetHashes,
1145
+ reason: forgetReason,
1146
+ signer: args.signer,
1147
+ hasher: args.hasher,
1148
+ fetch: args.fetch,
1149
+ channel: args.channel,
1150
+ apiHost: args.apiHost,
1151
+ now: args.now
1152
+ });
1153
+ forgetStageResults.push({ stage: "dependents", ...dependentStage });
1154
+ forgottenHashes.push(...dependentStage.forgottenHashes);
1155
+ outstandingForgetHashes.push(...dependentStage.outstandingForgetHashes);
1156
+ forgetStatuses = { ...forgetStatuses, ...dependentStage.statuses };
1157
+ if (!forgetResult && dependentStage.forgetResult) {
1158
+ forgetResult = dependentStage.forgetResult;
1159
+ }
1160
+ followUpForgetResults.push(...dependentStage.followUpForgetResults);
1161
+ } else {
1162
+ const dependentStageStatus = await classifyForgetHashes({
1163
+ hashes: dependentForgetHashes,
1164
+ fetch: args.fetch,
1165
+ apiHost: args.apiHost
1166
+ });
1167
+ forgetStageResults.push({
1168
+ stage: "dependents",
1169
+ hashes: dependentForgetHashes,
1170
+ skipped: true,
1171
+ skippedReason: "Waiting for instance forget stage to complete before pruning dependent store items.",
1172
+ forgottenHashes: dependentStageStatus.forgottenHashes,
1173
+ outstandingForgetHashes: dependentStageStatus.outstandingForgetHashes,
1174
+ statuses: dependentStageStatus.statuses,
1175
+ forgetResult: null,
1176
+ followUpForgetResults: []
1177
+ });
1178
+ forgottenHashes.push(...dependentStageStatus.forgottenHashes);
1179
+ outstandingForgetHashes.push(...dependentStageStatus.outstandingForgetHashes);
1180
+ forgetStatuses = { ...forgetStatuses, ...dependentStageStatus.statuses };
1181
+ }
1010
1182
  }
1183
+ forgottenHashes = uniqueHashes(forgottenHashes);
1184
+ outstandingForgetHashes = uniqueHashes(outstandingForgetHashes);
1011
1185
  return {
1012
1186
  sender: args.sender,
1013
1187
  aggregateKey,
@@ -1016,7 +1190,12 @@ async function retainSuccessfulDeployments(args) {
1016
1190
  retainedRecords,
1017
1191
  prunedRecords,
1018
1192
  forgetHashes,
1019
- forgetResult
1193
+ forgottenHashes,
1194
+ outstandingForgetHashes,
1195
+ forgetStatuses,
1196
+ forgetResult,
1197
+ followUpForgetResults,
1198
+ forgetStageResults
1020
1199
  };
1021
1200
  }
1022
1201
 
@@ -2098,7 +2277,8 @@ async function runActionMode(env = process.env, hooks = {}) {
2098
2277
  `- Keep count: \`${payload.keepCount}\``,
2099
2278
  `- Retained deployments: \`${payload.retainedRecords?.length ?? 0}\``,
2100
2279
  `- Pruned deployments: \`${payload.prunedRecords?.length ?? 0}\``,
2101
- `- Forgotten hashes: \`${(payload.forgetHashes ?? []).length}\``
2280
+ `- Forgotten hashes: \`${(payload.forgottenHashes ?? payload.forgetHashes ?? []).length}\``,
2281
+ `- Outstanding forget hashes: \`${(payload.outstandingForgetHashes ?? []).length}\``
2102
2282
  ], env);
2103
2283
  stdout(`${JSON.stringify(payload)}
2104
2284
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@le-space/node",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Node and GitHub Actions adapters for shared Aleph tooling.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -16,9 +16,9 @@
16
16
  "access": "public"
17
17
  },
18
18
  "dependencies": {
19
- "@le-space/core": "0.1.15",
20
- "@le-space/shared-types": "0.1.15",
21
- "@le-space/rootfs": "0.1.15",
19
+ "@le-space/core": "0.1.17",
20
+ "@le-space/shared-types": "0.1.17",
21
+ "@le-space/rootfs": "0.1.17",
22
22
  "ethers": "^6.15.0"
23
23
  }
24
24
  }