@xtrable-ltd/nanoesis 0.1.32 → 0.1.33

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.
@@ -135,7 +135,8 @@ declare class BlobArtifactSink implements ArtifactSink {
135
135
 
136
136
  /**
137
137
  * A ready-made {@link Storage} over an Azure Blob container: get/put/delete (via
138
- * {@link BlobContainerStore}) plus `wipe` (delete every blob). Pass it to `createEditor`
138
+ * {@link BlobContainerStore}) plus `wipe` (delete every blob) and `prune` (delete every blob
139
+ * not in a keep set, the zero-downtime publish sweep). Pass it to `createEditor`
139
140
  * as `editorFiles` (the working container) and `website` (the published `$web` container).
140
141
  * For the working container, also pass `enumerate: () => container.list('')` so the editor
141
142
  * can reconcile its content index.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  contentTypeFor
3
- } from "./chunk-D5G56CA4.js";
3
+ } from "./chunk-P6NDWIKK.js";
4
4
 
5
5
  // ../../adapters/azure-blob/src/container.ts
6
6
  var InMemoryBlobContainer = class {
@@ -217,6 +217,15 @@ function azureStorage(container) {
217
217
  putConditional: (key, bytes, expected) => store.putConditional(key, bytes, expected),
218
218
  wipe: async () => {
219
219
  for (const name of await container.list("")) await container.remove(name);
220
+ },
221
+ // Zero-downtime sweep (DESIGN §11): the publish overwrites every artifact in place, then
222
+ // calls this with the just-written path set so only orphaned blobs (pages the new publish
223
+ // did not emit) are removed. Every live URL keeps serving throughout, the old wipe-first
224
+ // path blanked the whole site for the upload window.
225
+ prune: async (keep) => {
226
+ for (const name of await container.list("")) {
227
+ if (!keep.has(name)) await container.remove(name);
228
+ }
220
229
  }
221
230
  };
222
231
  }
@@ -18,7 +18,7 @@ import {
18
18
  renderReferenceMarkdown,
19
19
  validateSite,
20
20
  workingStoreRoundTripDiagnostic
21
- } from "./chunk-D5G56CA4.js";
21
+ } from "./chunk-P6NDWIKK.js";
22
22
 
23
23
  // ../editor-api/src/scaffold.ts
24
24
  var HOME_HTML = `<!doctype html>
@@ -949,12 +949,12 @@ function createEditor(config) {
949
949
  };
950
950
  try {
951
951
  const validation = await validateSite(working);
952
- if (validation.ok && wipeBeforePublish && config.website.wipe !== void 0) {
952
+ if (validation.ok && wipeBeforePublish && config.website.prune === void 0 && config.website.wipe !== void 0) {
953
953
  await config.website.wipe();
954
954
  }
955
955
  const dir = config.users === void 0 ? void 0 : authorDirectory(await config.users());
956
956
  const prebuild = typeof config.prebuild === "function" ? await config.prebuild() : config.prebuild;
957
- return await publishSite(working, sink, {
957
+ const result = await publishSite(working, sink, {
958
958
  ...config.images !== void 0 && { imageEncoder: config.images },
959
959
  ...config.purge !== void 0 && { purge: config.purge },
960
960
  ...config.baseUrl !== void 0 && { baseUrl: config.baseUrl },
@@ -962,6 +962,10 @@ function createEditor(config) {
962
962
  ...prebuild !== void 0 && { prebuild },
963
963
  onProgress: record
964
964
  });
965
+ if (result.ok && wipeBeforePublish && config.website.prune !== void 0) {
966
+ await config.website.prune(new Set(result.written));
967
+ }
968
+ return result;
965
969
  } finally {
966
970
  progressRunning = false;
967
971
  }
@@ -3319,13 +3319,20 @@ async function publishSite(source, sink, options = {}) {
3319
3319
  );
3320
3320
  const byPath = /* @__PURE__ */ new Map();
3321
3321
  for (const artifact of [...stamped, ...passthrough]) byPath.set(artifact.path, artifact);
3322
+ const everything = [...byPath.values()];
3323
+ const pages = everything.filter((artifact) => artifact.path.endsWith(".html"));
3324
+ const resources = everything.filter((artifact) => !artifact.path.endsWith(".html"));
3322
3325
  const uploadTotal = byPath.size;
3323
3326
  let uploaded = 0;
3324
- await mapWithConcurrency([...byPath.values()], writeConcurrency, async (artifact) => {
3325
- await sink.write(artifact.path, artifact.contents, cacheControlFor(artifact));
3326
- uploaded += 1;
3327
- onProgress?.({ phase: "upload", written: uploaded, total: uploadTotal });
3328
- });
3327
+ const writeGroup = async (group) => {
3328
+ await mapWithConcurrency(group, writeConcurrency, async (artifact) => {
3329
+ await sink.write(artifact.path, artifact.contents, cacheControlFor(artifact));
3330
+ uploaded += 1;
3331
+ onProgress?.({ phase: "upload", written: uploaded, total: uploadTotal });
3332
+ });
3333
+ };
3334
+ await writeGroup(resources);
3335
+ await writeGroup(pages);
3329
3336
  if (purge !== noopPurgeService) onProgress?.({ phase: "purge" });
3330
3337
  await purge.purgeAll();
3331
3338
  onProgress?.({ phase: "done", written: uploadTotal, summary });
@@ -222,7 +222,12 @@ interface EditorConfig {
222
222
  readonly prebuild?: PreBuildHook | (() => Promise<PreBuildHook | undefined>);
223
223
  /** Admin diagnostics/self-heal; defaults to {@link buildDefaultDiagnostics}. */
224
224
  readonly diagnostics?: DiagnosticRegistry;
225
- /** Clear the website before republishing (default true). Needs `website.wipe`. */
225
+ /**
226
+ * Whether a publish clears pages the new build no longer emits (default true). A
227
+ * `prune`-capable website does this as a post-publish orphan sweep (overwrite-in-place,
228
+ * zero downtime); a wipe-only website clears up front instead. Set false to never delete
229
+ * (overwrite-only, stale pages linger). Names `wipeBeforePublish` for back-compat.
230
+ */
226
231
  readonly wipeBeforePublish?: boolean;
227
232
  }
228
233
  /** A wired editor: mount {@link handleApi} at `/api/*` and call {@link publish} to go live. */
@@ -244,12 +249,13 @@ interface Editor {
244
249
  * Wire a complete nanoesis editor from a storage pair, a login, and a few optional
245
250
  * capabilities. This is the whole integration surface (DESIGN §11c): everything that used
246
251
  * to be hand-assembled per host, the content index over the store, the publish source/sink,
247
- * index reconcile, the wipe-before-publish, and the {@link ApiDeps} bag, is built here, so
252
+ * index reconcile, the orphan sweep, and the {@link ApiDeps} bag, is built here, so
248
253
  * internals (`IndexedStore`, `ArtifactSink`, reconcile plumbing) never reach the adopter.
249
254
  *
250
- * `publish()` validates **before** it wipes: an invalid site returns its errors and never
251
- * touches the live files, so a failed publish can never blank the website (the footgun the
252
- * old "host wipes, then publishes" wiring carried).
255
+ * `publish()` validates **first**: an invalid site returns its errors and never touches the
256
+ * live files. A `prune`-capable website then overwrites every artifact in place and sweeps
257
+ * the orphans afterwards, so a publish never blanks the live site, not even for the upload
258
+ * window (the footgun the old "host wipes, then publishes" wiring carried).
253
259
  */
254
260
  declare function createEditor(config: EditorConfig): Editor;
255
261
  /**
@@ -21,8 +21,8 @@ import {
21
21
  serveEditorAsset,
22
22
  templateSnapshotIntegrityDiagnostic,
23
23
  templateSuffixConflictDiagnostic
24
- } from "./chunk-6Y3I6SYT.js";
25
- import "./chunk-D5G56CA4.js";
24
+ } from "./chunk-C3QPGXG5.js";
25
+ import "./chunk-P6NDWIKK.js";
26
26
  export {
27
27
  FileBrandingStore,
28
28
  InMemoryBrandingStore,
package/dist/index.d.ts CHANGED
@@ -204,6 +204,17 @@ interface Storage extends BlobStore {
204
204
  * (and so cannot clear itself) omits it; the publish then only overwrites.
205
205
  */
206
206
  wipe?(): Promise<void>;
207
+ /**
208
+ * Optional: delete every key **not** in `keep`, leaving the kept keys untouched. The
209
+ * zero-downtime alternative to {@link wipe} for a published website: the host overwrites
210
+ * every artifact in place (each key flips old→new atomically, so no URL is ever missing),
211
+ * then calls `prune` with the just-written path set to sweep only the orphans, pages that
212
+ * no longer exist. Unlike `wipe` (clear *before* the upload, blanking the live site for the
213
+ * whole write), `prune` runs *after* a successful upload and only removes what the new
214
+ * publish did not write. A store that cannot enumerate omits it; the host falls back to
215
+ * `wipe`, or to overwrite-only when neither is available.
216
+ */
217
+ prune?(keep: ReadonlySet<string>): Promise<void>;
207
218
  }
208
219
  /**
209
220
  * In-memory {@link BlobStore} backed by a plain map, the test double the engine's
package/dist/index.js CHANGED
@@ -83,7 +83,7 @@ import {
83
83
  versionNumber,
84
84
  wholeValueToken,
85
85
  workingStoreRoundTripDiagnostic
86
- } from "./chunk-D5G56CA4.js";
86
+ } from "./chunk-P6NDWIKK.js";
87
87
  export {
88
88
  CACHE_IMMUTABLE,
89
89
  CACHE_REVALIDATE,
package/dist/mcp.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  MCP_TOOLS,
4
4
  callMcpTool,
5
5
  readMcpResource
6
- } from "./chunk-6Y3I6SYT.js";
7
- import "./chunk-D5G56CA4.js";
6
+ } from "./chunk-C3QPGXG5.js";
7
+ import "./chunk-P6NDWIKK.js";
8
8
 
9
9
  // ../../hosts/host-mcp/src/http.ts
10
10
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
@@ -56,7 +56,7 @@ function createMcpServer(deps, identity) {
56
56
  }
57
57
 
58
58
  // ../../hosts/host-mcp/src/http.ts
59
- var DEFAULT_VERSION = true ? "0.1.32" : "0.0.0-workspace";
59
+ var DEFAULT_VERSION = true ? "0.1.33" : "0.0.0-workspace";
60
60
  async function handleMcpRequest(deps, request, opts) {
61
61
  const server = createMcpServer(deps, {
62
62
  name: opts?.name ?? "nanoesis",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xtrable-ltd/nanoesis",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "description": "nanoesis: a static-first publishing compiler. The engine, the mountable editor API, the storage/image/auth adapters, and the editor UI bundle, in one package (DESIGN 11c).",
5
5
  "license": "MIT",
6
6
  "author": "Xtrable Ltd",