@starkeep/protocol-primitives 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/dist/index.cjs +627 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +316 -0
- package/dist/index.d.ts +316 -0
- package/dist/index.js +594 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/identifiers/types.ts","../src/identifiers/ulid.ts","../src/hlc/clock.ts","../src/hlc/compare.ts","../src/hlc/serialize.ts","../src/records/builders.ts","../src/schema/registry.ts","../src/schema/validator.ts","../src/types/errors.ts","../src/types/common.ts","../src/types/core-types.ts","../src/storage/object-keys.ts"],"sourcesContent":["export * from \"./identifiers/index.js\";\nexport * from \"./hlc/index.js\";\nexport * from \"./records/index.js\";\nexport * from \"./schema/index.js\";\nexport * from \"./types/index.js\";\nexport * from \"./storage/index.js\";\n","export type StarkeepId = string & { readonly __brand: unique symbol };\n\nexport function createStarkeepId(value: string): StarkeepId {\n return value as StarkeepId;\n}\n\nexport function isStarkeepId(value: unknown): value is StarkeepId {\n return typeof value === \"string\" && value.length === 26;\n}\n","import { ulid, monotonicFactory } from \"ulidx\";\nimport type { StarkeepId } from \"./types.js\";\nimport { createStarkeepId } from \"./types.js\";\n\nconst monotonic = monotonicFactory();\n\nexport function generateId(): StarkeepId {\n return createStarkeepId(monotonic());\n}\n\nexport function generateIdAt(timestamp: number): StarkeepId {\n return createStarkeepId(ulid(timestamp));\n}\n","import type { HLCClock, HLCTimestamp } from \"./types.js\";\n\nexport interface ClockState {\n wallTime: number;\n counter: number;\n}\n\nexport interface ClockOptions {\n nodeId: string;\n wallClockFunction?: () => number;\n /**\n * Pre-seed the clock from persisted state so a post-restart HLC never\n * emits a timestamp earlier than one the node already sent.\n */\n initialState?: ClockState;\n /**\n * Invoked on every state change. Callers typically debounce and persist\n * to a SyncStateStore.\n */\n onTick?: (state: ClockState) => void;\n}\n\nexport function createHLCClock(options: ClockOptions): HLCClock {\n const { nodeId, wallClockFunction = Date.now, initialState, onTick } = options;\n\n let lastWallTime = initialState?.wallTime ?? 0;\n let lastCounter = initialState?.counter ?? 0;\n\n function emit(): void {\n if (onTick) onTick({ wallTime: lastWallTime, counter: lastCounter });\n }\n\n function now(): HLCTimestamp {\n const physicalTime = wallClockFunction();\n if (physicalTime > lastWallTime) {\n lastWallTime = physicalTime;\n lastCounter = 0;\n } else {\n lastCounter++;\n }\n emit();\n return { wallTime: lastWallTime, counter: lastCounter, nodeId };\n }\n\n function send(): HLCTimestamp {\n return now();\n }\n\n function receive(remote: HLCTimestamp): HLCTimestamp {\n const physicalTime = wallClockFunction();\n if (physicalTime > lastWallTime && physicalTime > remote.wallTime) {\n lastWallTime = physicalTime;\n lastCounter = 0;\n } else if (remote.wallTime > lastWallTime) {\n lastWallTime = remote.wallTime;\n lastCounter = remote.counter + 1;\n } else if (lastWallTime === remote.wallTime) {\n lastCounter = Math.max(lastCounter, remote.counter) + 1;\n } else {\n lastCounter++;\n }\n emit();\n return { wallTime: lastWallTime, counter: lastCounter, nodeId };\n }\n\n return { now, send, receive };\n}\n","import type { HLCTimestamp } from \"./types.js\";\n\n/** Identity element for HLC ordering. Useful as a default watermark / \"never seen\". */\nexport const ZERO_HLC: HLCTimestamp = { wallTime: 0, counter: 0, nodeId: \"\" };\n\nexport function compareHLC(a: HLCTimestamp, b: HLCTimestamp): -1 | 0 | 1 {\n if (a.wallTime < b.wallTime) return -1;\n if (a.wallTime > b.wallTime) return 1;\n if (a.counter < b.counter) return -1;\n if (a.counter > b.counter) return 1;\n if (a.nodeId < b.nodeId) return -1;\n if (a.nodeId > b.nodeId) return 1;\n return 0;\n}\n\nexport function maxHLC(a: HLCTimestamp, b: HLCTimestamp): HLCTimestamp {\n return compareHLC(a, b) >= 0 ? a : b;\n}\n","import type { HLCTimestamp } from \"./types.js\";\n\nconst SEPARATOR = \":\";\n\nexport function serializeHLC(timestamp: HLCTimestamp): string {\n const wallTimeHex = timestamp.wallTime.toString(16).padStart(12, \"0\");\n const counterHex = timestamp.counter.toString(16).padStart(4, \"0\");\n return `${wallTimeHex}${SEPARATOR}${counterHex}${SEPARATOR}${timestamp.nodeId}`;\n}\n\nexport function deserializeHLC(serializedString: string): HLCTimestamp {\n const parts = serializedString.split(SEPARATOR);\n if (parts.length !== 3) {\n throw new Error(`Invalid HLC timestamp string: ${serializedString}`);\n }\n return {\n wallTime: parseInt(parts[0], 16),\n counter: parseInt(parts[1], 16),\n nodeId: parts[2],\n };\n}\n","import type { StarkeepId } from \"../identifiers/types.js\";\nimport type { HLCClock } from \"../hlc/types.js\";\nimport { generateId } from \"../identifiers/ulid.js\";\nimport { type DataRecord } from \"./types.js\";\n\nexport interface CreateDataRecordInput {\n type: string;\n ownerId: string;\n originAppId: string;\n contentHash: string;\n objectStorageKey: string;\n mimeType: string;\n sizeBytes: number;\n originalFilename?: string | null;\n parentId?: StarkeepId | null;\n}\n\nexport function createDataRecord(input: CreateDataRecordInput, clock: HLCClock): DataRecord {\n const now = clock.now();\n return {\n id: generateId(),\n kind: \"data\",\n type: input.type,\n createdAt: now,\n updatedAt: now,\n ownerId: input.ownerId,\n deletedAt: null,\n version: 1,\n contentHash: input.contentHash,\n objectStorageKey: input.objectStorageKey,\n mimeType: input.mimeType,\n sizeBytes: input.sizeBytes,\n originalFilename: input.originalFilename ?? null,\n originAppId: input.originAppId,\n parentId: input.parentId ?? null,\n };\n}\n","import type * as v from \"valibot\";\n\nexport interface TypeDefinition {\n name: string;\n namespace: string;\n schema: v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>>;\n}\n\nexport interface TypeRegistry {\n register(definition: TypeDefinition): void;\n get(namespace: string, name: string): TypeDefinition | undefined;\n getByKey(key: string): TypeDefinition | undefined;\n has(namespace: string, name: string): boolean;\n list(): TypeDefinition[];\n}\n\nexport function createTypeRegistry(): TypeRegistry {\n const types = new Map<string, TypeDefinition>();\n\n return {\n register(definition: TypeDefinition): void {\n const key = `${definition.namespace}:${definition.name}`;\n if (types.has(key)) {\n throw new Error(`Type \"${key}\" is already registered`);\n }\n types.set(key, definition);\n },\n\n get(namespace: string, name: string): TypeDefinition | undefined {\n return types.get(`${namespace}:${name}`);\n },\n\n getByKey(key: string): TypeDefinition | undefined {\n return types.get(key);\n },\n\n has(namespace: string, name: string): boolean {\n return types.has(`${namespace}:${name}`);\n },\n\n list(): TypeDefinition[] {\n return Array.from(types.values());\n },\n };\n}\n","import * as v from \"valibot\";\n\nconst hlcTimestampSchema = v.object({\n wallTime: v.pipe(v.number(), v.integer(), v.minValue(0)),\n counter: v.pipe(v.number(), v.integer(), v.minValue(0)),\n nodeId: v.pipe(v.string(), v.minLength(1)),\n});\n\nconst baseRecordSchema = v.object({\n id: v.pipe(v.string(), v.length(26)),\n type: v.pipe(v.string(), v.minLength(1)),\n createdAt: hlcTimestampSchema,\n updatedAt: hlcTimestampSchema,\n ownerId: v.pipe(v.string(), v.minLength(1)),\n deletedAt: v.nullable(hlcTimestampSchema),\n version: v.pipe(v.number(), v.integer(), v.minValue(1)),\n});\n\nexport const dataRecordSchema = v.object({\n ...baseRecordSchema.entries,\n kind: v.literal(\"data\"),\n contentHash: v.pipe(v.string(), v.minLength(1)),\n objectStorageKey: v.pipe(v.string(), v.minLength(1)),\n mimeType: v.pipe(v.string(), v.minLength(1)),\n sizeBytes: v.pipe(v.number(), v.integer(), v.minValue(0)),\n originalFilename: v.nullable(v.string()),\n originAppId: v.pipe(v.string(), v.minLength(1)),\n parentId: v.nullable(v.string()),\n});\n\nexport function validateDataRecord(data: unknown) {\n return v.safeParse(dataRecordSchema, data);\n}\n","export class StarkeepError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"StarkeepError\";\n }\n}\n\nexport class ValidationError extends StarkeepError {\n constructor(message: string, cause?: unknown) {\n super(message, \"VALIDATION_ERROR\", cause);\n this.name = \"ValidationError\";\n }\n}\n\nexport class NotFoundError extends StarkeepError {\n constructor(entity: string, id: string) {\n super(`${entity} not found: ${id}`, \"NOT_FOUND\");\n this.name = \"NotFoundError\";\n }\n}\n\nexport class ConflictError extends StarkeepError {\n constructor(message: string) {\n super(message, \"CONFLICT\");\n this.name = \"ConflictError\";\n }\n}\n","export type Result<T, E = Error> =\n | { ok: true; value: T }\n | { ok: false; error: E };\n\nexport function ok<T>(value: T): Result<T, never> {\n return { ok: true, value };\n}\n\nexport function err<E>(error: E): Result<never, E> {\n return { ok: false, error };\n}\n\nexport interface PaginationOptions {\n limit: number;\n cursor?: string;\n}\n\nexport interface PaginatedResult<T> {\n items: T[];\n nextCursor: string | null;\n hasMore: boolean;\n}\n","/**\n * Single source of truth for Starkeep's shared core type system.\n *\n * Two registries derived from one place:\n * - EXTENSIONS: lowercase file extension → mapped Category. Identification is\n * by extension; MIME is never authoritative.\n * - CATEGORIES: the user-facing organizational layer (mobile-style: Images,\n * Videos, Documents…). Each mapped category owns one metadata table holding\n * cross-format properties derivable from the file bytes.\n *\n * A record's `type` is the lowercase extension verbatim (e.g. \"jpg\", \"md\",\n * \"xyz\"), even when the extension is unmapped. Its category is derived:\n * `category = EXTENSIONS[ext] ?? \"other\"`. `other` is the terminal catch-all\n * for unmapped / extension-less files — Drive-only, no metadata table, and no\n * installable app can ever be granted it (apps may only declare extensions that\n * are present in EXTENSIONS, and the unmapped set IS the `other` set).\n *\n * Every relevant system — manifest validation, IAM emission, DSQL schema-init,\n * SQLite bootstrap, object-key construction, and the local data-server's access\n * path — derives its view from the registries below. Adding an extension or a\n * metadata column is a one-file edit here. There is no runtime registration\n * path — apps cannot register new types or extend metadata columns.\n */\n\nexport type LogicalColumnType =\n | \"integer\"\n | \"bigint\"\n | \"real\"\n | \"text\"\n | \"timestamp\"\n | \"boolean\";\n\nexport interface CoreTypeMetadataColumn {\n name: string;\n type: LogicalColumnType;\n /** Defaults to true. Set explicitly when the column must be NOT NULL. */\n nullable?: boolean;\n}\n\n/** The fixed set of categories. `other` is the terminal catch-all (last). */\nexport type Category =\n | \"image\"\n | \"video\"\n | \"audio\"\n | \"document\"\n | \"text\"\n | \"code\"\n | \"font\"\n | \"archive\"\n | \"data\"\n | \"model3d\"\n | \"other\";\n\nexport interface CategoryDef {\n id: Category;\n description: string;\n /** Cross-format metadata columns. Empty for `other` (no metadata table). */\n metadataColumns: CoreTypeMetadataColumn[];\n}\n\nconst IMAGE_METADATA_COLUMNS: CoreTypeMetadataColumn[] = [\n { name: \"width\", type: \"integer\" },\n { name: \"height\", type: \"integer\" },\n { name: \"color_space\", type: \"text\" },\n { name: \"orientation\", type: \"integer\" },\n { name: \"captured_at\", type: \"timestamp\" },\n { name: \"camera_make\", type: \"text\" },\n { name: \"camera_model\", type: \"text\" },\n { name: \"lens_model\", type: \"text\" },\n { name: \"f_number\", type: \"real\" },\n { name: \"exposure_time\", type: \"text\" },\n { name: \"iso\", type: \"integer\" },\n { name: \"focal_length_mm\", type: \"real\" },\n { name: \"gps_lat\", type: \"real\" },\n { name: \"gps_lon\", type: \"real\" },\n];\n\nconst VIDEO_METADATA_COLUMNS: CoreTypeMetadataColumn[] = [\n { name: \"width\", type: \"integer\" },\n { name: \"height\", type: \"integer\" },\n { name: \"duration_ms\", type: \"bigint\" },\n { name: \"frame_rate\", type: \"real\" },\n { name: \"video_codec\", type: \"text\" },\n { name: \"audio_codec\", type: \"text\" },\n { name: \"bitrate\", type: \"bigint\" },\n { name: \"captured_at\", type: \"timestamp\" },\n { name: \"gps_lat\", type: \"real\" },\n { name: \"gps_lon\", type: \"real\" },\n];\n\nconst AUDIO_METADATA_COLUMNS: CoreTypeMetadataColumn[] = [\n { name: \"duration_ms\", type: \"bigint\" },\n { name: \"sample_rate\", type: \"integer\" },\n { name: \"channels\", type: \"integer\" },\n { name: \"bitrate\", type: \"bigint\" },\n { name: \"codec\", type: \"text\" },\n { name: \"title\", type: \"text\" },\n { name: \"artist\", type: \"text\" },\n { name: \"album\", type: \"text\" },\n { name: \"track_number\", type: \"integer\" },\n { name: \"year\", type: \"integer\" },\n { name: \"genre\", type: \"text\" },\n];\n\nconst DOCUMENT_METADATA_COLUMNS: CoreTypeMetadataColumn[] = [\n { name: \"page_count\", type: \"integer\" },\n { name: \"word_count\", type: \"integer\" },\n { name: \"author\", type: \"text\" },\n { name: \"title\", type: \"text\" },\n { name: \"created_at\", type: \"timestamp\" },\n { name: \"modified_at\", type: \"timestamp\" },\n { name: \"language\", type: \"text\" },\n];\n\nconst TEXT_METADATA_COLUMNS: CoreTypeMetadataColumn[] = [\n { name: \"line_count\", type: \"integer\" },\n { name: \"encoding\", type: \"text\" },\n];\n\nconst CODE_METADATA_COLUMNS: CoreTypeMetadataColumn[] = [\n { name: \"line_count\", type: \"integer\" },\n { name: \"encoding\", type: \"text\" },\n];\n\nconst FONT_METADATA_COLUMNS: CoreTypeMetadataColumn[] = [\n { name: \"family\", type: \"text\" },\n { name: \"subfamily\", type: \"text\" },\n { name: \"weight\", type: \"integer\" },\n { name: \"style\", type: \"text\" },\n { name: \"format\", type: \"text\" },\n];\n\nconst ARCHIVE_METADATA_COLUMNS: CoreTypeMetadataColumn[] = [\n { name: \"entry_count\", type: \"integer\" },\n { name: \"uncompressed_bytes\", type: \"bigint\" },\n { name: \"compression\", type: \"text\" },\n];\n\nconst DATA_METADATA_COLUMNS: CoreTypeMetadataColumn[] = [\n { name: \"row_count\", type: \"bigint\" },\n { name: \"column_count\", type: \"integer\" },\n { name: \"schema_json\", type: \"text\" },\n];\n\nconst MODEL3D_METADATA_COLUMNS: CoreTypeMetadataColumn[] = [\n { name: \"vertex_count\", type: \"bigint\" },\n { name: \"face_count\", type: \"bigint\" },\n { name: \"has_textures\", type: \"boolean\" },\n { name: \"has_animation\", type: \"boolean\" },\n];\n\nexport const CATEGORIES: readonly CategoryDef[] = [\n { id: \"image\", description: \"Raster and vector still images. Bytes in object storage; metadata holds dimensions, capture time, EXIF, orientation.\", metadataColumns: IMAGE_METADATA_COLUMNS },\n { id: \"video\", description: \"Moving-picture containers.\", metadataColumns: VIDEO_METADATA_COLUMNS },\n { id: \"audio\", description: \"Sound-only containers.\", metadataColumns: AUDIO_METADATA_COLUMNS },\n { id: \"document\", description: \"Office-suite and structured documents meant for human reading (incl. markdown, html, spreadsheets).\", metadataColumns: DOCUMENT_METADATA_COLUMNS },\n { id: \"text\", description: \"Plain-text formats: prose, config, structured serialization.\", metadataColumns: TEXT_METADATA_COLUMNS },\n { id: \"code\", description: \"Programming-language source files.\", metadataColumns: CODE_METADATA_COLUMNS },\n { id: \"font\", description: \"Typeface files.\", metadataColumns: FONT_METADATA_COLUMNS },\n { id: \"archive\", description: \"Compressed bundles.\", metadataColumns: ARCHIVE_METADATA_COLUMNS },\n { id: \"data\", description: \"Tabular / columnar / embedded-DB data files.\", metadataColumns: DATA_METADATA_COLUMNS },\n { id: \"model3d\", description: \"3D meshes and scenes.\", metadataColumns: MODEL3D_METADATA_COLUMNS },\n { id: \"other\", description: \"Terminal catch-all for unmapped or extension-less files. Drive-only; no metadata table; no installable-app grants.\", metadataColumns: [] },\n];\n\n/**\n * Extension (lowercase, no dot) → mapped category. `other` is NEVER a value\n * here — it is exclusively the `?? \"other\"` fallback in `categoryOf`, which is\n * why no app can ever declare an `other` extension.\n */\nexport const EXTENSIONS: Readonly<Record<string, Exclude<Category, \"other\">>> = {\n // image\n jpg: \"image\", jpeg: \"image\", png: \"image\", gif: \"image\", webp: \"image\",\n heic: \"image\", heif: \"image\", avif: \"image\", bmp: \"image\", tiff: \"image\",\n tif: \"image\", svg: \"image\", ico: \"image\",\n // video\n mp4: \"video\", mov: \"video\", m4v: \"video\", avi: \"video\", mkv: \"video\",\n webm: \"video\", mpg: \"video\", mpeg: \"video\", wmv: \"video\", flv: \"video\",\n // audio\n mp3: \"audio\", wav: \"audio\", flac: \"audio\", aac: \"audio\", ogg: \"audio\",\n oga: \"audio\", opus: \"audio\", m4a: \"audio\", aiff: \"audio\", wma: \"audio\",\n // document\n pdf: \"document\", md: \"document\", markdown: \"document\", html: \"document\",\n htm: \"document\", doc: \"document\", docx: \"document\", xls: \"document\",\n xlsx: \"document\", ppt: \"document\", pptx: \"document\", odt: \"document\",\n ods: \"document\", odp: \"document\", rtf: \"document\", epub: \"document\",\n pages: \"document\", numbers: \"document\", key: \"document\",\n // text\n txt: \"text\", log: \"text\", env: \"text\", json: \"text\", jsonc: \"text\",\n xml: \"text\", yaml: \"text\", yml: \"text\", toml: \"text\", ini: \"text\",\n conf: \"text\", tex: \"text\", rst: \"text\", adoc: \"text\",\n // code\n js: \"code\", mjs: \"code\", cjs: \"code\", ts: \"code\", tsx: \"code\", jsx: \"code\",\n py: \"code\", rb: \"code\", go: \"code\", rs: \"code\", java: \"code\", kt: \"code\",\n swift: \"code\", c: \"code\", h: \"code\", cpp: \"code\", hpp: \"code\", cs: \"code\",\n php: \"code\", sh: \"code\", bash: \"code\", zsh: \"code\", fish: \"code\",\n ps1: \"code\", lua: \"code\", r: \"code\", sql: \"code\", css: \"code\", scss: \"code\",\n sass: \"code\", less: \"code\", vue: \"code\", svelte: \"code\",\n dockerfile: \"code\", gitignore: \"code\", gitattributes: \"code\",\n // font\n ttf: \"font\", otf: \"font\", woff: \"font\", woff2: \"font\", eot: \"font\",\n // archive\n zip: \"archive\", tar: \"archive\", gz: \"archive\", tgz: \"archive\",\n bz2: \"archive\", tbz2: \"archive\", xz: \"archive\", txz: \"archive\",\n \"7z\": \"archive\", rar: \"archive\", zst: \"archive\",\n // data\n csv: \"data\", tsv: \"data\", parquet: \"data\", arrow: \"data\", feather: \"data\",\n sqlite: \"data\", sqlite3: \"data\", db: \"data\", jsonl: \"data\", ndjson: \"data\",\n hdf5: \"data\", h5: \"data\", orc: \"data\",\n // model3d\n obj: \"model3d\", stl: \"model3d\", gltf: \"model3d\", glb: \"model3d\",\n fbx: \"model3d\", dae: \"model3d\", \"3ds\": \"model3d\", blend: \"model3d\",\n ply: \"model3d\", usd: \"model3d\", usdz: \"model3d\",\n};\n\nexport const CATEGORY_IDS: readonly Category[] = CATEGORIES.map((c) => c.id);\n\n/**\n * Categories an installable app may be granted — every category a real\n * extension can map to, i.e. all categories EXCEPT `other`. Drive's all-access\n * (`fileAccessAll`) covers `other` as well, via its `shared/*` IAM ceiling.\n */\nexport const APP_GRANTABLE_CATEGORIES: readonly Category[] = CATEGORY_IDS.filter(\n (c) => c !== \"other\",\n);\n\n/** The set of known (mapped) extensions. */\nexport const KNOWN_EXTENSIONS: ReadonlySet<string> = new Set(Object.keys(EXTENSIONS));\n\n/**\n * Derived category for a record's extension/type. Unmapped or empty → \"other\".\n * Accepts the extension with or without a leading dot, any case.\n */\nexport function categoryOf(ext: string): Category {\n const normalized = ext.toLowerCase().replace(/^\\./, \"\");\n return EXTENSIONS[normalized] ?? \"other\";\n}\n\nexport function getCategory(id: string): CategoryDef | undefined {\n return CATEGORIES.find((c) => c.id === id);\n}\n\nexport function isCategoryId(id: string): id is Category {\n return CATEGORIES.some((c) => c.id === id);\n}\n\nfunction pgColumnType(t: LogicalColumnType): string {\n switch (t) {\n case \"integer\": return \"integer\";\n case \"bigint\": return \"bigint\";\n case \"real\": return \"double precision\";\n case \"text\": return \"text\";\n case \"timestamp\": return \"timestamptz\";\n case \"boolean\": return \"boolean\";\n }\n}\n\nfunction sqliteColumnType(t: LogicalColumnType): string {\n switch (t) {\n case \"integer\": return \"INTEGER\";\n case \"bigint\": return \"INTEGER\";\n case \"real\": return \"REAL\";\n case \"text\": return \"TEXT\";\n case \"timestamp\": return \"TEXT\";\n case \"boolean\": return \"INTEGER\";\n }\n}\n\n/**\n * Emits a `CREATE TABLE IF NOT EXISTS shared.record_<category>_metadata`\n * statement for DSQL. Single non-PL/pgSQL statement, no FK constraints — see\n * `dsql-schema-init.ts` for the DSQL surface caveats. Callers must skip the\n * `other` category (no metadata table).\n */\nexport function pgMetadataDdl(c: CategoryDef): string {\n const cols = [\n ` record_id text PRIMARY KEY`,\n ...c.metadataColumns.map((col) => {\n const nullSuffix = col.nullable === false ? \" NOT NULL\" : \"\";\n return ` ${col.name} ${pgColumnType(col.type)}${nullSuffix}`;\n }),\n ];\n return `CREATE TABLE IF NOT EXISTS ${pgMetadataTableName(c.id)} (\\n${cols.join(\",\\n\")}\\n )`;\n}\n\n/**\n * Emits a `CREATE TABLE IF NOT EXISTS shared_record_<category>_metadata`\n * statement for the local SQLite bootstrap. Callers must skip the `other`\n * category (no metadata table).\n */\nexport function sqliteMetadataDdl(c: CategoryDef): string {\n const cols = [\n ` record_id TEXT PRIMARY KEY`,\n ...c.metadataColumns.map((col) => {\n const nullSuffix = col.nullable === false ? \" NOT NULL\" : \"\";\n return ` ${col.name} ${sqliteColumnType(col.type)}${nullSuffix}`;\n }),\n ];\n return `CREATE TABLE IF NOT EXISTS ${sqliteMetadataTableName(c.id)} (\\n${cols.join(\",\\n\")}\\n )`;\n}\n\n/**\n * Returns the SQLite metadata table name for a record's type/extension or a\n * category id. The category is derived when an extension is passed, so storage\n * adapters that hold only `record.type` route to the correct per-category\n * table. Passing the literal `\"other\"` (or an unmapped extension) yields the\n * `other` table name, which is never created — callers must not write metadata\n * for `other` records.\n */\nexport function sqliteMetadataTableName(typeOrCategory: string): string {\n const category = isCategoryId(typeOrCategory) ? typeOrCategory : categoryOf(typeOrCategory);\n return `shared_record_${category}_metadata`;\n}\n\n/** DSQL/Postgres counterpart of {@link sqliteMetadataTableName}. */\nexport function pgMetadataTableName(typeOrCategory: string): string {\n const category = isCategoryId(typeOrCategory) ? typeOrCategory : categoryOf(typeOrCategory);\n return `shared.record_${category}_metadata`;\n}\n","// Object-storage key construction. Single source of truth for both the\n// local SDK and the cloud Lambda handler so the two stay aligned.\n//\n// Two namespaces:\n// shared/<category>/<2-char>/<hash> data record blobs, bucketed by the\n// derived category (image, document, …,\n// other). Governed by the per-app\n// access path + the per-category IAM\n// ceiling (shared/<category>/* per\n// granted category; Drive gets shared/*).\n// apps/<appId>/syncable/<...> app-specific syncable files, owned by\n// the named app, synced as a unit with\n// the rest of that app's syncable data\n//\n// The prefix is determined by what is being stored, NOT by who is writing it.\n// A `kind:\"data\"` record blob always lives under `shared/<category>/...`, even\n// when an app with `readwrite` access produced it — that's how a different app\n// with read access to the same category can resolve the key under its own IAM\n// grants, and it keeps the prefix set bounded (~11) so `other`/unmapped files\n// are enumerable. The system does not provide an app-private non-syncable\n// namespace; apps that want such storage handle it themselves.\n\nimport { categoryOf } from \"../types/core-types.js\";\n\n// `typeOrExt` is the record's `type` (lowercase extension). The category is\n// derived here so the key stays category-bucketed and unmapped/extension-less\n// records land under `shared/other/...`.\nexport function dataRecordObjectKey(typeOrExt: string, contentHash: string): string {\n const shard = contentHash.slice(0, 2);\n return `shared/${categoryOf(typeOrExt)}/${shard}/${contentHash}`;\n}\n\n// Build the canonical object key for an app's syncable file. `subKey` is the\n// app-relative path under apps/<appId>/syncable/. Idempotent if the caller\n// already passed a fully qualified key. Rejects keys that would escape the\n// namespace (`..` segments, absolute paths).\nexport function appSyncableObjectKey(appId: string, subKey: string): string {\n if (!appId || /[/\\s]/.test(appId)) {\n throw new Error(`appSyncableObjectKey: invalid appId ${JSON.stringify(appId)}`);\n }\n const prefix = `apps/${appId}/syncable/`;\n const relative = subKey.startsWith(prefix) ? subKey.slice(prefix.length) : subKey;\n if (relative.startsWith(\"/\")) {\n throw new Error(`appSyncableObjectKey: subKey must not start with \"/\" (got ${JSON.stringify(subKey)})`);\n }\n const segments = relative.split(\"/\");\n if (segments.some((s) => s === \"..\")) {\n throw new Error(`appSyncableObjectKey: subKey must not contain \"..\" (got ${JSON.stringify(subKey)})`);\n }\n return `${prefix}${relative}`;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import * as v from 'valibot';
|
|
2
|
+
|
|
3
|
+
type StarkeepId = string & {
|
|
4
|
+
readonly __brand: unique symbol;
|
|
5
|
+
};
|
|
6
|
+
declare function createStarkeepId(value: string): StarkeepId;
|
|
7
|
+
declare function isStarkeepId(value: unknown): value is StarkeepId;
|
|
8
|
+
|
|
9
|
+
declare function generateId(): StarkeepId;
|
|
10
|
+
declare function generateIdAt(timestamp: number): StarkeepId;
|
|
11
|
+
|
|
12
|
+
interface HLCTimestamp {
|
|
13
|
+
readonly wallTime: number;
|
|
14
|
+
readonly counter: number;
|
|
15
|
+
readonly nodeId: string;
|
|
16
|
+
}
|
|
17
|
+
interface HLCClock {
|
|
18
|
+
now(): HLCTimestamp;
|
|
19
|
+
send(): HLCTimestamp;
|
|
20
|
+
receive(remote: HLCTimestamp): HLCTimestamp;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ClockState {
|
|
24
|
+
wallTime: number;
|
|
25
|
+
counter: number;
|
|
26
|
+
}
|
|
27
|
+
interface ClockOptions {
|
|
28
|
+
nodeId: string;
|
|
29
|
+
wallClockFunction?: () => number;
|
|
30
|
+
/**
|
|
31
|
+
* Pre-seed the clock from persisted state so a post-restart HLC never
|
|
32
|
+
* emits a timestamp earlier than one the node already sent.
|
|
33
|
+
*/
|
|
34
|
+
initialState?: ClockState;
|
|
35
|
+
/**
|
|
36
|
+
* Invoked on every state change. Callers typically debounce and persist
|
|
37
|
+
* to a SyncStateStore.
|
|
38
|
+
*/
|
|
39
|
+
onTick?: (state: ClockState) => void;
|
|
40
|
+
}
|
|
41
|
+
declare function createHLCClock(options: ClockOptions): HLCClock;
|
|
42
|
+
|
|
43
|
+
/** Identity element for HLC ordering. Useful as a default watermark / "never seen". */
|
|
44
|
+
declare const ZERO_HLC: HLCTimestamp;
|
|
45
|
+
declare function compareHLC(a: HLCTimestamp, b: HLCTimestamp): -1 | 0 | 1;
|
|
46
|
+
declare function maxHLC(a: HLCTimestamp, b: HLCTimestamp): HLCTimestamp;
|
|
47
|
+
|
|
48
|
+
declare function serializeHLC(timestamp: HLCTimestamp): string;
|
|
49
|
+
declare function deserializeHLC(serializedString: string): HLCTimestamp;
|
|
50
|
+
|
|
51
|
+
interface BaseRecord {
|
|
52
|
+
readonly id: StarkeepId;
|
|
53
|
+
/**
|
|
54
|
+
* The record's lowercase file extension, verbatim (e.g. "jpg", "md", "xyz");
|
|
55
|
+
* "" for extension-less files. This is the identification key. The derived
|
|
56
|
+
* category (`categoryOf(type)`) determines the metadata table and storage
|
|
57
|
+
* prefix; unmapped/empty extensions derive category "other".
|
|
58
|
+
*/
|
|
59
|
+
readonly type: string;
|
|
60
|
+
readonly createdAt: HLCTimestamp;
|
|
61
|
+
updatedAt: HLCTimestamp;
|
|
62
|
+
readonly ownerId: string;
|
|
63
|
+
deletedAt: HLCTimestamp | null;
|
|
64
|
+
version: number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* A row in the shared records table. Every DataRecord is backed by a file in
|
|
68
|
+
* object storage (`objectStorageKey` + `contentHash`); metadata derived from
|
|
69
|
+
* the file lives in the per-category `record_<category>_metadata` table
|
|
70
|
+
* (category = `categoryOf(type)`; `other` records have no metadata table).
|
|
71
|
+
*
|
|
72
|
+
* App-level / user-authored fields that cannot be deterministically derived
|
|
73
|
+
* from the file (titles, captions, edit provenance, etc.) live in app-private
|
|
74
|
+
* storage, not on this row.
|
|
75
|
+
*/
|
|
76
|
+
interface DataRecord extends BaseRecord {
|
|
77
|
+
readonly kind: "data";
|
|
78
|
+
contentHash: string;
|
|
79
|
+
objectStorageKey: string;
|
|
80
|
+
mimeType: string;
|
|
81
|
+
sizeBytes: number;
|
|
82
|
+
originalFilename: string | null;
|
|
83
|
+
/**
|
|
84
|
+
* App identity that produced this record. Set by the data-server at write
|
|
85
|
+
* time from the authenticated subject. Required on every write.
|
|
86
|
+
*/
|
|
87
|
+
originAppId: string;
|
|
88
|
+
/**
|
|
89
|
+
* Optional parent record id linking this record to another shared record
|
|
90
|
+
* (e.g. a thumbnail's parent is its original). The parent may be of any
|
|
91
|
+
* type; cross-type parent links are permitted.
|
|
92
|
+
*/
|
|
93
|
+
parentId: StarkeepId | null;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* One row in a per-category metadata table (`shared_record_<category>_metadata`
|
|
97
|
+
* / `shared.record_<category>_metadata`). Columns are declared by the
|
|
98
|
+
* category's entry in `CATEGORIES`. Every column other than `recordId` must be
|
|
99
|
+
* deterministically derivable from the record's file bytes.
|
|
100
|
+
*/
|
|
101
|
+
interface MetadataRow {
|
|
102
|
+
recordId: StarkeepId;
|
|
103
|
+
[column: string]: unknown;
|
|
104
|
+
}
|
|
105
|
+
type AnyRecord = DataRecord;
|
|
106
|
+
|
|
107
|
+
interface CreateDataRecordInput {
|
|
108
|
+
type: string;
|
|
109
|
+
ownerId: string;
|
|
110
|
+
originAppId: string;
|
|
111
|
+
contentHash: string;
|
|
112
|
+
objectStorageKey: string;
|
|
113
|
+
mimeType: string;
|
|
114
|
+
sizeBytes: number;
|
|
115
|
+
originalFilename?: string | null;
|
|
116
|
+
parentId?: StarkeepId | null;
|
|
117
|
+
}
|
|
118
|
+
declare function createDataRecord(input: CreateDataRecordInput, clock: HLCClock): DataRecord;
|
|
119
|
+
|
|
120
|
+
interface TypeDefinition {
|
|
121
|
+
name: string;
|
|
122
|
+
namespace: string;
|
|
123
|
+
schema: v.BaseSchema<unknown, unknown, v.BaseIssue<unknown>>;
|
|
124
|
+
}
|
|
125
|
+
interface TypeRegistry {
|
|
126
|
+
register(definition: TypeDefinition): void;
|
|
127
|
+
get(namespace: string, name: string): TypeDefinition | undefined;
|
|
128
|
+
getByKey(key: string): TypeDefinition | undefined;
|
|
129
|
+
has(namespace: string, name: string): boolean;
|
|
130
|
+
list(): TypeDefinition[];
|
|
131
|
+
}
|
|
132
|
+
declare function createTypeRegistry(): TypeRegistry;
|
|
133
|
+
|
|
134
|
+
declare const dataRecordSchema: v.ObjectSchema<{
|
|
135
|
+
readonly kind: v.LiteralSchema<"data", undefined>;
|
|
136
|
+
readonly contentHash: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
137
|
+
readonly objectStorageKey: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
138
|
+
readonly mimeType: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
139
|
+
readonly sizeBytes: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
140
|
+
readonly originalFilename: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
141
|
+
readonly originAppId: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
142
|
+
readonly parentId: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
143
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.LengthAction<string, 26, undefined>]>;
|
|
144
|
+
readonly type: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
145
|
+
readonly createdAt: v.ObjectSchema<{
|
|
146
|
+
readonly wallTime: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
147
|
+
readonly counter: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
148
|
+
readonly nodeId: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
149
|
+
}, undefined>;
|
|
150
|
+
readonly updatedAt: v.ObjectSchema<{
|
|
151
|
+
readonly wallTime: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
152
|
+
readonly counter: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
153
|
+
readonly nodeId: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
154
|
+
}, undefined>;
|
|
155
|
+
readonly ownerId: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
156
|
+
readonly deletedAt: v.NullableSchema<v.ObjectSchema<{
|
|
157
|
+
readonly wallTime: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
158
|
+
readonly counter: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
159
|
+
readonly nodeId: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
160
|
+
}, undefined>, undefined>;
|
|
161
|
+
readonly version: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>;
|
|
162
|
+
}, undefined>;
|
|
163
|
+
declare function validateDataRecord(data: unknown): v.SafeParseResult<v.ObjectSchema<{
|
|
164
|
+
readonly kind: v.LiteralSchema<"data", undefined>;
|
|
165
|
+
readonly contentHash: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
166
|
+
readonly objectStorageKey: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
167
|
+
readonly mimeType: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
168
|
+
readonly sizeBytes: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
169
|
+
readonly originalFilename: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
170
|
+
readonly originAppId: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
171
|
+
readonly parentId: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
172
|
+
readonly id: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.LengthAction<string, 26, undefined>]>;
|
|
173
|
+
readonly type: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
174
|
+
readonly createdAt: v.ObjectSchema<{
|
|
175
|
+
readonly wallTime: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
176
|
+
readonly counter: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
177
|
+
readonly nodeId: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
178
|
+
}, undefined>;
|
|
179
|
+
readonly updatedAt: v.ObjectSchema<{
|
|
180
|
+
readonly wallTime: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
181
|
+
readonly counter: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
182
|
+
readonly nodeId: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
183
|
+
}, undefined>;
|
|
184
|
+
readonly ownerId: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
185
|
+
readonly deletedAt: v.NullableSchema<v.ObjectSchema<{
|
|
186
|
+
readonly wallTime: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
187
|
+
readonly counter: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
188
|
+
readonly nodeId: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>;
|
|
189
|
+
}, undefined>, undefined>;
|
|
190
|
+
readonly version: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.IntegerAction<number, undefined>, v.MinValueAction<number, 1, undefined>]>;
|
|
191
|
+
}, undefined>>;
|
|
192
|
+
|
|
193
|
+
declare class StarkeepError extends Error {
|
|
194
|
+
readonly code: string;
|
|
195
|
+
readonly cause?: unknown | undefined;
|
|
196
|
+
constructor(message: string, code: string, cause?: unknown | undefined);
|
|
197
|
+
}
|
|
198
|
+
declare class ValidationError extends StarkeepError {
|
|
199
|
+
constructor(message: string, cause?: unknown);
|
|
200
|
+
}
|
|
201
|
+
declare class NotFoundError extends StarkeepError {
|
|
202
|
+
constructor(entity: string, id: string);
|
|
203
|
+
}
|
|
204
|
+
declare class ConflictError extends StarkeepError {
|
|
205
|
+
constructor(message: string);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
type Result<T, E = Error> = {
|
|
209
|
+
ok: true;
|
|
210
|
+
value: T;
|
|
211
|
+
} | {
|
|
212
|
+
ok: false;
|
|
213
|
+
error: E;
|
|
214
|
+
};
|
|
215
|
+
declare function ok<T>(value: T): Result<T, never>;
|
|
216
|
+
declare function err<E>(error: E): Result<never, E>;
|
|
217
|
+
interface PaginationOptions {
|
|
218
|
+
limit: number;
|
|
219
|
+
cursor?: string;
|
|
220
|
+
}
|
|
221
|
+
interface PaginatedResult<T> {
|
|
222
|
+
items: T[];
|
|
223
|
+
nextCursor: string | null;
|
|
224
|
+
hasMore: boolean;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Single source of truth for Starkeep's shared core type system.
|
|
229
|
+
*
|
|
230
|
+
* Two registries derived from one place:
|
|
231
|
+
* - EXTENSIONS: lowercase file extension → mapped Category. Identification is
|
|
232
|
+
* by extension; MIME is never authoritative.
|
|
233
|
+
* - CATEGORIES: the user-facing organizational layer (mobile-style: Images,
|
|
234
|
+
* Videos, Documents…). Each mapped category owns one metadata table holding
|
|
235
|
+
* cross-format properties derivable from the file bytes.
|
|
236
|
+
*
|
|
237
|
+
* A record's `type` is the lowercase extension verbatim (e.g. "jpg", "md",
|
|
238
|
+
* "xyz"), even when the extension is unmapped. Its category is derived:
|
|
239
|
+
* `category = EXTENSIONS[ext] ?? "other"`. `other` is the terminal catch-all
|
|
240
|
+
* for unmapped / extension-less files — Drive-only, no metadata table, and no
|
|
241
|
+
* installable app can ever be granted it (apps may only declare extensions that
|
|
242
|
+
* are present in EXTENSIONS, and the unmapped set IS the `other` set).
|
|
243
|
+
*
|
|
244
|
+
* Every relevant system — manifest validation, IAM emission, DSQL schema-init,
|
|
245
|
+
* SQLite bootstrap, object-key construction, and the local data-server's access
|
|
246
|
+
* path — derives its view from the registries below. Adding an extension or a
|
|
247
|
+
* metadata column is a one-file edit here. There is no runtime registration
|
|
248
|
+
* path — apps cannot register new types or extend metadata columns.
|
|
249
|
+
*/
|
|
250
|
+
type LogicalColumnType = "integer" | "bigint" | "real" | "text" | "timestamp" | "boolean";
|
|
251
|
+
interface CoreTypeMetadataColumn {
|
|
252
|
+
name: string;
|
|
253
|
+
type: LogicalColumnType;
|
|
254
|
+
/** Defaults to true. Set explicitly when the column must be NOT NULL. */
|
|
255
|
+
nullable?: boolean;
|
|
256
|
+
}
|
|
257
|
+
/** The fixed set of categories. `other` is the terminal catch-all (last). */
|
|
258
|
+
type Category = "image" | "video" | "audio" | "document" | "text" | "code" | "font" | "archive" | "data" | "model3d" | "other";
|
|
259
|
+
interface CategoryDef {
|
|
260
|
+
id: Category;
|
|
261
|
+
description: string;
|
|
262
|
+
/** Cross-format metadata columns. Empty for `other` (no metadata table). */
|
|
263
|
+
metadataColumns: CoreTypeMetadataColumn[];
|
|
264
|
+
}
|
|
265
|
+
declare const CATEGORIES: readonly CategoryDef[];
|
|
266
|
+
/**
|
|
267
|
+
* Extension (lowercase, no dot) → mapped category. `other` is NEVER a value
|
|
268
|
+
* here — it is exclusively the `?? "other"` fallback in `categoryOf`, which is
|
|
269
|
+
* why no app can ever declare an `other` extension.
|
|
270
|
+
*/
|
|
271
|
+
declare const EXTENSIONS: Readonly<Record<string, Exclude<Category, "other">>>;
|
|
272
|
+
declare const CATEGORY_IDS: readonly Category[];
|
|
273
|
+
/**
|
|
274
|
+
* Categories an installable app may be granted — every category a real
|
|
275
|
+
* extension can map to, i.e. all categories EXCEPT `other`. Drive's all-access
|
|
276
|
+
* (`fileAccessAll`) covers `other` as well, via its `shared/*` IAM ceiling.
|
|
277
|
+
*/
|
|
278
|
+
declare const APP_GRANTABLE_CATEGORIES: readonly Category[];
|
|
279
|
+
/** The set of known (mapped) extensions. */
|
|
280
|
+
declare const KNOWN_EXTENSIONS: ReadonlySet<string>;
|
|
281
|
+
/**
|
|
282
|
+
* Derived category for a record's extension/type. Unmapped or empty → "other".
|
|
283
|
+
* Accepts the extension with or without a leading dot, any case.
|
|
284
|
+
*/
|
|
285
|
+
declare function categoryOf(ext: string): Category;
|
|
286
|
+
declare function getCategory(id: string): CategoryDef | undefined;
|
|
287
|
+
declare function isCategoryId(id: string): id is Category;
|
|
288
|
+
/**
|
|
289
|
+
* Emits a `CREATE TABLE IF NOT EXISTS shared.record_<category>_metadata`
|
|
290
|
+
* statement for DSQL. Single non-PL/pgSQL statement, no FK constraints — see
|
|
291
|
+
* `dsql-schema-init.ts` for the DSQL surface caveats. Callers must skip the
|
|
292
|
+
* `other` category (no metadata table).
|
|
293
|
+
*/
|
|
294
|
+
declare function pgMetadataDdl(c: CategoryDef): string;
|
|
295
|
+
/**
|
|
296
|
+
* Emits a `CREATE TABLE IF NOT EXISTS shared_record_<category>_metadata`
|
|
297
|
+
* statement for the local SQLite bootstrap. Callers must skip the `other`
|
|
298
|
+
* category (no metadata table).
|
|
299
|
+
*/
|
|
300
|
+
declare function sqliteMetadataDdl(c: CategoryDef): string;
|
|
301
|
+
/**
|
|
302
|
+
* Returns the SQLite metadata table name for a record's type/extension or a
|
|
303
|
+
* category id. The category is derived when an extension is passed, so storage
|
|
304
|
+
* adapters that hold only `record.type` route to the correct per-category
|
|
305
|
+
* table. Passing the literal `"other"` (or an unmapped extension) yields the
|
|
306
|
+
* `other` table name, which is never created — callers must not write metadata
|
|
307
|
+
* for `other` records.
|
|
308
|
+
*/
|
|
309
|
+
declare function sqliteMetadataTableName(typeOrCategory: string): string;
|
|
310
|
+
/** DSQL/Postgres counterpart of {@link sqliteMetadataTableName}. */
|
|
311
|
+
declare function pgMetadataTableName(typeOrCategory: string): string;
|
|
312
|
+
|
|
313
|
+
declare function dataRecordObjectKey(typeOrExt: string, contentHash: string): string;
|
|
314
|
+
declare function appSyncableObjectKey(appId: string, subKey: string): string;
|
|
315
|
+
|
|
316
|
+
export { APP_GRANTABLE_CATEGORIES, type AnyRecord, type BaseRecord, CATEGORIES, CATEGORY_IDS, type Category, type CategoryDef, type ClockOptions, ConflictError, type CoreTypeMetadataColumn, type CreateDataRecordInput, type DataRecord, EXTENSIONS, type HLCClock, type HLCTimestamp, KNOWN_EXTENSIONS, type LogicalColumnType, type MetadataRow, NotFoundError, type PaginatedResult, type PaginationOptions, type Result, StarkeepError, type StarkeepId, type TypeDefinition, type TypeRegistry, ValidationError, ZERO_HLC, appSyncableObjectKey, categoryOf, compareHLC, createDataRecord, createHLCClock, createStarkeepId, createTypeRegistry, dataRecordObjectKey, dataRecordSchema, deserializeHLC, err, generateId, generateIdAt, getCategory, isCategoryId, isStarkeepId, maxHLC, ok, pgMetadataDdl, pgMetadataTableName, serializeHLC, sqliteMetadataDdl, sqliteMetadataTableName, validateDataRecord };
|