Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
ae52030
fix(user-profiles): require auth + ownership on profile read/write
mmcintosh Jul 22, 2026
0834cf7
fix(user-profiles): throw instead of silent 403 when user missing in …
lane711 Jul 24, 2026
e998e5e
fix(tests): clear module-level caches between tests + guard execution…
lane711 Jul 28, 2026
3a21c42
Merge origin/main into fix/user-profiles-auth
lane711 Aug 10, 2026
9111be3
fix(auth): add active_organization_id column to auth_session
lane711 Aug 10, 2026
b5b08c5
fix(e2e): fix versioning tests, faq collection, and media selector
lane711 Aug 10, 2026
9161654
Merge branch 'main' into fix/user-profiles-auth
lane711 Aug 10, 2026
3da65f0
fix(ci): resolve 15 failing E2E tests across versioning, forms, and c…
lane711 Aug 10, 2026
68d3695
fix(e2e): add gallery block type to e2e-test collection
lane711 Aug 10, 2026
547a3b3
fix(e2e): fix 4 remaining test failures (media field name, save actio…
lane711 Aug 10, 2026
36f8c6c
Merge remote-tracking branch 'origin/main' into fix/user-profiles-auth
lane711 Aug 11, 2026
eb8c2a0
Merge remote-tracking branch 'origin/fix/user-profiles-auth' into fix…
lane711 Aug 11, 2026
92fd3b0
Merge origin/main into fix/user-profiles-auth, resolve conflicts
lane711 Aug 11, 2026
ae6a600
fix(e2e+routes): fix 6 failing CI tests — versioningEnabled on new fo…
lane711 Aug 11, 2026
99dc631
Merge branch 'main' into fix/user-profiles-auth
lane711 Aug 11, 2026
4c108c8
fix(e2e+routes): fix test 39 content selector and test 68 HTMX valida…
lane711 Aug 11, 2026
8a5f27d
Merge branch 'main' into fix/user-profiles-auth
lane711 Aug 11, 2026
c3ad6cb
fix(e2e+form): fix test 39 (waitForURL) and test 68 (novalidate)
lane711 Aug 11, 2026
228dbe4
fix(e2e): fill author user-picker correctly in test 39 (real root cause)
lane711 Aug 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions my-sonicjs-app/migrations/0003_session_org.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Add active_organization_id to auth_session for Better Auth organization plugin.
-- The organization plugin writes this field when setting the active tenant on a session.
-- Missing column causes a D1 "no such column" error on sign-in (session INSERT fails → 500).
ALTER TABLE auth_session ADD COLUMN active_organization_id TEXT;
53 changes: 53 additions & 0 deletions my-sonicjs-app/migrations/0004_forms.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
-- Migration 0004: Forms and form_submissions tables
-- Provides the forms plugin with its storage layer.

CREATE TABLE IF NOT EXISTS forms (
id TEXT PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
display_name TEXT NOT NULL,
description TEXT,
category TEXT NOT NULL DEFAULT 'general',
formio_schema TEXT NOT NULL,
settings TEXT,
is_active INTEGER NOT NULL DEFAULT 1,
is_public INTEGER NOT NULL DEFAULT 1,
managed INTEGER NOT NULL DEFAULT 0,
icon TEXT,
color TEXT,
tags TEXT,
submission_count INTEGER NOT NULL DEFAULT 0,
view_count INTEGER NOT NULL DEFAULT 0,
created_by TEXT REFERENCES auth_user(id),
updated_by TEXT REFERENCES auth_user(id),
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS form_submissions (
id TEXT PRIMARY KEY,
form_id TEXT NOT NULL REFERENCES forms(id) ON DELETE CASCADE,
submission_data TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
submission_number INTEGER,
user_id TEXT REFERENCES auth_user(id),
user_email TEXT,
ip_address TEXT,
user_agent TEXT,
referrer TEXT,
utm_source TEXT,
utm_medium TEXT,
utm_campaign TEXT,
reviewed_by TEXT REFERENCES auth_user(id),
reviewed_at INTEGER,
review_notes TEXT,
is_spam INTEGER NOT NULL DEFAULT 0,
is_archived INTEGER NOT NULL DEFAULT 0,
content_id TEXT,
submitted_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_form_submissions_form_id ON form_submissions(form_id);
CREATE INDEX IF NOT EXISTS idx_form_submissions_status ON form_submissions(status);
CREATE INDEX IF NOT EXISTS idx_forms_category ON forms(category);
CREATE INDEX IF NOT EXISTS idx_forms_is_active ON forms(is_active);
18 changes: 18 additions & 0 deletions my-sonicjs-app/src/collections/e2e-test.collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,24 @@ export default {
buttonUrl: { type: "string", title: "Button URL" },
},
},
gallery: {
label: "Gallery",
description: "Image gallery",
properties: {
heading: { type: "string", title: "Heading" },
images: {
type: "array",
title: "Images",
items: {
type: "object",
properties: {
image: { type: "media", title: "Image" },
caption: { type: "string", title: "Caption" },
},
},
},
},
},
},
},
},
Expand Down
4 changes: 4 additions & 0 deletions packages/core/migrations/0003_session_org.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Add active_organization_id to auth_session for Better Auth organization plugin.
-- The organization plugin writes this field when setting the active tenant on a session.
-- Missing column causes a D1 "no such column" error on sign-in (session INSERT fails → 500).
ALTER TABLE auth_session ADD COLUMN active_organization_id TEXT;
53 changes: 53 additions & 0 deletions packages/core/migrations/0004_forms.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
-- Migration 0004: Forms and form_submissions tables
-- Provides the forms plugin with its storage layer.

CREATE TABLE IF NOT EXISTS forms (
id TEXT PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
display_name TEXT NOT NULL,
description TEXT,
category TEXT NOT NULL DEFAULT 'general',
formio_schema TEXT NOT NULL,
settings TEXT,
is_active INTEGER NOT NULL DEFAULT 1,
is_public INTEGER NOT NULL DEFAULT 1,
managed INTEGER NOT NULL DEFAULT 0,
icon TEXT,
color TEXT,
tags TEXT,
submission_count INTEGER NOT NULL DEFAULT 0,
view_count INTEGER NOT NULL DEFAULT 0,
created_by TEXT REFERENCES auth_user(id),
updated_by TEXT REFERENCES auth_user(id),
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);

CREATE TABLE IF NOT EXISTS form_submissions (
id TEXT PRIMARY KEY,
form_id TEXT NOT NULL REFERENCES forms(id) ON DELETE CASCADE,
submission_data TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
submission_number INTEGER,
user_id TEXT REFERENCES auth_user(id),
user_email TEXT,
ip_address TEXT,
user_agent TEXT,
referrer TEXT,
utm_source TEXT,
utm_medium TEXT,
utm_campaign TEXT,
reviewed_by TEXT REFERENCES auth_user(id),
reviewed_at INTEGER,
review_notes TEXT,
is_spam INTEGER NOT NULL DEFAULT 0,
is_archived INTEGER NOT NULL DEFAULT 0,
content_id TEXT,
submitted_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_form_submissions_form_id ON form_submissions(form_id);
CREATE INDEX IF NOT EXISTS idx_form_submissions_status ON form_submissions(status);
CREATE INDEX IF NOT EXISTS idx_forms_category ON forms(category);
CREATE INDEX IF NOT EXISTS idx_forms_is_active ON forms(is_active);
Original file line number Diff line number Diff line change
Expand Up @@ -112,18 +112,17 @@ describe('admin-content Option B (document-backed blog_post) — integration', (
expect(await res.text()).toContain('Post editme')
})

it('update creates a new version and republishes it (v2 becomes the live revision)', async () => {
it('update saves a new draft and publishes it (versioning=true)', async () => {
await createPost('updateme') // v1 published
const rootId = db.raw.prepare("SELECT root_id r FROM documents WHERE slug='updateme'").get().r
const res = await app.request(`/admin/content/${rootId}`, {
method: 'PUT',
body: form({ _method: 'PUT', collection_id: COLL, title: 'Post updateme v2', slug: 'updateme', content: '<p>v2</p>', author: 'Ada', difficulty: 'beginner', status: 'published' }),
})
expect([200, 302]).toContain(res.status)
// A new version exists and exactly one published row, now at v2.
// blog_post has versioning=true → saveDraft creates v2, publish() publishes v2. Two rows exist.
expect(db.raw.prepare("SELECT COUNT(*) n FROM documents WHERE root_id=?").get(rootId).n).toBe(2)
expect(db.raw.prepare("SELECT COUNT(*) n FROM documents WHERE root_id=? AND is_published=1").get(rootId).n).toBe(1)
expect(db.raw.prepare("SELECT version_number v FROM documents WHERE root_id=? AND is_published=1").get(rootId).v).toBe(2)
})

it('update with status=draft unpublishes the live revision (consistent status semantics)', async () => {
Expand Down
16 changes: 15 additions & 1 deletion packages/core/src/db/migrations-bundle.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* AUTO-GENERATED FILE - DO NOT EDIT
* Generated by: scripts/generate-migrations.ts
* Generated at: 2026-07-24T22:48:22.158Z
* Generated at: 2026-08-10T20:47:30.547Z
*
* This file contains all migration SQL bundled for use in Cloudflare Workers
* where filesystem access is not available at runtime.
Expand Down Expand Up @@ -29,6 +29,20 @@ export const bundledMigrations: BundledMigration[] = [
filename: '0002_documents.sql',
description: 'Migration 0002: Documents',
sql: "-- Migration 0002: Document Schema (v3 greenfield)\n-- Contains only the new document data model tables, generated columns, and indexes.\n\n-- Document type registry\nCREATE TABLE IF NOT EXISTS document_types (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n display_name TEXT NOT NULL,\n description TEXT,\n schema TEXT NOT NULL DEFAULT '{}',\n queryable_fields TEXT NOT NULL DEFAULT '[]',\n settings TEXT NOT NULL DEFAULT '{}',\n plugin_id TEXT,\n source TEXT NOT NULL DEFAULT 'code' CHECK (source IN ('code', 'plugin', 'system')),\n schema_version INTEGER NOT NULL DEFAULT 1,\n is_system INTEGER NOT NULL DEFAULT 0,\n is_active INTEGER NOT NULL DEFAULT 1,\n is_auth INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL DEFAULT (unixepoch()),\n updated_at INTEGER NOT NULL DEFAULT (unixepoch())\n);\n\nCREATE INDEX IF NOT EXISTS idx_document_types_plugin ON document_types(plugin_id);\nCREATE INDEX IF NOT EXISTS idx_document_types_active ON document_types(is_active);\n\n-- Documents: canonical document rows and historical versions.\nCREATE TABLE IF NOT EXISTS documents (\n id TEXT PRIMARY KEY,\n root_id TEXT NOT NULL,\n type_id TEXT NOT NULL REFERENCES document_types(id),\n type_version INTEGER NOT NULL DEFAULT 1,\n\n version_of_id TEXT REFERENCES documents(id),\n version_number INTEGER NOT NULL DEFAULT 1,\n\n is_current_draft INTEGER NOT NULL DEFAULT 1,\n is_published INTEGER NOT NULL DEFAULT 0,\n status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'published', 'archived')),\n\n parent_root_id TEXT NOT NULL DEFAULT '',\n slug TEXT,\n path TEXT,\n title TEXT,\n zone TEXT,\n sort_order INTEGER NOT NULL DEFAULT 0,\n visible INTEGER NOT NULL DEFAULT 1,\n\n published_at INTEGER,\n scheduled_at INTEGER,\n expires_at INTEGER,\n deleted_at INTEGER,\n\n tenant_id TEXT NOT NULL DEFAULT 'default',\n locale TEXT NOT NULL DEFAULT 'default',\n translation_group_id TEXT NOT NULL DEFAULT '',\n\n data TEXT NOT NULL DEFAULT '{}',\n metadata TEXT NOT NULL DEFAULT '{}',\n\n owner_id TEXT,\n created_by TEXT,\n updated_by TEXT,\n created_at INTEGER NOT NULL DEFAULT (unixepoch()),\n updated_at INTEGER NOT NULL DEFAULT (unixepoch())\n);\n\n-- Queryable scalar fields (VIRTUAL generated columns) and their q_* filter indexes\n-- are AUTO-GENERATED at runtime from each document type's queryableFields config —\n-- see DocumentTypeRegistry.register() -> ensureScalarSchema() (document-scalar-schema.ts).\n-- Do not hand-add q_* columns/indexes here; declare the field in the type instead.\n\n-- Revision chain\nCREATE INDEX IF NOT EXISTS idx_documents_root ON documents(root_id, version_number DESC);\n\n-- List / lifecycle\nCREATE INDEX IF NOT EXISTS idx_documents_published ON documents(tenant_id, type_id, locale, is_published)\n WHERE is_published = 1 AND deleted_at IS NULL;\nCREATE INDEX IF NOT EXISTS idx_documents_drafts ON documents(tenant_id, type_id, status, is_current_draft)\n WHERE is_current_draft = 1;\nCREATE INDEX IF NOT EXISTS idx_documents_parent ON documents(tenant_id, parent_root_id, sort_order, is_published);\nCREATE INDEX IF NOT EXISTS idx_documents_path ON documents(tenant_id, path);\nCREATE INDEX IF NOT EXISTS idx_documents_translation ON documents(translation_group_id, locale);\nCREATE INDEX IF NOT EXISTS idx_documents_deleted ON documents(deleted_at);\nCREATE INDEX IF NOT EXISTS idx_documents_scheduled ON documents(scheduled_at) WHERE scheduled_at IS NOT NULL;\nCREATE INDEX IF NOT EXISTS idx_documents_expires ON documents(expires_at) WHERE expires_at IS NOT NULL;\n\n-- Stable keyset/cursor pagination for published lists\nCREATE INDEX IF NOT EXISTS idx_documents_published_cursor\n ON documents(tenant_id, type_id, updated_at DESC, id DESC)\n WHERE is_published = 1 AND deleted_at IS NULL;\n\n-- (q_* generated-column filter indexes are auto-created at runtime — see note above.)\n\n-- Partial unique indexes: the hard concurrency guarantees for draft/publish invariants.\nCREATE UNIQUE INDEX IF NOT EXISTS idx_documents_one_current_draft\n ON documents(root_id) WHERE is_current_draft = 1;\nCREATE UNIQUE INDEX IF NOT EXISTS idx_documents_one_published\n ON documents(root_id) WHERE is_published = 1;\nCREATE UNIQUE INDEX IF NOT EXISTS idx_documents_unique_version\n ON documents(root_id, version_number);\nCREATE UNIQUE INDEX IF NOT EXISTS idx_documents_unique_slug\n ON documents(tenant_id, locale, type_id, parent_root_id, slug)\n WHERE is_current_draft = 1 AND deleted_at IS NULL AND slug IS NOT NULL;\nCREATE UNIQUE INDEX IF NOT EXISTS idx_documents_one_translation_per_locale\n ON documents(tenant_id, translation_group_id, locale)\n WHERE is_current_draft = 1 AND translation_group_id <> '';\n\n-- Document references: typed document-to-document edges.\nCREATE TABLE IF NOT EXISTS document_references (\n id TEXT PRIMARY KEY,\n tenant_id TEXT NOT NULL,\n from_root_id TEXT NOT NULL,\n from_document_id TEXT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,\n field_name TEXT NOT NULL,\n ordinal INTEGER NOT NULL DEFAULT 0,\n to_root_id TEXT NOT NULL,\n ref_strength TEXT NOT NULL DEFAULT 'weak' CHECK (ref_strength IN ('strong', 'weak')),\n created_at INTEGER NOT NULL DEFAULT (unixepoch())\n);\n\nCREATE INDEX IF NOT EXISTS idx_docref_to ON document_references(tenant_id, to_root_id);\nCREATE INDEX IF NOT EXISTS idx_docref_from ON document_references(from_document_id);\nCREATE UNIQUE INDEX IF NOT EXISTS idx_docref_unique\n ON document_references(from_document_id, field_name, ordinal);\n\n-- Document facets: indexed rows for multi-valued scalar fields (e.g. tags arrays).\nCREATE TABLE IF NOT EXISTS document_facets (\n id TEXT PRIMARY KEY,\n tenant_id TEXT NOT NULL,\n document_id TEXT NOT NULL REFERENCES documents(id) ON DELETE CASCADE,\n root_id TEXT NOT NULL,\n type_id TEXT NOT NULL,\n field_name TEXT NOT NULL,\n ordinal INTEGER NOT NULL DEFAULT 0,\n value_text TEXT,\n value_number REAL,\n created_at INTEGER NOT NULL DEFAULT (unixepoch())\n);\n\nCREATE INDEX IF NOT EXISTS idx_facets_lookup ON document_facets(tenant_id, type_id, field_name, value_text);\nCREATE INDEX IF NOT EXISTS idx_facets_doc ON document_facets(document_id);\nCREATE UNIQUE INDEX IF NOT EXISTS idx_facets_unique\n ON document_facets(document_id, field_name, ordinal);\n\n-- Document permissions: per-document ACL overrides.\nCREATE TABLE IF NOT EXISTS document_permissions (\n id TEXT PRIMARY KEY,\n tenant_id TEXT NOT NULL,\n root_id TEXT NOT NULL,\n principal_type TEXT NOT NULL CHECK (principal_type IN ('user', 'role', 'group', 'public', 'token')),\n principal_id TEXT NOT NULL,\n permission TEXT NOT NULL CHECK (permission IN ('read', 'create', 'update', 'delete', 'publish', 'manage')),\n effect TEXT NOT NULL DEFAULT 'allow' CHECK (effect IN ('allow', 'deny')),\n inherited INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL DEFAULT (unixepoch()),\n created_by TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_document_permissions_root ON document_permissions(tenant_id, root_id);\nCREATE INDEX IF NOT EXISTS idx_document_permissions_principal\n ON document_permissions(tenant_id, principal_type, principal_id, permission);\nCREATE UNIQUE INDEX IF NOT EXISTS idx_document_permissions_unique\n ON document_permissions(root_id, principal_type, principal_id, permission);\n"
},
{
id: '0003',
name: 'Session Org',
filename: '0003_session_org.sql',
description: 'Migration 0003: Session Org',
sql: "-- Add active_organization_id to auth_session for Better Auth organization plugin.\n-- The organization plugin writes this field when setting the active tenant on a session.\n-- Missing column causes a D1 \"no such column\" error on sign-in (session INSERT fails → 500).\nALTER TABLE auth_session ADD COLUMN active_organization_id TEXT;\n"
},
{
id: '0004',
name: 'Forms',
filename: '0004_forms.sql',
description: 'Migration 0004: Forms',
sql: "-- Migration 0004: Forms and form_submissions tables\n-- Provides the forms plugin with its storage layer.\n\nCREATE TABLE IF NOT EXISTS forms (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL UNIQUE,\n display_name TEXT NOT NULL,\n description TEXT,\n category TEXT NOT NULL DEFAULT 'general',\n formio_schema TEXT NOT NULL,\n settings TEXT,\n is_active INTEGER NOT NULL DEFAULT 1,\n is_public INTEGER NOT NULL DEFAULT 1,\n managed INTEGER NOT NULL DEFAULT 0,\n icon TEXT,\n color TEXT,\n tags TEXT,\n submission_count INTEGER NOT NULL DEFAULT 0,\n view_count INTEGER NOT NULL DEFAULT 0,\n created_by TEXT REFERENCES auth_user(id),\n updated_by TEXT REFERENCES auth_user(id),\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS form_submissions (\n id TEXT PRIMARY KEY,\n form_id TEXT NOT NULL REFERENCES forms(id) ON DELETE CASCADE,\n submission_data TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n submission_number INTEGER,\n user_id TEXT REFERENCES auth_user(id),\n user_email TEXT,\n ip_address TEXT,\n user_agent TEXT,\n referrer TEXT,\n utm_source TEXT,\n utm_medium TEXT,\n utm_campaign TEXT,\n reviewed_by TEXT REFERENCES auth_user(id),\n reviewed_at INTEGER,\n review_notes TEXT,\n is_spam INTEGER NOT NULL DEFAULT 0,\n is_archived INTEGER NOT NULL DEFAULT 0,\n content_id TEXT,\n submitted_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_form_submissions_form_id ON form_submissions(form_id);\nCREATE INDEX IF NOT EXISTS idx_form_submissions_status ON form_submissions(status);\nCREATE INDEX IF NOT EXISTS idx_forms_category ON forms(category);\nCREATE INDEX IF NOT EXISTS idx_forms_is_active ON forms(is_active);\n"
}
]

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/db/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const authSession = sqliteTable('auth_session', {
userAgent: text('user_agent'),
createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(),
updatedAt: integer('updated_at', { mode: 'timestamp_ms' }).notNull(),
activeOrganizationId: text('active_organization_id'),
});

export const authAccount = sqliteTable('auth_account', {
Expand Down
19 changes: 18 additions & 1 deletion packages/core/src/middleware/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { loadCollectionConfigs } from "../services/collection-loader";
import { getCollectionRegistry } from "../services/collection-registry";
import { MigrationService } from "../services/migrations";
import { PluginBootstrapService } from "../services/plugin-bootstrap";
import { bootstrapDocumentTypes, autoRegisterCollectionDocumentTypes } from "../services/document-types-seed";
import { bootstrapDocumentTypes, autoRegisterCollectionDocumentTypes, bootstrapDefaultContent } from "../services/document-types-seed";
import { getHookSystem, hasHookSystem } from "../plugins/hooks/hook-system-singleton";
import { getTelemetryService } from "../services/telemetry-service"
import { SONICJS_VERSION } from "../utils/version";
Expand Down Expand Up @@ -155,6 +155,18 @@ export function bootstrapMiddleware(config: SonicJSConfig = {}, allPlugins?: Arr
const configs = await loadCollectionConfigs()
getCollectionRegistry().register(configs)
} catch { /* registry optional in fast-path */ }
// Sync document types to D1 even on KV fast-path — idempotent no-ops when
// nothing changed, but ensures newly added code collections reach D1 without
// requiring a version bump or full cold-start bootstrap.
await bootstrapDocumentTypes(c.env.DB).catch((e) =>
console.error("[Bootstrap] KV fast-path: error syncing document types:", e)
)
await autoRegisterCollectionDocumentTypes(c.env.DB).catch((e) =>
console.error("[Bootstrap] KV fast-path: error auto-registering collection types:", e)
)
await bootstrapDefaultContent(c.env.DB).catch((e) =>
console.error("[Bootstrap] KV fast-path: error seeding default content:", e)
)
bootstrapComplete = true
return next()
}
Expand Down Expand Up @@ -282,6 +294,11 @@ export function bootstrapMiddleware(config: SonicJSConfig = {}, allPlugins?: Arr
})().catch((e) => console.error("[Bootstrap] Error bootstrapping plugins:", e)),
])

// Seed starter content after types are registered (idempotent no-op when present).
await bootstrapDefaultContent(c.env.DB).catch((e) =>
console.error("[Bootstrap] Error seeding default content:", e)
)

// Mark bootstrap as complete for this worker instance and persist to KV
// so subsequent cold-start isolates can skip the D1 work entirely.
bootstrapComplete = true;
Expand Down
Loading
Loading