@gmickel/gno 0.41.0 → 0.42.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.
@@ -91,6 +91,12 @@ import {
91
91
  import { searchHybrid } from "../../pipeline/hybrid";
92
92
  import { validateQueryModes } from "../../pipeline/query-modes";
93
93
  import { searchBm25 } from "../../pipeline/search";
94
+ import {
95
+ derivePublishArtifactFilename,
96
+ isPublishVisibility,
97
+ type PublishVisibility,
98
+ } from "../../publish/artifact";
99
+ import { exportPublishArtifact } from "../../publish/export-service";
94
100
  import { buildBrowseTree, normalizeBrowsePath } from "../browse-tree";
95
101
  import { applyConfigChange, applyConfigChangeTyped } from "../config-sync";
96
102
  import { getConnectorStatuses, installConnector } from "../connectors";
@@ -333,6 +339,14 @@ export interface CreateEditableCopyRequestBody {
333
339
  uri?: string;
334
340
  }
335
341
 
342
+ export interface PublishExportRequestBody {
343
+ slug?: string;
344
+ summary?: string;
345
+ target: string;
346
+ title?: string;
347
+ visibility?: PublishVisibility;
348
+ }
349
+
336
350
  // ─────────────────────────────────────────────────────────────────────────────
337
351
  // Helpers
338
352
  // ─────────────────────────────────────────────────────────────────────────────
@@ -726,6 +740,70 @@ export async function handleCollections(config: Config): Promise<Response> {
726
740
  );
727
741
  }
728
742
 
743
+ /**
744
+ * POST /api/publish/export
745
+ * Build a gno.sh-compatible publish artifact for a collection or single doc.
746
+ */
747
+ export async function handlePublishExport(
748
+ config: Config,
749
+ store: SqliteAdapter,
750
+ req: Request
751
+ ): Promise<Response> {
752
+ let body: PublishExportRequestBody;
753
+ try {
754
+ body = (await req.json()) as PublishExportRequestBody;
755
+ } catch {
756
+ return errorResponse("VALIDATION", "Invalid JSON body");
757
+ }
758
+
759
+ if (!body.target || typeof body.target !== "string") {
760
+ return errorResponse("VALIDATION", "Missing or invalid target");
761
+ }
762
+ if (body.slug !== undefined && typeof body.slug !== "string") {
763
+ return errorResponse("VALIDATION", "slug must be a string");
764
+ }
765
+ if (body.summary !== undefined && typeof body.summary !== "string") {
766
+ return errorResponse("VALIDATION", "summary must be a string");
767
+ }
768
+ if (body.title !== undefined && typeof body.title !== "string") {
769
+ return errorResponse("VALIDATION", "title must be a string");
770
+ }
771
+ if (body.visibility !== undefined && !isPublishVisibility(body.visibility)) {
772
+ return errorResponse(
773
+ "VALIDATION",
774
+ "visibility must be public, secret-link, invite-only, or encrypted"
775
+ );
776
+ }
777
+
778
+ try {
779
+ const artifact = await exportPublishArtifact({
780
+ collections: config.collections,
781
+ options: {
782
+ routeSlug: body.slug,
783
+ summary: body.summary,
784
+ title: body.title,
785
+ visibility: body.visibility,
786
+ },
787
+ store,
788
+ target: body.target.trim(),
789
+ });
790
+
791
+ return jsonResponse({
792
+ artifact,
793
+ fileName: derivePublishArtifactFilename(artifact),
794
+ uploadUrl: "https://gno.sh/studio",
795
+ });
796
+ } catch (error) {
797
+ return errorResponse(
798
+ "RUNTIME",
799
+ error instanceof Error
800
+ ? error.message
801
+ : "Failed to export publish artifact",
802
+ 500
803
+ );
804
+ }
805
+ }
806
+
729
807
  /**
730
808
  * POST /api/collections
731
809
  * Create a new collection and start sync job.
@@ -3894,6 +3972,10 @@ export async function routeApi(
3894
3972
  return handleCollections(config);
3895
3973
  }
3896
3974
 
3975
+ if (path === "/api/publish/export" && req.method === "POST") {
3976
+ return handlePublishExport(config, store, req);
3977
+ }
3978
+
3897
3979
  if (path === "/api/docs") {
3898
3980
  return handleDocs(store, url);
3899
3981
  }
@@ -42,6 +42,7 @@ import {
42
42
  handleJob,
43
43
  handleModelPull,
44
44
  handleModelStatus,
45
+ handlePublishExport,
45
46
  handlePresets,
46
47
  handleQuery,
47
48
  handleRefactorPlan,
@@ -234,6 +235,17 @@ export async function startServer(
234
235
  );
235
236
  },
236
237
  },
238
+ "/api/publish/export": {
239
+ POST: async (req: Request) => {
240
+ if (!isRequestAllowed(req, port)) {
241
+ return withSecurityHeaders(forbiddenResponse(), isDev);
242
+ }
243
+ return withSecurityHeaders(
244
+ await handlePublishExport(ctxHolder.config, store, req),
245
+ isDev
246
+ );
247
+ },
248
+ },
237
249
  "/api/sync": {
238
250
  POST: async (req: Request) => {
239
251
  if (!isRequestAllowed(req, port)) {
@@ -117,10 +117,12 @@ export async function createVectorIndexPort(
117
117
  `);
118
118
 
119
119
  // Prepared statements for vec0 table (if available)
120
- const upsertVecStmt = searchAvailable
121
- ? db.prepare(
122
- `INSERT OR REPLACE INTO ${tableName} (chunk_id, embedding) VALUES (?, ?)`
123
- )
120
+ const deleteVecChunkStmt = searchAvailable
121
+ ? db.prepare(`DELETE FROM ${tableName} WHERE chunk_id = ?`)
122
+ : null;
123
+
124
+ const insertVecStmt = searchAvailable
125
+ ? db.prepare(`INSERT INTO ${tableName} (chunk_id, embedding) VALUES (?, ?)`)
124
126
  : null;
125
127
 
126
128
  const searchStmt = searchAvailable
@@ -175,12 +177,15 @@ export async function createVectorIndexPort(
175
177
  }
176
178
 
177
179
  // 2. Best-effort update vec0 (graceful degradation)
178
- if (upsertVecStmt) {
180
+ if (deleteVecChunkStmt && insertVecStmt) {
179
181
  try {
180
182
  db.transaction(() => {
181
183
  for (const row of rows) {
182
184
  const chunkId = `${row.mirrorHash}:${row.seq}`;
183
- upsertVecStmt.run(chunkId, encodeEmbedding(row.embedding));
185
+ // sqlite-vec vec0 tables do not reliably support OR REPLACE semantics.
186
+ // Delete first, then insert the fresh vector row.
187
+ deleteVecChunkStmt.run(chunkId);
188
+ insertVecStmt.run(chunkId, encodeEmbedding(row.embedding));
184
189
  }
185
190
  })();
186
191
  } catch (e) {