skeleton schemas

This commit is contained in:
Harivansh Rathi 2026-04-01 03:11:43 +00:00
parent 0032487ca1
commit 4f174ec3a8
9 changed files with 470 additions and 42 deletions

View file

@ -0,0 +1,42 @@
# `@betternas/contracts`
This package is the machine-readable source of truth for shared interfaces in
betterNAS.
Use it to keep the four product parts aligned:
- NAS node
- control plane
- local device
- cloud/web layer
## What belongs here
- shared TypeScript types
- route constants
- JSON schemas for payloads we want to validate outside TypeScript
## What does not belong here
- business logic
- per-service config
- implementation-specific helpers
## Current contract layers
- [`src/control-plane.ts`](/home/rathi/Documents/GitHub/betterNAS/packages/contracts/src/control-plane.ts)
- current runtime scaffold for health and version
- [`src/foundation.ts`](/home/rathi/Documents/GitHub/betterNAS/packages/contracts/src/foundation.ts)
- first product-level entities and route constants for node, mount, and cloud flows
- [`schemas/`](/home/rathi/Documents/GitHub/betterNAS/packages/contracts/schemas)
- JSON schema mirrors for the first shared entities
## Change rules
1. Shared API shape changes happen here first.
2. If the boundary changes, also update
[`docs/architecture.md`](/home/rathi/Documents/GitHub/betterNAS/docs/architecture.md).
3. Prefer additive changes until all four parts are live.
4. Do not put Nextcloud-only assumptions into the core contracts unless the
field is explicitly part of the cloud adapter.
5. Keep the first version narrow. Over-modeling early is another form of drift.

View file

@ -0,0 +1,30 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://betternas.local/schemas/cloud-profile.schema.json",
"type": "object",
"additionalProperties": false,
"required": [
"id",
"exportId",
"provider",
"baseUrl",
"path"
],
"properties": {
"id": {
"type": "string"
},
"exportId": {
"type": "string"
},
"provider": {
"const": "nextcloud"
},
"baseUrl": {
"type": "string"
},
"path": {
"type": "string"
}
}
}

View file

@ -0,0 +1,41 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://betternas.local/schemas/mount-profile.schema.json",
"type": "object",
"additionalProperties": false,
"required": [
"id",
"exportId",
"protocol",
"displayName",
"mountUrl",
"readonly",
"credentialMode"
],
"properties": {
"id": {
"type": "string"
},
"exportId": {
"type": "string"
},
"protocol": {
"const": "webdav"
},
"displayName": {
"type": "string"
},
"mountUrl": {
"type": "string"
},
"readonly": {
"type": "boolean"
},
"credentialMode": {
"enum": [
"session-token",
"app-password"
]
}
}
}

View file

@ -0,0 +1,52 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://betternas.local/schemas/nas-node.schema.json",
"type": "object",
"additionalProperties": false,
"required": [
"id",
"machineId",
"displayName",
"agentVersion",
"status",
"lastSeenAt",
"directAddress",
"relayAddress"
],
"properties": {
"id": {
"type": "string"
},
"machineId": {
"type": "string"
},
"displayName": {
"type": "string"
},
"agentVersion": {
"type": "string"
},
"status": {
"enum": [
"online",
"offline",
"degraded"
]
},
"lastSeenAt": {
"type": "string"
},
"directAddress": {
"type": [
"string",
"null"
]
},
"relayAddress": {
"type": [
"string",
"null"
]
}
}
}

View file

@ -0,0 +1,47 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://betternas.local/schemas/storage-export.schema.json",
"type": "object",
"additionalProperties": false,
"required": [
"id",
"nasNodeId",
"label",
"path",
"protocols",
"capacityBytes",
"tags"
],
"properties": {
"id": {
"type": "string"
},
"nasNodeId": {
"type": "string"
},
"label": {
"type": "string"
},
"path": {
"type": "string"
},
"protocols": {
"type": "array",
"items": {
"const": "webdav"
}
},
"capacityBytes": {
"type": [
"number",
"null"
]
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
}
}
}

View file

@ -0,0 +1,97 @@
export const FOUNDATION_API_ROUTES = {
registerNode: "/api/v1/nodes/register",
nodeHeartbeat: "/api/v1/nodes/:nodeId/heartbeat",
listExports: "/api/v1/exports",
issueMountProfile: "/api/v1/mount-profiles/issue",
issueCloudProfile: "/api/v1/cloud-profiles/issue"
} as const;
export type NasNodeStatus = "online" | "offline" | "degraded";
export type StorageAccessProtocol = "webdav";
export type AccessMode = "mount" | "cloud";
export type AccessPrincipalType = "user" | "device";
export type MountCredentialMode = "session-token" | "app-password";
export type CloudProvider = "nextcloud";
export interface NasNode {
id: string;
machineId: string;
displayName: string;
agentVersion: string;
status: NasNodeStatus;
lastSeenAt: string;
directAddress: string | null;
relayAddress: string | null;
}
export interface StorageExport {
id: string;
nasNodeId: string;
label: string;
path: string;
protocols: StorageAccessProtocol[];
capacityBytes: number | null;
tags: string[];
}
export interface AccessGrant {
id: string;
exportId: string;
principalType: AccessPrincipalType;
principalId: string;
modes: AccessMode[];
readonly: boolean;
}
export interface MountProfile {
id: string;
exportId: string;
protocol: "webdav";
displayName: string;
mountUrl: string;
readonly: boolean;
credentialMode: MountCredentialMode;
}
export interface CloudProfile {
id: string;
exportId: string;
provider: CloudProvider;
baseUrl: string;
path: string;
}
export interface StorageExportInput {
label: string;
path: string;
protocols: StorageAccessProtocol[];
capacityBytes: number | null;
tags: string[];
}
export interface NodeRegistrationRequest {
machineId: string;
displayName: string;
agentVersion: string;
directAddress: string | null;
relayAddress: string | null;
exports: StorageExportInput[];
}
export interface NodeHeartbeatRequest {
nodeId: string;
status: NasNodeStatus;
lastSeenAt: string;
}
export interface MountProfileRequest {
userId: string;
deviceId: string;
exportId: string;
}
export interface CloudProfileRequest {
userId: string;
exportId: string;
provider: CloudProvider;
}

View file

@ -1,2 +1,2 @@
export * from "./control-plane.js";
export * from "./foundation.js";