@avocadostudio-ai/site-sdk 0.26.0 → 0.28.0

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.
@@ -18,10 +18,64 @@ import { pageDocSchemaLenient, siteConfigSchema } from "@avocadostudio-ai/shared
18
18
  * may import the other. See `orchestrator-core/src/handler/library-mount.ts`.
19
19
  */
20
20
  const LIBRARY_MOUNT_KEY = "__avocado_library_mount__";
21
+ function libraryMount() {
22
+ return globalThis[LIBRARY_MOUNT_KEY];
23
+ }
21
24
  function libraryModeOrchestratorUrl() {
22
- const mount = globalThis[LIBRARY_MOUNT_KEY];
25
+ const mount = libraryMount();
23
26
  return mount?.observed ?? mount?.declared ?? null;
24
27
  }
28
+ /*
29
+ * Read from this process's own orchestrator directly, when that is the one meant.
30
+ *
31
+ * Over HTTP a draft read is a request like any other, so a credentialed mount
32
+ * refuses it unless this site also holds `ORCHESTRATOR_ACCESS_TOKEN`. A site
33
+ * that set only the password rendered published content in its preview, with
34
+ * a 200 everywhere, and every edit landed in a draft nobody could see. A mount
35
+ * in this process registers a dispatcher instead (orchestrator-core's
36
+ * `library-mount.ts`), and a read through it needs no network, no URL and no
37
+ * credential.
38
+ *
39
+ * It is the mount that is meant unless something says otherwise: a per-call
40
+ * `orchestratorUrl` always goes over HTTP, and so does an `ORCHESTRATOR_URL`
41
+ * whose path is not this mount's — that names a different orchestrator.
42
+ * `ORCHESTRATOR_URL` pointing at the mount itself, the usual library-mode
43
+ * setting, still reads in-process.
44
+ */
45
+ function inProcessDispatch(explicitUrl) {
46
+ if (explicitUrl)
47
+ return null;
48
+ const mount = libraryMount();
49
+ if (!mount?.dispatch || mount.basePath === undefined)
50
+ return null;
51
+ const configured = process.env.ORCHESTRATOR_URL?.trim();
52
+ if (configured) {
53
+ let configuredPath;
54
+ try {
55
+ configuredPath = new URL(configured).pathname.replace(/\/+$/, "");
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ if (configuredPath !== mount.basePath)
61
+ return null;
62
+ }
63
+ const { dispatch, basePath } = mount;
64
+ return (pathAndQuery) => dispatch(new Request(`http://in-process.invalid${basePath}${pathAndQuery}`));
65
+ }
66
+ /** The in-process answer, or null when the read should go over HTTP instead. */
67
+ async function readInProcess(pathAndQuery, options) {
68
+ const local = inProcessDispatch(options?.orchestratorUrl);
69
+ if (!local)
70
+ return null;
71
+ try {
72
+ return await local(pathAndQuery);
73
+ }
74
+ catch (error) {
75
+ console.warn(`[site-sdk/draft] in-process read of ${pathAndQuery.split("?")[0]} failed; trying HTTP —`, error);
76
+ return null;
77
+ }
78
+ }
25
79
  let warnedAboutFallback = false;
26
80
  export function getOrchestratorUrl() {
27
81
  const value = process.env.ORCHESTRATOR_URL?.trim();
@@ -116,7 +170,10 @@ function warnRefused(fn, status, hadToken) {
116
170
  console.warn(`[site-sdk/draft] ${fn}: the orchestrator answered ${status}. ` +
117
171
  (hadToken
118
172
  ? "The token that was sent is not the one it accepts — check ORCHESTRATOR_ACCESS_TOKEN on both sides."
119
- : "No credential was sent. Set ORCHESTRATOR_ACCESS_TOKEN, or pass `accessToken`.") +
173
+ : "No credential was sent. A library-mode site reads its own orchestrator in-process and needs " +
174
+ "none; this read went over HTTP because ORCHESTRATOR_URL names a different path, or because " +
175
+ "the orchestrator route has not been loaded in this process (separate serverless functions). " +
176
+ "Over HTTP, set ORCHESTRATOR_ACCESS_TOKEN or pass `accessToken`.") +
120
177
  " Drafts are unavailable, so this page is rendering published content.");
121
178
  }
122
179
  /** For tests: forget which refusals have already been reported. */
@@ -124,6 +181,18 @@ export function resetDraftFetchWarnings() {
124
181
  warned.clear();
125
182
  }
126
183
  export async function fetchEditorPage(slug, session, siteId, options) {
184
+ const query = `/draft/pages?session=${encodeURIComponent(session)}&siteId=${encodeURIComponent(siteId)}&slug=${encodeURIComponent(slug)}`;
185
+ const local = await readInProcess(query, options);
186
+ if (local) {
187
+ if (!local.ok)
188
+ return null;
189
+ const parsed = pageDocSchemaLenient.safeParse(await local.json());
190
+ if (parsed.success)
191
+ return parsed.data;
192
+ const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
193
+ console.warn(`[site-sdk/draft] fetchEditorPage: schema parse failed for slug=${slug} session=${session} siteId=${siteId} — ${issues}`);
194
+ return null;
195
+ }
127
196
  const configuredBaseUrl = options?.orchestratorUrl ?? getOrchestratorUrl();
128
197
  if (!configuredBaseUrl)
129
198
  return null;
@@ -132,7 +201,7 @@ export async function fetchEditorPage(slug, session, siteId, options) {
132
201
  const baseUrls = buildCandidateBaseUrls(configuredBaseUrl);
133
202
  for (const baseUrl of baseUrls) {
134
203
  try {
135
- const url = `${baseUrl}/draft/pages?session=${encodeURIComponent(session)}&siteId=${encodeURIComponent(siteId)}&slug=${encodeURIComponent(slug)}`;
204
+ const url = `${baseUrl}${query}`;
136
205
  const res = await fetchWithTimeout(url, timeout, token);
137
206
  if (REFUSAL_STATUSES.has(res.status)) {
138
207
  warnRefused("fetchEditorPage", res.status, Boolean(token));
@@ -158,6 +227,16 @@ export async function fetchEditorPage(slug, session, siteId, options) {
158
227
  return null;
159
228
  }
160
229
  export async function fetchEditorSlugs(session, siteId, options) {
230
+ const query = `/draft/slugs?session=${encodeURIComponent(session)}&siteId=${encodeURIComponent(siteId)}`;
231
+ const local = await readInProcess(query, options);
232
+ if (local) {
233
+ if (!local.ok)
234
+ return [];
235
+ const payload = (await local.json());
236
+ return Array.isArray(payload.slugs)
237
+ ? payload.slugs.filter((item) => typeof item === "string" && item.length > 0)
238
+ : [];
239
+ }
161
240
  const configuredBaseUrl = options?.orchestratorUrl ?? getOrchestratorUrl();
162
241
  if (!configuredBaseUrl)
163
242
  return [];
@@ -166,7 +245,7 @@ export async function fetchEditorSlugs(session, siteId, options) {
166
245
  const baseUrls = buildCandidateBaseUrls(configuredBaseUrl);
167
246
  for (const baseUrl of baseUrls) {
168
247
  try {
169
- const url = `${baseUrl}/draft/slugs?session=${encodeURIComponent(session)}&siteId=${encodeURIComponent(siteId)}`;
248
+ const url = `${baseUrl}${query}`;
170
249
  const res = await fetchWithTimeout(url, timeout, token);
171
250
  if (REFUSAL_STATUSES.has(res.status)) {
172
251
  warnRefused("fetchEditorSlugs", res.status, Boolean(token));
@@ -188,6 +267,14 @@ export async function fetchEditorSlugs(session, siteId, options) {
188
267
  return [];
189
268
  }
190
269
  export async function fetchEditorSiteConfig(session, siteId, options) {
270
+ const query = `/draft/site-config?session=${encodeURIComponent(session)}&siteId=${encodeURIComponent(siteId)}`;
271
+ const local = await readInProcess(query, options);
272
+ if (local) {
273
+ if (!local.ok)
274
+ return {};
275
+ const parsed = siteConfigSchema.safeParse(await local.json());
276
+ return parsed.success ? parsed.data : {};
277
+ }
191
278
  const configuredBaseUrl = options?.orchestratorUrl ?? getOrchestratorUrl();
192
279
  if (!configuredBaseUrl)
193
280
  return {};
@@ -196,7 +283,7 @@ export async function fetchEditorSiteConfig(session, siteId, options) {
196
283
  const baseUrls = buildCandidateBaseUrls(configuredBaseUrl);
197
284
  for (const baseUrl of baseUrls) {
198
285
  try {
199
- const url = `${baseUrl}/draft/site-config?session=${encodeURIComponent(session)}&siteId=${encodeURIComponent(siteId)}`;
286
+ const url = `${baseUrl}${query}`;
200
287
  const res = await fetchWithTimeout(url, timeout, token);
201
288
  if (REFUSAL_STATUSES.has(res.status)) {
202
289
  warnRefused("fetchEditorSiteConfig", res.status, Boolean(token));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@avocadostudio-ai/site-sdk",
3
- "version": "0.26.0",
3
+ "version": "0.28.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -159,17 +159,17 @@
159
159
  ],
160
160
  "dependencies": {
161
161
  "zod": "^4.3.6",
162
- "@avocadostudio-ai/blocks": "^0.26.0",
163
- "@avocadostudio-ai/preview-adapter": "^0.26.0",
164
- "@avocadostudio-ai/shared": "^0.26.0",
165
- "@avocadostudio-ai/richtext": "^0.26.0"
162
+ "@avocadostudio-ai/blocks": "^0.28.0",
163
+ "@avocadostudio-ai/preview-adapter": "^0.28.0",
164
+ "@avocadostudio-ai/richtext": "^0.28.0",
165
+ "@avocadostudio-ai/shared": "^0.28.0"
166
166
  },
167
167
  "peerDependencies": {
168
168
  "better-sqlite3": ">=12.0.0",
169
169
  "next": ">=15.0.0",
170
170
  "react": ">=19.0.0",
171
171
  "react-dom": ">=19.0.0",
172
- "@avocadostudio-ai/orchestrator-core": "^0.26.0"
172
+ "@avocadostudio-ai/orchestrator-core": "^0.28.0"
173
173
  },
174
174
  "peerDependenciesMeta": {
175
175
  "next": {