Local Provider
Setup
Wire the local provider by combining storage logic, procedures, and provider components.
Required parts
- a concrete local drive service implementation
- a contract implementation created from
localDriveRouterContract - a
LocalDriveProvideron the client - optional upload client wiring for direct uploads
In practice, most screens also need:
- a route handler for opening files
- a query layer that feeds
LocalDriveViewProvider - breadcrumbs derived from
getFolderParents - mutation commands such as rename, create folder, archive, and delete
Main entrypoints
import { localDriveRouterContract } from "@tulip-systems/drive-local/server";
import { createLocalDriveRouteHandler } from "@tulip-systems/drive-local/server";
import { LocalDriveProvider } from "@tulip-systems/drive-local/client";
import { createLocalDriveUploadClient } from "@tulip-systems/drive-local/client";
import { implement } from "@orpc/server";
import type { RPCContext } from "@/server/router/init";Recommended server pattern
Use contract-first implementation with your app's own RPC context and middleware:
const localDriveProcedure = implement(localDriveRouterContract)
.$context<RPCContext>()
.use(sessionMiddleware);Add the local drive schema
Define local-drive tables in your app so you control base columns and can add app-specific fields.
import { imageVariants, nodeSubtypes } from "@tulip-systems/drive-local";
import { imageDispositions } from "@tulip-systems/storage";
import { relations } from "drizzle-orm";
import { type AnyPgColumn, boolean, pgEnum, pgTable, unique } from "drizzle-orm/pg-core";
import { baseColumns } from "@/server/db/helpers";
import { storageAssets } from "@/server/storage/schema";
export const nodeTypeEnum = pgEnum("node_types", ["file", "folder"]);
export const nodes = pgTable("nodes", (t) => ({
...baseColumns,
name: t.text().notNull(),
namespace: t.text().notNull().default("global"),
type: nodeTypeEnum(),
subtype: t.text({ enum: nodeSubtypes }).notNull().default("other"),
size: t.integer(),
contentType: t.varchar({ length: 255 }),
readonly: boolean().default(false),
hidden: boolean().default(false),
archivedAt: t.timestamp(),
parentId: t.uuid().references((): AnyPgColumn => nodes.id, { onDelete: "cascade" }),
assetId: t.uuid().references(() => storageAssets.id, { onDelete: "cascade" }),
}));
export const nodesRelations = relations(nodes, ({ one, many }) => ({
parent: one(nodes, { fields: [nodes.parentId], references: [nodes.id], relationName: "parent" }),
children: many(nodes, { relationName: "parent" }),
urls: many(nodePresignedUrls),
variants: many(nodeVariants),
}));
export const nodeVariants = pgTable("node_variants", (t) => ({
...baseColumns,
nodeId: t.uuid().notNull().references(() => nodes.id, { onDelete: "cascade" }),
assetId: t.uuid().notNull().references(() => storageAssets.id, { onDelete: "cascade" }),
variant: t.text({ enum: imageVariants }).notNull(),
width: t.integer().notNull(),
}));
export const nodeVariantsRelations = relations(nodeVariants, ({ one }) => ({
node: one(nodes, { fields: [nodeVariants.nodeId], references: [nodes.id], relationName: "node" }),
}));
export const nodePresignedUrls = pgTable(
"node_presigned_urls",
(t) => ({
...baseColumns,
url: t.text().notNull().unique(),
variant: t.text({ enum: imageVariants }).notNull(),
disposition: t.text({ enum: imageDispositions }).notNull(),
expiresAt: t.timestamp().notNull(),
nodeId: t.uuid().notNull().references(() => nodes.id, { onDelete: "cascade" }),
variantId: t.uuid().references(() => nodeVariants.id, { onDelete: "set null" }),
}),
(t) => [unique("node_presigned_url_unique").on(t.nodeId, t.variant, t.disposition)],
);
export const nodePresignedUrlsRelations = relations(nodePresignedUrls, ({ one }) => ({
node: one(nodes, { fields: [nodePresignedUrls.nodeId], references: [nodes.id], relationName: "node" }),
}));Pass those tables to the Drizzle adapter when you create the service.
import { localDriveDrizzleAdapter } from "@tulip-systems/drive-local/drizzle";
import { LocalDrive } from "@tulip-systems/drive-local/server";
import { db } from "@/server/db/init";
import * as driveSchema from "@/server/drive-local/schema";
import { storage } from "@/server/storage/init";
import * as storageSchema from "@/server/storage/schema";
export const drive = new LocalDrive({
database: localDriveDrizzleAdapter({
db,
schema: { ...driveSchema, storageAssets: storageSchema.storageAssets },
}),
storage,
});Provider responsibilities
LocalDriveProvider composes selection, view state, drag and drop, permission checks, and
upload-zone state in one place.
Important LocalDriveProvider props
The provider accepts more than just namespace.
Useful props include:
namespacepermissionmetareadonlyinitialViewselectionselectionConditionsonMovedriveUploadClientuploadHooksonUploadCompletedonUploadFailedoptimisticdisabledparentId
Recommended setup order
- implement the server service
- implement the contract and expose route handlers
- build the upload client from those endpoints
- mount
LocalDriveProvider - feed
LocalDriveViewProviderwith data and commands