@xtrable-ltd/nanoesis 0.1.30 → 0.1.32
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/adapter-azure-blob.d.ts +39 -3
- package/dist/adapter-azure-blob.js +81 -4
- package/dist/{chunk-BCWZRKMF.js → chunk-6Y3I6SYT.js} +8 -2
- package/dist/{chunk-GFQT7BYP.js → chunk-D5G56CA4.js} +189 -78
- package/dist/editor-api.js +2 -2
- package/dist/index.d.ts +91 -2
- package/dist/index.js +9 -1
- package/dist/mcp.js +3 -3
- package/editor/assets/{MigrationsPane-CnTDtciW.js → MigrationsPane-BuaKhsie.js} +1 -1
- package/editor/assets/{TemplatesPane-ec4tLp_Z.js → TemplatesPane-Bg_b0htA.js} +7 -7
- package/editor/assets/{cssMode-BemeFqgE.js → cssMode-Bcwjp8T3.js} +1 -1
- package/editor/assets/{freemarker2-BYGmC0-W.js → freemarker2-Cgqj3dEx.js} +1 -1
- package/editor/assets/{handlebars-DWwfZvMl.js → handlebars-CLf2-RZf.js} +1 -1
- package/editor/assets/{html-zjex7QYA.js → html-BrWGWRh6.js} +1 -1
- package/editor/assets/{htmlMode-Cd0ZPDb8.js → htmlMode-D9skx8gp.js} +1 -1
- package/editor/assets/index-CY5Kehuu.js +142 -0
- package/editor/assets/{javascript-DNYu6_LN.js → javascript-KaABDboM.js} +1 -1
- package/editor/assets/{jsonMode-DHt9XVJd.js → jsonMode-0usLs2ME.js} +1 -1
- package/editor/assets/{liquid-oUgE4Rib.js → liquid-DYna-Clp.js} +1 -1
- package/editor/assets/{mdx-CD5NlTbI.js → mdx-f7LAIls6.js} +1 -1
- package/editor/assets/{python-B_aadh3t.js → python-Bh21_mvq.js} +1 -1
- package/editor/assets/{razor-CBk7aqLU.js → razor-D_530Ymg.js} +1 -1
- package/editor/assets/{tsMode-6QXuvvH6.js → tsMode-CTRVjt9n.js} +1 -1
- package/editor/assets/{typescript-WhmGbU6r.js → typescript-D084tkOf.js} +1 -1
- package/editor/assets/{xml-DjYLc8K1.js → xml-BFulX1C6.js} +1 -1
- package/editor/assets/{yaml-D-Bj1RMQ.js → yaml-CgZsYmUt.js} +1 -1
- package/editor/index.html +1 -1
- package/package.json +1 -1
- package/editor/assets/index-iXj0nhkt.js +0 -142
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ContainerClient } from '@azure/storage-blob';
|
|
2
|
-
import { BlobStore, ArtifactSink, Storage } from '@nanoesis/engine';
|
|
2
|
+
import { BlobStore, ConditionalBlobStore, ArtifactSink, Storage } from '@nanoesis/engine';
|
|
3
3
|
export { contentTypeFor } from '@nanoesis/engine';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -28,6 +28,23 @@ interface BlobContainer {
|
|
|
28
28
|
write(name: string, data: Uint8Array, contentType: string, cacheControl?: string): Promise<void>;
|
|
29
29
|
/** Delete a blob; a missing blob is a no-op (idempotent). */
|
|
30
30
|
remove(name: string): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* A blob's bytes plus its current version tag (the Azure ETag), or null if absent. Pairs
|
|
33
|
+
* with {@link writeConditional} for the optimistic-concurrency the content index needs
|
|
34
|
+
* (DESIGN §11d): the bytes and the tag are captured together so the tag always describes
|
|
35
|
+
* the bytes returned.
|
|
36
|
+
*/
|
|
37
|
+
readVersioned(name: string): Promise<{
|
|
38
|
+
bytes: Uint8Array;
|
|
39
|
+
version: string;
|
|
40
|
+
} | null>;
|
|
41
|
+
/**
|
|
42
|
+
* Write a blob only if its current version tag matches `expected` (or, when `expected` is
|
|
43
|
+
* null, only if it does not yet exist). Resolves to the new version tag on success, or
|
|
44
|
+
* `undefined` when the precondition failed because another writer got there first. A
|
|
45
|
+
* non-precondition failure still rejects.
|
|
46
|
+
*/
|
|
47
|
+
writeConditional(name: string, data: Uint8Array, contentType: string, expected: string | null): Promise<string | undefined>;
|
|
31
48
|
}
|
|
32
49
|
/**
|
|
33
50
|
* An in-memory {@link BlobContainer} backed by a name→bytes map. The test double for
|
|
@@ -36,11 +53,20 @@ interface BlobContainer {
|
|
|
36
53
|
declare class InMemoryBlobContainer implements BlobContainer {
|
|
37
54
|
private readonly blobs;
|
|
38
55
|
private readonly cacheControls;
|
|
56
|
+
/** Per-blob version, bumped on every write, the in-memory stand-in for an ETag. */
|
|
57
|
+
private readonly versions;
|
|
58
|
+
private nextVersion;
|
|
39
59
|
constructor(seed?: Readonly<Record<string, Uint8Array | string>>);
|
|
60
|
+
private store;
|
|
40
61
|
list(prefix: string): Promise<readonly string[]>;
|
|
41
62
|
read(name: string): Promise<Uint8Array | null>;
|
|
42
63
|
write(name: string, data: Uint8Array, contentType?: string, cacheControl?: string): Promise<void>;
|
|
43
64
|
remove(name: string): Promise<void>;
|
|
65
|
+
readVersioned(name: string): Promise<{
|
|
66
|
+
bytes: Uint8Array;
|
|
67
|
+
version: string;
|
|
68
|
+
} | null>;
|
|
69
|
+
writeConditional(name: string, data: Uint8Array, contentType: string, expected: string | null): Promise<string | undefined>;
|
|
44
70
|
/** Test helper: the current blob names. */
|
|
45
71
|
get names(): readonly string[];
|
|
46
72
|
/** Test helper: the `Cache-Control` a blob was written with, if any. */
|
|
@@ -65,6 +91,11 @@ declare class AzureBlobContainer implements BlobContainer {
|
|
|
65
91
|
read(name: string): Promise<Uint8Array | null>;
|
|
66
92
|
write(name: string, data: Uint8Array, contentType: string, cacheControl?: string): Promise<void>;
|
|
67
93
|
remove(name: string): Promise<void>;
|
|
94
|
+
readVersioned(name: string): Promise<{
|
|
95
|
+
bytes: Uint8Array;
|
|
96
|
+
version: string;
|
|
97
|
+
} | null>;
|
|
98
|
+
writeConditional(name: string, data: Uint8Array, contentType: string, expected: string | null): Promise<string | undefined>;
|
|
68
99
|
}
|
|
69
100
|
|
|
70
101
|
/**
|
|
@@ -74,12 +105,17 @@ declare class AzureBlobContainer implements BlobContainer {
|
|
|
74
105
|
* in-memory container in tests (LSP). Keys are POSIX-style; `put` stamps a content type
|
|
75
106
|
* from the key's extension so a published blob serves correctly as a static-website file.
|
|
76
107
|
*/
|
|
77
|
-
declare class BlobContainerStore implements BlobStore {
|
|
108
|
+
declare class BlobContainerStore implements BlobStore, ConditionalBlobStore {
|
|
78
109
|
private readonly container;
|
|
79
110
|
constructor(container: BlobContainer);
|
|
80
111
|
get(key: string): Promise<Uint8Array | undefined>;
|
|
81
112
|
put(key: string, bytes: Uint8Array, cacheControl?: string): Promise<void>;
|
|
82
113
|
delete(key: string): Promise<void>;
|
|
114
|
+
getVersioned(key: string): Promise<{
|
|
115
|
+
bytes: Uint8Array;
|
|
116
|
+
version: string;
|
|
117
|
+
} | undefined>;
|
|
118
|
+
putConditional(key: string, bytes: Uint8Array, expected: string | null): Promise<string | undefined>;
|
|
83
119
|
}
|
|
84
120
|
|
|
85
121
|
/**
|
|
@@ -104,7 +140,7 @@ declare class BlobArtifactSink implements ArtifactSink {
|
|
|
104
140
|
* For the working container, also pass `enumerate: () => container.list('')` so the editor
|
|
105
141
|
* can reconcile its content index.
|
|
106
142
|
*/
|
|
107
|
-
declare function azureStorage(container: BlobContainer): Storage;
|
|
143
|
+
declare function azureStorage(container: BlobContainer): Storage & ConditionalBlobStore;
|
|
108
144
|
|
|
109
145
|
/**
|
|
110
146
|
* Pure path/name helper for the blob adapters. Blob storage is a *flat* namespace, a blob
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
2
|
contentTypeFor
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-D5G56CA4.js";
|
|
4
4
|
|
|
5
5
|
// ../../adapters/azure-blob/src/container.ts
|
|
6
6
|
var InMemoryBlobContainer = class {
|
|
7
7
|
blobs = /* @__PURE__ */ new Map();
|
|
8
8
|
cacheControls = /* @__PURE__ */ new Map();
|
|
9
|
+
/** Per-blob version, bumped on every write, the in-memory stand-in for an ETag. */
|
|
10
|
+
versions = /* @__PURE__ */ new Map();
|
|
11
|
+
nextVersion = 1;
|
|
9
12
|
constructor(seed = {}) {
|
|
10
13
|
for (const [name, value] of Object.entries(seed)) {
|
|
11
|
-
this.
|
|
14
|
+
this.store(name, typeof value === "string" ? new TextEncoder().encode(value) : value);
|
|
12
15
|
}
|
|
13
16
|
}
|
|
17
|
+
store(name, bytes) {
|
|
18
|
+
this.blobs.set(name, bytes);
|
|
19
|
+
const version = this.nextVersion;
|
|
20
|
+
this.nextVersion += 1;
|
|
21
|
+
this.versions.set(name, version);
|
|
22
|
+
return version;
|
|
23
|
+
}
|
|
14
24
|
async list(prefix) {
|
|
15
25
|
const names = [];
|
|
16
26
|
for (const name of this.blobs.keys()) {
|
|
@@ -23,11 +33,24 @@ var InMemoryBlobContainer = class {
|
|
|
23
33
|
}
|
|
24
34
|
async write(name, data, contentType, cacheControl) {
|
|
25
35
|
void contentType;
|
|
26
|
-
this.
|
|
36
|
+
this.store(name, data);
|
|
27
37
|
if (cacheControl !== void 0) this.cacheControls.set(name, cacheControl);
|
|
28
38
|
}
|
|
29
39
|
async remove(name) {
|
|
30
40
|
this.blobs.delete(name);
|
|
41
|
+
this.versions.delete(name);
|
|
42
|
+
}
|
|
43
|
+
async readVersioned(name) {
|
|
44
|
+
const bytes = this.blobs.get(name);
|
|
45
|
+
if (bytes === void 0) return null;
|
|
46
|
+
return { bytes, version: String(this.versions.get(name)) };
|
|
47
|
+
}
|
|
48
|
+
async writeConditional(name, data, contentType, expected) {
|
|
49
|
+
void contentType;
|
|
50
|
+
const current = this.versions.get(name);
|
|
51
|
+
const currentTag = current === void 0 ? null : String(current);
|
|
52
|
+
if (currentTag !== expected) return void 0;
|
|
53
|
+
return String(this.store(name, data));
|
|
31
54
|
}
|
|
32
55
|
/** Test helper: the current blob names. */
|
|
33
56
|
get names() {
|
|
@@ -41,8 +64,15 @@ var InMemoryBlobContainer = class {
|
|
|
41
64
|
|
|
42
65
|
// ../../adapters/azure-blob/src/azure-container.ts
|
|
43
66
|
import { BlobServiceClient } from "@azure/storage-blob";
|
|
67
|
+
function statusCodeOf(error) {
|
|
68
|
+
return typeof error === "object" && error !== null && "statusCode" in error ? error.statusCode : void 0;
|
|
69
|
+
}
|
|
44
70
|
function isNotFound(error) {
|
|
45
|
-
return
|
|
71
|
+
return statusCodeOf(error) === 404;
|
|
72
|
+
}
|
|
73
|
+
function isPreconditionFailure(error) {
|
|
74
|
+
const status = statusCodeOf(error);
|
|
75
|
+
return status === 412 || status === 409;
|
|
46
76
|
}
|
|
47
77
|
var AzureBlobContainer = class _AzureBlobContainer {
|
|
48
78
|
constructor(client) {
|
|
@@ -85,7 +115,41 @@ var AzureBlobContainer = class _AzureBlobContainer {
|
|
|
85
115
|
async remove(name) {
|
|
86
116
|
await this.client.getBlockBlobClient(name).deleteIfExists();
|
|
87
117
|
}
|
|
118
|
+
async readVersioned(name) {
|
|
119
|
+
try {
|
|
120
|
+
const response = await this.client.getBlockBlobClient(name).download();
|
|
121
|
+
const buffer = await streamToBuffer(response.readableStreamBody);
|
|
122
|
+
if (response.etag === void 0) {
|
|
123
|
+
throw new Error(`Azure blob ${name} returned no ETag on download`);
|
|
124
|
+
}
|
|
125
|
+
return { bytes: new Uint8Array(buffer), version: response.etag };
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (isNotFound(error)) return null;
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async writeConditional(name, data, contentType, expected) {
|
|
132
|
+
const conditions = expected === null ? { ifNoneMatch: "*" } : { ifMatch: expected };
|
|
133
|
+
try {
|
|
134
|
+
const response = await this.client.getBlockBlobClient(name).uploadData(Buffer.from(data), {
|
|
135
|
+
blobHTTPHeaders: { blobContentType: contentType },
|
|
136
|
+
conditions
|
|
137
|
+
});
|
|
138
|
+
return response.etag;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (isPreconditionFailure(error)) return void 0;
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
88
144
|
};
|
|
145
|
+
async function streamToBuffer(stream) {
|
|
146
|
+
if (stream === void 0) return Buffer.alloc(0);
|
|
147
|
+
const chunks = [];
|
|
148
|
+
for await (const chunk of stream) {
|
|
149
|
+
chunks.push(chunk instanceof Buffer ? chunk : Buffer.from(chunk));
|
|
150
|
+
}
|
|
151
|
+
return Buffer.concat(chunks);
|
|
152
|
+
}
|
|
89
153
|
|
|
90
154
|
// ../../adapters/azure-blob/src/paths.ts
|
|
91
155
|
function normalizeBlobName(path) {
|
|
@@ -108,6 +172,13 @@ var BlobContainerStore = class {
|
|
|
108
172
|
async delete(key) {
|
|
109
173
|
await this.container.remove(normalizeBlobName(key));
|
|
110
174
|
}
|
|
175
|
+
async getVersioned(key) {
|
|
176
|
+
return await this.container.readVersioned(normalizeBlobName(key)) ?? void 0;
|
|
177
|
+
}
|
|
178
|
+
async putConditional(key, bytes, expected) {
|
|
179
|
+
const name = normalizeBlobName(key);
|
|
180
|
+
return this.container.writeConditional(name, bytes, contentTypeFor(name), expected);
|
|
181
|
+
}
|
|
111
182
|
};
|
|
112
183
|
|
|
113
184
|
// ../../adapters/azure-blob/src/blob-sink.ts
|
|
@@ -138,6 +209,12 @@ function azureStorage(container) {
|
|
|
138
209
|
// strips the publish Cache-Control before it reaches the blob.
|
|
139
210
|
put: (key, bytes, cacheControl) => store.put(key, bytes, cacheControl),
|
|
140
211
|
delete: (key) => store.delete(key),
|
|
212
|
+
// Optimistic-concurrency capability (DESIGN §11d): surfaced so a horizontally-scaled
|
|
213
|
+
// host's IndexedStore compare-and-sets the content index instead of blind-overwriting
|
|
214
|
+
// it, which is what stops two instances losing each other's updates
|
|
215
|
+
// (NANOESIS-MCP-ISSUES Issue 1). Azure blobs back this with native ETag conditions.
|
|
216
|
+
getVersioned: (key) => store.getVersioned(key),
|
|
217
|
+
putConditional: (key, bytes, expected) => store.putConditional(key, bytes, expected),
|
|
141
218
|
wipe: async () => {
|
|
142
219
|
for (const name of await container.list("")) await container.remove(name);
|
|
143
220
|
}
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
renderReferenceMarkdown,
|
|
19
19
|
validateSite,
|
|
20
20
|
workingStoreRoundTripDiagnostic
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-D5G56CA4.js";
|
|
22
22
|
|
|
23
23
|
// ../editor-api/src/scaffold.ts
|
|
24
24
|
var HOME_HTML = `<!doctype html>
|
|
@@ -559,7 +559,7 @@ async function dispatchApi(deps, req) {
|
|
|
559
559
|
return json(422, {
|
|
560
560
|
ok: false,
|
|
561
561
|
errors: result.validation.errors.map((e) => e.message),
|
|
562
|
-
...result.summary !== void 0 && {
|
|
562
|
+
...result.summary !== void 0 && { wouldPublish: result.summary }
|
|
563
563
|
});
|
|
564
564
|
}
|
|
565
565
|
case "/api/scaffold": {
|
|
@@ -1170,6 +1170,12 @@ var TOOL_SPECS = [
|
|
|
1170
1170
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
1171
1171
|
toRequest: (_args, token) => request("POST", "/api/publish", {}, void 0, token)
|
|
1172
1172
|
},
|
|
1173
|
+
{
|
|
1174
|
+
name: "reconcile",
|
|
1175
|
+
description: "Rebuild the content index from the files actually in storage, recovering any that drifted out of the index. Use this if `validate`, `list_dir`, or a `reference-missing` error reports a file as absent that `read_file` can still return: the file is stored but unenumerable, and reconcile makes it visible again without re-writing it. Idempotent and a no-op when nothing drifted. Returns the count added/removed and the new total. Reports an error on a host that cannot enumerate its store.",
|
|
1176
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
1177
|
+
toRequest: (_args, token) => request("POST", "/api/reconcile", {}, void 0, token)
|
|
1178
|
+
},
|
|
1173
1179
|
{
|
|
1174
1180
|
name: "list_pending_migrations",
|
|
1175
1181
|
description: "List content items whose JSON fields don't match their bound template's schema (orphan fields or missing required fields). Use this after a destructive template edit (you'll see a `stamped` record on write_file) to see what needs migrating, or any time to check site-wide drift.",
|