Skip to content

feat: add useAppObjects hook and record mutation hooks#221

Merged
hotlong merged 3 commits intomainfrom
copilot/build-core-infrastructure
Feb 8, 2026
Merged

feat: add useAppObjects hook and record mutation hooks#221
hotlong merged 3 commits intomainfrom
copilot/build-core-infrastructure

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 8, 2026

Completes the Phase 1 Foundation gaps: missing useAppObjects metadata hook and CRUD mutation hooks for records.

use-metadata.tsuseAppObjects

  • Resolves all ObjectDefinition entries for an app by name, with mock fallback
  • Uses Promise.allSettled for concurrent fetching

use-records.ts — Mutation hooks

  • useCreateRecord / useUpdateRecord / useDeleteRecord via @objectstack/client SDK
  • Each mutation invalidates the appropriate TanStack Query cache keys on success
const create = useCreateRecord({ objectName: 'lead' });
const update = useUpdateRecord({ objectName: 'lead', recordId: 'lead-001' });
const del = useDeleteRecord({ objectName: 'lead' });

create.mutate({ name: 'New Lead', email: 'new@example.com' });
update.mutate({ status: 'qualified' });
del.mutate('lead-001');

Tests

  • resolveFields() utility coverage (5 tests)
  • Hook export validation for metadata and record hooks (9 tests)
  • All 65 tests pass, 0 CodeQL alerts

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
objectos Error Error Feb 8, 2026 3:02pm

Request Review

Copilot AI and others added 2 commits February 8, 2026 14:59
…/delete)

- Add useAppObjects hook to use-metadata.ts for fetching all object definitions belonging to an app
- Add useCreateRecord, useUpdateRecord, useDeleteRecord mutation hooks to use-records.ts
- Add tests for resolveFields utility, metadata hooks exports, and record hooks exports
- All 65 tests pass, TypeScript type check passes

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…Objects

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Build core infrastructure for metadata-driven business apps feat: add useAppObjects hook and record mutation hooks Feb 8, 2026
Copilot AI requested a review from hotlong February 8, 2026 15:02
@hotlong hotlong marked this pull request as ready for review February 8, 2026 15:08
Copilot AI review requested due to automatic review settings February 8, 2026 15:08
@hotlong hotlong merged commit 8809a5e into main Feb 8, 2026
5 of 6 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds missing frontend data hooks in apps/web to complete Phase 1 “foundation” gaps by exposing app-scoped object metadata (useAppObjects) and CRUD mutation hooks for records, plus basic test scaffolding.

Changes:

  • Added useCreateRecord, useUpdateRecord, and useDeleteRecord TanStack Query mutation hooks with cache invalidation.
  • Added useAppObjects(appId) metadata hook to resolve an app’s object definitions concurrently with mock fallback.
  • Added tests for resolveFields() and “hook export exists” validations.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
apps/web/src/hooks/use-records.ts Adds record mutation hooks and query cache invalidation logic.
apps/web/src/hooks/use-metadata.ts Adds useAppObjects to fetch all object definitions for an app.
apps/web/src/types/metadata.ts Adds resolveFields() utility to normalize field definitions.
apps/web/src/tests/types/metadata.test.ts Adds unit tests for resolveFields().
apps/web/src/tests/hooks/use-records.test.ts Adds export/type presence tests for record hooks.
apps/web/src/tests/hooks/use-metadata.test.ts Adds export/type presence tests for metadata hooks.

Comment on lines 1 to 11
/**
* TanStack Query hooks for CRUD record operations.
*
* Uses the official @objectstack/client SDK to fetch from the server.
* Falls back to mock data when the server is unreachable.
*/

import { useQuery } from '@tanstack/react-query';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import type { RecordData, RecordListResponse } from '@/types/metadata';
import { objectStackClient } from '@/lib/api';
import { getMockRecords, getMockRecord } from '@/lib/mock-data';
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

The file header says record hooks "fall back to mock data when the server is unreachable", but the newly added mutation hooks (create/update/delete) do not have any mock fallback and will error if the backend is down. Please update the header comment to reflect the actual behavior, or implement a consistent fallback strategy for mutations if that’s intended.

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +89
const settled = await Promise.allSettled(
objectNames.map((name) =>
objectStackClient.meta.getObject(name).then((r) =>
r ? (r as ObjectDefinition) : getMockObjectDefinition(name),
).catch(() => getMockObjectDefinition(name)),
),
);
return settled
.filter((r): r is PromiseFulfilledResult<ObjectDefinition | undefined> =>
r.status === 'fulfilled')
.map((r) => r.value)
.filter((v): v is ObjectDefinition => !!v);
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

useAppObjects wraps each per-object fetch with .catch(() => getMockObjectDefinition(name)), so none of the mapped promises should reject. Given that, Promise.allSettled(...) plus filtering status === 'fulfilled' is redundant and adds complexity; consider switching to Promise.all(...) (or remove the inner catch if you truly want allSettled semantics).

Suggested change
const settled = await Promise.allSettled(
objectNames.map((name) =>
objectStackClient.meta.getObject(name).then((r) =>
r ? (r as ObjectDefinition) : getMockObjectDefinition(name),
).catch(() => getMockObjectDefinition(name)),
),
);
return settled
.filter((r): r is PromiseFulfilledResult<ObjectDefinition | undefined> =>
r.status === 'fulfilled')
.map((r) => r.value)
.filter((v): v is ObjectDefinition => !!v);
const results = await Promise.all(
objectNames.map((name) =>
objectStackClient.meta
.getObject(name)
.then((r) => (r ? (r as ObjectDefinition) : getMockObjectDefinition(name)))
.catch(() => getMockObjectDefinition(name)),
),
);
return results.filter((v): v is ObjectDefinition => !!v);

Copilot uses AI. Check for mistakes.
Comment on lines +79 to +112
export function useCreateRecord({ objectName }: UseCreateRecordOptions) {
const queryClient = useQueryClient();

return useMutation<RecordData, Error, Partial<RecordData>>({
mutationFn: async (data) => {
const result = await objectStackClient.data.create(objectName, data);
return (result?.record ?? data) as RecordData;
},
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: ['records', objectName] });
},
});
}

// ── Update record ───────────────────────────────────────────────

interface UseUpdateRecordOptions {
objectName: string;
recordId: string;
}

export function useUpdateRecord({ objectName, recordId }: UseUpdateRecordOptions) {
const queryClient = useQueryClient();

return useMutation<RecordData, Error, Partial<RecordData>>({
mutationFn: async (data) => {
const result = await objectStackClient.data.update(objectName, recordId, data);
return (result?.record ?? data) as RecordData;
},
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: ['records', objectName] });
void queryClient.invalidateQueries({ queryKey: ['record', objectName, recordId] });
},
});
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

The new mutation hooks add important cache-invalidation behavior (invalidate list queries; invalidate/remove detail queries), but the current tests only validate that the hooks are exported. Please add at least one behavioral test that asserts the expected query cache interactions (e.g., using a QueryClient wrapper + a mocked objectStackClient.data.* method) so regressions in query keys/invalidation are caught.

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +71 to +92
export function useAppObjects(appId: string | undefined) {
const appQuery = useAppDefinition(appId);

return useQuery<ObjectDefinition[]>({
queryKey: ['metadata', 'appObjects', appId],
queryFn: async () => {
const objectNames = appQuery.data?.objects ?? [];
const settled = await Promise.allSettled(
objectNames.map((name) =>
objectStackClient.meta.getObject(name).then((r) =>
r ? (r as ObjectDefinition) : getMockObjectDefinition(name),
).catch(() => getMockObjectDefinition(name)),
),
);
return settled
.filter((r): r is PromiseFulfilledResult<ObjectDefinition | undefined> =>
r.status === 'fulfilled')
.map((r) => r.value)
.filter((v): v is ObjectDefinition => !!v);
},
enabled: !!appId && !!appQuery.data,
});
Copy link

Copilot AI Feb 8, 2026

Choose a reason for hiding this comment

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

useAppObjects contains non-trivial behavior (dependency on useAppDefinition, concurrent fetch with fallback-to-mock per object, and a specific queryKey). Current tests only validate that the hook is exported; please add a behavioral test that validates the resolution/fallback logic (e.g., when one object fetch rejects, the hook still returns mock for that object).

Copilot generated this review using guidance from repository custom instructions.
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.

3 participants