Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: CI

on:
pull_request:
push:
branches: [main, dev]

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
verify:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_USER: tablecraft
POSTGRES_PASSWORD: tablecraft
POSTGRES_DB: tablecraft_test
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U tablecraft -d tablecraft_test"
--health-interval 5s
--health-timeout 5s
--health-retries 10
env:
DATABASE_URL: postgres://tablecraft:tablecraft@localhost:5432/tablecraft_test
steps:
- uses: actions/checkout@v4
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with:
persist-credentials: false
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.14
- run: bun install --frozen-lockfile
# Workspace packages publish types from dist. Build the foundational
# packages first so a clean checkout can typecheck downstream packages.
- name: Bootstrap workspace types
run: |
bun run --filter @tablecraft/engine build
bun run --filter @tablecraft/client build
bun run --filter @tablecraft/adapter-hono build
- run: bun run typecheck
- run: bun run test
- run: bun run --filter hono-example test:integration
- run: bun run build
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,6 @@ request.json

.explore
apps-explore/tnks-data-table
studio/
studio/

graphify-out
49 changes: 49 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,55 @@ All notable changes to TableCraft will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

## [0.2.27] - 2026-07-12

### Added

- Row grouping with controlled and imperative expansion APIs.
- Static and lazy tree adapters backed by `useTreeAdapter` and `useStaticAdapter`.
- Lazy-tree `sourceKey` lifecycle handling for safe source-switch refetches.
- Cross-page selection and export support across paginated views.
- Custom static-tree ID and child resolvers for arbitrary client hierarchies.
- Toolbar placement APIs (`toolbar` and `toolbarPlacement` options).
- Retail hierarchy demonstration and PostgreSQL integration coverage for the
end-to-end Hono example.

### Changed

- Stable lazy-tree adapter identity without unnecessary root refetches on
re-render.
- Deterministic retail sorting and pagination in the integration demo.
- Exact selected-ID validation when persisting tree selection state.
- Improved grouping callback consistency for controlled and uncontrolled
group expansion.
- Patched Next.js peer dependency floors to `>=15.5.18 <16` or `>=16.2.6`.

### Fixed

- Interactive cell controls no longer select or click rows on click or focus.
- Group rows are excluded from selection and row-action handlers.
- Stale tree requests and source-switch data are safely cancelled on unmount
and re-source.
- CSV formula injection protection by prefixing dangerous cells with a tab.
- CSV download test harness behavior aligned with the production export flow.
- Tree URL query parameter cleanup when a tree adapter unmounts.
- Duplicate and cyclic tree handling in the static adapter.

### Security

- `@tablecraft/adapter-next` now requires Next.js `>=15.5.18 <16` or
`>=16.2.6`, excluding releases affected by
[GHSA-gx5p-jg67-6x7h](https://github.com/advisories/GHSA-gx5p-jg67-6x7h) and
[GHSA-26hh-7cqf-hhc6](https://github.com/advisories/GHSA-26hh-7cqf-hhc6).
- Prevented unsafe CSV formula execution by neutralizing `=`, `+`, `-`, `@`,
tab, and carriage-return prefixes.
- Disabled persisted checkout credentials in the CI checkout step.

See [#38](https://github.com/jacksonkasi1/TableCraft/pull/38) for the full
release pull request.

## [0.1.1] - 2026-04-30

### Security
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,4 @@ For full guides, API reference, and examples:
## 📄 License

MIT

9 changes: 6 additions & 3 deletions apps/hono-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
"db:generate": "drizzle-kit generate",
"db:push": "drizzle-kit push",
"seed": "bun run src/db/seed.ts",
"test": "bun test",
"seed:retail": "bun run src/db/seed-retail.ts",
"test": "vitest run --passWithNoTests --exclude 'test/**'",
"test:integration": "vitest run test/retail.integration.test.ts",
"typecheck": "bun run --bun tsc --noEmit"
},
"dependencies": {
"hono": "^4.12.14",
"hono": "^4.12.18",
"drizzle-orm": "^0.45.2",
"postgres": "^3.4.9",
"dotenv": "^17.4.2",
Expand All @@ -21,6 +23,7 @@
},
"devDependencies": {
"drizzle-kit": "^0.31.10",
"bun-types": "latest"
"bun-types": "latest",
"vitest": "^4.1.5"
}
}
4 changes: 2 additions & 2 deletions apps/hono-example/src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import postgres from 'postgres';
import * as schema from './schema';
import 'dotenv/config';

const client = postgres(process.env.DATABASE_URL!);
export const db = drizzle(client, { schema });
export const dbClient = postgres(process.env.DATABASE_URL!);
export const db = drizzle(dbClient, { schema });
44 changes: 43 additions & 1 deletion apps/hono-example/src/db/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { pgTable, serial, text, integer, timestamp, boolean, jsonb, decimal, uuid } from 'drizzle-orm/pg-core';
import { pgTable, serial, text, integer, timestamp, boolean, jsonb, decimal, uuid, real } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';

// 1. Tenants (SaaS context)
Expand Down Expand Up @@ -53,6 +53,48 @@ export const orderItems = pgTable('order_items', {
unitPrice: decimal('unit_price', { precision: 10, scale: 2 }).notNull(),
});

// ─── Retail tree (Regions → Stores → Products) ────────────────────────────

export const retailRegions = pgTable('retail_regions', {
id: text('id').primaryKey(),
name: text('name').notNull(),
totalSales: integer('total_sales').notNull().default(0),
revenue: integer('revenue').notNull().default(0),
stores: integer('stores').notNull().default(0),
avgRating: real('avg_rating'),
});

export const retailStores = pgTable('retail_stores', {
id: text('id').primaryKey(),
regionId: text('region_id').notNull().references(() => retailRegions.id),
name: text('name').notNull(),
totalSales: integer('total_sales').notNull().default(0),
revenue: integer('revenue').notNull().default(0),
avgRating: real('avg_rating'),
});

export const retailProducts = pgTable('retail_products', {
id: text('id').primaryKey(),
storeId: text('store_id').notNull().references(() => retailStores.id),
name: text('name').notNull(),
totalSales: integer('total_sales').notNull().default(0),
revenue: integer('revenue').notNull().default(0),
avgRating: real('avg_rating'),
});

export const retailRegionRelations = relations(retailRegions, ({ many }) => ({
stores: many(retailStores),
}));

export const retailStoreRelations = relations(retailStores, ({ one, many }) => ({
region: one(retailRegions, { fields: [retailStores.regionId], references: [retailRegions.id] }),
products: many(retailProducts),
}));

export const retailProductRelations = relations(retailProducts, ({ one }) => ({
store: one(retailStores, { fields: [retailProducts.storeId], references: [retailStores.id] }),
}));

// --- Relations ---

export const tenantRelations = relations(tenants, ({ many }) => ({
Expand Down
Loading
Loading