Skip to content

refactor: revert zod to Elysia Types for type safety, type soundness - #163

Merged
DaxServer merged 3 commits into
mainfrom
elysia-types
Sep 27, 2025
Merged

DaxServer merged 3 commits into
mainfrom
elysia-types

Conversation

@DaxServer

Copy link
Copy Markdown
Owner

No description provided.

@coderabbitai

coderabbitai Bot commented Sep 27, 2025 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added Wikibase endpoint to fetch CSRF tokens.
    • Enhanced API responses and validation across project and Wikibase routes.
  • Refactor

    • Migrated backend validation from Zod to Elysia schemas, standardizing error formats.
    • Create-schema request no longer includes projectId in the payload.
  • Chores

    • Removed Zod dependency; upgraded Elysia; locked @primeuix/themes to 1.2.5.
  • Documentation

    • Updated guidelines to reflect Elysia-based validation and type inference.
  • Tests

    • Updated API tests to align with new validation, error shapes, and payload changes.

Walkthrough

Migrates backend validation from Zod to Elysia across APIs/types/tests, inlines route schemas, removes zod/OpenAPI mapping, adds a Wikibase CSRF endpoint, introduces closeDb(), updates frontend create-schema payload, pins a frontend theme, and updates docs/prompts.

Changes

Cohort / File(s) Summary
Dependency updates
package.json, backend/package.json, frontend/package.json
Remove zod from workspace/backend, bump elysia to 1.4.8, pin @primeuix/themes to 1.2.5.
Backend: global schema migration (Zod → Elysia)
backend/src/types/error-schemas.ts, backend/src/types/wikibase-schema.ts, backend/src/api/project/schemas.ts, backend/src/api/_meta_projects.ts
Replace Zod schemas with Elysia t.* equivalents; update exported type aliases to use .static; introduce UUID regex pattern and adjust various public types.
Backend: Project routes & import
backend/src/api/project/index.ts, backend/src/api/project/import.ts
Move route schemas inline using Elysia t; add/expand response typings; remove exported ProjectImportSchema and ProjectImportFileAltSchema.
Backend: Wikibase types, routes & schemas
backend/src/api/project/project.wikibase.ts, backend/src/api/wikibase/schemas.ts, backend/src/api/wikibase/index.ts
Convert Wikibase/Zod schemas to Elysia t.*; expose new public types (e.g., WikibaseCreateSchema), expand route typings/metadata, and add POST /:instanceId/csrf-token.
Backend: OpenAPI and boot
backend/src/index.ts
Remove Zod import and zod→JSON schema mapping for OpenAPI generation.
Backend: Database lifecycle
backend/src/plugins/database.ts
Add exported closeDb() and call it from initializeDb() for cleanup.
Frontend: schema API & errors
frontend/src/features/wikibase-schema/composables/useSchemaApi.ts, frontend/src/shared/types/client-errors.ts
createSchema POST payload no longer includes projectId; rename createFrontendError parameter to _message.
Docs/Guides & prompts
guidelines/reference/ELYSIA_EDEN.md, prompts/Backend.md, prompts/Frontend.md
Update guidance from Zod to Elysia-based validation and type inference.
Tests adjusted
backend/tests/api/project/project.create.test.ts, backend/tests/api/project/project.delete.test.ts, backend/tests/api/project/project.get.test.ts, backend/tests/api/project/project.wikibase.test.ts, frontend/src/features/wikibase-schema/composables/__tests__/useSchemaApi.test.ts
Align assertions to new Elysia validation/error shape (schema-driven errors), import UUID_REGEX, update Wikibase test helpers/types and API composition; remove one deleted Wikibase entities test file.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Frontend
  participant Backend as "Backend Wikibase API"
  participant Wikibase as "Wikibase Service"

  rect rgb(242,248,255)
  note over Frontend,Backend: New CSRF token flow
  Frontend->>Backend: POST /wikibase/:instanceId/csrf-token { oauth? }
  Backend->>Wikibase: request tokens (action=query&meta=tokens&type=csrf)
  Wikibase-->>Backend: { query: { tokens: { csrftoken } } }
  Backend-->>Frontend: 200 { data: csrftoken } or 4xx/5xx ApiErrors
  end
Loading
sequenceDiagram
  autonumber
  actor Client
  participant API as "Backend API (projects/wikibase)"
  note over Client,API: Inline Elysia validation used in routes
  Client->>API: POST /projects { body }
  API->>API: Validate body using Elysia t.Object
  API-->>Client: 201 { data } or 4xx/422 ApiErrors (schema-shaped)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The pull request description is empty and does not provide any details about the changes made, leaving reviewers without context or an overview of the migration from Zod to Elysia schemas. Please add a brief description summarizing the main objectives of this refactor, such as migrating schema definitions from Zod to Elysia types and highlighting any key impacts on the API surface and tests.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title explicitly refers to the refactoring change from Zod to Elysia types, which is the primary change affecting schema definitions and runtime validations throughout the codebase, making it a clear and focused summary of the main change.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0134491 and 5ea3cfb.

📒 Files selected for processing (3)
  • backend/src/api/wikibase/schemas.ts (1 hunks)
  • backend/tests/api/wikibase/entities.test.ts (0 hunks)
  • frontend/src/features/wikibase-schema/composables/__tests__/useSchemaApi.test.ts (0 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (10)
backend/src/plugins/database.ts (2)

65-73: Add onStop hook to close the database

Ensure clean shutdown in dev/test and production.

Apply:

 export const databasePlugin = new Elysia({ name: 'database', seed: 'database-plugin' })
   .onStart(async () => {
     console.log('Initializing database...')
     const dbPath = process.env.DB_PATH || path.join(Bun.main, '../../dataforge.db')
     await initializeDb(dbPath)
     console.log('Database initialized successfully')
   })
+  .onStop(async () => {
+    await closeDb()
+  })
   .decorate('db', getDb)

57-63: Make closeDb synchronous

closeDb only uses synchronous calls (connection.closeSync()) and doesn’t await anything—remove the async keyword and Promise return type. There is no DuckDBInstance.close() method in the current @duckdb/node-api so instances are only cleaned up via GC (github.com)

backend/tests/api/project/project.get.test.ts (1)

66-69: Fix: Bun.file() has no delete(); use fs.rm

This is likely causing typecheck failure. Prefer Node fs per your docs (“except fs for file operations”).

Apply:

+import { rm, mkdir } from 'node:fs/promises'
@@
   const cleanupTestData = async () => {
@@
-    const tempFilePath = './temp/test-data.json'
-    const tempFile = Bun.file(tempFilePath)
-    await tempFile.delete()
+    const tempFilePath = './temp/test-data.json'
+    await rm(tempFilePath, { force: true })
   }
@@
   const importTestData = async () => {
     const tempFilePath = './temp/test-data.json'
-    await Bun.write(tempFilePath, JSON.stringify(TEST_DATA))
+    await mkdir('./temp', { recursive: true })
+    await Bun.write(tempFilePath, JSON.stringify(TEST_DATA))
backend/src/api/project/index.ts (4)

165-174: On table-exists, clean up created project and return 409

Avoid leaving orphan metadata and use Conflict.

-      if (tableExistsReader.getRows().length > 0) {
-        // await cleanupProject(db, project.id, tempFilePath)
-
-        return status(
-          500,
-          ApiErrorHandler.dataImportErrorWithData(
-            `Table with name 'project_${project.id}' already exists`,
-          ),
-        )
-      }
+      if (tableExistsReader.getRows().length > 0) {
+        await cleanupProject(db, project.id, tempFilePath)
+        return status(
+          409,
+          ApiErrorHandler.tableExistsErrorWithData(`project_${project.id}`),
+        )
+      }

127-137: Ensure temp directory exists before writing/deleting

Bun.write doesn’t create parent dirs; mkdir first. Also deletion should tolerate missing files.

+import { mkdir } from 'node:fs/promises'
+import { resolve } from 'node:path'
@@
-      const tempFilePath = `./temp/${tempFileName}`
+      const tempDir = resolve('./temp')
+      await mkdir(tempDir, { recursive: true })
+      const tempFilePath = `${tempDir}/${tempFileName}`
@@
-      await Bun.file(tempFilePath).delete()
+      try { await Bun.file(tempFilePath).delete() } catch {}

And mirror the same mkdir logic in "/:projectId/import/file" before Bun.write (see below).

Also applies to: 220-222


402-409: Return null body to match 201: t.Null()

Response object breaks the declared schema.

-        // @ts-expect-error ToDo: Fix
-        return status(201, new Response(null))
+        return status(201, null)

371-387: Restrict filePath to the temp directory (path traversal/data exfiltration risk)

User-controlled absolute/relative paths can read arbitrary files. Restrict to ./temp.

-    async ({ db, params: { projectId }, body: { filePath }, status }) => {
-      const fileExists = await Bun.file(filePath).exists()
+    async ({ db, params: { projectId }, body: { filePath }, status }) => {
+      const { resolve, sep } = await import('node:path')
+      const baseDir = resolve('./temp') + sep
+      const resolved = resolve(filePath)
+      if (!resolved.startsWith(baseDir)) {
+        return status(400, ApiErrorHandler.validationError('File path must be within temp directory'))
+      }
+      const fileExists = await Bun.file(resolved).exists()
       if (!fileExists) {
-        return status(400, ApiErrorHandler.fileNotFoundError(filePath))
+        return status(400, ApiErrorHandler.fileNotFoundError(resolved))
       }
@@
-        await db().run(`CREATE TABLE "project_${projectId}" AS SELECT * FROM read_json_auto(?)`, [
-          filePath,
+        await db().run(`CREATE TABLE "project_${projectId}" AS SELECT * FROM read_json_auto(?)`, [
+          resolved,
         ])
backend/src/api/wikibase/index.ts (2)

96-109: Response shape mismatch on properties/search

Handler returns { data: results } but schema declares { data: { results, totalCount, hasMore, query } }.

-      const results = await wikibase.searchProperties(instanceId, q, {
+      const search = await wikibase.searchProperties(instanceId, q, {
         limit,
         offset,
         language,
         languageFallback,
       })
-
-      return { data: results }
+      const { results, totalCount, hasMore } = search
+      return { data: { results, totalCount, hasMore, query: q } }

Also applies to: 110-130


371-389: Response shape mismatch on items/search

Same wrapping issue as properties/search.

-      const results = await wikibase.searchItems(instanceId, q, {
+      const search = await wikibase.searchItems(instanceId, q, {
         limit,
         offset,
         language,
         languageFallback,
       })
-
-      return { data: results }
+      const { results, totalCount, hasMore } = search
+      return { data: { results, totalCount, hasMore, query: q } }

Also applies to: 391-423

backend/src/api/project/project.wikibase.ts (1)

14-151: Rename schema constants to unblock the typecheck

The typecheck failure comes from reusing the same identifier for both the schema value and its exported static type (const TransformationRule + export type TransformationRule = typeof TransformationRule.static). In TypeScript the value and type namespaces collide here, so typeof TransformationRule resolves to the type alias you are defining, producing a duplicate identifier/self-reference error. This repeats for every schema in the file (e.g., ColumnMapping, PropertyReference, WikibaseSchemaResponse, etc.), so the whole file fails to compile. Please rename the schema values (or the exported types) so they differ—for example:

-const TransformationRule = t.Object({
+const TransformationRuleSchema = t.Object({
   type: t.Union([t.Literal('constant'), t.Literal('expression'), t.Literal('lookup')]),
   value: t.String(),
   parameters: t.Optional(t.Record(t.String(), t.Any())),
 })
-export type TransformationRule = typeof TransformationRule.static
+export type TransformationRule = typeof TransformationRuleSchema.static

and then update downstream references to use the renamed schema constant. Apply the same pattern to every other schema/type pair in this file to restore type safety and keep the pipeline green.

🧹 Nitpick comments (16)
backend/src/plugins/database.ts (1)

50-55: getDb throws but guidelines say “NEVER throw”

prompts/Backend.md forbids throwing; align by either guaranteeing initialization before access (derive/decorate a ready connection) or returning a Result-like shape.

As per coding guidelines.

prompts/Backend.md (1)

63-63: Docs vs code: “NEVER throw” conflicts with getDb implementation

Either relax the rule here or adapt getDb to avoid throws to keep docs truthful.

As per coding guidelines.

backend/tests/api/project/project.get.test.ts (2)

157-166: Avoid brittle assertions on validator internals

Hard-coding type: 53 ties you to validator internals. Assert stable fields only.

Apply:

-      {
-        errors: [],
-        message: `Expected string to match '${UUID_REGEX}'`,
-        path: '/projectId',
-        schema: {
-          pattern: UUID_REGEX,
-          type: 'string',
-        },
-        type: 53,
-        value: 'invalid-uuid',
-      },
+      expect.objectContaining({
+        message: expect.stringContaining(UUID_REGEX),
+        path: '/projectId',
+        schema: expect.objectContaining({
+          pattern: UUID_REGEX,
+          type: 'string',
+        }),
+        value: INVALID_UUID,
+      }),

39-47: Optional: stabilize meta expectations

Consider asserting only keys/shape for meta to reduce coupling if pagination metadata evolves.

frontend/src/shared/types/client-errors.ts (1)

35-41: Parameter rename to _message is noisy for public API

Underscore suggests “unused”. Keep message as the param name; map directly.

Apply:

-export const createFrontendError = (
-  code: FrontendErrorCode,
-  _message: string,
-  details?: any[],
-): ExtendedError => ({
-  code,
-  message: _message,
-  details,
-})
+export const createFrontendError = (
+  code: FrontendErrorCode,
+  message: string,
+  details?: any[],
+): ExtendedError => ({
+  code,
+  message,
+  details,
+})
backend/tests/api/project/project.delete.test.ts (1)

75-84: Reduce coupling to validator internals

Same concern as in project.get.test.ts—avoid asserting numeric type code.

Apply:

-      {
-        errors: [],
-        message: `Expected string to match '${UUID_REGEX}'`,
-        path: '/projectId',
-        schema: {
-          pattern: UUID_REGEX,
-          type: 'string',
-        },
-        type: 53,
-        value: 'invalid-uuid-format',
-      },
+      expect.objectContaining({
+        message: expect.stringContaining(UUID_REGEX),
+        path: '/projectId',
+        schema: expect.objectContaining({
+          pattern: UUID_REGEX,
+          type: 'string',
+        }),
+        value: 'invalid-uuid-format',
+      }),
backend/tests/api/project/project.create.test.ts (2)

51-61: Make validation assertions resilient (avoid brittle numeric codes/messages).

type: 54 and the exact message string are internal to TypeBox/Elysia and can change between versions. Assert on stable fields (path, schema constraints, optional value) using partial matchers.

Apply this diff:

-      expect(error).toHaveProperty('value', [
-        {
-          errors: [],
-          message: 'Expected string',
-          path: '/name',
-          schema: {
-            error: 'Project name is required and must be at least 1 character long',
-            minLength: 1,
-            maxLength: 255,
-            type: 'string',
-          },
-          type: 54,
-        },
-      ])
+      expect(error?.value).toEqual(
+        expect.arrayContaining([
+          expect.objectContaining({
+            path: '/name',
+            schema: expect.objectContaining({
+              minLength: 1,
+              maxLength: 255,
+              type: 'string',
+            }),
+          }),
+        ]),
+      )

75-85: Same here: assert on stable shape, not internal codes.

Keep value: '' if you want, but drop hard-coded type: 52 and exact message.

Apply this diff:

-        {
-          errors: [],
-          message: 'Expected string length greater or equal to 1',
-          path: '/name',
-          schema: {
-            error: 'Project name is required and must be at least 1 character long',
-            minLength: 1,
-            maxLength: 255,
-            type: 'string',
-          },
-          type: 52,
-          value: '',
-        },
+        expect.objectContaining({
+          path: '/name',
+          value: '',
+          schema: expect.objectContaining({
+            minLength: 1,
+            maxLength: 255,
+            type: 'string',
+          }),
+        }),
backend/src/api/_meta_projects.ts (2)

7-23: Schema looks good; consider stricter types for IDs and timestamps.

Optional but recommended:

  • Constrain id to UUID with t.String({ format: 'uuid' }) if you’re using OpenAPI/clients.
  • Constrain dates with t.String({ format: 'date-time' }) if they’re ISO; if space‑separated, keep as is.

I can provide a diff if you confirm the formats you want to expose.


66-74: JSON.parse can throw; guard to prevent 500s from malformed JSON columns.

Wrap parse in try/catch so a single bad row doesn’t break the endpoint.

Apply:

-        jsonColumns.forEach((column) => {
-          if (newRow[column] && typeof newRow[column] === 'string') {
-            newRow[column] = JSON.parse(newRow[column] as string)
-          }
-        })
+        jsonColumns.forEach((column) => {
+          const v = newRow[column]
+          if (typeof v === 'string') {
+            try {
+              newRow[column] = JSON.parse(v)
+            } catch {
+              // leave as-is if parsing fails
+            }
+          }
+        })
backend/src/api/wikibase/schemas.ts (1)

53-76: Bound pagination inputs to protect the service

Cap limit and validate offset to avoid heavy scans.

-  limit: t.Number({
-    default: 10,
-    description: 'Maximum number of results',
-  }),
-  offset: t.Number({
-    default: 0,
-    description: 'Offset for pagination',
-  }),
+  limit: t.Number({
+    default: 10,
+    min: 1,
+    max: 100,
+    description: 'Maximum number of results',
+  }),
+  offset: t.Number({
+    default: 0,
+    min: 0,
+    description: 'Offset for pagination',
+  }),
backend/src/api/wikibase/index.ts (1)

241-266: Validate property route: context should be optional; otherwise callers must always send it

If intentional, ignore. Otherwise, make context optional.

-      body: t.Object({
+      body: t.Object({
         propertyId: PropertyId,
         value: t.Any({
           description: 'Value to validate against property constraints',
         }),
-        context: t.Object({
+        context: t.Optional(t.Object({
           entityId: t.Optional(
             t.String({
               description: 'Entity ID for context',
             }),
           ),
           additionalProperties: t.Optional(
             t.Array(
               t.Object({
                 property: t.String(),
                 value: t.Any(),
               }),
             ),
           ),
-        }),
+        })),

Also applies to: 267-299

backend/tests/api/project/project.wikibase.test.ts (3)

33-47: Trim object churn in label builder.

Avoid repeated Object.assign and mutable let; build in one pass with a reducer.

-const deterministicLabels = (count = 2): Label => {
-  let labels: Label = {}
-  for (let i = 0; i < count; i++) {
-    const lang = deterministicLang(i)
-    labels = Object.assign({}, labels, {
-      [lang]: {
-        columnName: deterministicString('Label'),
-        dataType: 'monolingualtext',
-      },
-    } as Label)
-  }
-  return labels
-}
+const deterministicLabels = (count = 2): Label =>
+  Array.from({ length: count }, (_, i) => deterministicLang(i)).reduce<Label>((acc, lang) => {
+    acc[lang] = { columnName: deterministicString('Label'), dataType: 'monolingualtext' }
+    return acc
+  }, {} as Label)

49-63: Mirror the same simplification for descriptions.

-const deterministicDescriptions = (count = 2): Label => {
-  let descriptions: Label = {}
-  for (let i = 0; i < count; i++) {
-    const lang = deterministicLang(i)
-    descriptions = Object.assign({}, descriptions, {
-      [lang]: {
-        columnName: deterministicString('Description'),
-        dataType: 'monolingualtext',
-      },
-    })
-  }
-  return descriptions
-}
+const deterministicDescriptions = (count = 2): Label =>
+  Array.from({ length: count }, (_, i) => deterministicLang(i)).reduce<Label>((acc, lang) => {
+    acc[lang] = { columnName: deterministicString('Description'), dataType: 'monolingualtext' }
+    return acc
+  }, {} as Label)

65-81: And for aliases, build immutably.

-const deterministicAliases = (count = 2): Alias => {
-  let aliases: Alias = {}
-  for (let i = 0; i < count; i++) {
-    const lang = deterministicLang(i)
-    aliases = Object.assign({}, aliases, {
-      [lang]: [
-        {
-          columnName: deterministicString('Alias'),
-          dataType: 'monolingualtext',
-        },
-      ],
-    })
-  }
-  return aliases
-}
+const deterministicAliases = (count = 2): Alias =>
+  Array.from({ length: count }, (_, i) => deterministicLang(i)).reduce<Alias>((acc, lang) => {
+    acc[lang] = [{ columnName: deterministicString('Alias'), dataType: 'monolingualtext' }]
+    return acc
+  }, {} as Alias)
backend/src/api/project/schemas.ts (1)

22-25: Prefer integer defaults for pagination (optional).

If offsets must be integers, consider t.Integer({ default: 0 }) and t.Integer({ default: 25 }) for stricter validation.

-export const PaginationQuery = t.Object({
-  offset: t.Number({ default: 0 }),
-  limit: t.Number({ default: 25 }),
-})
+export const PaginationQuery = t.Object({
+  offset: t.Integer({ default: 0 }),
+  limit: t.Integer({ default: 25 }),
+})
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 163eb3c and 2423772.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (22)
  • backend/package.json (1 hunks)
  • backend/src/api/_meta_projects.ts (2 hunks)
  • backend/src/api/project/import.ts (0 hunks)
  • backend/src/api/project/index.ts (8 hunks)
  • backend/src/api/project/project.wikibase.ts (7 hunks)
  • backend/src/api/project/schemas.ts (1 hunks)
  • backend/src/api/wikibase/index.ts (10 hunks)
  • backend/src/api/wikibase/schemas.ts (1 hunks)
  • backend/src/index.ts (0 hunks)
  • backend/src/plugins/database.ts (1 hunks)
  • backend/src/types/error-schemas.ts (1 hunks)
  • backend/src/types/wikibase-schema.ts (1 hunks)
  • backend/tests/api/project/project.create.test.ts (2 hunks)
  • backend/tests/api/project/project.delete.test.ts (2 hunks)
  • backend/tests/api/project/project.get.test.ts (2 hunks)
  • backend/tests/api/project/project.wikibase.test.ts (17 hunks)
  • frontend/package.json (1 hunks)
  • frontend/src/shared/types/client-errors.ts (1 hunks)
  • guidelines/reference/ELYSIA_EDEN.md (1 hunks)
  • package.json (1 hunks)
  • prompts/Backend.md (2 hunks)
  • prompts/Frontend.md (1 hunks)
💤 Files with no reviewable changes (2)
  • backend/src/index.ts
  • backend/src/api/project/import.ts
🧰 Additional context used
🧬 Code graph analysis (8)
backend/tests/api/project/project.delete.test.ts (1)
backend/src/api/project/schemas.ts (1)
  • UUID_REGEX (4-5)
frontend/src/shared/types/client-errors.ts (1)
frontend/auto-imports.d.ts (1)
  • ExtendedError (386-386)
backend/tests/api/project/project.wikibase.test.ts (2)
backend/src/api/project/project.wikibase.ts (4)
  • Label (91-91)
  • Alias (95-95)
  • wikibaseRoutes (160-365)
  • WikibaseCreateSchema (150-150)
backend/src/plugins/database.ts (1)
  • databasePlugin (65-72)
backend/src/api/project/index.ts (2)
backend/src/api/project/schemas.ts (3)
  • ProjectResponseSchema (13-18)
  • PaginationQuery (22-25)
  • ResponseSchema (41-50)
backend/src/types/error-schemas.ts (2)
  • ApiErrors (20-26)
  • ApiErrors (27-27)
backend/src/api/project/project.wikibase.ts (4)
backend/src/types/wikibase-schema.ts (8)
  • PropertyId (11-16)
  • PropertyId (17-17)
  • WikibaseDataType (26-37)
  • WikibaseDataType (38-38)
  • StatementRank (19-23)
  • StatementRank (24-24)
  • ItemId (3-8)
  • ItemId (9-9)
backend/src/api/project/schemas.ts (1)
  • UUIDPattern (9-11)
backend/src/api/wikibase/schemas.ts (1)
  • InstanceId (48-51)
backend/src/types/error-schemas.ts (2)
  • ApiErrors (20-26)
  • ApiErrors (27-27)
backend/src/api/wikibase/schemas.ts (2)
backend/src/types/wikibase-schema.ts (2)
  • WikibaseDataType (26-37)
  • WikibaseDataType (38-38)
backend/src/types/wikibase-api.ts (1)
  • PropertySearchResult (84-93)
backend/tests/api/project/project.get.test.ts (1)
backend/src/api/project/schemas.ts (1)
  • UUID_REGEX (4-5)
backend/src/api/wikibase/index.ts (3)
backend/src/api/wikibase/schemas.ts (7)
  • InstanceId (48-51)
  • OAuthCredentials (4-17)
  • OAuthCredentials (18-18)
  • QuerySchema (53-76)
  • PropertySearchResultSchema (32-45)
  • Term (29-29)
  • Term (30-30)
backend/src/types/error-schemas.ts (2)
  • ApiErrors (20-26)
  • ApiErrors (27-27)
backend/src/types/wikibase-schema.ts (6)
  • PropertyId (11-16)
  • PropertyId (17-17)
  • WikibaseDataType (26-37)
  • WikibaseDataType (38-38)
  • ItemId (3-8)
  • ItemId (9-9)
🪛 GitHub Actions: Typecheck code
backend/src/plugins/database.ts

[error] Process exited with code 2

backend/tests/api/project/project.delete.test.ts

[error] Process exited with code 2

frontend/src/shared/types/client-errors.ts

[error] Typecheck exited with code 2

backend/tests/api/project/project.wikibase.test.ts

[error] Process exited with code 2

backend/src/types/wikibase-schema.ts

[error] Process exited with code 2

backend/src/api/project/index.ts

[error] Process exited with code 2

package.json

[error] 1-1: bun typecheck failed due to multiple TypeScript errors.

backend/src/api/project/project.wikibase.ts

[error] Process exited with code 2

backend/tests/api/project/project.create.test.ts

[error] Process exited with code 2

frontend/package.json

[error] 1-1: bun typecheck failed due to multiple TypeScript errors.

backend/src/api/wikibase/schemas.ts

[error] Process exited with code 2

backend/src/types/error-schemas.ts

[error] Process exited with code 2

backend/tests/api/project/project.get.test.ts

[error] Process exited with code 2

backend/src/api/_meta_projects.ts

[error] Process exited with code 2

backend/package.json

[error] 1-1: bun typecheck failed due to multiple TypeScript errors.

backend/src/api/project/schemas.ts

[error] Process exited with code 2

backend/src/api/wikibase/index.ts

[error] 35-35: Property 'getCsrfToken' does not exist on type 'WikibaseService'.


[error] 45-45: Cannot find name 'OAuthCredentials'.


[error] 78-78: Cannot find name 'ApiErrors'.


[error] 79-79: Cannot find name 'ApiErrors'.


[error] 120-120: Cannot find name 'ApiErrors'.


[error] 121-121: Cannot find name 'ApiErrors'.


[error] 122-122: Cannot find name 'ApiErrors'.


[error] 159-159: Cannot find name 'PropertyId'.


[error] 179-179: Cannot find name 'WikibaseDataType'.


[error] 186-186: Cannot find name 'ApiErrors'.


[error] 187-187: Cannot find name 'ApiErrors'.


[error] 188-188: Cannot find name 'ApiErrors'.


[error] 189-189: Cannot find name 'ApiErrors'.


[error] 212-212: Cannot find name 'PropertyId'.


[error] 218-218: Cannot find name 'ApiErrors'.


[error] 219-219: Cannot find name 'ApiErrors'.


[error] 220-220: Cannot find name 'ApiErrors'.


[error] 246-246: Cannot find name 'PropertyId'.


[error] 290-290: Cannot find name 'ApiErrors'.


[error] 291-291: Cannot find name 'ApiErrors'.


[error] 360-360: Cannot find name 'ApiErrors'.


[error] 361-361: Cannot find name 'ApiErrors'.


[error] 412-412: Cannot find name 'ApiErrors'.


[error] 413-413: Cannot find name 'ApiErrors'.


[error] 414-414: Cannot find name 'ApiErrors'.


[error] 436-436: Cannot find name 'ItemId'.


[error] 455-455: Cannot find name 'ApiErrors'.


[error] 456-456: Cannot find name 'ApiErrors'.


[error] 457-457: Cannot find name 'ApiErrors'.


[error] 458-458: Cannot find name 'ApiErrors'.


[error] 35-35: Property 'getCsrfToken' does not exist on type 'WikibaseService'.


[error] 45-45: Cannot find name 'OAuthCredentials'.


[error] 78-78: Cannot find name 'ApiErrors'.


[error] 79-79: Cannot find name 'ApiErrors'.


[error] 120-120: Cannot find name 'ApiErrors'.


[error] 121-121: Cannot find name 'ApiErrors'.


[error] 122-122: Cannot find name 'ApiErrors'.


[error] 159-159: Cannot find name 'PropertyId'.


[error] 179-179: Cannot find name 'WikibaseDataType'.


[error] 186-186: Cannot find name 'ApiErrors'.


[error] 187-187: Cannot find name 'ApiErrors'.


[error] 188-188: Cannot find name 'ApiErrors'.


[error] 189-189: Cannot find name 'ApiErrors'.


[error] 212-212: Cannot find name 'PropertyId'.


[error] 218-218: Cannot find name 'ApiErrors'.


[error] 219-219: Cannot find name 'ApiErrors'.


[error] 220-220: Cannot find name 'ApiErrors'.


[error] 246-246: Cannot find name 'PropertyId'.


[error] 290-290: Cannot find name 'ApiErrors'.


[error] 291-291: Cannot find name 'ApiErrors'.


[error] 360-360: Cannot find name 'ApiErrors'.


[error] 361-361: Cannot find name 'ApiErrors'.


[error] 412-412: Cannot find name 'ApiErrors'.


[error] 413-413: Cannot find name 'ApiErrors'.


[error] 414-414: Cannot find name 'ApiErrors'.


[error] 436-436: Cannot find name 'ItemId'.


[error] 455-455: Cannot find name 'ApiErrors'.


[error] 456-456: Cannot find name 'ApiErrors'.


[error] 457-457: Cannot find name 'ApiErrors'.


[error] 458-458: Cannot find name 'ApiErrors'.

🪛 GitHub Check: test
backend/src/api/wikibase/index.ts

[failure] 45-45: ReferenceError: OAuthCredentials is not defined

  at /home/runner/work/dataforge/dataforge/backend/src/api/wikibase/index.ts:45:22
  at loadAndEvaluateModule (2:1)
🔇 Additional comments (22)
prompts/Backend.md (2)

37-37: Good: reinforce exporting App and using Elysia schemas

This supports eden/treaty type inference across test/client.


56-56: Doc states “Server-side validation (Elysia schemas)”—OK

Matches the PR objective to replace Zod with t.*.

backend/tests/api/project/project.get.test.ts (1)

2-2: Importing UUID_REGEX is correct

Keeps tests coupled to the actual schema pattern instead of duplicating it.

backend/tests/api/project/project.delete.test.ts (2)

2-2: Importing UUID_REGEX is correct

Prevents regex drift between schema and tests.


38-40: No changes needed for toBeEmpty() matcher
Bun’s built-in test runner (bun:test) provides toBeEmpty() and toBeArrayOfSize() out of the box, so these assertions are valid without additional typings or jest-extended configuration.

backend/tests/api/project/project.create.test.ts (1)

2-2: UUID_REGEX_PATTERN is still exported
The constant is defined in backend/src/api/project/schemas.ts (line 8), so the import remains valid.

backend/src/api/_meta_projects.ts (1)

80-84: Response schema verified: 500 uses ApiErrors (an array of error objects).

backend/src/api/wikibase/schemas.ts (3)

4-17: OAuthCredentials schema looks good

Covers required OAuth 1.0 keys; consistent with usage.


20-27: CSRFTokenResponse shape OK

Matches MediaWiki “query.tokens.csrftoken” response.


29-30: Term union is clear and minimal

Appropriate literal union for match.type.

backend/src/api/wikibase/index.ts (1)

435-446: Item details route: include instanceId in params and import ItemId

Keep validation consistent and fix missing type import.

-      params: t.Object({
-        itemId: ItemId,
-      }),
+      params: t.Object({
+        instanceId: InstanceId,
+        itemId: ItemId,
+      }),

Likely an incorrect or invalid review comment.

backend/src/types/wikibase-schema.ts (2)

19-24: LGTM: StatementRank union.

The literal union matches Wikibase ranks.


26-38: LGTM: WikibaseDataType literals.

Set aligns with common Wikibase datatypes including commonsMedia.

backend/tests/api/project/project.wikibase.test.ts (6)

25-32: LGTM: deterministic language selection.

Simple, predictable fallback via _strCounter % length.


160-162: DB plugin lifecycle in tests.

Using databasePlugin plus calling initializeDb(':memory:') in beforeEach is fine since onStart hooks won’t run without listen(). Just calling out to avoid double init surprises if listen() is introduced later.


174-188: LGTM: schema generator matches API contract.

Shape aligns with WikibaseCreateSchema and route defaults.


216-236: Helper assertion reads well.

Covers id, project_id, timestamps, and echoes body.


373-403: Note: eden/treaty 201 typing quirk.

The @ts-expect-error is a known eden quirk on 201 responses; keep until eden updates fix the union narrowing.


448-469: Good: verify hard delete via follow-up GET 404.

End-to-end deletion check is solid.

backend/src/api/project/schemas.ts (3)

9-11: LGTM: UUID pattern.

Case-insensitive, hyphenated UUID pattern is correct; exposed as t.String({ pattern }) for reuse.


27-36: Name shadowing is acceptable here.

export type DuckDBColumnSchema = typeof DuckDBColumnSchema.static follows the Elysia pattern and won’t collide at runtime.


41-50: ResponseSchema looks consistent.

meta.schema reuses DuckDBColumnSchema value-schema; totals as numbers are fine.

import type { DuckDBColumnNameAndType } from '@backend/utils/duckdb-types'
import { Elysia } from 'elysia'
import z from 'zod'
import { Elysia, t } from 'elysia'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Import Static for schema inference.

Use Static<typeof ...>; relying on .static can fail typecheck.

Apply:

-import { Elysia, t } from 'elysia'
+import { Elysia, t, Static } from 'elysia'

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In backend/src/api/_meta_projects.ts around line 5, the code relies on
schemaName.static for typing which can fail typechecks; import the Static helper
(e.g. import type { Static } from '@sinclair/typebox' or the appropriate typebox
package used in the project) and replace usages of SchemaName.static with
Static<typeof SchemaName>, updating any type annotations accordingly so they use
Static<typeof ...> instead of .static.

),
})
type MetaProject = z.infer<typeof MetaProject>
type MetaProject = typeof MetaProject.static

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use Static for type inference.

Avoid .static to fix typecheck and align with Elysia docs.

Apply:

-type MetaProject = typeof MetaProject.static
+type MetaProject = Static<typeof MetaProject>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type MetaProject = typeof MetaProject.static
type MetaProject = Static<typeof MetaProject>
🤖 Prompt for AI Agents
In backend/src/api/_meta_projects.ts around line 24, the type alias currently
uses "typeof MetaProject.static", which should be replaced with "Static<typeof
MetaProject>" to match Elysia docs and fix typechecking; update the type to use
Static and add an import for Static from 'elysia' at the top of the file if it's
not already imported.

Comment on lines +475 to +482
body: t.Object({
file: t.File({
// Note: File type validation has known issues in Elysia 1.3.x
// See: https://github.com/elysiajs/elysia/issues/1073
// type: ['application/json', 'text/csv'], // Temporarily disabled due to validation issues
minSize: 1, // Reject empty files
}),
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Also mkdir before writing in /:projectId/import/file

Mirror temp dir creation to avoid ENOENT.

-        const tempFilePath = `./temp/${tempFileName}`
+        const { resolve } = await import('node:path')
+        const { mkdir } = await import('node:fs/promises')
+        const tempDir = resolve('./temp')
+        await mkdir(tempDir, { recursive: true })
+        const tempFilePath = `${tempDir}/${tempFileName}`
@@
-        await Bun.write(tempFilePath, uint8Array)
+        await Bun.write(tempFilePath, uint8Array)

Also applies to: 455-461

import { cors } from '@elysiajs/cors'
import { Elysia } from 'elysia'
import z from 'zod'
import { Elysia, t } from 'elysia'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Add missing imports (fixes most type errors)

ApiErrors, OAuthCredentials, and Wikibase ID types are used but not imported.

-import { Elysia, t } from 'elysia'
+import { Elysia, t } from 'elysia'
+import { ApiErrors } from '@backend/types/error-schemas'
+import { OAuthCredentials } from '@backend/api/wikibase/schemas'
+import { ItemId, PropertyId, WikibaseDataType } from '@backend/types/wikibase-schema'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { Elysia, t } from 'elysia'
import { Elysia, t } from 'elysia'
import { ApiErrors } from '@backend/types/error-schemas'
import { OAuthCredentials } from '@backend/api/wikibase/schemas'
import { ItemId, PropertyId, WikibaseDataType } from '@backend/types/wikibase-schema'
🤖 Prompt for AI Agents
In backend/src/api/wikibase/index.ts around line 13, the file uses ApiErrors,
OAuthCredentials, and the Wikibase ID types but does not import them; add the
appropriate named imports at the top of the file (e.g. import { ApiErrors } from
'<module_where_errors_are_defined>'; import { OAuthCredentials } from
'<auth/module>'; import { WikibaseId /* or the exact ID type name used */ } from
'<types/module>') using the real module paths/exports in the repo so TypeScript
resolves those symbols and fixes the type errors.

Comment on lines +28 to +56
.post(
'/:instanceId/csrf-token',
async ({ params: { instanceId }, body: { endpoint, credentials }, wikibase }) => {
const {
query: {
tokens: { csrftoken },
},
} = await wikibase.getCsrfToken(instanceId, endpoint, credentials)
return {
token: csrftoken,
}
},
{
body: t.Object({
endpoint: t.String({
description: 'Wikibase endpoint URL',
}),
credentials: OAuthCredentials,
}),
response: t.Object({
token: t.String(),
}),
detail: {
summary: 'Get CSRF token',
description: 'Get a CSRF token for a Wikibase instance using OAuth credentials',
tags: ['Wikibase'],
},
},
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

wikibase.getCsrfToken() missing in service; either implement or adjust call

Build fails: “Property 'getCsrfToken' does not exist on type 'WikibaseService'.” Add the method to the service and its interface.

Example (outside this file, in WikibaseService):

// services/wikibase.service.ts
async getCsrfToken(instanceId: string, endpoint: string, credentials: OAuthCredentials) {
  const url = new URL(endpoint)
  url.searchParams.set('action', 'query')
  url.searchParams.set('meta', 'tokens')
  url.searchParams.set('type', 'csrf')
  url.searchParams.set('format', 'json')
  // TODO: sign request with OAuth 1.0 using credentials
  const res = await fetch(url, { method: 'POST' })
  if (!res.ok) throw new Error(`Failed to fetch CSRF token: ${res.status}`)
  return (await res.json()) as { query: { tokens: { csrftoken: string } } }
}

Alternatively, switch to an existing method if present.


🏁 Script executed:

#!/bin/bash
# Confirm whether getCsrfToken exists and where WikibaseService is defined
rg -nP '\bclass\s+WikibaseService\b|interface\s+WikibaseService\b' -C3
rg -n '\bgetCsrfToken\s*\(' -C2

Length of output: 789


Add getCsrfToken to WikibaseService
Build is failing because getCsrfToken isn’t defined on WikibaseService; implement

async getCsrfToken(instanceId: string, endpoint: string, credentials: OAuthCredentials) { … }

(and update its interface) in backend/src/services/wikibase.service.ts to match its use in backend/src/api/wikibase/index.ts.

🧰 Tools
🪛 GitHub Check: test

[failure] 45-45: ReferenceError: OAuthCredentials is not defined

  at /home/runner/work/dataforge/dataforge/backend/src/api/wikibase/index.ts:45:22
  at loadAndEvaluateModule (2:1)
🪛 GitHub Actions: Typecheck code

[error] 35-35: Property 'getCsrfToken' does not exist on type 'WikibaseService'.


[error] 45-45: Cannot find name 'OAuthCredentials'.


[error] 35-35: Property 'getCsrfToken' does not exist on type 'WikibaseService'.


[error] 45-45: Cannot find name 'OAuthCredentials'.

if (instance) {
instance = null
}
closeDb()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Await closeDb() to avoid race on re-initialization

closeDb returns a Promise; not awaiting risks creating a new instance before teardown completes.

Apply:

-  closeDb()
+  await closeDb()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
closeDb()
await closeDb()
🤖 Prompt for AI Agents
In backend/src/plugins/database.ts around line 10, the call to closeDb() is not
awaited which can cause a race where a new DB instance is created before
teardown completes; make the caller async (or ensure it returns/handles a
Promise) and await closeDb() so the teardown finishes before re-initialization,
propagating or handling any errors from closeDb().

t.Literal('NOT_FOUND'),
])
export type ErrorCode = z.infer<typeof ErrorCodeSchema>
export type ErrorCode = typeof ErrorCodeSchema.static

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Fix type inference: use Static<typeof ...> instead of .static (likely source of typecheck error).

In Elysia 1.x, recommended inference is Static<typeof Schema>. The .static accessor may not exist depending on config and can fail typecheck.

Apply:

-import { t } from 'elysia'
+import { t, Static } from 'elysia'
@@
-export type ErrorCode = typeof ErrorCodeSchema.static
+export type ErrorCode = Static<typeof ErrorCodeSchema>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export type ErrorCode = typeof ErrorCodeSchema.static
// backend/src/types/error-schemas.ts
import { t, Static } from 'elysia'
// … other code …
export type ErrorCode = Static<typeof ErrorCodeSchema>
🤖 Prompt for AI Agents
In backend/src/types/error-schemas.ts around line 18, the type alias uses
"typeof ErrorCodeSchema.static" which is not reliable; replace it with
"Static<typeof ErrorCodeSchema>" and add the appropriate import for Static (from
@sinclair/typebox) at the top of the file so the type is inferred correctly from
the schema.

Comment on lines +20 to +27
export const ApiErrors = t.Array(
t.Object({
code: ErrorCodeSchema,
message: t.String(),
details: t.Array(t.Any()),
}),
)
export type ApiErrors = z.infer<typeof ApiErrors>
export type ApiErrors = typeof ApiErrors.static

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Same: replace .static with Static<typeof ...> and consider optional details.

Two parts:

  • Use Static<typeof ApiErrors> for the type.
  • If details isn’t guaranteed, mark it optional to avoid runtime/type mismatches.

Apply:

 export const ApiErrors = t.Array(
   t.Object({
     code: ErrorCodeSchema,
     message: t.String(),
-    details: t.Array(t.Any()),
+    details: t.Optional(t.Array(t.Any())),
   }),
 )
-export type ApiErrors = typeof ApiErrors.static
+export type ApiErrors = Static<typeof ApiErrors>

Run to find any other .static usages:


🏁 Script executed:

#!/bin/bash
rg -nP '\.static\b' --type=ts --type=tsx

Length of output: 27


🏁 Script executed:

#!/usr/bin/env bash
rg -n '\.static\b' --glob '*.ts' --glob '*.tsx' -g '!node_modules/*'

Length of output: 2819


Replace .static with Static<typeof …> and make details optional

  • Update the ErrorCode export to use Static<typeof ErrorCodeSchema>
  • Wrap details in t.Optional and switch ApiErrors to Static<typeof ApiErrors>
  • Audit other schema files (e.g. wikibase-schema.ts, project/schemas.ts, etc.) for remaining .static usages and update for consistency

Apply to backend/src/types/error-schemas.ts:

--- a/backend/src/types/error-schemas.ts
+++ b/backend/src/types/error-schemas.ts
@@
-export type ErrorCode = typeof ErrorCodeSchema.static
+export type ErrorCode = Static<typeof ErrorCodeSchema>
@@
-    details: t.Array(t.Any()),
+    details: t.Optional(t.Array(t.Any())),
@@
-export type ApiErrors = typeof ApiErrors.static
+export type ApiErrors = Static<typeof ApiErrors>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const ApiErrors = t.Array(
t.Object({
code: ErrorCodeSchema,
message: t.String(),
details: t.Array(t.Any()),
}),
)
export type ApiErrors = z.infer<typeof ApiErrors>
export type ApiErrors = typeof ApiErrors.static
export type ErrorCode = Static<typeof ErrorCodeSchema>
export const ApiErrors = t.Array(
t.Object({
code: ErrorCodeSchema,
message: t.String(),
details: t.Optional(t.Array(t.Any())),
}),
)
export type ApiErrors = Static<typeof ApiErrors>
🤖 Prompt for AI Agents
In backend/src/types/error-schemas.ts around lines 20 to 27, replace usages of
the outdated .static type accessors with io-ts-type Static<typeof ...> and make
the details field optional: change the exported ErrorCode type to use
Static<typeof ErrorCodeSchema>, wrap the details schema in t.Optional(...) so it
becomes optional, and change the ApiErrors exported type to export type
ApiErrors = Static<typeof ApiErrors>; after making these edits, run a quick grep
across the repo (e.g. wikibase-schema.ts, project/schemas.ts) for any other
occurrences of ".static" and update them to Static<typeof ...> for consistency.

Comment on lines +3 to +8
export const ItemId = t.TemplateLiteral([
t.Literal('Q'),
t.Number({
min: 1,
}),
])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix: use Integer with 'minimum' (not 'min') inside TemplateLiteral.

t.Number({ min: 1 }) doesn’t match Elysia’s options and will fail typecheck; also IDs must be integer. Use t.Integer({ minimum: 1 }).

Apply this diff:

-export const ItemId = t.TemplateLiteral([
-  t.Literal('Q'),
-  t.Number({
-    min: 1,
-  }),
-])
+export const ItemId = t.TemplateLiteral([
+  t.Literal('Q'),
+  t.Integer({
+    minimum: 1,
+  }),
+])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const ItemId = t.TemplateLiteral([
t.Literal('Q'),
t.Number({
min: 1,
}),
])
export const ItemId = t.TemplateLiteral([
t.Literal('Q'),
t.Integer({
minimum: 1,
}),
])
🤖 Prompt for AI Agents
In backend/src/types/wikibase-schema.ts around lines 3 to 8, the TemplateLiteral
uses t.Number({ min: 1 }) which is invalid for Elysia and allows non-integers;
replace it with t.Integer({ minimum: 1 }) so the ID is validated as an integer
and the option name matches Elysia's schema. Update the TemplateLiteral element
accordingly and ensure imports/types support t.Integer if needed.

Comment on lines +11 to +17
export const PropertyId = t.TemplateLiteral([
t.Literal('P'),
t.Number({
min: 1,
}),
])
export type PropertyId = typeof PropertyId.static

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Same issue for PropertyId: switch to Integer + 'minimum'.

Mirror the fix for P identifiers.

-export const PropertyId = t.TemplateLiteral([
-  t.Literal('P'),
-  t.Number({
-    min: 1,
-  }),
-])
+export const PropertyId = t.TemplateLiteral([
+  t.Literal('P'),
+  t.Integer({
+    minimum: 1,
+  }),
+])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const PropertyId = t.TemplateLiteral([
t.Literal('P'),
t.Number({
min: 1,
}),
])
export type PropertyId = typeof PropertyId.static
export const PropertyId = t.TemplateLiteral([
t.Literal('P'),
t.Integer({
minimum: 1,
}),
])
export type PropertyId = typeof PropertyId.static
🤖 Prompt for AI Agents
In backend/src/types/wikibase-schema.ts around lines 11 to 17, the PropertyId
template uses t.Number({ min: 1 }) which should mirror the fix for P
identifiers; replace the Number validator with an Integer validator using the
JSON-schema style option name 'minimum' (e.g. t.Integer({ minimum: 1 })) so the
TemplateLiteral becomes ['P', Integer({ minimum: 1 })] while keeping the
exported type line unchanged.

@DaxServer
DaxServer merged commit ff1800b into main Sep 27, 2025
4 of 5 checks passed
@DaxServer
DaxServer deleted the elysia-types branch September 27, 2025 16:27

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
backend/src/plugins/database.ts (1)

57-63: Missing instance.close() leaks the DuckDB instance handle.

We null out instance without closing it, leaving the underlying DuckDB file handle/process alive. Subsequent re-inits can fail or leak resources. Close the instance before resetting it.

Apply this diff to fix the issue:

 export const closeDb = async (): Promise<void> => {
   if (connection) {
     connection.closeSync()
     connection = null
   }
-  instance = null
+  if (instance) {
+    await instance.close()
+    instance = null
+  }
 }
backend/tests/api/project/project.wikibase.test.ts (1)

453-455: Use toBeUndefined() for 204 responses.

Bun/Eden typically yields undefined bodies for 204. toBeEmpty() may not exist or may be brittle.

-      expect(data).toBeEmpty()
+      expect(data).toBeUndefined()
🧹 Nitpick comments (15)
frontend/src/shared/types/client-errors.ts (1)

35-40: Restore descriptive parameter name

Prefixing the parameter with _ signals to our lint rules that it’s intentionally unused, yet we still reference it on Line 39. This can mask real unused-variable issues and makes the code harder to read. Please revert to message (or drop the underscore) so the intent stays clear.

-export const createFrontendError = (
-  code: FrontendErrorCode,
-  _message: string,
+export const createFrontendError = (
+  code: FrontendErrorCode,
+  message: string,
   details?: any[],
 ): ExtendedError => ({
   code,
-  message: _message,
+  message,
   details,
 })
backend/package.json (1)

25-26: Pin or move sharp to catalog to reduce install flakiness on Bun

sharp’s prebuilds occasionally drift; pinning to an exact version (or using your catalog) helps reproducible CI, especially on Bun/N-API. Consider locking it or promoting to catalog for centralized control.

Apply:

-    "sharp": "^0.34.4"
+    "sharp": "0.34.4"

Optionally also declare Bun runtime for clarity:

   "type": "module",
+  "engines": { "bun": ">=1.1.0" },
backend/src/api/_meta_projects.ts (3)

7-24: Tighten schema: prefer Nullable and avoid Any; consider naming schema constant MetaProjectSchema

  • Use t.Nullable for schema_for.
  • Avoid t.Any; t.Unknown (or a concrete union) is safer.
  • Consider renaming the runtime schema to MetaProjectSchema to reduce type/value name shadowing.

Apply:

-const MetaProject = t.Object({
+const MetaProjectSchema = t.Object({
   id: t.String(),
   name: t.String(),
-  schema_for: t.Union([t.String(), t.Null()]),
-  schema: t.Any(),
+  schema_for: t.Nullable(t.String()),
+  schema: t.Unknown(),
   created_at: t.String(),
   updated_at: t.String(),
   wikibase_schema: t.Array(
     t.Object({
       id: t.String(),
       wikibase: t.String(),
       name: t.String(),
       created_at: t.String(),
       updated_at: t.String(),
     }),
   ),
 })
-type MetaProject = typeof MetaProject.static
+type MetaProject = typeof MetaProjectSchema.static

And in the response schema:

-        200: t.Object({
-          data: t.Array(MetaProject),
-        }),
+        200: t.Object({ data: t.Array(MetaProjectSchema) }),

(Optional) If you rely on OpenAPI, consider t.String({ format: 'date-time' }) for created_at/updated_at if your tooling supports it. Based on learnings.


66-74: Harden JSON parsing and be explicit about output typing

Guard JSON.parse to avoid crashing on malformed rows and declare the resulting array’s type.

Apply:

-      const data = reader.getRowObjectsJson().map((row) => {
+      const data: MetaProject[] = reader.getRowObjectsJson().map((row) => {
         const newRow: Record<string, unknown> = { ...row }
         jsonColumns.forEach((column) => {
-          if (newRow[column] && typeof newRow[column] === 'string') {
-            newRow[column] = JSON.parse(newRow[column] as string)
+          if (newRow[column] && typeof newRow[column] === 'string') {
+            try {
+              newRow[column] = JSON.parse(newRow[column] as string)
+            } catch {
+              // leave as-is; error handler will catch downstream if it matters
+            }
           }
         })
         return newRow as MetaProject
       })

26-86: Enable runtime response validation
Elysia’s response schemas only power type generation/OpenAPI docs; with no .use(openapi()) plugin or strict: true flag on your new Elysia(...) instances (including metaProjectsRoutes and your test setups), responses aren’t checked at runtime. If you require enforcement, install and use the openapi() or validator() plugin or configure new Elysia({ strict: true, … }).

backend/src/types/error-schemas.ts (1)

20-27: Make details optional and unknown; add examples for better OpenAPI

Reduce coupling by not requiring details on every error and avoid Any.

Apply:

 export const ApiErrors = t.Array(
   t.Object({
     code: ErrorCodeSchema,
-    message: t.String(),
-    details: t.Array(t.Any()),
+    message: t.String({ examples: ['Invalid payload', 'Not found'] }),
+    details: t.Optional(t.Array(t.Unknown())),
   }),
 )
backend/src/types/wikibase-schema.ts (2)

3-9: Disallow leading zeros in ItemId if you need strict Wikibase IDs

t.Number in TemplateLiteral will accept "Q01" (parses to 1). If that’s undesirable, switch to a pattern.

Apply:

-export const ItemId = t.TemplateLiteral([
-  t.Literal('Q'),
-  t.Number({
-    min: 1,
-  }),
-])
+export const ItemId = t.String({ pattern: '^Q[1-9]\\d*$' })

11-17: Same for PropertyId

Prevent "P00" if strictness is desired.

Apply:

-export const PropertyId = t.TemplateLiteral([
-  t.Literal('P'),
-  t.Number({
-    min: 1,
-  }),
-])
+export const PropertyId = t.String({ pattern: '^P[1-9]\\d*$' })
backend/src/api/wikibase/schemas.ts (2)

53-76: Tighten query params: bounds, coercion, and default semantics.

  • Add sane bounds for limit/offset to protect the endpoint.
  • languageFallback with default and Optional is redundant; pick one (default only).
  • Validate language roughly to BCP‑47.
   limit: t.Number({
-    default: 10,
+    default: 10,
+    minimum: 1,
+    maximum: 50,
     description: 'Maximum number of results',
   }),
   offset: t.Number({
-    default: 0,
+    default: 0,
+    minimum: 0,
     description: 'Offset for pagination',
   }),
   language: t.String({
-    default: 'en',
+    default: 'en',
+    pattern: '^[A-Za-z]{2,3}(?:-[A-Za-z0-9]+)*$',
     description: 'Language code for search results',
   }),
-  languageFallback: t.Optional(
-    t.Boolean({
-      default: true,
-      description: 'Enable language fallback for partial matches',
-    }),
-  ),
+  languageFallback: t.Boolean({
+    default: true,
+    description: 'Enable language fallback for partial matches',
+  }),

Based on learnings.


48-51: Constrain instance id to supported values.

If only specific instances are supported, constrain via pattern to keep default while validating input.

 export const InstanceId = t.String({
   description: 'Wikibase instance ID',
   default: 'wikidata',
+  pattern: '^(wikidata|commons)$',
 })
backend/tests/api/project/project.wikibase.test.ts (5)

202-214: Relax strict error shape assertion to reduce brittleness.

Pinning the entire value array makes tests fragile. Assert only the stable parts.

-  expect(error).toHaveProperty('value', [
-    {
-      code: 'NOT_FOUND',
-      details: [],
-      message,
-    },
-  ])
+  expect(error).toHaveProperty(
+    'value',
+    [expect.objectContaining({ code: 'NOT_FOUND', message })]
+  )

33-47: Simplify object building (avoid Object.assign in loop).

Direct assignment is clearer and faster.

-  let labels: Label = {}
-  for (let i = 0; i < count; i++) {
-    const lang = deterministicLang(i)
-    labels = Object.assign({}, labels, {
-      [lang]: {
-        columnName: deterministicString('Label'),
-        dataType: 'monolingualtext',
-      },
-    } as Label)
-  }
+  const labels: Label = {}
+  for (let i = 0; i < count; i++) {
+    const lang = deterministicLang(i)
+    labels[lang] = {
+      columnName: deterministicString('Label'),
+      dataType: 'monolingualtext',
+    } as Label[string]
+  }
   return labels

65-81: Same simplification for aliases.

Use direct assignment.

-  let aliases: Alias = {}
+  const aliases: Alias = {}
   for (let i = 0; i < count; i++) {
     const lang = deterministicLang(i)
-    aliases = Object.assign({}, aliases, {
-      [lang]: [
-        {
-          columnName: deterministicString('Alias'),
-          dataType: 'monolingualtext',
-        },
-      ],
-    })
+    aliases[lang] = [
+      {
+        columnName: deterministicString('Alias'),
+        dataType: 'monolingualtext',
+      },
+    ]
   }
   return aliases

19-21: Remove commented-out helpers.

Dead code increases noise in tests.


374-385: Handle 201 in Eden’s response types
package.json and bun.lock pin @elysiajs/eden@1.4.1, but that version still types only 200 as success. Extend or wrap the treaty client to include 201 in its response union (or upgrade once a release adds 201 support) so tests no longer need @ts-expect-error.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 163eb3c and 0134491.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (23)
  • backend/package.json (1 hunks)
  • backend/src/api/_meta_projects.ts (2 hunks)
  • backend/src/api/project/import.ts (0 hunks)
  • backend/src/api/project/index.ts (8 hunks)
  • backend/src/api/project/project.wikibase.ts (7 hunks)
  • backend/src/api/project/schemas.ts (1 hunks)
  • backend/src/api/wikibase/index.ts (10 hunks)
  • backend/src/api/wikibase/schemas.ts (1 hunks)
  • backend/src/index.ts (0 hunks)
  • backend/src/plugins/database.ts (1 hunks)
  • backend/src/types/error-schemas.ts (1 hunks)
  • backend/src/types/wikibase-schema.ts (1 hunks)
  • backend/tests/api/project/project.create.test.ts (2 hunks)
  • backend/tests/api/project/project.delete.test.ts (2 hunks)
  • backend/tests/api/project/project.get.test.ts (2 hunks)
  • backend/tests/api/project/project.wikibase.test.ts (17 hunks)
  • frontend/package.json (1 hunks)
  • frontend/src/features/wikibase-schema/composables/useSchemaApi.ts (0 hunks)
  • frontend/src/shared/types/client-errors.ts (1 hunks)
  • guidelines/reference/ELYSIA_EDEN.md (1 hunks)
  • package.json (1 hunks)
  • prompts/Backend.md (2 hunks)
  • prompts/Frontend.md (1 hunks)
💤 Files with no reviewable changes (3)
  • backend/src/index.ts
  • backend/src/api/project/import.ts
  • frontend/src/features/wikibase-schema/composables/useSchemaApi.ts
🧰 Additional context used
🧬 Code graph analysis (8)
frontend/src/shared/types/client-errors.ts (1)
frontend/auto-imports.d.ts (1)
  • ExtendedError (386-386)
backend/tests/api/project/project.get.test.ts (1)
backend/src/api/project/schemas.ts (1)
  • UUID_REGEX (4-5)
backend/tests/api/project/project.delete.test.ts (1)
backend/src/api/project/schemas.ts (1)
  • UUID_REGEX (4-5)
backend/tests/api/project/project.wikibase.test.ts (2)
backend/src/api/project/project.wikibase.ts (4)
  • Label (91-91)
  • Alias (95-95)
  • wikibaseRoutes (160-365)
  • WikibaseCreateSchema (150-150)
backend/src/plugins/database.ts (1)
  • databasePlugin (65-72)
backend/src/api/wikibase/schemas.ts (2)
backend/src/types/wikibase-schema.ts (2)
  • WikibaseDataType (26-37)
  • WikibaseDataType (38-38)
backend/src/types/wikibase-api.ts (1)
  • PropertySearchResult (84-93)
backend/src/api/wikibase/index.ts (3)
backend/src/api/wikibase/schemas.ts (7)
  • InstanceId (48-51)
  • OAuthCredentials (4-17)
  • OAuthCredentials (18-18)
  • QuerySchema (53-76)
  • PropertySearchResultSchema (32-45)
  • Term (29-29)
  • Term (30-30)
backend/src/types/error-schemas.ts (2)
  • ApiErrors (20-26)
  • ApiErrors (27-27)
backend/src/types/wikibase-schema.ts (6)
  • PropertyId (11-16)
  • PropertyId (17-17)
  • WikibaseDataType (26-37)
  • WikibaseDataType (38-38)
  • ItemId (3-8)
  • ItemId (9-9)
backend/src/api/project/index.ts (2)
backend/src/api/project/schemas.ts (4)
  • ProjectResponseSchema (13-18)
  • PaginationQuery (22-25)
  • GetProjectByIdResponse (41-50)
  • GetProjectByIdResponse (51-51)
backend/src/types/error-schemas.ts (2)
  • ApiErrors (20-26)
  • ApiErrors (27-27)
backend/src/api/project/project.wikibase.ts (4)
backend/src/types/wikibase-schema.ts (8)
  • PropertyId (11-16)
  • PropertyId (17-17)
  • WikibaseDataType (26-37)
  • WikibaseDataType (38-38)
  • StatementRank (19-23)
  • StatementRank (24-24)
  • ItemId (3-8)
  • ItemId (9-9)
backend/src/api/project/schemas.ts (1)
  • UUIDPattern (9-11)
backend/src/api/wikibase/schemas.ts (1)
  • InstanceId (48-51)
backend/src/types/error-schemas.ts (2)
  • ApiErrors (20-26)
  • ApiErrors (27-27)
🪛 GitHub Check: test
backend/src/api/wikibase/index.ts

[failure] 47-47: ReferenceError: OAuthCredentials is not defined

  at /home/runner/work/dataforge/dataforge/backend/src/api/wikibase/index.ts:47:22
  at loadAndEvaluateModule (2:1)
🪛 GitHub Actions: Typecheck code
backend/src/api/wikibase/index.ts

[error] 37-37: bun typecheck: Property 'getCsrfToken' does not exist on type 'WikibaseService'.


[error] 47-47: bun typecheck: Cannot find name 'OAuthCredentials'.

🔇 Additional comments (6)
backend/src/types/error-schemas.ts (1)

3-17: ErrorCodeSchema includes all emitted codes. Verified that every code: '…' value in the backend is present in the union; no emitters fall outside this schema.

backend/package.json (1)

16-26: Zod removal verified. No lingering zod imports or z. usages remain in the codebase.

frontend/package.json (1)

16-17: Approve: theme and PrimeVue versions aligned
@primeuix/themes is pinned at 1.2.5 and primevue at 4.3.9—major versions match and no other references found.

backend/src/api/wikibase/schemas.ts (2)

29-30: LGTM: clear, closed union for Term.

The union exactly matches expected values.


20-27: LGTM: CSRF token response shape matches MediaWiki API.

The nested tokens.csrftoken path looks correct.

If upstream adds fields, consider additionalProperties: false if your validator supports it.

backend/tests/api/project/project.wikibase.test.ts (1)

160-162: Potential double-init hazard if app.start() is introduced later.

You call initializeDb manually and also use databasePlugin (which initializes on .onStart). Today this is fine (no .listen), but if tests start the app in future, DB may be initialized twice.

Consider a test-only databasePlugin variant or a guard in initializeDb to no-op if already initialized.

Comment on lines 1 to 6
import {
InstanceId,
InstancePropertyConstraintsSchema,
InstancePropertyDetailsSchema,
ItemDetailsRouteSchema,
ItemSearchSchema,
PropertySearchSchema,
PropertyValidationSchema,
SchemaValidationSchema,
WikibasePropertiesFetchSchema,
PropertySearchResultSchema,
QuerySchema,
Term,
} from '@backend/api/wikibase/schemas'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Import OAuthCredentials to satisfy runtime usage

The route config references OAuthCredentials, but the symbol isn’t imported, which is currently crashing the typecheck/runtime (see the GitHub check error). Please pull it in from @backend/api/wikibase/schemas.

-import {
-  InstanceId,
-  PropertySearchResultSchema,
-  QuerySchema,
-  Term,
-} from '@backend/api/wikibase/schemas'
+import {
+  InstanceId,
+  PropertySearchResultSchema,
+  QuerySchema,
+  Term,
+  OAuthCredentials,
+} from '@backend/api/wikibase/schemas'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import {
InstanceId,
InstancePropertyConstraintsSchema,
InstancePropertyDetailsSchema,
ItemDetailsRouteSchema,
ItemSearchSchema,
PropertySearchSchema,
PropertyValidationSchema,
SchemaValidationSchema,
WikibasePropertiesFetchSchema,
PropertySearchResultSchema,
QuerySchema,
Term,
} from '@backend/api/wikibase/schemas'
import {
InstanceId,
PropertySearchResultSchema,
QuerySchema,
Term,
OAuthCredentials,
} from '@backend/api/wikibase/schemas'
🤖 Prompt for AI Agents
In backend/src/api/wikibase/index.ts around lines 1 to 6, the symbol
OAuthCredentials is referenced at runtime but not imported from
@backend/api/wikibase/schemas; add OAuthCredentials to the existing named import
list (alongside InstanceId, PropertySearchResultSchema, QuerySchema, Term) so
the route config can reference it without crashing the typecheck/runtime.

Comment on lines +33 to +38
const {
query: {
tokens: { csrftoken },
},
} = await wikibase.getCsrfToken(instanceId, endpoint, credentials)
return {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

wikibase.getCsrfToken is undefined

WikibaseService doesn’t expose a getCsrfToken method, so this call fails to typecheck (and would throw at runtime). Either implement getCsrfToken on the service/plugin or call the appropriate existing helper that returns the CSRF token.

🧰 Tools
🪛 GitHub Actions: Typecheck code

[error] 37-37: bun typecheck: Property 'getCsrfToken' does not exist on type 'WikibaseService'.

🤖 Prompt for AI Agents
In backend/src/api/wikibase/index.ts around lines 33–38, the code calls
wikibase.getCsrfToken but WikibaseService does not expose that method; replace
this incorrect call by either adding a getCsrfToken(instanceId, endpoint,
credentials) method to the WikibaseService (implementing the logic that returns
{ query: { tokens: { csrftoken } } } and export it from the service) or invoke
the existing helper on the service/plugin that actually returns the CSRF token
(locate the correct exported function, call it with the same parameters, and
adjust the destructuring to match its return shape). Update the service/type
definitions so the method exists in the public API and fix any import/usage
sites or tests that rely on the new/renamed function.

Comment thread backend/src/api/wikibase/schemas.ts Outdated
Comment on lines +4 to +17
export const OAuthCredentials = t.Object({
consumerKey: t.String({
description: 'Consumer key',
}),
})
export type PropertySearchResult = z.infer<typeof PropertySearchResultSchema>

export const InstanceId = z.string().describe('Wikibase instance ID').default('wikidata')

// Route schemas
export const PropertySearchSchema = {
query: z.object({
q: z.string().min(1).describe('Search query for properties'),
limit: z.coerce.number().default(10).describe('Maximum number of results'),
offset: z.coerce.number().default(0).describe('Offset for pagination'),
language: z.string().default('en').describe('Language code for search results'),
languageFallback: z
.boolean()
.default(true)
.describe('Enable language fallback for partial matches')
.optional(),
consumerSecret: t.String({
description: 'Consumer secret',
}),
response: {
200: z.object({
data: z.object({
results: z.array(PropertySearchResultSchema),
totalCount: z.number(),
hasMore: z.boolean(),
query: z.string(),
}),
}),
400: ApiErrors,
422: ApiErrors,
500: ApiErrors,
},
detail: {
summary: 'Search Wikibase properties',
description:
'Search for properties in a Wikibase instance with enhanced filtering and language fallback support',
tags: ['Wikibase', 'Properties', 'Search'],
},
}

// Instance-specific schemas for the new API structure
export const InstancePropertyDetailsSchema = {
params: z.object({
propertyId: PropertyId,
accessToken: t.String({
description: 'Access token',
}),
query: z.object({
includeConstraints: z
.boolean()
.default(false)
.describe('Include property constraints in response')
.optional(),
accessTokenSecret: t.String({
description: 'Access secret',
}),
response: {
200: z.object({
data: z.object({
id: z.string(),
pageid: z.number().optional(),
ns: z.number().optional(),
title: z.string().optional(),
lastrevid: z.number().optional(),
modified: z.string().optional(),
type: z.literal('property'),
datatype: WikibaseDataType,
labels: z.record(z.string(), z.string()).optional(),
descriptions: z.record(z.string(), z.string()).optional(),
aliases: z.record(z.string(), z.array(z.string())).optional(),
claims: z.record(z.string(), z.array(z.any())).optional(),
}),
}),
400: ApiErrors,
404: ApiErrors,
422: ApiErrors,
500: ApiErrors,
},
detail: {
summary: 'Get property details',
description:
'Retrieve detailed information about a specific property with optional constraint information',
tags: ['Wikibase', 'Properties'],
},
}

export const InstancePropertyConstraintsSchema = {
params: z.object({
propertyId: PropertyId,
}),
response: {
200: z.object({
data: z.any(),
}),
400: ApiErrors,
404: ApiErrors,
500: ApiErrors,
},
detail: {
summary: 'Get property constraints',
description: 'Retrieve all constraints for a specific property',
tags: ['Wikibase', 'Properties', 'Constraints'],
},
}
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Mark OAuth secrets as write-only to avoid exposure in generated docs/logs.

These fields are sensitive; set writeOnly to signal tooling and discourage echoing. Also avoid defaults/examples for secrets.

   consumerSecret: t.String({
-    description: 'Consumer secret',
+    description: 'Consumer secret',
+    writeOnly: true,
   }),
   accessToken: t.String({
-    description: 'Access token',
+    description: 'Access token',
+    writeOnly: true,
   }),
   accessTokenSecret: t.String({
-    description: 'Access secret',
+    description: 'Access secret',
+    writeOnly: true,
   }),

Based on learnings.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const OAuthCredentials = t.Object({
consumerKey: t.String({
description: 'Consumer key',
}),
})
export type PropertySearchResult = z.infer<typeof PropertySearchResultSchema>
export const InstanceId = z.string().describe('Wikibase instance ID').default('wikidata')
// Route schemas
export const PropertySearchSchema = {
query: z.object({
q: z.string().min(1).describe('Search query for properties'),
limit: z.coerce.number().default(10).describe('Maximum number of results'),
offset: z.coerce.number().default(0).describe('Offset for pagination'),
language: z.string().default('en').describe('Language code for search results'),
languageFallback: z
.boolean()
.default(true)
.describe('Enable language fallback for partial matches')
.optional(),
consumerSecret: t.String({
description: 'Consumer secret',
}),
response: {
200: z.object({
data: z.object({
results: z.array(PropertySearchResultSchema),
totalCount: z.number(),
hasMore: z.boolean(),
query: z.string(),
}),
}),
400: ApiErrors,
422: ApiErrors,
500: ApiErrors,
},
detail: {
summary: 'Search Wikibase properties',
description:
'Search for properties in a Wikibase instance with enhanced filtering and language fallback support',
tags: ['Wikibase', 'Properties', 'Search'],
},
}
// Instance-specific schemas for the new API structure
export const InstancePropertyDetailsSchema = {
params: z.object({
propertyId: PropertyId,
accessToken: t.String({
description: 'Access token',
}),
query: z.object({
includeConstraints: z
.boolean()
.default(false)
.describe('Include property constraints in response')
.optional(),
accessTokenSecret: t.String({
description: 'Access secret',
}),
response: {
200: z.object({
data: z.object({
id: z.string(),
pageid: z.number().optional(),
ns: z.number().optional(),
title: z.string().optional(),
lastrevid: z.number().optional(),
modified: z.string().optional(),
type: z.literal('property'),
datatype: WikibaseDataType,
labels: z.record(z.string(), z.string()).optional(),
descriptions: z.record(z.string(), z.string()).optional(),
aliases: z.record(z.string(), z.array(z.string())).optional(),
claims: z.record(z.string(), z.array(z.any())).optional(),
}),
}),
400: ApiErrors,
404: ApiErrors,
422: ApiErrors,
500: ApiErrors,
},
detail: {
summary: 'Get property details',
description:
'Retrieve detailed information about a specific property with optional constraint information',
tags: ['Wikibase', 'Properties'],
},
}
export const InstancePropertyConstraintsSchema = {
params: z.object({
propertyId: PropertyId,
}),
response: {
200: z.object({
data: z.any(),
}),
400: ApiErrors,
404: ApiErrors,
500: ApiErrors,
},
detail: {
summary: 'Get property constraints',
description: 'Retrieve all constraints for a specific property',
tags: ['Wikibase', 'Properties', 'Constraints'],
},
}
})
export const OAuthCredentials = t.Object({
consumerKey: t.String({
description: 'Consumer key',
}),
consumerSecret: t.String({
description: 'Consumer secret',
writeOnly: true,
}),
accessToken: t.String({
description: 'Access token',
writeOnly: true,
}),
accessTokenSecret: t.String({
description: 'Access secret',
writeOnly: true,
}),
})
🤖 Prompt for AI Agents
In backend/src/api/wikibase/schemas.ts around lines 4 to 17, the OAuth secret
fields are not marked write-only and may be exposed in generated docs/logs;
update the schema so that sensitive fields (consumerSecret, accessToken,
accessTokenSecret — and accessToken if considered sensitive) include writeOnly:
true in their t.String options and remove any example/default values for these
fields; keep descriptions but ensure tooling will treat them as write-only to
avoid echoing secrets.

Comment on lines +32 to +46
export const PropertySearchResultSchema = t.Object({
id: t.String(),
title: t.String(),
pageid: t.Number(),
datatype: WikibaseDataType,
label: t.String(),
description: t.String(),
aliases: t.Array(t.String()),
match: t.Object({
type: Term,
language: t.String(),
text: t.String(),
}),
response: {
200: z.object({
data: z.object({
results: z.array(
z.object({
id: z.string(),
label: z.string(),
description: z.string().optional(),
match: z.object({
type: Term,
text: z.string(),
}),
}),
),
totalCount: z.number(),
hasMore: z.boolean(),
query: z.string(),
}),
}),
400: ApiErrors,
422: ApiErrors,
500: ApiErrors,
},
detail: {
summary: 'Search Wikibase items',
description:
'Search for items in a Wikibase instance with enhanced filtering and language fallback support',
tags: ['Wikibase', 'Items', 'Search'],
},
}
})
export type PropertySearchResult = typeof PropertySearchResultSchema.static

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Unify PropertySearchResult shape with existing interface (and relax optional fields).

This schema diverges from backend/src/types/wikibase-api.ts: it adds title, pageid, aliases, and match.language, while the interface marks description as optional and doesn’t include several of these fields. This will cause type drift and brittle consumers. At minimum, make description/aliases optional and constrain id to a PropertyId-like pattern. Either also update the interface (preferred) or keep this schema compatible.

Apply this diff to improve compatibility and validation:

 export const PropertySearchResultSchema = t.Object({
-  id: t.String(),
+  id: t.String({ pattern: '^P[1-9]\\d*$', description: 'Wikibase PropertyId (e.g., P123)' }),
   title: t.String(),
-  pageid: t.Number(),
+  pageid: t.Number({ minimum: 1 }),
   datatype: WikibaseDataType,
   label: t.String(),
-  description: t.String(),
-  aliases: t.Array(t.String()),
+  description: t.Optional(t.String()),
+  aliases: t.Optional(t.Array(t.String())),
   match: t.Object({
-    type: Term,
-    language: t.String(),
-    text: t.String(),
+    type: Term,
+    text: t.String(),
+    language: t.Optional(t.String()),
   }),
 })

Run to locate all PropertySearchResult definitions/usages and ensure consistency:


🏁 Script executed:

#!/bin/bash
rg -nP -C3 '(interface|type)\s+PropertySearchResult\b|PropertySearchResultSchema\b'

Length of output: 2210


Unify PropertySearchResultSchema with existing interface
The schema adds title, pageid, datatype, aliases, and a required match.language, while the PropertySearchResult interface (lines 84–87 in backend/src/types/wikibase-api.ts) only has

id: PropertyId  
label: string  
description?: string  

To prevent type drift and brittle consumers, either update the interface to match the schema or relax the schema to align. At minimum:

  • Make description and aliases optional
  • Constrain id to the PropertyId pattern
  • Remove or optionalize title, pageid, datatype, and match.language unless the interface is extended accordingly.
🤖 Prompt for AI Agents
In backend/src/api/wikibase/schemas.ts around lines 32 to 46, the
PropertySearchResultSchema diverges from the existing PropertySearchResult
interface — make the schema match the interface (or vice versa) by: constrain id
to the PropertyId pattern (use the same regex/type used in
backend/src/types/wikibase-api.ts), mark description and aliases as optional,
and remove or make optional the fields title, pageid, datatype, and
match.language unless you intend to extend the interface to include them; update
the schema accordingly so consumers and types remain consistent.

if (instance) {
instance = null
}
closeDb()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Await closeDb before reinitializing.

closeDb() returns a Promise, so initializeDb should await it to guarantee teardown finishes before creating a new instance; otherwise any asynchronous cleanup we add (e.g., closing the DuckDB instance) can race with the new initialization.

Apply this diff to fix the issue:

-  closeDb()
+  await closeDb()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
closeDb()
await closeDb()
🤖 Prompt for AI Agents
In backend/src/plugins/database.ts around line 10, the call to closeDb() is not
awaited before reinitializing the DB; since closeDb() returns a Promise this can
race with new initialization. Make initializeDb await closeDb() (e.g., add await
closeDb()), ensure initializeDb is declared async if it isn't already, and
propagate/handle the returned Promise so callers wait for teardown to complete
before creating a new DB instance.

Comment on lines +26 to +38
export const WikibaseDataType = t.Union([
t.Literal('string'),
t.Literal('wikibase-item'),
t.Literal('wikibase-property'),
t.Literal('quantity'),
t.Literal('time'),
t.Literal('globe-coordinate'),
t.Literal('url'),
t.Literal('external-id'),
t.Literal('monolingualtext'),
t.Literal('commonsMedia'),
])
export type WikibaseDataType = z.infer<typeof WikibaseDataType>
export type WikibaseDataType = typeof WikibaseDataType.static

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

Validate Wikibase datatype set against your target instance

Wikibase also supports types like geo-shape, tabular-data, math, musical-notation (varies by extensions/instance). Verify completeness for your use case.


🌐 Web query:

List the complete set of Wikibase statement datatypes supported in current Wikibase (as of 2025-09-27), including any commonly used extension datatypes.

💡 Result:

Below is the set of Wikibase statement datatypes in current Wikibase installations (core + commonly used extension datatypes). Sources: Wikibase/DataModel and Wikidata help pages and the running Wikidata Special:ListDatatypes. [1][2][3]

Core Wikibase datatypes

  • wikibase-entityid (entity links — e.g., Wikibase item, property). [1][2]
  • string (literal string). [1][2]
  • monolingualtext (monolingual text values). [1][2]
  • time (timestamp / time value). [1][2]
  • globecoordinate (latitude/longitude + globe, precision). [1][2]
  • geo-shape (geographic shape pointing to map data). [2][3]
  • quantity (number with unit and uncertainty). [1][2]
  • commonsMedia (media file reference, e.g., Commons). [2][3]
  • url (URL literal). [2][3]
  • tabular-data (link to tabular data files). [2][3]
  • external-id (external identifier string). [2][3]

Commonly used extension / wiki-specific datatypes

  • Lexeme, Form, Sense (entity-link datatypes added by WikibaseLexeme for lexicographic data). [2]
  • EntitySchema (entity-schema link datatype used when EntitySchema extension is present). [2][3]
  • Mathematical expression (math/LaTeX expression — added by Math extension). [2]
  • Musical Notation / Score (musical-notation, e.g., LilyPond — added by Score/Music extensions). [2]

Notes

  • The core datatype set is extensible by installing/registering additional datatype handlers; the exact available set on any installation can be confirmed via that wiki’s Special:ListDatatypes (Wikidata’s running list is an example). [3]

Sources

  1. MediaWiki / Wikibase: DataModel.
  2. Wikidata — Help:Data type and Wikidata:Data model (extensions noted).
  3. Wikidata Special:ListDatatypes (live list of datatypes on the site).

In backend/src/types/wikibase-schema.ts, update WikibaseDataType union to include all supported Wikibase datatypes

  • Replace globe-coordinate with the exact core id globecoordinate
  • Add missing core types: geo-shape, tabular-data
  • Add common extension types (if your instance uses them): math, musical-notation, wikibase-lexeme, wikibase-form, wikibase-sense, EntitySchema
  • Confirm the final list against Special:ListDatatypes on your Wikibase instance
🤖 Prompt for AI Agents
In backend/src/types/wikibase-schema.ts around lines 26 to 38, the
WikibaseDataType union is incomplete and uses the wrong id for globe-coordinate;
update the union to replace 'globe-coordinate' with 'globecoordinate', add the
missing core types 'geo-shape' and 'tabular-data', and add the optional/common
extension types 'math', 'musical-notation', 'wikibase-lexeme', 'wikibase-form',
'wikibase-sense', and 'EntitySchema' (or remove any your instance does not
support); then regenerate the exported type so it reflects the updated union and
verify the final list against Special:ListDatatypes on your Wikibase instance.

Comment thread package.json
"@typescript-eslint/parser": "^8.44.0",
"bun-types": "1.2.22",
"elysia": "1.4.7",
"elysia": "1.4.8",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Pin elysia to a published version.

elysia@1.4.8 isn’t on the public registry yet (only experimental builds exist), so installs will fail and the workspace can’t bootstrap. Please stick with the latest stable tag (currently 1.4.7) or wait until 1.4.8 is released.

-      "elysia": "1.4.8",
+      "elysia": "1.4.7",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"elysia": "1.4.8",
"elysia": "1.4.7",
🤖 Prompt for AI Agents
In package.json around line 34, the dependency "elysia": "1.4.8" points to an
unpublished/experimental release causing installs to fail; change it to a
published stable version (e.g., "1.4.7") or otherwise pin to a released tag;
update the version string accordingly and run yarn/npm install to verify
bootstrapping succeeds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant