@webmaster-droid/server 0.1.0 → 0.2.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.
@@ -0,0 +1,416 @@
1
+ // src/storage-supabase/index.ts
2
+ import {
3
+ createClient
4
+ } from "@supabase/supabase-js";
5
+ import { normalizeCmsDocument } from "@webmaster-droid/contracts";
6
+ function createId(prefix) {
7
+ return `${prefix}_${Math.random().toString(36).slice(2, 10)}`;
8
+ }
9
+ function nowIso() {
10
+ return (/* @__PURE__ */ new Date()).toISOString();
11
+ }
12
+ function monthKey(date = /* @__PURE__ */ new Date()) {
13
+ const year = date.getUTCFullYear();
14
+ const month = `${date.getUTCMonth() + 1}`.padStart(2, "0");
15
+ return `${year}-${month}`;
16
+ }
17
+ function keySafeTimestamp(date = /* @__PURE__ */ new Date()) {
18
+ return `${date.getTime()}`;
19
+ }
20
+ function parseIdFromKey(key) {
21
+ const filename = key.split("/").pop() ?? key;
22
+ const parts = filename.split("__");
23
+ if (parts.length !== 2) {
24
+ return filename.replace(/\.json$/, "");
25
+ }
26
+ return parts[1].replace(/\.json$/, "");
27
+ }
28
+ function parseTimestampFromKey(key) {
29
+ const filename = key.split("/").pop() ?? key;
30
+ const parts = filename.split("__");
31
+ if (parts.length !== 2) {
32
+ return (/* @__PURE__ */ new Date(0)).toISOString();
33
+ }
34
+ const encoded = Number(parts[0]);
35
+ const parsed = new Date(encoded);
36
+ if (Number.isNaN(parsed.getTime())) {
37
+ return (/* @__PURE__ */ new Date(0)).toISOString();
38
+ }
39
+ return parsed.toISOString();
40
+ }
41
+ function isRecord(value) {
42
+ return typeof value === "object" && value !== null && !Array.isArray(value);
43
+ }
44
+ function parseCheckpointEnvelope(value) {
45
+ if (!isRecord(value)) {
46
+ return null;
47
+ }
48
+ const checkpoint = value.checkpoint;
49
+ const content = value.content;
50
+ if (!isRecord(checkpoint) || !isRecord(content)) {
51
+ return null;
52
+ }
53
+ const id = typeof checkpoint.id === "string" ? checkpoint.id : "";
54
+ const createdAt = typeof checkpoint.createdAt === "string" ? checkpoint.createdAt : "";
55
+ const reason = typeof checkpoint.reason === "string" ? checkpoint.reason : "";
56
+ const createdBy = typeof checkpoint.createdBy === "string" ? checkpoint.createdBy : void 0;
57
+ if (!id || !createdAt || !reason) {
58
+ return null;
59
+ }
60
+ return {
61
+ checkpoint: {
62
+ id,
63
+ createdAt,
64
+ createdBy,
65
+ reason
66
+ },
67
+ content
68
+ };
69
+ }
70
+ function mergeLegacyValue(target, incoming) {
71
+ if (!isRecord(target) || !isRecord(incoming)) {
72
+ return incoming;
73
+ }
74
+ for (const [key, value] of Object.entries(incoming)) {
75
+ if (target[key] === void 0) {
76
+ target[key] = value;
77
+ continue;
78
+ }
79
+ target[key] = mergeLegacyValue(target[key], value);
80
+ }
81
+ return target;
82
+ }
83
+ function normalizeLegacyIndexedKeys(root) {
84
+ let changed = false;
85
+ const visit = (node) => {
86
+ if (Array.isArray(node)) {
87
+ for (const item of node) {
88
+ visit(item);
89
+ }
90
+ return;
91
+ }
92
+ if (!isRecord(node)) {
93
+ return;
94
+ }
95
+ const record = node;
96
+ const keys = Object.keys(record);
97
+ for (const key of keys) {
98
+ const match = /^([^\[\]]+)\[(\d+)\]$/.exec(key);
99
+ if (!match) {
100
+ continue;
101
+ }
102
+ const baseKey = match[1];
103
+ const index = Number(match[2]);
104
+ if (Number.isNaN(index)) {
105
+ continue;
106
+ }
107
+ const baseValue = record[baseKey];
108
+ if (baseValue !== void 0 && !Array.isArray(baseValue)) {
109
+ continue;
110
+ }
111
+ const targetArray = record[baseKey] ?? [];
112
+ record[baseKey] = targetArray;
113
+ const legacyValue = record[key];
114
+ const existing = targetArray[index];
115
+ if (existing === void 0) {
116
+ targetArray[index] = legacyValue;
117
+ } else {
118
+ targetArray[index] = mergeLegacyValue(existing, legacyValue);
119
+ }
120
+ delete record[key];
121
+ changed = true;
122
+ }
123
+ for (const value of Object.values(record)) {
124
+ visit(value);
125
+ }
126
+ };
127
+ visit(root);
128
+ return changed;
129
+ }
130
+ function normalizeStoragePath(value) {
131
+ return value.replace(/^\/+/, "").replace(/\/+$/, "");
132
+ }
133
+ function toStorageError(error) {
134
+ if (!error || typeof error !== "object") {
135
+ return {};
136
+ }
137
+ return error;
138
+ }
139
+ var SupabaseCmsStorage = class {
140
+ bucket;
141
+ client;
142
+ prefix;
143
+ constructor(options) {
144
+ this.bucket = options.bucket.trim();
145
+ if (!this.bucket) {
146
+ throw new Error("Supabase bucket name is required.");
147
+ }
148
+ this.prefix = options.prefix?.replace(/\/$/, "") ?? "cms";
149
+ this.client = options.client ?? createClient(options.supabaseUrl, options.serviceRoleKey, {
150
+ auth: {
151
+ persistSession: false,
152
+ autoRefreshToken: false
153
+ }
154
+ });
155
+ }
156
+ async ensureInitialized(seed) {
157
+ const [live, draft] = await Promise.all([
158
+ this.tryGetStage("live"),
159
+ this.tryGetStage("draft")
160
+ ]);
161
+ if (!live) {
162
+ await this.saveStage("live", seed);
163
+ }
164
+ if (!draft) {
165
+ await this.saveStage("draft", seed);
166
+ }
167
+ }
168
+ async getContent(stage) {
169
+ const key = this.stageKey(stage);
170
+ const text = await this.getText(key);
171
+ const parsed = JSON.parse(text);
172
+ normalizeLegacyIndexedKeys(parsed);
173
+ return normalizeCmsDocument(parsed);
174
+ }
175
+ async saveDraft(content) {
176
+ await this.saveStage("draft", content);
177
+ }
178
+ async saveLive(content) {
179
+ await this.saveStage("live", content);
180
+ }
181
+ async putPublicAsset(input) {
182
+ const key = input.key.replace(/^\/+/, "");
183
+ if (!key) {
184
+ throw new Error("Public asset key is required.");
185
+ }
186
+ const { error } = await this.client.storage.from(this.bucket).upload(key, input.body, {
187
+ upsert: true,
188
+ contentType: input.contentType,
189
+ cacheControl: input.cacheControl
190
+ });
191
+ if (error) {
192
+ throw new Error(`Failed to upload public asset (${key}): ${error.message}`);
193
+ }
194
+ }
195
+ async createCheckpoint(content, input) {
196
+ const createdAt = nowIso();
197
+ const id = createId("cp");
198
+ const key = `${this.prefix}/checkpoints/${keySafeTimestamp()}__${id}.json`;
199
+ const checkpointMeta = {
200
+ id,
201
+ createdAt,
202
+ createdBy: input.createdBy,
203
+ reason: input.reason
204
+ };
205
+ const payload = {
206
+ checkpoint: checkpointMeta,
207
+ content: {
208
+ ...content,
209
+ meta: {
210
+ ...content.meta,
211
+ updatedAt: createdAt,
212
+ updatedBy: input.createdBy,
213
+ sourceCheckpointId: id
214
+ }
215
+ }
216
+ };
217
+ await this.putJson(key, payload);
218
+ return checkpointMeta;
219
+ }
220
+ async deleteCheckpoint(id) {
221
+ const targetId = id.trim();
222
+ if (!targetId) {
223
+ return false;
224
+ }
225
+ const keys = await this.listKeys(`${this.prefix}/checkpoints`);
226
+ const key = keys.find((candidate) => parseIdFromKey(candidate) === targetId);
227
+ if (!key) {
228
+ return false;
229
+ }
230
+ const { error } = await this.client.storage.from(this.bucket).remove([key]);
231
+ if (error) {
232
+ throw new Error(`Failed to delete checkpoint (${targetId}): ${error.message}`);
233
+ }
234
+ return true;
235
+ }
236
+ async listCheckpoints() {
237
+ const keys = await this.listKeys(`${this.prefix}/checkpoints`);
238
+ const items = await Promise.all(
239
+ keys.map(async (key) => {
240
+ const fallback = {
241
+ id: parseIdFromKey(key),
242
+ createdAt: parseTimestampFromKey(key),
243
+ reason: "auto-checkpoint"
244
+ };
245
+ try {
246
+ const text = await this.getText(key);
247
+ const parsed = JSON.parse(text);
248
+ const envelope = parseCheckpointEnvelope(parsed);
249
+ if (!envelope) {
250
+ return fallback;
251
+ }
252
+ return {
253
+ id: envelope.checkpoint.id || fallback.id,
254
+ createdAt: envelope.checkpoint.createdAt || fallback.createdAt,
255
+ createdBy: envelope.checkpoint.createdBy,
256
+ reason: envelope.checkpoint.reason || fallback.reason
257
+ };
258
+ } catch {
259
+ return fallback;
260
+ }
261
+ })
262
+ );
263
+ return items.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
264
+ }
265
+ async publishDraft(input) {
266
+ const createdAt = nowIso();
267
+ const id = createId("pub");
268
+ const key = `${this.prefix}/published/${keySafeTimestamp()}__${id}.json`;
269
+ const payload = {
270
+ ...input.content,
271
+ meta: {
272
+ ...input.content.meta,
273
+ updatedAt: createdAt,
274
+ updatedBy: input.createdBy,
275
+ contentVersion: id
276
+ }
277
+ };
278
+ await this.putJson(key, payload);
279
+ return {
280
+ id,
281
+ createdAt,
282
+ createdBy: input.createdBy,
283
+ sourceContentVersion: input.content.meta.contentVersion
284
+ };
285
+ }
286
+ async listPublishedVersions() {
287
+ const keys = await this.listKeys(`${this.prefix}/published`);
288
+ const items = keys.map((key) => {
289
+ const id = parseIdFromKey(key);
290
+ const createdAt = parseTimestampFromKey(key);
291
+ return {
292
+ id,
293
+ createdAt,
294
+ sourceContentVersion: id
295
+ };
296
+ });
297
+ return items.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
298
+ }
299
+ async getSnapshot(input) {
300
+ const folder = input.sourceType === "checkpoint" ? `${this.prefix}/checkpoints` : `${this.prefix}/published`;
301
+ const keys = await this.listKeys(folder);
302
+ const key = keys.find((candidate) => parseIdFromKey(candidate) === input.sourceId);
303
+ if (!key) {
304
+ return null;
305
+ }
306
+ const text = await this.getText(key);
307
+ const parsed = JSON.parse(text);
308
+ const envelope = parseCheckpointEnvelope(parsed);
309
+ const content = envelope ? envelope.content : parsed;
310
+ normalizeLegacyIndexedKeys(content);
311
+ return normalizeCmsDocument(content);
312
+ }
313
+ async appendEvent(event) {
314
+ const key = `${this.prefix}/events/${monthKey()}.jsonl`;
315
+ const existing = await this.tryGetText(key);
316
+ const line = `${JSON.stringify(event)}
317
+ `;
318
+ const body = `${existing ?? ""}${line}`;
319
+ const { error } = await this.client.storage.from(this.bucket).upload(key, body, {
320
+ upsert: true,
321
+ contentType: "application/x-ndjson"
322
+ });
323
+ if (error) {
324
+ throw new Error(`Failed to append event log (${key}): ${error.message}`);
325
+ }
326
+ }
327
+ async tryGetStage(stage) {
328
+ try {
329
+ return await this.getContent(stage);
330
+ } catch (error) {
331
+ if (this.isMissingObjectError(error)) {
332
+ return null;
333
+ }
334
+ throw error;
335
+ }
336
+ }
337
+ async saveStage(stage, content) {
338
+ await this.putJson(this.stageKey(stage), content);
339
+ }
340
+ stageKey(stage) {
341
+ return `${this.prefix}/${stage}/current.json`;
342
+ }
343
+ async putJson(key, value) {
344
+ const { error } = await this.client.storage.from(this.bucket).upload(key, JSON.stringify(value, null, 2), {
345
+ upsert: true,
346
+ contentType: "application/json"
347
+ });
348
+ if (error) {
349
+ throw new Error(`Failed to write JSON (${key}): ${error.message}`);
350
+ }
351
+ }
352
+ async listKeys(prefix) {
353
+ const normalizedPrefix = normalizeStoragePath(prefix);
354
+ const limit = 1e3;
355
+ let offset = 0;
356
+ const out = [];
357
+ while (true) {
358
+ const { data, error } = await this.client.storage.from(this.bucket).list(normalizedPrefix, {
359
+ limit,
360
+ offset,
361
+ sortBy: {
362
+ column: "name",
363
+ order: "asc"
364
+ }
365
+ });
366
+ if (error) {
367
+ throw new Error(`Failed to list keys (${normalizedPrefix}): ${error.message}`);
368
+ }
369
+ const rows = data ?? [];
370
+ for (const row of rows) {
371
+ if (!row.name) {
372
+ continue;
373
+ }
374
+ out.push(`${normalizedPrefix}/${row.name}`);
375
+ }
376
+ if (rows.length < limit) {
377
+ break;
378
+ }
379
+ offset += rows.length;
380
+ }
381
+ return out;
382
+ }
383
+ async tryGetText(key) {
384
+ try {
385
+ return await this.getText(key);
386
+ } catch (error) {
387
+ if (this.isMissingObjectError(error)) {
388
+ return null;
389
+ }
390
+ throw error;
391
+ }
392
+ }
393
+ async getText(key) {
394
+ const { data, error } = await this.client.storage.from(this.bucket).download(key);
395
+ if (error) {
396
+ throw new Error(`Failed to read object (${key}): ${error.message}`);
397
+ }
398
+ return data.text();
399
+ }
400
+ isMissingObjectError(error) {
401
+ const parsed = toStorageError(error);
402
+ if (parsed.statusCode === "404") {
403
+ return true;
404
+ }
405
+ const message = parsed.message?.toLowerCase() ?? "";
406
+ if (message.includes("not found") || message.includes("not exists")) {
407
+ return true;
408
+ }
409
+ const status = parsed.error?.toLowerCase() ?? "";
410
+ return status.includes("not found");
411
+ }
412
+ };
413
+
414
+ export {
415
+ SupabaseCmsStorage
416
+ };
@@ -1047,7 +1047,7 @@ function buildAgentTools(input) {
1047
1047
  }
1048
1048
  }),
1049
1049
  generate_image: tool({
1050
- description: "Generates an image with Gemini Image Preview, uploads it to S3, and stages a CMS image URL update. Edit-mode references must be JPEG or PNG.",
1050
+ description: "Generates an image with Gemini Image Preview, uploads it to the configured storage backend, and stages a CMS image URL update. Edit-mode references must be JPEG or PNG.",
1051
1051
  inputSchema: z2.object({
1052
1052
  targetPath: z2.string().min(3).max(320),
1053
1053
  prompt: z2.string().min(3).max(2500),
@@ -1499,6 +1499,9 @@ async function runAgentTurn(service, input) {
1499
1499
  const hasContentChanges = stagedContentOperations.length > 0;
1500
1500
  const hasThemeChanges = Object.keys(stagedThemeTokens).length > 0;
1501
1501
  const mutationsApplied = hasContentChanges || hasThemeChanges;
1502
+ const imageOperations = stagedContentOperations.filter(
1503
+ (operation) => /(?:^|[.\]])image(?:$|[.\[])/i.test(operation.path)
1504
+ ).length;
1502
1505
  let updatedDraft;
1503
1506
  if (mutationsApplied) {
1504
1507
  const checkpointReason = resolveCheckpointReason({
@@ -1535,7 +1538,12 @@ async function runAgentTurn(service, input) {
1535
1538
  thinking,
1536
1539
  toolEvents,
1537
1540
  updatedDraft,
1538
- mutationsApplied
1541
+ mutationsApplied,
1542
+ mutationSummary: mutationsApplied ? {
1543
+ contentOperations: stagedContentOperations.length,
1544
+ themeTokenChanges: Object.keys(stagedThemeTokens).length,
1545
+ imageOperations
1546
+ } : void 0
1539
1547
  };
1540
1548
  }
1541
1549
 
@@ -0,0 +1,138 @@
1
+ // src/api-aws/auth.ts
2
+ import { createRemoteJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
3
+ var jwksCache = null;
4
+ function getJwks() {
5
+ const jwksUrl = process.env.SUPABASE_JWKS_URL;
6
+ if (!jwksUrl) {
7
+ throw new Error("SUPABASE_JWKS_URL is not configured");
8
+ }
9
+ if (!jwksCache) {
10
+ jwksCache = createRemoteJWKSet(new URL(jwksUrl));
11
+ }
12
+ return jwksCache;
13
+ }
14
+ function buildSupabaseUserEndpoint() {
15
+ const explicitBaseUrl = process.env.SUPABASE_URL?.trim();
16
+ if (explicitBaseUrl) {
17
+ return `${explicitBaseUrl.replace(/\/$/, "")}/auth/v1/user`;
18
+ }
19
+ const jwksUrl = process.env.SUPABASE_JWKS_URL?.trim();
20
+ if (!jwksUrl) {
21
+ throw new Error("SUPABASE_JWKS_URL is not configured");
22
+ }
23
+ const parsed = new URL(jwksUrl);
24
+ return `${parsed.origin}/auth/v1/user`;
25
+ }
26
+ function getSupabaseAnonKey() {
27
+ const key = process.env.SUPABASE_ANON_KEY?.trim() ?? process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY?.trim();
28
+ if (!key) {
29
+ throw new Error("SUPABASE_ANON_KEY is required for HS256 token verification fallback.");
30
+ }
31
+ return key;
32
+ }
33
+ async function verifyHs256ViaSupabase(token) {
34
+ const response = await fetch(buildSupabaseUserEndpoint(), {
35
+ method: "GET",
36
+ headers: {
37
+ authorization: `Bearer ${token}`,
38
+ apikey: getSupabaseAnonKey()
39
+ }
40
+ });
41
+ if (!response.ok) {
42
+ const detail = await response.text();
43
+ throw new Error(`Supabase token verification failed: ${response.status} ${detail}`);
44
+ }
45
+ const user = await response.json();
46
+ const sub = typeof user.id === "string" ? user.id : "";
47
+ if (!sub) {
48
+ throw new Error("Supabase token verification returned no user id.");
49
+ }
50
+ return {
51
+ sub,
52
+ email: typeof user.email === "string" ? user.email : void 0,
53
+ role: typeof user.role === "string" ? user.role : void 0
54
+ };
55
+ }
56
+ function getBearerToken(headers) {
57
+ const value = headers.authorization ?? headers.Authorization;
58
+ if (!value) {
59
+ return null;
60
+ }
61
+ const [prefix, token] = value.split(" ");
62
+ if (prefix?.toLowerCase() !== "bearer" || !token) {
63
+ return null;
64
+ }
65
+ return token;
66
+ }
67
+ async function verifyAdminToken(token) {
68
+ const header = decodeProtectedHeader(token);
69
+ const algorithm = typeof header.alg === "string" ? header.alg : "";
70
+ if (algorithm === "HS256") {
71
+ const secret = process.env.SUPABASE_JWT_SECRET?.trim();
72
+ if (secret) {
73
+ const result2 = await jwtVerify(token, new TextEncoder().encode(secret), {
74
+ algorithms: ["HS256"]
75
+ });
76
+ const payload2 = result2.payload;
77
+ const identity3 = {
78
+ sub: String(payload2.sub ?? ""),
79
+ email: typeof payload2.email === "string" ? payload2.email : void 0,
80
+ role: typeof payload2.role === "string" ? payload2.role : typeof payload2.user_role === "string" ? payload2.user_role : void 0
81
+ };
82
+ if (!identity3.sub) {
83
+ throw new Error("Invalid token: subject is missing.");
84
+ }
85
+ const enforcedAdminEmail3 = process.env.ADMIN_EMAIL;
86
+ if (enforcedAdminEmail3 && identity3.email?.toLowerCase() !== enforcedAdminEmail3.toLowerCase()) {
87
+ throw new Error("Authenticated user is not allowed for admin access.");
88
+ }
89
+ return identity3;
90
+ }
91
+ const identity2 = await verifyHs256ViaSupabase(token);
92
+ const enforcedAdminEmail2 = process.env.ADMIN_EMAIL;
93
+ if (enforcedAdminEmail2 && identity2.email?.toLowerCase() !== enforcedAdminEmail2.toLowerCase()) {
94
+ throw new Error("Authenticated user is not allowed for admin access.");
95
+ }
96
+ return identity2;
97
+ }
98
+ const result = await jwtVerify(token, getJwks(), {
99
+ algorithms: ["RS256", "ES256"]
100
+ });
101
+ const payload = result.payload;
102
+ const identity = {
103
+ sub: String(payload.sub ?? ""),
104
+ email: typeof payload.email === "string" ? payload.email : void 0,
105
+ role: typeof payload.role === "string" ? payload.role : typeof payload.user_role === "string" ? payload.user_role : void 0
106
+ };
107
+ if (!identity.sub) {
108
+ throw new Error("Invalid token: subject is missing.");
109
+ }
110
+ const enforcedAdminEmail = process.env.ADMIN_EMAIL;
111
+ if (enforcedAdminEmail && identity.email?.toLowerCase() !== enforcedAdminEmail.toLowerCase()) {
112
+ throw new Error("Authenticated user is not allowed for admin access.");
113
+ }
114
+ return identity;
115
+ }
116
+
117
+ // src/api-aws/normalize-editable-path.ts
118
+ var EDITABLE_ROOTS = ["pages.", "layout.", "seo.", "themeTokens."];
119
+ var MAX_PATH_LENGTH = 320;
120
+ function normalizeEditablePath(value) {
121
+ if (typeof value !== "string") {
122
+ return null;
123
+ }
124
+ const trimmed = value.trim();
125
+ if (!trimmed || trimmed.length > MAX_PATH_LENGTH) {
126
+ return null;
127
+ }
128
+ if (!EDITABLE_ROOTS.some((prefix) => trimmed.startsWith(prefix))) {
129
+ return null;
130
+ }
131
+ return trimmed;
132
+ }
133
+
134
+ export {
135
+ getBearerToken,
136
+ verifyAdminToken,
137
+ normalizeEditablePath
138
+ };
package/dist/index.d.ts CHANGED
@@ -2,8 +2,11 @@ export { A as AgentBatchMutationInput, a as AnyCmsDocument, C as CmsMutationInpu
2
2
  export { applyPatch, applyThemeTokenPatch, readByPath, validatePatch } from './core/index.js';
3
3
  export { C as CmsService, c as createPatchFromAgentOperations, a as createThemePatchFromAgentOperations } from './service-BYwdlvCI.js';
4
4
  export { S3CmsStorage } from './storage-s3/index.js';
5
+ export { SupabaseCmsStorage } from './storage-supabase/index.js';
5
6
  export { AgentRunnerInput, AgentRunnerResult, StaticToolName, listStaticToolNames, runAgentTurn } from './agent/index.js';
6
7
  export { handler, streamHandler } from './api-aws/index.js';
8
+ export { default as supabaseHandler } from './api-supabase/index.js';
7
9
  import '@webmaster-droid/contracts';
8
10
  import '@aws-sdk/client-s3';
11
+ import '@supabase/supabase-js';
9
12
  import 'aws-lambda';
package/dist/index.js CHANGED
@@ -1,11 +1,15 @@
1
1
  import {
2
2
  handler,
3
3
  streamHandler
4
- } from "./chunk-6RB7H6PA.js";
4
+ } from "./chunk-6M55DMUE.js";
5
+ import {
6
+ handler as handler2
7
+ } from "./chunk-JIGCFERP.js";
8
+ import "./chunk-SIXK4BMG.js";
5
9
  import {
6
10
  listStaticToolNames,
7
11
  runAgentTurn
8
- } from "./chunk-IK36OUJQ.js";
12
+ } from "./chunk-PS4GESOZ.js";
9
13
  import {
10
14
  CmsService,
11
15
  applyPatch,
@@ -18,9 +22,13 @@ import {
18
22
  import {
19
23
  S3CmsStorage
20
24
  } from "./chunk-MLID7STX.js";
25
+ import {
26
+ SupabaseCmsStorage
27
+ } from "./chunk-OWXROQ4O.js";
21
28
  export {
22
29
  CmsService,
23
30
  S3CmsStorage,
31
+ SupabaseCmsStorage,
24
32
  applyPatch,
25
33
  applyThemeTokenPatch,
26
34
  createPatchFromAgentOperations,
@@ -30,5 +38,6 @@ export {
30
38
  readByPath,
31
39
  runAgentTurn,
32
40
  streamHandler,
41
+ handler2 as supabaseHandler,
33
42
  validatePatch
34
43
  };
@@ -0,0 +1,50 @@
1
+ import { SupabaseClient } from '@supabase/supabase-js';
2
+ import { CmsDocument, CmsStage, CheckpointMeta, PublishedVersionMeta, RollbackRequest, AuditEvent } from '@webmaster-droid/contracts';
3
+ import { S as StorageAdapter } from '../types-OKJgq7Oo.js';
4
+
5
+ interface SupabaseCmsStorageOptions {
6
+ supabaseUrl: string;
7
+ serviceRoleKey: string;
8
+ bucket: string;
9
+ prefix?: string;
10
+ client?: SupabaseClient;
11
+ }
12
+ declare class SupabaseCmsStorage implements StorageAdapter {
13
+ private readonly bucket;
14
+ private readonly client;
15
+ private readonly prefix;
16
+ constructor(options: SupabaseCmsStorageOptions);
17
+ ensureInitialized(seed: CmsDocument): Promise<void>;
18
+ getContent(stage: CmsStage): Promise<CmsDocument>;
19
+ saveDraft(content: CmsDocument): Promise<void>;
20
+ saveLive(content: CmsDocument): Promise<void>;
21
+ putPublicAsset(input: {
22
+ key: string;
23
+ body: Uint8Array;
24
+ contentType: string;
25
+ cacheControl?: string;
26
+ }): Promise<void>;
27
+ createCheckpoint(content: CmsDocument, input: {
28
+ createdBy?: string;
29
+ reason: string;
30
+ }): Promise<CheckpointMeta>;
31
+ deleteCheckpoint(id: string): Promise<boolean>;
32
+ listCheckpoints(): Promise<CheckpointMeta[]>;
33
+ publishDraft(input: {
34
+ content: CmsDocument;
35
+ createdBy?: string;
36
+ }): Promise<PublishedVersionMeta>;
37
+ listPublishedVersions(): Promise<PublishedVersionMeta[]>;
38
+ getSnapshot(input: RollbackRequest): Promise<CmsDocument | null>;
39
+ appendEvent(event: AuditEvent): Promise<void>;
40
+ private tryGetStage;
41
+ private saveStage;
42
+ private stageKey;
43
+ private putJson;
44
+ private listKeys;
45
+ private tryGetText;
46
+ private getText;
47
+ private isMissingObjectError;
48
+ }
49
+
50
+ export { SupabaseCmsStorage };
@@ -0,0 +1,6 @@
1
+ import {
2
+ SupabaseCmsStorage
3
+ } from "../chunk-OWXROQ4O.js";
4
+ export {
5
+ SupabaseCmsStorage
6
+ };