diff --git a/.changeset/perf-taxonomy-defs-isolate-cache.md b/.changeset/perf-taxonomy-defs-isolate-cache.md new file mode 100644 index 0000000000..f212d27f22 --- /dev/null +++ b/.changeset/perf-taxonomy-defs-isolate-cache.md @@ -0,0 +1,11 @@ +--- +"emdash": patch +--- + +Cut per-render D1 round trips on public pages by caching taxonomy definitions across the worker isolate, and harden the runtime/DB singletons against bundler module duplication. + +Every public render that hydrates entry terms read `SELECT * FROM _emdash_taxonomy_defs` (via `getTaxonomyDefs` → `getCollectionTaxonomyNames`), which only had per-request caching. On Cloudflare D1, where the worker colo is often far from the database primary, each query is a ~40ms cross-region round trip, so this fired on every warm request for no benefit — taxonomy _definitions_ change extremely rarely (created via the admin API or a seed; there is no edit/delete-def path). They're now cached per-isolate behind a `globalThis` Symbol holder (the same two-tier pattern as `settings/index.ts` and the byline field-defs cache), keyed by resolved locale and invalidated in-memory by every def write (`handleTaxonomyCreate`, seed application). Invalidation is in-memory rather than a persisted version probe on purpose: a per-request version read would merely replace the query being removed, yielding no net saving on warm isolates. Isolated databases (playground / DO preview) bypass the cache. + +Separately, the cached runtime instance, the DB-instance cache, and the in-flight DB-init promise (`astro/middleware.ts`, `emdash-runtime.ts`) were plain module-scoped variables. Under Vite SSR chunk duplication those can become multiple independent copies, letting cold-start migrations and bootstrap reads re-run on requests that should have hit the warm cache. They now live on `globalThis` behind Symbol keys, matching the existing `SETUP_VERIFIED_KEY` / request-context / request-cache singletons. + +No schema changes, no public API changes, fully backwards compatible. diff --git a/packages/core/src/api/handlers/taxonomies.ts b/packages/core/src/api/handlers/taxonomies.ts index 068240730d..280baa9512 100644 --- a/packages/core/src/api/handlers/taxonomies.ts +++ b/packages/core/src/api/handlers/taxonomies.ts @@ -12,7 +12,7 @@ import { ulid } from "ulidx"; import { TaxonomyRepository } from "../../database/repositories/taxonomy.js"; import type { Database, TaxonomyDefTable } from "../../database/types.js"; -import { invalidateTermCache } from "../../taxonomies/index.js"; +import { invalidateTaxonomyDefsCache, invalidateTermCache } from "../../taxonomies/index.js"; import type { ApiResult } from "../types.js"; const NAME_PATTERN = /^[a-z][a-z0-9_]*$/; @@ -290,6 +290,10 @@ export async function handleTaxonomyCreate( }) .execute(); + // A new def changes which taxonomies exist — drop the isolate-wide + // defs/names caches so this isolate reflects it immediately. + invalidateTaxonomyDefsCache(); + const row = await db .selectFrom("_emdash_taxonomy_defs") .selectAll() diff --git a/packages/core/src/loader.ts b/packages/core/src/loader.ts index 5f09371bf8..693f42f285 100644 --- a/packages/core/src/loader.ts +++ b/packages/core/src/loader.ts @@ -296,6 +296,18 @@ async function getTaxonomyNames(db: Kysely): Promise> { } } +/** + * Reset the module-scoped taxonomy-names cache. + * + * Called from `invalidateTaxonomyDefsCache()` so that creating or seeding a + * taxonomy definition is reflected within the current isolate instead of + * waiting for the isolate to recycle. Keeps this cache consistent with the + * isolate-wide taxonomy-defs cache in `taxonomies/index.ts`. + */ +export function resetTaxonomyNamesCache(): void { + taxonomyNames = null; +} + /** * System columns to include in data (mapped to camelCase where needed) */ diff --git a/packages/core/src/object-cache/index.ts b/packages/core/src/object-cache/index.ts index 57fd29be16..a5e8b62964 100644 --- a/packages/core/src/object-cache/index.ts +++ b/packages/core/src/object-cache/index.ts @@ -167,7 +167,7 @@ async function getBackend(): Promise { } catch (error) { // Importing the virtual module fails outside an Astro/Vite context // (e.g. unit tests, CLI). Treat as "no cache configured". - if (import.meta.env.DEV) { + if (import.meta.env?.DEV) { console.warn("[object-cache] backend unavailable:", error); } holder.backend = null; @@ -378,7 +378,7 @@ export async function cachedQuery(options: CachedQueryOptions): Promise const encoded = encode({ e: currentEpochs, v: value } satisfies CacheEnvelope); await backend.set(fullKey, encoded, ttl); } catch (error) { - if (import.meta.env.DEV) { + if (import.meta.env?.DEV) { console.warn("[object-cache] set failed:", error); } } @@ -388,6 +388,12 @@ export async function cachedQuery(options: CachedQueryOptions): Promise return value; } +/** Whether object-cache reads are active for the current request. */ +export async function isObjectCacheActive(): Promise { + const backend = await getBackend(); + return backend !== null && !shouldBypass(); +} + /** * Invalidate every cached value in `namespace` by bumping its epoch. * diff --git a/packages/core/src/seed/apply.ts b/packages/core/src/seed/apply.ts index f060a93db2..12befad3d4 100644 --- a/packages/core/src/seed/apply.ts +++ b/packages/core/src/seed/apply.ts @@ -346,6 +346,12 @@ export async function applySeed( } } } + + // Seeded/updated defs change which taxonomies exist — clear the + // isolate-wide defs + names caches so later reads in this isolate + // (e.g. an auto-seed triggered mid-request) reflect them immediately. + const { invalidateTaxonomyDefsCache } = await import("../taxonomies/index.js"); + invalidateTaxonomyDefsCache(); } // 6. Bylines diff --git a/packages/core/src/taxonomies/index.ts b/packages/core/src/taxonomies/index.ts index 66b3339997..0b0be90252 100644 --- a/packages/core/src/taxonomies/index.ts +++ b/packages/core/src/taxonomies/index.ts @@ -12,14 +12,16 @@ */ import { resolveLocale, resolveLocaleChain } from "../i18n/resolve.js"; -import { getDb } from "../loader.js"; +import { getDb, resetTaxonomyNamesCache } from "../loader.js"; import { cachedQuery, CacheNamespace, contentNamespace, invalidateTaxonomyObjectCache, + isObjectCacheActive, } from "../object-cache/index.js"; import { peekRequestCache, requestCached, setRequestCacheEntry } from "../request-cache.js"; +import { getRequestContext } from "../request-context.js"; import { chunks, SQL_BATCH_SIZE } from "../utils/chunks.js"; import { isMissingTableError } from "../utils/db-errors.js"; import type { TaxonomyDef, TaxonomyTerm, TaxonomyTermRow } from "./types.js"; @@ -28,35 +30,136 @@ export interface TaxonomyQueryOptions { locale?: string; } +/** Invalidate cached taxonomy term data and any content that hydrates terms. */ +export function invalidateTermCache(): void { + invalidateTaxonomyObjectCache(); +} + /** - * Invalidate cached taxonomy data in the distributed object cache (and any - * content that hydrates taxonomy terms). The legacy in-isolate term cache was - * removed, so this used to be a no-op; it now drives object-cache invalidation. + * Worker-isolate cache for taxonomy definitions, keyed by resolved locale. + * + * Taxonomy *definitions* (the "category"/"tag" taxonomies themselves, not + * their terms) are read on every public render that hydrates entry terms — + * `getAllTermsForEntries` → `getCollectionTaxonomyNames` → `getTaxonomyDefs` — + * but change extremely rarely: they're created via the admin API or applied + * from a seed, and there is no edit/delete-def path. Caching them across the + * isolate lifetime drops the per-render `SELECT * FROM _emdash_taxonomy_defs` + * to once-per-isolate. + * + * Stored on globalThis behind a Symbol key (same pattern as + * `settings/index.ts`) so the bundler duplicating this module across SSR + * chunks can't produce two independent caches. + * + * When the distributed object cache is configured, it is authoritative and this + * fallback is bypassed so cross-isolate epoch invalidation cannot be undercut + * by stale per-isolate data on an object-cache miss. + * + * **Isolated databases bypass the cache.** Playground / DO preview requests + * set `requestContext.dbIsIsolated`; they point at a divergent schema, so we + * skip both reading and writing the global holder and fall back to the + * per-request cache (same precedent as `getTaxonomyNames` / byline field defs). */ -export function invalidateTermCache(): void { +interface TaxonomyDefsHolder { + version: number; + /** locale key ("*" for "all locales") → { version it was fetched at, promise }. */ + cache: Map }>; +} + +const TAXONOMY_DEFS_CACHE_KEY = Symbol.for("emdash:taxonomy-defs"); +const taxonomyDefsStore = globalThis as Record; +const defsHolder: TaxonomyDefsHolder = + // eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis singleton pattern (see settings/index.ts) + (taxonomyDefsStore[TAXONOMY_DEFS_CACHE_KEY] as TaxonomyDefsHolder | undefined) ?? + (() => { + const h: TaxonomyDefsHolder = { version: 0, cache: new Map() }; + taxonomyDefsStore[TAXONOMY_DEFS_CACHE_KEY] = h; + return h; + })(); + +/** + * Invalidate the isolate-wide taxonomy-definitions cache (and the related + * loader taxonomy-names cache). Called from every taxonomy-def write path + * (`handleTaxonomyCreate`, seed application). Other isolates refresh on their + * next recycle — staleness bounded by isolate lifetime. + */ +export function invalidateTaxonomyDefsCache(): void { + defsHolder.version++; + defsHolder.cache.clear(); + resetTaxonomyNamesCache(); invalidateTaxonomyObjectCache(); } +/** + * Test/internal helper: clear the per-isolate taxonomy-defs cache. Useful for + * unit tests that insert defs directly and need to force a refetch without + * going through a write path. Production code should rely on + * `invalidateTaxonomyDefsCache()`. + */ +export function resetTaxonomyDefsCacheForTests(): void { + defsHolder.version++; + defsHolder.cache.clear(); +} + +/** + * Fetch taxonomy definitions straight from the database (no caching). + */ +async function fetchTaxonomyDefs(locale: string | undefined): Promise { + const db = await getDb(); + let query = db.selectFrom("_emdash_taxonomy_defs").selectAll(); + if (locale !== undefined) query = query.where("locale", "=", locale); + const rows = await query.execute(); + return rows.map(rowToTaxonomyDef); +} + +/** + * Resolve taxonomy defs through the isolate fallback cache, bypassing it for + * isolated databases. The returned promise is cached (not the resolved value) + * so concurrent cold-isolate readers share one in-flight query; a rejection + * evicts the entry so the next caller retries. + */ +function loadTaxonomyDefs(localeKey: string, locale: string | undefined): Promise { + if (getRequestContext()?.dbIsIsolated === true) { + return fetchTaxonomyDefs(locale); + } + const existing = defsHolder.cache.get(localeKey); + if (existing && existing.version === defsHolder.version) { + return existing.promise; + } + const version = defsHolder.version; + const promise = fetchTaxonomyDefs(locale).catch((error: unknown) => { + const current = defsHolder.cache.get(localeKey); + if (current && current.promise === promise) { + defsHolder.cache.delete(localeKey); + } + throw error; + }); + defsHolder.cache.set(localeKey, { version, promise }); + return promise; +} + /** * Get every taxonomy definition. Definitions are per-locale (one row per * locale inside the same translation_group) — by default we resolve to the * active locale. + * + * Two-tier cache: per-request via `requestCached` (so a single render that + * hydrates terms for several collections pays at most one call), then + * per-isolate via the global holder (so warm renders issue zero queries). + * The `requestCached` key is unchanged so `getTaxonomyDef`'s peek still hits. */ export async function getTaxonomyDefs(options: TaxonomyQueryOptions = {}): Promise { const locale = resolveLocale(options.locale); - return requestCached(`taxonomy-defs:${locale ?? "*"}`, () => - cachedQuery({ - namespace: CacheNamespace.TAXONOMIES, - key: `defs:${locale ?? "*"}`, - load: async () => { - const db = await getDb(); - let query = db.selectFrom("_emdash_taxonomy_defs").selectAll(); - if (locale !== undefined) query = query.where("locale", "=", locale); - const rows = await query.execute(); - return rows.map(rowToTaxonomyDef); - }, - }), - ); + const localeKey = locale ?? "*"; + return requestCached(`taxonomy-defs:${localeKey}`, async () => { + if (await isObjectCacheActive()) { + return cachedQuery({ + namespace: CacheNamespace.TAXONOMIES, + key: `defs:${localeKey}`, + load: () => fetchTaxonomyDefs(locale), + }); + } + return loadTaxonomyDefs(localeKey, locale); + }); } /** diff --git a/packages/core/tests/unit/taxonomies/defs-cache.test.ts b/packages/core/tests/unit/taxonomies/defs-cache.test.ts new file mode 100644 index 0000000000..2bc4116733 --- /dev/null +++ b/packages/core/tests/unit/taxonomies/defs-cache.test.ts @@ -0,0 +1,128 @@ +/** + * Isolate-wide taxonomy-definitions cache (perf: removes the per-render + * `SELECT * FROM _emdash_taxonomy_defs` on warm isolates). + * + * The cache lives on globalThis and is keyed by resolved locale. Because + * `requestCached` dedupes within a single request scope, we exercise the + * isolate cache by running each `getTaxonomyDefs()` call inside its own + * `runWithContext` scope (a fresh context object => a fresh per-request + * cache bucket), so a second call only avoids the DB if the *isolate* + * cache served it. + */ + +import Database from "better-sqlite3"; +import { Kysely, SqliteDialect } from "kysely"; +import { ulid } from "ulidx"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; + +import { runMigrations } from "../../../src/database/migrations/runner.js"; +import type { Database as EmDashDatabase } from "../../../src/database/types.js"; +import { runWithContext } from "../../../src/request-context.js"; +import { + getTaxonomyDefs, + invalidateTaxonomyDefsCache, + resetTaxonomyDefsCacheForTests, +} from "../../../src/taxonomies/index.js"; + +let queryCount = 0; + +function makeDb(): { db: Kysely; sqlite: Database.Database } { + const sqlite = new Database(":memory:"); + const db = new Kysely({ + dialect: new SqliteDialect({ database: sqlite }), + log(event) { + if (event.level === "query" && event.query.sql.includes("_emdash_taxonomy_defs")) { + queryCount += 1; + } + }, + }); + return { db, sqlite }; +} + +async function insertDef(db: Kysely, name: string): Promise { + await db + .insertInto("_emdash_taxonomy_defs") + .values({ + id: ulid(), + name, + label: name, + label_singular: null, + hierarchical: 0, + collections: JSON.stringify(["posts"]), + }) + .execute(); +} + +/** Run a getter in a fresh per-request scope with the test db as the ALS db. */ +function inScope( + db: Kysely, + fn: () => Promise, + opts?: { dbIsIsolated?: boolean }, +): Promise { + return runWithContext({ editMode: false, db, ...opts }, fn); +} + +describe("getTaxonomyDefs — isolate cache", () => { + let db: Kysely; + let sqlite: Database.Database; + + beforeEach(async () => { + ({ db, sqlite } = makeDb()); + await runMigrations(db); + // Holder lives on globalThis; reset so sibling tests don't leak the + // previous test's db/promise into this one. + resetTaxonomyDefsCacheForTests(); + await insertDef(db, "genre"); + queryCount = 0; + }); + + afterEach(async () => { + await db.destroy(); + sqlite.close(); + }); + + it("queries once per isolate, serving later requests from cache", async () => { + const first = await inScope(db, () => getTaxonomyDefs()); + expect(queryCount).toBe(1); + expect(first.map((d) => d.name)).toContain("genre"); + + // A separate request scope: per-request cache can't help, so a second + // query would fire unless the isolate cache served it. + const second = await inScope(db, () => getTaxonomyDefs()); + expect(queryCount).toBe(1); + expect(second.map((d) => d.name).toSorted()).toEqual(first.map((d) => d.name).toSorted()); + }); + + it("re-queries after invalidateTaxonomyDefsCache()", async () => { + await inScope(db, () => getTaxonomyDefs()); + expect(queryCount).toBe(1); + + invalidateTaxonomyDefsCache(); + + await inScope(db, () => getTaxonomyDefs()); + expect(queryCount).toBe(2); + }); + + it("reflects a newly inserted def only after invalidation (in-memory invalidation semantics)", async () => { + const before = await inScope(db, () => getTaxonomyDefs()); + expect(before.map((d) => d.name)).not.toContain("topic"); + + await insertDef(db, "topic"); + + // Still cached — stale read is expected without an explicit bump. + const stale = await inScope(db, () => getTaxonomyDefs()); + expect(stale.map((d) => d.name)).not.toContain("topic"); + + invalidateTaxonomyDefsCache(); + + const fresh = await inScope(db, () => getTaxonomyDefs()); + expect(fresh.map((d) => d.name)).toContain("topic"); + }); + + it("bypasses the isolate cache for isolated databases (playground / DO preview)", async () => { + await inScope(db, () => getTaxonomyDefs(), { dbIsIsolated: true }); + await inScope(db, () => getTaxonomyDefs(), { dbIsIsolated: true }); + // Never cached across requests => one query each. + expect(queryCount).toBe(2); + }); +}); diff --git a/packages/core/tests/utils/test-db.ts b/packages/core/tests/utils/test-db.ts index 0fe7c69806..8fed1ca3f7 100644 --- a/packages/core/tests/utils/test-db.ts +++ b/packages/core/tests/utils/test-db.ts @@ -9,6 +9,22 @@ import { getMigrationStatus, runMigrations } from "../../src/database/migrations import type { MigrationStatus } from "../../src/database/migrations/runner.js"; import type { Database as DatabaseSchema } from "../../src/database/types.js"; import { SchemaRegistry } from "../../src/schema/registry.js"; +import { resetTaxonomyDefsCacheForTests } from "../../src/taxonomies/index.js"; + +/** + * Clear the isolate-wide, schema-derived caches that live on globalThis and + * therefore persist across tests within a vitest worker. A freshly created + * test database must never be served another database's cached taxonomy + * definitions, so we reset every time a new test DB is created. + * + * Note: we deliberately don't import from `../../src/loader.js` here — several + * test files `vi.mock` that module to stub `getDb`, and pulling another export + * through this shared util would blow up under those mocks. The loader's own + * taxonomy-names cache predates this util and is reset via its public path. + */ +function resetSchemaCachesForTests(): void { + resetTaxonomyDefsCacheForTests(); +} // --------------------------------------------------------------------------- // Environment @@ -33,6 +49,7 @@ export const hasPgTestDatabase = PG_CONNECTION_STRING.length > 0; * Create an in-memory SQLite database for testing */ export function createTestDatabase(): Kysely { + resetSchemaCachesForTests(); const sqlite = new Database(":memory:"); return new Kysely({ @@ -246,6 +263,7 @@ export interface PgTestContext { * Call `teardownTestPostgresDatabase()` in afterEach to drop the schema. */ export async function createTestPostgresDatabase(): Promise { + resetSchemaCachesForTests(); const connectionString = await getWorkerConnectionString(); const pool = await getSharedPool(); const schemaName = uniqueSchemaName(); diff --git a/scripts/query-counts.queries.d1.json b/scripts/query-counts.queries.d1.json index e0fd40c6fd..51a7c53d38 100644 --- a/scripts/query-counts.queries.d1.json +++ b/scripts/query-counts.queries.d1.json @@ -39,7 +39,7 @@ "select * from \"taxonomies\" where \"name\" = ? and \"slug\" = ? order by \"locale\" asc": 1, "select * from \"taxonomies\" where \"parent_id\" = ? and \"locale\" = ? order by \"label\" asc": 1, "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_pages\".id AND t.locale = \"ec_pages\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_pages\".id AND b.locale = \"ec_pages\".locale) AS \"_emdash_bylines\" FROM \"ec_pages\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) ORDER BY \"created_at\" DESC, \"id\" DESC": 1, - "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.locale = \"ec_posts\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_posts\".id AND b.locale = \"ec_posts\".locale) AS \"_emdash_bylines\" FROM \"ec_posts\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) AND EXISTS ( SELECT 1 FROM content_taxonomies ct INNER JOIN taxonomies t ON t.id = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.name = ? AND t.slug in (...) ) ORDER BY \"published_at\" DESC, \"id\" DESC": 1, + "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.locale = \"ec_posts\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_posts\".id AND b.locale = \"ec_posts\".locale) AS \"_emdash_bylines\" FROM \"ec_posts\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) AND EXISTS ( SELECT 1 FROM content_taxonomies ct INNER JOIN taxonomies t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.name = ? AND t.slug in (...) ) ORDER BY \"published_at\" DESC, \"id\" DESC": 1, "select count(\"entry_id\") as \"count\" from \"content_taxonomies\" where \"taxonomy_id\" = ?": 1, "select count(*) as \"count\" from \"_emdash_collections\"": 1, "SELECT COUNT(*) as count FROM \"_emdash_migrations\"": 1, @@ -53,7 +53,7 @@ "select * from \"taxonomies\" where \"name\" = ? and \"slug\" = ? order by \"locale\" asc": 1, "select * from \"taxonomies\" where \"parent_id\" = ? and \"locale\" = ? order by \"label\" asc": 1, "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_pages\".id AND t.locale = \"ec_pages\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_pages\".id AND b.locale = \"ec_pages\".locale) AS \"_emdash_bylines\" FROM \"ec_pages\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) ORDER BY \"created_at\" DESC, \"id\" DESC": 1, - "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.locale = \"ec_posts\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_posts\".id AND b.locale = \"ec_posts\".locale) AS \"_emdash_bylines\" FROM \"ec_posts\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) AND EXISTS ( SELECT 1 FROM content_taxonomies ct INNER JOIN taxonomies t ON t.id = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.name = ? AND t.slug in (...) ) ORDER BY \"published_at\" DESC, \"id\" DESC": 1, + "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.locale = \"ec_posts\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_posts\".id AND b.locale = \"ec_posts\".locale) AS \"_emdash_bylines\" FROM \"ec_posts\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) AND EXISTS ( SELECT 1 FROM content_taxonomies ct INNER JOIN taxonomies t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.name = ? AND t.slug in (...) ) ORDER BY \"published_at\" DESC, \"id\" DESC": 1, "select count(\"entry_id\") as \"count\" from \"content_taxonomies\" where \"taxonomy_id\" = ?": 1 }, "GET /contributors (cold)": { @@ -263,7 +263,7 @@ "select * from \"taxonomies\" where \"name\" = ? and \"slug\" = ? order by \"locale\" asc": 1, "select * from \"taxonomies\" where \"parent_id\" = ? and \"locale\" = ? order by \"label\" asc": 1, "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_pages\".id AND t.locale = \"ec_pages\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_pages\".id AND b.locale = \"ec_pages\".locale) AS \"_emdash_bylines\" FROM \"ec_pages\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) ORDER BY \"created_at\" DESC, \"id\" DESC": 1, - "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.locale = \"ec_posts\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_posts\".id AND b.locale = \"ec_posts\".locale) AS \"_emdash_bylines\" FROM \"ec_posts\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) AND EXISTS ( SELECT 1 FROM content_taxonomies ct INNER JOIN taxonomies t ON t.id = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.name = ? AND t.slug in (...) ) ORDER BY \"published_at\" DESC, \"id\" DESC": 1, + "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.locale = \"ec_posts\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_posts\".id AND b.locale = \"ec_posts\".locale) AS \"_emdash_bylines\" FROM \"ec_posts\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) AND EXISTS ( SELECT 1 FROM content_taxonomies ct INNER JOIN taxonomies t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.name = ? AND t.slug in (...) ) ORDER BY \"published_at\" DESC, \"id\" DESC": 1, "select count(\"entry_id\") as \"count\" from \"content_taxonomies\" where \"taxonomy_id\" = ?": 1, "select count(*) as \"count\" from \"_emdash_collections\"": 1, "SELECT COUNT(*) as count FROM \"_emdash_migrations\"": 1, @@ -277,7 +277,7 @@ "select * from \"taxonomies\" where \"name\" = ? and \"slug\" = ? order by \"locale\" asc": 1, "select * from \"taxonomies\" where \"parent_id\" = ? and \"locale\" = ? order by \"label\" asc": 1, "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_pages\".id AND t.locale = \"ec_pages\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_pages\".id AND b.locale = \"ec_pages\".locale) AS \"_emdash_bylines\" FROM \"ec_pages\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) ORDER BY \"created_at\" DESC, \"id\" DESC": 1, - "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.locale = \"ec_posts\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_posts\".id AND b.locale = \"ec_posts\".locale) AS \"_emdash_bylines\" FROM \"ec_posts\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) AND EXISTS ( SELECT 1 FROM content_taxonomies ct INNER JOIN taxonomies t ON t.id = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.name = ? AND t.slug in (...) ) ORDER BY \"published_at\" DESC, \"id\" DESC": 1, + "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.locale = \"ec_posts\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_posts\".id AND b.locale = \"ec_posts\".locale) AS \"_emdash_bylines\" FROM \"ec_posts\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) AND EXISTS ( SELECT 1 FROM content_taxonomies ct INNER JOIN taxonomies t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.name = ? AND t.slug in (...) ) ORDER BY \"published_at\" DESC, \"id\" DESC": 1, "select count(\"entry_id\") as \"count\" from \"content_taxonomies\" where \"taxonomy_id\" = ?": 1 } } diff --git a/scripts/query-counts.queries.sqlite.json b/scripts/query-counts.queries.sqlite.json index 875a298a10..85601ab7b6 100644 --- a/scripts/query-counts.queries.sqlite.json +++ b/scripts/query-counts.queries.sqlite.json @@ -24,7 +24,7 @@ "select * from \"taxonomies\" where \"name\" = ? and \"slug\" = ? order by \"locale\" asc": 1, "select * from \"taxonomies\" where \"parent_id\" = ? and \"locale\" = ? order by \"label\" asc": 1, "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_pages\".id AND t.locale = \"ec_pages\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_pages\".id AND b.locale = \"ec_pages\".locale) AS \"_emdash_bylines\" FROM \"ec_pages\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) ORDER BY \"created_at\" DESC, \"id\" DESC": 1, - "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.locale = \"ec_posts\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_posts\".id AND b.locale = \"ec_posts\".locale) AS \"_emdash_bylines\" FROM \"ec_posts\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) AND EXISTS ( SELECT 1 FROM content_taxonomies ct INNER JOIN taxonomies t ON t.id = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.name = ? AND t.slug in (...) ) ORDER BY \"published_at\" DESC, \"id\" DESC": 1, + "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.locale = \"ec_posts\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_posts\".id AND b.locale = \"ec_posts\".locale) AS \"_emdash_bylines\" FROM \"ec_posts\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) AND EXISTS ( SELECT 1 FROM content_taxonomies ct INNER JOIN taxonomies t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.name = ? AND t.slug in (...) ) ORDER BY \"published_at\" DESC, \"id\" DESC": 1, "select count(\"entry_id\") as \"count\" from \"content_taxonomies\" where \"taxonomy_id\" = ?": 1 }, "GET /category/development (warm)": { @@ -35,7 +35,7 @@ "select * from \"taxonomies\" where \"name\" = ? and \"slug\" = ? order by \"locale\" asc": 1, "select * from \"taxonomies\" where \"parent_id\" = ? and \"locale\" = ? order by \"label\" asc": 1, "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_pages\".id AND t.locale = \"ec_pages\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_pages\".id AND b.locale = \"ec_pages\".locale) AS \"_emdash_bylines\" FROM \"ec_pages\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) ORDER BY \"created_at\" DESC, \"id\" DESC": 1, - "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.locale = \"ec_posts\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_posts\".id AND b.locale = \"ec_posts\".locale) AS \"_emdash_bylines\" FROM \"ec_posts\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) AND EXISTS ( SELECT 1 FROM content_taxonomies ct INNER JOIN taxonomies t ON t.id = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.name = ? AND t.slug in (...) ) ORDER BY \"published_at\" DESC, \"id\" DESC": 1, + "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.locale = \"ec_posts\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_posts\".id AND b.locale = \"ec_posts\".locale) AS \"_emdash_bylines\" FROM \"ec_posts\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) AND EXISTS ( SELECT 1 FROM content_taxonomies ct INNER JOIN taxonomies t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.name = ? AND t.slug in (...) ) ORDER BY \"published_at\" DESC, \"id\" DESC": 1, "select count(\"entry_id\") as \"count\" from \"content_taxonomies\" where \"taxonomy_id\" = ?": 1 }, "GET /contributors (cold)": { @@ -176,7 +176,7 @@ "select * from \"taxonomies\" where \"name\" = ? and \"slug\" = ? order by \"locale\" asc": 1, "select * from \"taxonomies\" where \"parent_id\" = ? and \"locale\" = ? order by \"label\" asc": 1, "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_pages\".id AND t.locale = \"ec_pages\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_pages\".id AND b.locale = \"ec_pages\".locale) AS \"_emdash_bylines\" FROM \"ec_pages\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) ORDER BY \"created_at\" DESC, \"id\" DESC": 1, - "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.locale = \"ec_posts\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_posts\".id AND b.locale = \"ec_posts\".locale) AS \"_emdash_bylines\" FROM \"ec_posts\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) AND EXISTS ( SELECT 1 FROM content_taxonomies ct INNER JOIN taxonomies t ON t.id = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.name = ? AND t.slug in (...) ) ORDER BY \"published_at\" DESC, \"id\" DESC": 1, + "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.locale = \"ec_posts\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_posts\".id AND b.locale = \"ec_posts\".locale) AS \"_emdash_bylines\" FROM \"ec_posts\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) AND EXISTS ( SELECT 1 FROM content_taxonomies ct INNER JOIN taxonomies t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.name = ? AND t.slug in (...) ) ORDER BY \"published_at\" DESC, \"id\" DESC": 1, "select count(\"entry_id\") as \"count\" from \"content_taxonomies\" where \"taxonomy_id\" = ?": 1 }, "GET /tag/webdev (warm)": { @@ -187,7 +187,7 @@ "select * from \"taxonomies\" where \"name\" = ? and \"slug\" = ? order by \"locale\" asc": 1, "select * from \"taxonomies\" where \"parent_id\" = ? and \"locale\" = ? order by \"label\" asc": 1, "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_pages\".id AND t.locale = \"ec_pages\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_pages\".id AND b.locale = \"ec_pages\".locale) AS \"_emdash_bylines\" FROM \"ec_pages\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) ORDER BY \"created_at\" DESC, \"id\" DESC": 1, - "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.locale = \"ec_posts\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_posts\".id AND b.locale = \"ec_posts\".locale) AS \"_emdash_bylines\" FROM \"ec_posts\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) AND EXISTS ( SELECT 1 FROM content_taxonomies ct INNER JOIN taxonomies t ON t.id = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.name = ? AND t.slug in (...) ) ORDER BY \"published_at\" DESC, \"id\" DESC": 1, + "SELECT *, (SELECT json_group_array(json_object('id', t.id, 'name', t.name, 'slug', t.slug, 'label', t.label, 'parent_id', t.parent_id, 'locale', t.locale, 'translation_group', t.translation_group)) FROM \"content_taxonomies\" AS ct JOIN \"taxonomies\" AS t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.locale = \"ec_posts\".locale) AS \"_emdash_terms\", (SELECT json_group_array(json_object('roleLabel', cb.role_label, 'sortOrder', cb.sort_order, 'byline', json_object('id', b.id, 'slug', b.slug, 'displayName', b.display_name, 'bio', b.bio, 'avatarMediaId', b.avatar_media_id, 'avatarStorageKey', m.storage_key, 'avatarAlt', m.alt, 'avatarBlurhash', m.blurhash, 'avatarDominantColor', m.dominant_color, 'websiteUrl', b.website_url, 'userId', b.user_id, 'isGuest', b.is_guest, 'createdAt', b.created_at, 'updatedAt', b.updated_at, 'locale', b.locale, 'translationGroup', b.translation_group))) FROM \"_emdash_content_bylines\" AS cb JOIN \"_emdash_bylines\" AS b ON b.translation_group = cb.byline_id LEFT JOIN \"media\" AS m ON m.id = b.avatar_media_id WHERE cb.collection_slug = ? AND cb.content_id = \"ec_posts\".id AND b.locale = \"ec_posts\".locale) AS \"_emdash_bylines\" FROM \"ec_posts\" WHERE deleted_at IS NULL AND (\"status\" = 'published' OR (\"status\" = 'scheduled' AND \"scheduled_at\" <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))) AND EXISTS ( SELECT 1 FROM content_taxonomies ct INNER JOIN taxonomies t ON t.translation_group = ct.taxonomy_id WHERE ct.collection = ? AND ct.entry_id = \"ec_posts\".id AND t.name = ? AND t.slug in (...) ) ORDER BY \"published_at\" DESC, \"id\" DESC": 1, "select count(\"entry_id\") as \"count\" from \"content_taxonomies\" where \"taxonomy_id\" = ?": 1 } }