cvx-kit 0.1.13 → 0.1.15
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/AGENTS.md +47 -0
- package/CHANGELOG.md +33 -0
- package/README.md +142 -0
- package/dist/auth.d.mts +3 -1
- package/dist/auth.mjs +1 -1
- package/dist/auth.mjs.map +1 -1
- package/dist/index.d.mts +5 -2
- package/dist/index.mjs +1 -1
- package/dist/modules/effect/api-zod.d.mts +1 -1
- package/dist/oxlint/architecture.d.mts +2 -0
- package/dist/oxlint/architecture.mjs +1 -1
- package/dist/oxlint/architecture.mjs.map +1 -1
- package/dist/oxlint/ast.d.mts +10 -1
- package/dist/oxlint/ast.mjs +1 -1
- package/dist/oxlint/ast.mjs.map +1 -1
- package/dist/oxlint.d.mts +1 -1
- package/dist/oxlint.mjs +1 -1
- package/dist/oxlint.mjs.map +1 -1
- package/dist/querying/cursors.d.mts +50 -0
- package/dist/querying/cursors.mjs +2 -0
- package/dist/querying/cursors.mjs.map +1 -0
- package/dist/querying/execution.d.mts +30 -0
- package/dist/querying/execution.mjs +2 -0
- package/dist/querying/execution.mjs.map +1 -0
- package/dist/querying/filters.d.mts +136 -0
- package/dist/querying/filters.mjs +2 -0
- package/dist/querying/filters.mjs.map +1 -0
- package/dist/querying/planner.d.mts +50 -0
- package/dist/querying/planner.mjs +2 -0
- package/dist/querying/planner.mjs.map +1 -0
- package/dist/querying/relationships.d.mts +41 -0
- package/dist/querying/relationships.mjs +2 -0
- package/dist/querying/relationships.mjs.map +1 -0
- package/dist/querying/security.d.mts +107 -0
- package/dist/querying/security.mjs +2 -0
- package/dist/querying/security.mjs.map +1 -0
- package/dist/querying.d.mts +53 -0
- package/dist/querying.mjs +2 -0
- package/dist/querying.mjs.map +1 -0
- package/dist/relations-public.d.mts +2 -0
- package/dist/relations-public.mjs +1 -0
- package/dist/relations.d.mts +155 -0
- package/dist/relations.mjs +2 -0
- package/dist/relations.mjs.map +1 -0
- package/dist/zod-convex.d.mts +10 -0
- package/dist/zod-convex.mjs +2 -0
- package/dist/zod-convex.mjs.map +1 -0
- package/dist/zod-registration.d.mts +10 -0
- package/dist/zod-registration.mjs +2 -0
- package/dist/zod-registration.mjs.map +1 -0
- package/dist/zod-table.d.mts +63 -8
- package/dist/zod-table.mjs +1 -1
- package/dist/zod-table.mjs.map +1 -1
- package/docs/conventions.md +12 -0
- package/docs/llms.md +15 -5
- package/docs/oxlint.md +135 -25
- package/docs/querying.md +329 -0
- package/docs/zod-table.md +93 -13
- package/package.json +11 -1
- package/scripts/owned-process.mjs +140 -0
- package/scripts/owned-process.test.mjs +107 -0
- package/scripts/smoke-packed-querying.mjs +298 -0
- package/scripts/test-packed-effect.mjs +8 -1
- package/scripts/test-packed-oxlint.mjs +5 -1
- package/scripts/test-packed-querying.mjs +185 -0
package/dist/zod-table.d.mts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ExactScalarSelection, ScalarPaginationSchema, ScalarSelection } from "./querying/filters.mjs";
|
|
2
|
+
import { zodToConvex } from "./zod-convex.mjs";
|
|
3
|
+
import { ConvexValidatorFromZod, Zid, convexToZod, zid, zid as zid$1 } from "convex-helpers/server/zod4";
|
|
2
4
|
import { TableDefinition } from "convex/server";
|
|
3
5
|
import { z } from "zod";
|
|
4
6
|
import { GenericId } from "convex/values";
|
|
@@ -16,17 +18,71 @@ declare const timestampFields: {
|
|
|
16
18
|
archivedAt: z.ZodOptional<z.ZodNumber>;
|
|
17
19
|
};
|
|
18
20
|
type TimestampFields = typeof timestampFields;
|
|
21
|
+
type ReservedStorageField = '_id' | '_creationTime' | keyof TimestampFields;
|
|
22
|
+
type OwnedFields<Fields> = { [Key in keyof Fields]: Key extends ReservedStorageField ? never : Fields[Key]; };
|
|
23
|
+
type SystemFields<Table extends string> = {
|
|
24
|
+
_id: Zid<Table>;
|
|
25
|
+
_creationTime: z.ZodNumber;
|
|
26
|
+
};
|
|
19
27
|
export declare const TIMESTAMP_FIELDS: readonly ['createdAt', 'updatedAt', 'archivedAt'];
|
|
20
28
|
export type TableBoundaryOptions<Fields extends FieldSchemas, ServerFields extends readonly FieldKey<Fields>[], CommandFields extends readonly FieldKey<Fields>[], PublicFields extends readonly FieldKey<Fields & TimestampFields>[]> = {
|
|
21
29
|
serverFields?: ServerFields;
|
|
22
|
-
commandFields?: CommandFields;
|
|
30
|
+
commandFields?: CommandFields & readonly Exclude<FieldKey<Fields>, ServerFields[number] | keyof TimestampFields>[];
|
|
23
31
|
publicFields?: PublicFields;
|
|
24
32
|
};
|
|
25
33
|
type StrictObject<Fields extends FieldSchemas> = z.ZodObject<Fields, z.core.$strict>;
|
|
26
34
|
type WriteFields<Fields extends FieldSchemas, ServerFields extends readonly FieldKey<Fields>[]> = Omit<Fields & TimestampFields, ServerFields[number] | (typeof TIMESTAMP_FIELDS)[number]>;
|
|
27
35
|
type PartialFields<Fields extends FieldSchemas> = { [Key in keyof Fields]: z.ZodOptional<Fields[Key]>; };
|
|
36
|
+
/** Options shared by the DTO return schema and row projector.
|
|
37
|
+
* Promised contributions must be present, including in reusable options.
|
|
38
|
+
* New extension schemas support validation only: no transforms, pipes,
|
|
39
|
+
* coercion, preprocess, defaults, catches, or overwrites, even when nested.
|
|
40
|
+
* Refinements must validate without mutating their input.
|
|
41
|
+
*/
|
|
42
|
+
export type DtoOptions<IncludeId extends boolean = false, Extension extends FieldSchemas = {}> = ([IncludeId] extends [true] ? {
|
|
43
|
+
_id: IncludeId;
|
|
44
|
+
} : {
|
|
45
|
+
_id?: IncludeId;
|
|
46
|
+
}) & (keyof Extension extends never ? {
|
|
47
|
+
extend?: Extension;
|
|
48
|
+
} : {
|
|
49
|
+
extend: Extension;
|
|
50
|
+
});
|
|
51
|
+
type DtoProjectionOptions<IncludeId extends boolean> = DtoOptions<IncludeId> & {
|
|
52
|
+
extend?: Record<string, never>;
|
|
53
|
+
values?: never;
|
|
54
|
+
};
|
|
55
|
+
type ReservedDtoField = '_id' | '_creationTime' | 'tenant' | '__proto__' | (typeof TIMESTAMP_FIELDS)[number];
|
|
56
|
+
type DtoExtension<Public extends FieldSchemas, Extension extends FieldSchemas> = { [Key in keyof Extension]: Key extends keyof Public | ReservedDtoField ? never : Extension[Key] extends z.ZodPipe | z.ZodTransform ? never : unknown; };
|
|
57
|
+
type KnownExtension<Extension> = string extends keyof Extension ? {} : Extension;
|
|
58
|
+
type DtoSchema<Table extends string, Public extends FieldSchemas, IncludeId extends boolean, Extension extends FieldSchemas = {}> = IncludeId extends true ? StrictObject<Public & {
|
|
59
|
+
_id: Zid<Table>;
|
|
60
|
+
} & KnownExtension<Extension>> : StrictObject<Public & KnownExtension<Extension>>;
|
|
61
|
+
type DtoRow<Row, IncludeId extends boolean> = Row & (true extends IncludeId ? {
|
|
62
|
+
_id: string;
|
|
63
|
+
} : {});
|
|
64
|
+
type DtoValues<Extension extends FieldSchemas> = keyof Extension extends never ? {
|
|
65
|
+
values?: never;
|
|
66
|
+
} : {
|
|
67
|
+
values: z.input<StrictObject<NoInfer<Extension>>>;
|
|
68
|
+
};
|
|
69
|
+
type DtoBoundary<Table extends string, Fields extends FieldSchemas, Public extends FieldSchemas> = {
|
|
70
|
+
dto(): StrictObject<Public>;
|
|
71
|
+
dto<const IncludeId extends boolean>(includeId: IncludeId): DtoSchema<Table, Public, IncludeId>;
|
|
72
|
+
dto<const IncludeId extends boolean = false>(options: DtoProjectionOptions<IncludeId>): DtoSchema<Table, Public, IncludeId>;
|
|
73
|
+
dto<const IncludeId extends boolean = false, const Extension extends FieldSchemas = {}>(options: DtoOptions<IncludeId, Extension> & {
|
|
74
|
+
extend?: DtoExtension<Public, Extension>;
|
|
75
|
+
}): DtoSchema<Table, Public, IncludeId, Extension>;
|
|
76
|
+
toPublicDto(row: z.input<StrictObject<Fields>>): z.output<StrictObject<Public>>;
|
|
77
|
+
toPublicDto<const IncludeId extends boolean>(row: DtoRow<z.input<StrictObject<Fields>>, IncludeId>, includeId: IncludeId): z.output<DtoSchema<Table, Public, IncludeId>>;
|
|
78
|
+
toPublicDto<const IncludeId extends boolean = false>(row: DtoRow<z.input<StrictObject<Fields>>, IncludeId>, options: DtoProjectionOptions<IncludeId>): z.output<DtoSchema<Table, Public, IncludeId>>;
|
|
79
|
+
toPublicDto<const IncludeId extends boolean = false, const Extension extends FieldSchemas = {}>(row: DtoRow<z.input<StrictObject<Fields>>, IncludeId>, options: DtoOptions<IncludeId, Extension> & {
|
|
80
|
+
extend?: DtoExtension<Public, Extension>;
|
|
81
|
+
} & DtoValues<Extension>): z.output<DtoSchema<Table, Public, IncludeId, Extension>>;
|
|
82
|
+
};
|
|
28
83
|
/** Named schema relationships keep generated declarations bounded and consumers fully typed. */
|
|
29
|
-
export type TableBoundary<Table extends string, Fields extends FieldSchemas, ServerFields extends readonly FieldKey<Fields>[], CommandFields extends readonly FieldKey<Fields>[], PublicFields extends readonly FieldKey<Fields & TimestampFields>[]> = {
|
|
84
|
+
export type TableBoundary<Table extends string, Fields extends FieldSchemas, ServerFields extends readonly FieldKey<Fields>[], CommandFields extends readonly FieldKey<Fields>[], PublicFields extends readonly FieldKey<Fields & TimestampFields>[]> = DtoBoundary<Table, Fields & TimestampFields, Pick<Fields & TimestampFields, PublicFields[number]>> & {
|
|
85
|
+
paginationOpts<const Selection extends ScalarSelection<Fields & TimestampFields & SystemFields<Table>>>(selection: ExactScalarSelection<Fields & TimestampFields & SystemFields<Table>, Selection>): ScalarPaginationSchema<Fields & TimestampFields & SystemFields<Table>, Selection>;
|
|
30
86
|
tableName: Table;
|
|
31
87
|
storage: StrictObject<Fields & TimestampFields>;
|
|
32
88
|
schema: ReturnType<typeof documentSchema<Fields & TimestampFields, Table>>;
|
|
@@ -34,7 +90,6 @@ export type TableBoundary<Table extends string, Fields extends FieldSchemas, Ser
|
|
|
34
90
|
updateSchema: StrictObject<PartialFields<WriteFields<Fields, ServerFields>>>;
|
|
35
91
|
commandInput: StrictObject<Pick<Fields & TimestampFields, CommandFields[number]>>;
|
|
36
92
|
publicDto: StrictObject<Pick<Fields & TimestampFields, PublicFields[number]>>;
|
|
37
|
-
toPublicDto: (row: z.input<StrictObject<Fields & TimestampFields>>) => z.output<StrictObject<Pick<Fields & TimestampFields, PublicFields[number]>>>;
|
|
38
93
|
table: TableDefinition<ConvexValidatorFromZod<StrictObject<Fields & TimestampFields>, 'required'>>;
|
|
39
94
|
insert(): StrictObject<WriteFields<Fields, ServerFields>>;
|
|
40
95
|
insert<Mask extends z.util.Mask<keyof WriteFields<Fields, ServerFields>>>(omit: Mask): ZodObjectOmit<StrictObject<WriteFields<Fields, ServerFields>>, Mask>;
|
|
@@ -52,7 +107,7 @@ export type TableBoundary<Table extends string, Fields extends FieldSchemas, Ser
|
|
|
52
107
|
};
|
|
53
108
|
};
|
|
54
109
|
/** Defines one table and derives its storage, document, write, and DTO boundaries. */
|
|
55
|
-
export declare function zodTable<Table extends string, Fields extends FieldSchemas, const ServerFields extends readonly FieldKey<Fields>[] = readonly [], const CommandFields extends readonly FieldKey<Fields>[] = readonly [], const PublicFields extends readonly FieldKey<Fields & TimestampFields>[] = readonly []>(tableName: Table, fields: (id: typeof zid$1) => Fields
|
|
110
|
+
export declare function zodTable<Table extends string, Fields extends FieldSchemas, const ServerFields extends readonly FieldKey<Fields>[] = readonly [], const CommandFields extends readonly FieldKey<Fields>[] = readonly [], const PublicFields extends readonly FieldKey<Fields & TimestampFields>[] = readonly []>(tableName: Table, fields: (id: typeof zid$1) => Fields & OwnedFields<Fields>, options?: TableBoundaryOptions<Fields, ServerFields, CommandFields, PublicFields>): TableBoundary<Table, Fields, ServerFields, CommandFields, PublicFields>;
|
|
56
111
|
declare const tenantFields: {
|
|
57
112
|
tenant: z.ZodString;
|
|
58
113
|
};
|
|
@@ -65,7 +120,7 @@ type TenantFields = typeof tenantFields;
|
|
|
65
120
|
* trigger forbids reassigning it. Expose it via publicFields only when a DTO
|
|
66
121
|
* genuinely needs it.
|
|
67
122
|
*/
|
|
68
|
-
export declare function tenantTable<Table extends string, Fields extends FieldSchemas, const ServerFields extends readonly FieldKey<Fields>[] = readonly [], const CommandFields extends readonly FieldKey<Fields>[] = readonly [], const PublicFields extends readonly FieldKey<Fields & TenantFields & TimestampFields>[] = readonly []>(tableName: Table, fields: (id: typeof zid$1) => Fields, options?: TableBoundaryOptions<Fields & TenantFields, readonly (ServerFields[number] | 'tenant')[], CommandFields, PublicFields>): TableBoundary<Table, Fields & {
|
|
123
|
+
export declare function tenantTable<Table extends string, Fields extends FieldSchemas, const ServerFields extends readonly FieldKey<Fields>[] = readonly [], const CommandFields extends readonly FieldKey<Fields>[] = readonly [], const PublicFields extends readonly FieldKey<Fields & TenantFields & TimestampFields>[] = readonly []>(tableName: Table, fields: (id: typeof zid$1) => Fields & OwnedFields<Fields> & { [Key in keyof Fields]: Key extends 'tenant' ? never : Fields[Key]; }, options?: TableBoundaryOptions<Fields & TenantFields, readonly (ServerFields[number] | 'tenant')[], CommandFields, PublicFields>): TableBoundary<Table, Fields & {
|
|
69
124
|
tenant: z.ZodString;
|
|
70
125
|
}, readonly ("tenant" | ServerFields[number])[], CommandFields, PublicFields>;
|
|
71
126
|
/**
|
|
@@ -120,10 +175,10 @@ export declare function zodVariantTable<Table extends string, Schema extends z.Z
|
|
|
120
175
|
export declare function jsonSafeZid<Table extends string>(tableName: Table): z.ZodType<GenericId<Table>, string>;
|
|
121
176
|
type ZodObjectOmit<Schema extends z.ZodObject<any, any>, OmitMask extends z.util.Mask<keyof Schema['shape']>> = Schema extends z.ZodObject<infer ObjectFields, infer Config> ? z.ZodObject<z.util.Flatten<Omit<ObjectFields, Extract<keyof ObjectFields, keyof OmitMask>>>, Config> : never;
|
|
122
177
|
declare function documentSchema<Fields extends FieldSchemas, Table extends string>(storage: StrictObject<Fields>, tableName: Table): z.ZodObject<(keyof Fields & ("_creationTime" | "_id") extends never ? Fields & {
|
|
123
|
-
_id:
|
|
178
|
+
_id: Zid<Table>;
|
|
124
179
|
_creationTime: z.ZodNumber;
|
|
125
180
|
} : { [K in keyof Fields as K extends "_creationTime" | "_id" ? never : K]: Fields[K]; } & {
|
|
126
|
-
_id:
|
|
181
|
+
_id: Zid<Table>;
|
|
127
182
|
_creationTime: z.ZodNumber;
|
|
128
183
|
}) extends (infer T) ? { [k in keyof T]: T[k]; } : never, z.core.$strict>;
|
|
129
184
|
//#endregion
|
package/dist/zod-table.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{zodToConvex as e}from"./zod-convex.mjs";import{scalarPaginationSchema as t}from"./querying/filters.mjs";import{convexToZod as n,convexToZod as r,zid as i,zid as a}from"convex-helpers/server/zod4";import{defineTable as o,paginationOptsValidator as s}from"convex/server";import{z as c}from"zod";const l={createdAt:c.number().optional(),updatedAt:c.number().optional(),archivedAt:c.number().optional()},u=[`createdAt`,`updatedAt`,`archivedAt`];function d(n,r,i={}){let s=r(a);S(s,[`_id`,`_creationTime`,...u]);let d={...s,...l};C(d,i.serverFields??[],i.commandFields??[],i.publicFields??[]);let f=c.object(d).strict(),p=v(f,n),m=[...u,...i.serverFields??[]],h=i.commandFields??[],g=i.publicFields??[],w=c.object(b(d,m)).strict(),T=w.partial(),E=c.object(y(d,h)).strict(),D=c.object(y(d,g)).strict(),O=/* @__PURE__ */ new Set([`_id`,`_creationTime`,`tenant`,`__proto__`,...u]);function k(e){return A(e)}function A(e){let t=e===!0||e===!1?{_id:e}:e,r=t?.extend??{};for(let e of Object.keys(r)){if(O.has(e)||Object.hasOwn(D.shape,e))throw Error(`DTO extension collision for field "${e}" on table "${n}"`);x(r[e])}if(!t?._id&&Object.keys(r).length===0)return D;let i=c.object({...D.shape,...r}).strict();return t?._id?i.extend({_id:p.shape._id}):i}function j(e,t){let n=t===!0||t===!1?{_id:t}:t;A(t);let r=D.parse(Object.fromEntries(Object.keys(D.shape).map(t=>[t,e[t]]))),i=c.object(n?.extend??{}).strict().parse(n?.values??{}),a={...r,...i};return n?._id&&(a._id=p.shape._id.parse(`_id`in e?e._id:void 0)),a}function M(e){return e?w.omit(e):w}function N(e){return e?T.omit(e):T}let P=e(f),F=o(P);function I(n){return t(e(p).fields,n)}return{tableName:n,schema:p,storage:f,insertSchema:w,updateSchema:T,commandInput:E,publicDto:D,dto:k,toPublicDto:j,paginationOpts:I,table:F,insert:M,update:N,tools:{insert:E,update:c.object({data:E.partial(),id:_(n)}).strict(),id:c.object({id:_(n)}).strict()}}}const f={tenant:c.string().min(1)};function p(e,t,n={}){return d(e,e=>{let n=t(e);return S(n,[`tenant`,`_id`,`_creationTime`,...u]),{...n,...f}},{...n,serverFields:[...n.serverFields??[],`tenant`]})}function m(...e){let t={};for(let n of e)for(let[e,r]of Object.entries(n)){if(e in t)throw Error(`Table "${e}" is declared by more than one module`);t[e]=r}return t}function h(e){return{args:{paginationOpts:r(s)},result:c.object({page:c.array(e),isDone:c.boolean(),continueCursor:c.string(),splitCursor:c.string().nullable().optional(),pageStatus:c.enum([`SplitRecommended`,`SplitRequired`]).nullable().optional()})}}function g(t,n){return{tableName:t,storage:n,insertSchema:n,table:o(e(n))}}function _(e){let t=a(e);return c.string().refine(e=>t.safeParse(e).success).describe(`Convex document id for table "${e}"; validated against the table by Convex`)}function v(e,t){return e.extend({_id:a(t),_creationTime:c.number()})}function y(e,t){return Object.fromEntries(t.map(t=>[t,e[t]]))}function b(e,t){let n=new Set(t);return Object.fromEntries(Object.entries(e).filter(([e])=>!n.has(e)))}function x(e,t=/* @__PURE__ */ new Set){if(t.has(e))return;t.add(e);let n=e._zod.def,r=()=>{throw Error(`DTO extensions require supported value-preserving validation schemas`)};`coerce`in n&&n.coerce&&r(),`normalize`in n&&n.normalize&&r();let i=e=>x(e,t);for(let e of n.checks??[])if(e._zod.def.check===`overwrite`&&r(),`normalize`in e._zod.def&&e._zod.def.normalize&&r(),e._zod.def.check===`property`&&i(e._zod.def.schema),e._zod.def.check===`properties`)for(let t of Object.values(e._zod.def.shape))i(t);switch(n.type){case`string`:case`number`:case`boolean`:case`bigint`:case`date`:case`symbol`:case`null`:case`undefined`:case`void`:case`never`:case`literal`:case`enum`:return;case`object`:for(let e of Object.values(n.shape))i(e);n.catchall&&i(n.catchall);return;case`array`:i(n.element);return;case`optional`:case`nullable`:case`nonoptional`:i(n.innerType);return;case`union`:for(let e of n.options)i(e);return;case`intersection`:i(n.left),i(n.right);return;case`tuple`:for(let e of n.items)i(e);n.rest&&i(n.rest);return;case`record`:i(n.keyType),i(n.valueType);return;default:r()}}function S(e,t){for(let n of t)if(Object.hasOwn(e,n))throw Error(`Reserved table field "${n}" cannot be declared`)}function C(e,t,n,r){for(let i of[...t,...n,...r])if(!Object.hasOwn(e,i))throw Error(`Unknown table field "${i}"`);let i=/* @__PURE__ */ new Set([...u,...t]);for(let e of n)if(i.has(e))throw Error(`Server-owned field "${e}" cannot be a command input`)}export{u as TIMESTAMP_FIELDS,n as convexToZod,m as createModule,_ as jsonSafeZid,h as paginated,p as tenantTable,i as zid,d as zodTable,e as zodToConvex,g as zodVariantTable};
|
|
2
2
|
//# sourceMappingURL=zod-table.mjs.map
|
package/dist/zod-table.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zod-table.mjs","names":["zid","zodToConvex","convexToZod","convexToZod$1","zid$1","zodToConvex$1","timestampFields","z","TIMESTAMP_FIELDS","zodTable","columns","fields","storage","schema","documentSchema","tableName","omittedKeys","options","commandKeys","publicKeys","insertSchema","omitFields","updateSchema","commandInput","pickFields","publicDto","toPublicDto","key","row","insert","omit","update","defineTable","jsonSafeZid","tenantFields","tenantTable","id","createModule","maps","combined","map","definition","paginated","paginationOptsValidator","dto","zodVariantTable","value","keys","omitted","convexToZod","zid","zodToConvex"],"sources":["../src/zod-table.ts"],"sourcesContent":["import {\n\tconvexToZod,\n\tzid,\n\tzodToConvex,\n\ttype ConvexValidatorFromZod,\n} from 'convex-helpers/server/zod4'\nimport { defineTable, paginationOptsValidator, type TableDefinition } from 'convex/server'\nimport type { GenericId } from 'convex/values'\nimport { z } from 'zod'\n\n// Use this entry point when deriving validators from kit-owned schemas so\n// the producer and converter consult the same convex-helpers ID registry.\nexport { zid, zodToConvex, convexToZod } from 'convex-helpers/server/zod4'\n\ntype FieldSchemas = Record<string, z.ZodType>\ntype FieldKey<Fields extends FieldSchemas> = Extract<keyof Fields, string>\n\n/**\n * Opinionated lifecycle timestamps — every table has them, server-owned,\n * always excluded from insert/command boundaries. Maintained by the\n * `timestamps` trigger helper (see cvx-kit/triggers).\n */\nconst timestampFields = {\n\tcreatedAt: z.number().optional(),\n\tupdatedAt: z.number().optional(),\n\tarchivedAt: z.number().optional(),\n} satisfies FieldSchemas\n\ntype TimestampFields = typeof timestampFields\n\nexport const TIMESTAMP_FIELDS = ['createdAt', 'updatedAt', 'archivedAt'] as const\n\nexport type TableBoundaryOptions<\n\tFields extends FieldSchemas,\n\tServerFields extends readonly FieldKey<Fields>[],\n\tCommandFields extends readonly FieldKey<Fields>[],\n\tPublicFields extends readonly FieldKey<Fields & TimestampFields>[],\n> = {\n\tserverFields?: ServerFields\n\tcommandFields?: CommandFields\n\tpublicFields?: PublicFields\n}\n\ntype StrictObject<Fields extends FieldSchemas> = z.ZodObject<Fields, z.core.$strict>\ntype WriteFields<\n\tFields extends FieldSchemas,\n\tServerFields extends readonly FieldKey<Fields>[],\n> = Omit<Fields & TimestampFields, ServerFields[number] | (typeof TIMESTAMP_FIELDS)[number]>\ntype PartialFields<Fields extends FieldSchemas> = {\n\t[Key in keyof Fields]: z.ZodOptional<Fields[Key]>\n}\n\n/** Named schema relationships keep generated declarations bounded and consumers fully typed. */\nexport type TableBoundary<\n\tTable extends string,\n\tFields extends FieldSchemas,\n\tServerFields extends readonly FieldKey<Fields>[],\n\tCommandFields extends readonly FieldKey<Fields>[],\n\tPublicFields extends readonly FieldKey<Fields & TimestampFields>[],\n> = {\n\ttableName: Table\n\tstorage: StrictObject<Fields & TimestampFields>\n\tschema: ReturnType<typeof documentSchema<Fields & TimestampFields, Table>>\n\tinsertSchema: StrictObject<WriteFields<Fields, ServerFields>>\n\tupdateSchema: StrictObject<PartialFields<WriteFields<Fields, ServerFields>>>\n\tcommandInput: StrictObject<Pick<Fields & TimestampFields, CommandFields[number]>>\n\tpublicDto: StrictObject<Pick<Fields & TimestampFields, PublicFields[number]>>\n\ttoPublicDto: (\n\t\trow: z.input<StrictObject<Fields & TimestampFields>>,\n\t) => z.output<StrictObject<Pick<Fields & TimestampFields, PublicFields[number]>>>\n\ttable: TableDefinition<ConvexValidatorFromZod<StrictObject<Fields & TimestampFields>, 'required'>>\n\tinsert(): StrictObject<WriteFields<Fields, ServerFields>>\n\tinsert<Mask extends z.util.Mask<keyof WriteFields<Fields, ServerFields>>>(\n\t\tomit: Mask,\n\t): ZodObjectOmit<StrictObject<WriteFields<Fields, ServerFields>>, Mask>\n\tupdate(): StrictObject<PartialFields<WriteFields<Fields, ServerFields>>>\n\tupdate<Mask extends z.util.Mask<keyof WriteFields<Fields, ServerFields>>>(\n\t\tomit: Mask,\n\t): ZodObjectOmit<StrictObject<PartialFields<WriteFields<Fields, ServerFields>>>, Mask>\n\ttools: {\n\t\tinsert: StrictObject<Pick<Fields & TimestampFields, CommandFields[number]>>\n\t\tupdate: StrictObject<{\n\t\t\tdata: StrictObject<PartialFields<Pick<Fields & TimestampFields, CommandFields[number]>>>\n\t\t\tid: z.ZodType<GenericId<Table>, string>\n\t\t}>\n\t\tid: StrictObject<{ id: z.ZodType<GenericId<Table>, string> }>\n\t}\n}\n\n/** Defines one table and derives its storage, document, write, and DTO boundaries. */\nexport function zodTable<\n\tTable extends string,\n\tFields extends FieldSchemas,\n\tconst ServerFields extends readonly FieldKey<Fields>[] = readonly [],\n\tconst CommandFields extends readonly FieldKey<Fields>[] = readonly [],\n\tconst PublicFields extends readonly FieldKey<Fields & TimestampFields>[] = readonly [],\n>(\n\ttableName: Table,\n\tfields: (id: typeof zid) => Fields,\n\toptions: TableBoundaryOptions<Fields, ServerFields, CommandFields, PublicFields> = {},\n): TableBoundary<Table, Fields, ServerFields, CommandFields, PublicFields> {\n\tconst columns: Fields & TimestampFields = { ...fields(zid), ...timestampFields }\n\tconst storage: z.ZodObject<Fields & TimestampFields, z.core.$strict> = z.object(columns).strict()\n\tconst schema = documentSchema(storage, tableName)\n\tconst omittedKeys: readonly (ServerFields[number] | (typeof TIMESTAMP_FIELDS)[number])[] = [\n\t\t...TIMESTAMP_FIELDS,\n\t\t...(options.serverFields ?? []),\n\t]\n\tconst commandKeys: readonly CommandFields[number][] = options.commandFields ?? []\n\tconst publicKeys: readonly PublicFields[number][] = options.publicFields ?? []\n\tconst insertSchema: z.ZodObject<WriteFields<Fields, ServerFields>, z.core.$strict> = z\n\t\t.object(omitFields(columns, omittedKeys))\n\t\t.strict()\n\tconst updateSchema = insertSchema.partial()\n\tconst commandInput: z.ZodObject<\n\t\tPick<Fields & TimestampFields, CommandFields[number]>,\n\t\tz.core.$strict\n\t> = z.object(pickFields(columns, commandKeys)).strict()\n\tconst publicDto: z.ZodObject<\n\t\tPick<Fields & TimestampFields, PublicFields[number]>,\n\t\tz.core.$strict\n\t> = z.object(pickFields(columns, publicKeys)).strict()\n\tconst toPublicDto = (row: z.input<typeof storage>) =>\n\t\tpublicDto.parse(\n\t\t\tObject.fromEntries(\n\t\t\t\tObject.keys(publicDto.shape).map((key) => {\n\t\t\t\t\t// SAFETY: publicDto contains only keys selected from the storage fields.\n\t\t\t\t\treturn [key, row[key as keyof typeof row]]\n\t\t\t\t}),\n\t\t\t),\n\t\t)\n\n\ttype InsertSchema = typeof insertSchema\n\ttype UpdateSchema = typeof updateSchema\n\ttype InsertMask = z.util.Mask<keyof InsertSchema['shape']>\n\ttype UpdateMask = z.util.Mask<keyof UpdateSchema['shape']>\n\n\tfunction insert(): InsertSchema\n\tfunction insert<OmitMask extends InsertMask>(\n\t\tomit: OmitMask,\n\t): ZodObjectOmit<InsertSchema, OmitMask>\n\tfunction insert(omit?: InsertMask) {\n\t\t// SAFETY: InsertMask contains only insertSchema keys; the overload retains Zod's exact mask relationship.\n\t\treturn omit ? insertSchema.omit(omit as never) : insertSchema\n\t}\n\n\tfunction update(): UpdateSchema\n\tfunction update<OmitMask extends UpdateMask>(\n\t\tomit: OmitMask,\n\t): ZodObjectOmit<UpdateSchema, OmitMask>\n\tfunction update(omit?: UpdateMask) {\n\t\t// SAFETY: UpdateMask contains only updateSchema keys; the overload retains Zod's exact mask relationship.\n\t\treturn omit ? updateSchema.omit(omit as never) : updateSchema\n\t}\n\n\tconst table: TableDefinition<ConvexValidatorFromZod<typeof storage, 'required'>> = defineTable(\n\t\tzodToConvex(storage),\n\t)\n\treturn {\n\t\ttableName,\n\t\tschema,\n\t\tstorage,\n\t\tinsertSchema,\n\t\tupdateSchema,\n\t\tcommandInput,\n\t\tpublicDto,\n\t\ttoPublicDto,\n\t\ttable,\n\t\tinsert,\n\t\tupdate,\n\t\ttools: {\n\t\t\t// jsonSafeZid (not zid): tool masks feed generated JSON schemas, so\n\t\t\t// ids must present as plain strings while keeping the Id<...> type.\n\t\t\tinsert: commandInput,\n\t\t\tupdate: z.object({ data: commandInput.partial(), id: jsonSafeZid(tableName) }).strict(),\n\t\t\tid: z.object({ id: jsonSafeZid(tableName) }).strict(),\n\t\t},\n\t}\n}\n\nconst tenantFields = {\n\ttenant: z.string().min(1),\n} satisfies FieldSchemas\n\ntype TenantFields = typeof tenantFields\n\n/**\n * A zodTable whose rows are tenant-owned: injects the server-owned `tenant`\n * field (the tenancy boundary — see cvx-kit/tenancy). Like timestamps, it is\n * excluded from insert/update/command boundaries; handlers stamp it from\n * ctx.tenant, row-level security matches on it, and the tenantOwnership\n * trigger forbids reassigning it. Expose it via publicFields only when a DTO\n * genuinely needs it.\n */\nexport function tenantTable<\n\tTable extends string,\n\tFields extends FieldSchemas,\n\tconst ServerFields extends readonly FieldKey<Fields>[] = readonly [],\n\tconst CommandFields extends readonly FieldKey<Fields>[] = readonly [],\n\tconst PublicFields extends readonly FieldKey<Fields & TenantFields & TimestampFields>[] =\n\t\treadonly [],\n>(\n\ttableName: Table,\n\tfields: (id: typeof zid) => Fields,\n\toptions: TableBoundaryOptions<\n\t\tFields & TenantFields,\n\t\treadonly (ServerFields[number] | 'tenant')[],\n\t\tCommandFields,\n\t\tPublicFields\n\t> = {},\n) {\n\treturn zodTable<\n\t\tTable,\n\t\tFields & TenantFields,\n\t\treadonly (ServerFields[number] | 'tenant')[],\n\t\tCommandFields,\n\t\tPublicFields\n\t>(tableName, (id) => ({ ...fields(id), ...tenantFields }), {\n\t\t...options,\n\t\tserverFields: [...(options.serverFields ?? []), 'tenant'],\n\t})\n}\n\n/**\n * Merges per-module table maps into the application schema map, rejecting\n * duplicate table names across modules — the module-registry combinator for\n * domain/table.ts: `defineSchema(createModule(catalogTables, salesTables))`.\n */\nexport function createModule<const Maps extends readonly Record<string, TableDefinition<any>>[]>(\n\t...maps: Maps\n): UnionToIntersection<Maps[number]> {\n\tconst combined: Record<string, TableDefinition<any>> = {}\n\tfor (const map of maps) {\n\t\tfor (const [tableName, definition] of Object.entries(map)) {\n\t\t\tif (tableName in combined) {\n\t\t\t\tthrow new Error(`Table \"${tableName}\" is declared by more than one module`)\n\t\t\t}\n\t\t\tcombined[tableName] = definition\n\t\t}\n\t}\n\t// SAFETY: every input entry is copied, and duplicate keys are rejected above.\n\treturn combined as UnionToIntersection<Maps[number]>\n}\n\ntype UnionToIntersection<Union> = (\n\tUnion extends unknown ? (member: Union) => void : never\n) extends (member: infer Intersection) => void\n\t? Intersection\n\t: never\n\n/**\n * Cursor-pagination boundary schemas bridged from Convex's own validators:\n * `args.paginationOpts` for function args, `result` for the page shape.\n * Pair with `include(...).paginate(opts)` (cvx-kit/auth). The result object\n * is deliberately non-strict — Convex may attach engine fields (splitCursor,\n * pageStatus); the page items themselves are validated against the dto.\n * Note: under row-level security, pages may be SHORTER than numItems when\n * rules reject rows mid-page; cursors remain correct.\n */\nexport function paginated<Dto extends z.ZodType>(dto: Dto) {\n\treturn {\n\t\targs: { paginationOpts: convexToZod(paginationOptsValidator) },\n\t\tresult: z.object({\n\t\t\tpage: z.array(dto),\n\t\t\tisDone: z.boolean(),\n\t\t\tcontinueCursor: z.string(),\n\t\t\tsplitCursor: z.string().nullable().optional(),\n\t\t\tpageStatus: z.enum(['SplitRecommended', 'SplitRequired']).nullable().optional(),\n\t\t}),\n\t}\n}\n\n/** Preserves discriminated-union storage while retaining the same table owner. */\nexport function zodVariantTable<Table extends string, Schema extends z.ZodType>(\n\ttableName: Table,\n\tstorage: Schema,\n) {\n\treturn {\n\t\ttableName,\n\t\tstorage,\n\t\tinsertSchema: storage,\n\t\ttable: defineTable(zodToConvex(storage)),\n\t}\n}\n\n/**\n * A zid that presents as a plain string in generated JSON schemas so\n * LLM-tool inputs stay primitive, while keeping the Id type at compile time.\n */\nexport function jsonSafeZid<Table extends string>(\n\ttableName: Table,\n): z.ZodType<GenericId<Table>, string> {\n\tconst id = zid(tableName)\n\treturn z\n\t\t.string()\n\t\t.refine((value): value is GenericId<Table> => id.safeParse(value).success)\n\t\t.describe(`Convex document id for table \"${tableName}\"; validated against the table by Convex`)\n}\n\ntype ZodObjectOmit<\n\tSchema extends z.ZodObject<any, any>,\n\tOmitMask extends z.util.Mask<keyof Schema['shape']>,\n> =\n\tSchema extends z.ZodObject<infer ObjectFields, infer Config>\n\t\t? z.ZodObject<\n\t\t\t\tz.util.Flatten<Omit<ObjectFields, Extract<keyof ObjectFields, keyof OmitMask>>>,\n\t\t\t\tConfig\n\t\t\t>\n\t\t: never\n\nfunction documentSchema<Fields extends FieldSchemas, Table extends string>(\n\tstorage: StrictObject<Fields>,\n\ttableName: Table,\n) {\n\treturn storage.extend({ _id: zid(tableName), _creationTime: z.number() })\n}\n\nfunction pickFields<Fields extends FieldSchemas, const Keys extends readonly FieldKey<Fields>[]>(\n\tfields: Fields,\n\tkeys: Keys,\n) {\n\t// SAFETY: each entry uses a requested key and its original validator, preserving Pick.\n\treturn Object.fromEntries(keys.map((key) => [key, fields[key]])) as Pick<Fields, Keys[number]>\n}\n\nfunction omitFields<Fields extends FieldSchemas, const Keys extends readonly FieldKey<Fields>[]>(\n\tfields: Fields,\n\tkeys: Keys,\n) {\n\tconst omitted = new Set<string>(keys)\n\t// SAFETY: filtering removes exactly the supplied keys and keeps every remaining validator.\n\treturn Object.fromEntries(Object.entries(fields).filter(([key]) => !omitted.has(key))) as Omit<\n\t\tFields,\n\t\tKeys[number]\n\t>\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"zod-table.mjs","names":["zid","convexToZod","convexToZod$1","zid$1","timestampFields","z","TIMESTAMP_FIELDS","zodTable","declared","fields","assertOwnedFields","columns","assertFieldLists","options","storage","schema","documentSchema","tableName","omittedKeys","commandKeys","publicKeys","insertSchema","omitFields","updateSchema","commandInput","pickFields","publicDto","reservedDtoFields","dto","deriveDto","normalized","extension","key","assertDtoValidationSchema","resultSchema","toPublicDto","projected","row","result","insert","omit","update","nativeStorage","zodToConvex","table","defineTable","paginationOpts","scalarPaginationSchema","selection","jsonSafeZid","tenantFields","tenantTable","id","createModule","maps","combined","map","definition","paginated","paginationOptsValidator","zodVariantTable","value","keys","omitted","seen","def","reject","visit","child","check","reserved","server","command","managed","convexToZod","zid"],"sources":["../src/zod-table.ts"],"sourcesContent":["import { convexToZod, zid, type ConvexValidatorFromZod, type Zid } from 'convex-helpers/server/zod4'\nimport { defineTable, paginationOptsValidator, type TableDefinition } from 'convex/server'\nimport type { GenericId } from 'convex/values'\nimport { z } from 'zod'\n\nimport { zodToConvex } from './zod-convex'\n\nimport {\n\tscalarPaginationSchema,\n\ttype ScalarPaginationSchema,\n\ttype ScalarSelection,\n\ttype ExactScalarSelection,\n} from './querying/filters'\n\n// Use this entry point when deriving validators from kit-owned schemas so\n// the producer and converter consult the same convex-helpers ID registry.\nexport { zid, convexToZod } from 'convex-helpers/server/zod4'\nexport { zodToConvex } from './zod-convex'\n\ntype FieldSchemas = Record<string, z.ZodType>\ntype FieldKey<Fields extends FieldSchemas> = Extract<keyof Fields, string>\n\n/**\n * Opinionated lifecycle timestamps — every table has them, server-owned,\n * always excluded from insert/command boundaries. Maintained by the\n * `timestamps` trigger helper (see cvx-kit/triggers).\n */\nconst timestampFields = {\n\tcreatedAt: z.number().optional(),\n\tupdatedAt: z.number().optional(),\n\tarchivedAt: z.number().optional(),\n} satisfies FieldSchemas\n\ntype TimestampFields = typeof timestampFields\ntype ReservedStorageField = '_id' | '_creationTime' | keyof TimestampFields\ntype OwnedFields<Fields> = {\n\t[Key in keyof Fields]: Key extends ReservedStorageField ? never : Fields[Key]\n}\ntype SystemFields<Table extends string> = { _id: Zid<Table>; _creationTime: z.ZodNumber }\n\nexport const TIMESTAMP_FIELDS = ['createdAt', 'updatedAt', 'archivedAt'] as const\n\nexport type TableBoundaryOptions<\n\tFields extends FieldSchemas,\n\tServerFields extends readonly FieldKey<Fields>[],\n\tCommandFields extends readonly FieldKey<Fields>[],\n\tPublicFields extends readonly FieldKey<Fields & TimestampFields>[],\n> = {\n\tserverFields?: ServerFields\n\tcommandFields?: CommandFields &\n\t\treadonly Exclude<FieldKey<Fields>, ServerFields[number] | keyof TimestampFields>[]\n\tpublicFields?: PublicFields\n}\n\ntype StrictObject<Fields extends FieldSchemas> = z.ZodObject<Fields, z.core.$strict>\ntype WriteFields<\n\tFields extends FieldSchemas,\n\tServerFields extends readonly FieldKey<Fields>[],\n> = Omit<Fields & TimestampFields, ServerFields[number] | (typeof TIMESTAMP_FIELDS)[number]>\ntype PartialFields<Fields extends FieldSchemas> = {\n\t[Key in keyof Fields]: z.ZodOptional<Fields[Key]>\n}\n\n/** Options shared by the DTO return schema and row projector.\n * Promised contributions must be present, including in reusable options.\n * New extension schemas support validation only: no transforms, pipes,\n * coercion, preprocess, defaults, catches, or overwrites, even when nested.\n * Refinements must validate without mutating their input.\n */\nexport type DtoOptions<IncludeId extends boolean = false, Extension extends FieldSchemas = {}> = ([\n\tIncludeId,\n] extends [true]\n\t? { _id: IncludeId }\n\t: { _id?: IncludeId }) &\n\t(keyof Extension extends never ? { extend?: Extension } : { extend: Extension })\n\ntype DtoProjectionOptions<IncludeId extends boolean> = DtoOptions<IncludeId> & {\n\textend?: Record<string, never>\n\tvalues?: never\n}\n\ntype ReservedDtoField =\n\t| '_id'\n\t| '_creationTime'\n\t| 'tenant'\n\t| '__proto__'\n\t| (typeof TIMESTAMP_FIELDS)[number]\ntype DtoExtension<Public extends FieldSchemas, Extension extends FieldSchemas> = {\n\t[Key in keyof Extension]: Key extends keyof Public | ReservedDtoField\n\t\t? never\n\t\t: Extension[Key] extends z.ZodPipe | z.ZodTransform\n\t\t\t? never\n\t\t\t: unknown\n}\n// Broad dictionaries cannot promise any particular extension field.\ntype KnownExtension<Extension> = string extends keyof Extension ? {} : Extension\n\ntype DtoSchema<\n\tTable extends string,\n\tPublic extends FieldSchemas,\n\tIncludeId extends boolean,\n\tExtension extends FieldSchemas = {},\n> = IncludeId extends true\n\t? StrictObject<Public & { _id: Zid<Table> } & KnownExtension<Extension>>\n\t: StrictObject<Public & KnownExtension<Extension>>\ntype DtoRow<Row, IncludeId extends boolean> = Row & (true extends IncludeId ? { _id: string } : {})\ntype DtoValues<Extension extends FieldSchemas> = keyof Extension extends never\n\t? { values?: never }\n\t: { values: z.input<StrictObject<NoInfer<Extension>>> }\n\ntype DtoBoundary<Table extends string, Fields extends FieldSchemas, Public extends FieldSchemas> = {\n\tdto(): StrictObject<Public>\n\tdto<const IncludeId extends boolean>(includeId: IncludeId): DtoSchema<Table, Public, IncludeId>\n\tdto<const IncludeId extends boolean = false>(\n\t\toptions: DtoProjectionOptions<IncludeId>,\n\t): DtoSchema<Table, Public, IncludeId>\n\tdto<const IncludeId extends boolean = false, const Extension extends FieldSchemas = {}>(\n\t\toptions: DtoOptions<IncludeId, Extension> & { extend?: DtoExtension<Public, Extension> },\n\t): DtoSchema<Table, Public, IncludeId, Extension>\n\ttoPublicDto(row: z.input<StrictObject<Fields>>): z.output<StrictObject<Public>>\n\ttoPublicDto<const IncludeId extends boolean>(\n\t\trow: DtoRow<z.input<StrictObject<Fields>>, IncludeId>,\n\t\tincludeId: IncludeId,\n\t): z.output<DtoSchema<Table, Public, IncludeId>>\n\ttoPublicDto<const IncludeId extends boolean = false>(\n\t\trow: DtoRow<z.input<StrictObject<Fields>>, IncludeId>,\n\t\toptions: DtoProjectionOptions<IncludeId>,\n\t): z.output<DtoSchema<Table, Public, IncludeId>>\n\ttoPublicDto<const IncludeId extends boolean = false, const Extension extends FieldSchemas = {}>(\n\t\trow: DtoRow<z.input<StrictObject<Fields>>, IncludeId>,\n\t\toptions: DtoOptions<IncludeId, Extension> & {\n\t\t\textend?: DtoExtension<Public, Extension>\n\t\t} & DtoValues<Extension>,\n\t): z.output<DtoSchema<Table, Public, IncludeId, Extension>>\n}\n\n/** Named schema relationships keep generated declarations bounded and consumers fully typed. */\nexport type TableBoundary<\n\tTable extends string,\n\tFields extends FieldSchemas,\n\tServerFields extends readonly FieldKey<Fields>[],\n\tCommandFields extends readonly FieldKey<Fields>[],\n\tPublicFields extends readonly FieldKey<Fields & TimestampFields>[],\n> = DtoBoundary<\n\tTable,\n\tFields & TimestampFields,\n\tPick<Fields & TimestampFields, PublicFields[number]>\n> & {\n\tpaginationOpts<\n\t\tconst Selection extends ScalarSelection<Fields & TimestampFields & SystemFields<Table>>,\n\t>(\n\t\tselection: ExactScalarSelection<Fields & TimestampFields & SystemFields<Table>, Selection>,\n\t): ScalarPaginationSchema<Fields & TimestampFields & SystemFields<Table>, Selection>\n\ttableName: Table\n\tstorage: StrictObject<Fields & TimestampFields>\n\tschema: ReturnType<typeof documentSchema<Fields & TimestampFields, Table>>\n\tinsertSchema: StrictObject<WriteFields<Fields, ServerFields>>\n\tupdateSchema: StrictObject<PartialFields<WriteFields<Fields, ServerFields>>>\n\tcommandInput: StrictObject<Pick<Fields & TimestampFields, CommandFields[number]>>\n\tpublicDto: StrictObject<Pick<Fields & TimestampFields, PublicFields[number]>>\n\ttable: TableDefinition<ConvexValidatorFromZod<StrictObject<Fields & TimestampFields>, 'required'>>\n\tinsert(): StrictObject<WriteFields<Fields, ServerFields>>\n\tinsert<Mask extends z.util.Mask<keyof WriteFields<Fields, ServerFields>>>(\n\t\tomit: Mask,\n\t): ZodObjectOmit<StrictObject<WriteFields<Fields, ServerFields>>, Mask>\n\tupdate(): StrictObject<PartialFields<WriteFields<Fields, ServerFields>>>\n\tupdate<Mask extends z.util.Mask<keyof WriteFields<Fields, ServerFields>>>(\n\t\tomit: Mask,\n\t): ZodObjectOmit<StrictObject<PartialFields<WriteFields<Fields, ServerFields>>>, Mask>\n\ttools: {\n\t\tinsert: StrictObject<Pick<Fields & TimestampFields, CommandFields[number]>>\n\t\tupdate: StrictObject<{\n\t\t\tdata: StrictObject<PartialFields<Pick<Fields & TimestampFields, CommandFields[number]>>>\n\t\t\tid: z.ZodType<GenericId<Table>, string>\n\t\t}>\n\t\tid: StrictObject<{ id: z.ZodType<GenericId<Table>, string> }>\n\t}\n}\n\n/** Defines one table and derives its storage, document, write, and DTO boundaries. */\nexport function zodTable<\n\tTable extends string,\n\tFields extends FieldSchemas,\n\tconst ServerFields extends readonly FieldKey<Fields>[] = readonly [],\n\tconst CommandFields extends readonly FieldKey<Fields>[] = readonly [],\n\tconst PublicFields extends readonly FieldKey<Fields & TimestampFields>[] = readonly [],\n>(\n\ttableName: Table,\n\tfields: (id: typeof zid) => Fields & OwnedFields<Fields>,\n\toptions: TableBoundaryOptions<Fields, ServerFields, CommandFields, PublicFields> = {},\n): TableBoundary<Table, Fields, ServerFields, CommandFields, PublicFields> {\n\tconst declared = fields(zid)\n\tassertOwnedFields(declared, ['_id', '_creationTime', ...TIMESTAMP_FIELDS])\n\tconst columns: Fields & TimestampFields = { ...declared, ...timestampFields }\n\tassertFieldLists(\n\t\tcolumns,\n\t\toptions.serverFields ?? [],\n\t\toptions.commandFields ?? [],\n\t\toptions.publicFields ?? [],\n\t)\n\tconst storage: z.ZodObject<Fields & TimestampFields, z.core.$strict> = z.object(columns).strict()\n\tconst schema = documentSchema(storage, tableName)\n\tconst omittedKeys: readonly (ServerFields[number] | (typeof TIMESTAMP_FIELDS)[number])[] = [\n\t\t...TIMESTAMP_FIELDS,\n\t\t...(options.serverFields ?? []),\n\t]\n\tconst commandKeys: readonly CommandFields[number][] = options.commandFields ?? []\n\tconst publicKeys: readonly PublicFields[number][] = options.publicFields ?? []\n\tconst insertSchema: z.ZodObject<WriteFields<Fields, ServerFields>, z.core.$strict> = z\n\t\t.object(omitFields(columns, omittedKeys))\n\t\t.strict()\n\tconst updateSchema = insertSchema.partial()\n\tconst commandInput: z.ZodObject<\n\t\tPick<Fields & TimestampFields, CommandFields[number]>,\n\t\tz.core.$strict\n\t> = z.object(pickFields(columns, commandKeys)).strict()\n\tconst publicDto: z.ZodObject<\n\t\tPick<Fields & TimestampFields, PublicFields[number]>,\n\t\tz.core.$strict\n\t> = z.object(pickFields(columns, publicKeys)).strict()\n\ttype Public = Pick<Fields & TimestampFields, PublicFields[number]>\n\ttype Row = z.input<typeof storage>\n\ttype RuntimeDtoFields = Public & FieldSchemas\n\ttype RuntimeDto = z.output<StrictObject<RuntimeDtoFields>> & { _id?: GenericId<Table> }\n\ttype RuntimeOptions = { _id?: boolean; extend?: FieldSchemas; values?: unknown }\n\tconst reservedDtoFields = new Set<string>([\n\t\t'_id',\n\t\t'_creationTime',\n\t\t'tenant',\n\t\t'__proto__',\n\t\t...TIMESTAMP_FIELDS,\n\t])\n\n\tfunction dto(): typeof publicDto\n\tfunction dto<const IncludeId extends boolean>(\n\t\tincludeId: IncludeId,\n\t): DtoSchema<Table, Public, IncludeId>\n\tfunction dto<const IncludeId extends boolean = false>(\n\t\toptions: DtoProjectionOptions<IncludeId>,\n\t): DtoSchema<Table, Public, IncludeId>\n\tfunction dto<const IncludeId extends boolean = false, const Extension extends FieldSchemas = {}>(\n\t\toptions: DtoOptions<IncludeId, Extension> & { extend?: DtoExtension<Public, Extension> },\n\t): DtoSchema<Table, Public, IncludeId, Extension>\n\tfunction dto(options?: boolean | RuntimeOptions) {\n\t\treturn deriveDto(options)\n\t}\n\n\tfunction deriveDto(options?: boolean | RuntimeOptions) {\n\t\tconst normalized = options === true || options === false ? { _id: options } : options\n\t\tconst extension = normalized?.extend ?? {}\n\t\tfor (const key of Object.keys(extension)) {\n\t\t\tif (reservedDtoFields.has(key) || Object.hasOwn(publicDto.shape, key)) {\n\t\t\t\tthrow new Error(`DTO extension collision for field \"${key}\" on table \"${tableName}\"`)\n\t\t\t}\n\t\t\tassertDtoValidationSchema(extension[key])\n\t\t}\n\t\tif (!normalized?._id && Object.keys(extension).length === 0) return publicDto\n\t\tconst resultSchema = z.object({ ...publicDto.shape, ...extension }).strict()\n\t\treturn normalized?._id ? resultSchema.extend({ _id: schema.shape._id }) : resultSchema\n\t}\n\n\tfunction toPublicDto(row: Row): z.output<typeof publicDto>\n\tfunction toPublicDto<const IncludeId extends boolean>(\n\t\trow: DtoRow<Row, IncludeId>,\n\t\tincludeId: IncludeId,\n\t): z.output<DtoSchema<Table, Public, IncludeId>>\n\tfunction toPublicDto<const IncludeId extends boolean = false>(\n\t\trow: DtoRow<Row, IncludeId>,\n\t\toptions: DtoProjectionOptions<IncludeId>,\n\t): z.output<DtoSchema<Table, Public, IncludeId>>\n\tfunction toPublicDto<\n\t\tconst IncludeId extends boolean = false,\n\t\tconst Extension extends FieldSchemas = {},\n\t>(\n\t\trow: DtoRow<Row, IncludeId>,\n\t\toptions: DtoOptions<IncludeId, Extension> & {\n\t\t\textend?: DtoExtension<Public, Extension>\n\t\t} & DtoValues<Extension>,\n\t): z.output<DtoSchema<Table, Public, IncludeId, Extension>>\n\tfunction toPublicDto(row: Row, options?: boolean | RuntimeOptions): RuntimeDto {\n\t\tconst normalized = options === true || options === false ? { _id: options } : options\n\t\t// Enforce extension collisions and validation-only schemas before parsing values.\n\t\tderiveDto(options)\n\t\tconst projected = publicDto.parse(\n\t\t\tObject.fromEntries(\n\t\t\t\tObject.keys(publicDto.shape).map((key) => {\n\t\t\t\t\t// SAFETY: publicDto contains only keys selected from the storage fields.\n\t\t\t\t\treturn [key, row[key as keyof typeof row]]\n\t\t\t\t}),\n\t\t\t),\n\t\t)\n\t\tconst extension = z\n\t\t\t.object(normalized?.extend ?? {})\n\t\t\t.strict()\n\t\t\t.parse(normalized?.values ?? {})\n\t\tconst result: RuntimeDto = { ...projected, ...extension }\n\t\tif (normalized?._id) {\n\t\t\tresult._id = schema.shape._id.parse('_id' in row ? row._id : undefined)\n\t\t}\n\t\treturn result\n\t}\n\n\ttype InsertSchema = typeof insertSchema\n\ttype UpdateSchema = typeof updateSchema\n\ttype InsertMask = z.util.Mask<keyof InsertSchema['shape']>\n\ttype UpdateMask = z.util.Mask<keyof UpdateSchema['shape']>\n\n\tfunction insert(): InsertSchema\n\tfunction insert<OmitMask extends InsertMask>(\n\t\tomit: OmitMask,\n\t): ZodObjectOmit<InsertSchema, OmitMask>\n\tfunction insert(omit?: InsertMask) {\n\t\t// SAFETY: InsertMask contains only insertSchema keys; the overload retains Zod's exact mask relationship.\n\t\treturn omit ? insertSchema.omit(omit as never) : insertSchema\n\t}\n\n\tfunction update(): UpdateSchema\n\tfunction update<OmitMask extends UpdateMask>(\n\t\tomit: OmitMask,\n\t): ZodObjectOmit<UpdateSchema, OmitMask>\n\tfunction update(omit?: UpdateMask) {\n\t\t// SAFETY: UpdateMask contains only updateSchema keys; the overload retains Zod's exact mask relationship.\n\t\treturn omit ? updateSchema.omit(omit as never) : updateSchema\n\t}\n\n\tconst nativeStorage = zodToConvex(storage)\n\tconst table: TableDefinition<ConvexValidatorFromZod<typeof storage, 'required'>> =\n\t\tdefineTable(nativeStorage)\n\ttype EntityFields = Fields & TimestampFields & SystemFields<Table>\n\tfunction paginationOpts<const Selection extends ScalarSelection<EntityFields>>(\n\t\tselection: ExactScalarSelection<EntityFields, Selection>,\n\t): ScalarPaginationSchema<EntityFields, Selection> {\n\t\treturn scalarPaginationSchema<EntityFields, Selection>(zodToConvex(schema).fields, selection)\n\t}\n\treturn {\n\t\ttableName,\n\t\tschema,\n\t\tstorage,\n\t\tinsertSchema,\n\t\tupdateSchema,\n\t\tcommandInput,\n\t\tpublicDto,\n\t\tdto,\n\t\ttoPublicDto,\n\t\tpaginationOpts,\n\t\ttable,\n\t\tinsert,\n\t\tupdate,\n\t\ttools: {\n\t\t\t// jsonSafeZid (not zid): tool masks feed generated JSON schemas, so\n\t\t\t// ids must present as plain strings while keeping the Id<...> type.\n\t\t\tinsert: commandInput,\n\t\t\tupdate: z.object({ data: commandInput.partial(), id: jsonSafeZid(tableName) }).strict(),\n\t\t\tid: z.object({ id: jsonSafeZid(tableName) }).strict(),\n\t\t},\n\t}\n}\n\nconst tenantFields = {\n\ttenant: z.string().min(1),\n} satisfies FieldSchemas\n\ntype TenantFields = typeof tenantFields\n\n/**\n * A zodTable whose rows are tenant-owned: injects the server-owned `tenant`\n * field (the tenancy boundary — see cvx-kit/tenancy). Like timestamps, it is\n * excluded from insert/update/command boundaries; handlers stamp it from\n * ctx.tenant, row-level security matches on it, and the tenantOwnership\n * trigger forbids reassigning it. Expose it via publicFields only when a DTO\n * genuinely needs it.\n */\nexport function tenantTable<\n\tTable extends string,\n\tFields extends FieldSchemas,\n\tconst ServerFields extends readonly FieldKey<Fields>[] = readonly [],\n\tconst CommandFields extends readonly FieldKey<Fields>[] = readonly [],\n\tconst PublicFields extends readonly FieldKey<Fields & TenantFields & TimestampFields>[] =\n\t\treadonly [],\n>(\n\ttableName: Table,\n\tfields: (\n\t\tid: typeof zid,\n\t) => Fields &\n\t\tOwnedFields<Fields> & { [Key in keyof Fields]: Key extends 'tenant' ? never : Fields[Key] },\n\toptions: TableBoundaryOptions<\n\t\tFields & TenantFields,\n\t\treadonly (ServerFields[number] | 'tenant')[],\n\t\tCommandFields,\n\t\tPublicFields\n\t> = {},\n) {\n\treturn zodTable<\n\t\tTable,\n\t\tFields & TenantFields,\n\t\treadonly (ServerFields[number] | 'tenant')[],\n\t\tCommandFields,\n\t\tPublicFields\n\t>(\n\t\ttableName,\n\t\t(id) => {\n\t\t\tconst declared = fields(id)\n\t\t\tassertOwnedFields(declared, ['tenant', '_id', '_creationTime', ...TIMESTAMP_FIELDS])\n\t\t\t// SAFETY: reserved keys were rejected before injecting tenant; the generic\n\t\t\t// wrapper forwards the same checked fields to the ordinary table constructor.\n\t\t\treturn { ...declared, ...tenantFields } as Fields &\n\t\t\t\tTenantFields &\n\t\t\t\tOwnedFields<Fields & TenantFields>\n\t\t},\n\t\t{\n\t\t\t...options,\n\t\t\tserverFields: [...(options.serverFields ?? []), 'tenant'],\n\t\t},\n\t)\n}\n\n/**\n * Merges per-module table maps into the application schema map, rejecting\n * duplicate table names across modules — the module-registry combinator for\n * domain/table.ts: `defineSchema(createModule(catalogTables, salesTables))`.\n */\nexport function createModule<const Maps extends readonly Record<string, TableDefinition<any>>[]>(\n\t...maps: Maps\n): UnionToIntersection<Maps[number]> {\n\tconst combined: Record<string, TableDefinition<any>> = {}\n\tfor (const map of maps) {\n\t\tfor (const [tableName, definition] of Object.entries(map)) {\n\t\t\tif (tableName in combined) {\n\t\t\t\tthrow new Error(`Table \"${tableName}\" is declared by more than one module`)\n\t\t\t}\n\t\t\tcombined[tableName] = definition\n\t\t}\n\t}\n\t// SAFETY: every input entry is copied, and duplicate keys are rejected above.\n\treturn combined as UnionToIntersection<Maps[number]>\n}\n\ntype UnionToIntersection<Union> = (\n\tUnion extends unknown ? (member: Union) => void : never\n) extends (member: infer Intersection) => void\n\t? Intersection\n\t: never\n\n/**\n * Cursor-pagination boundary schemas bridged from Convex's own validators:\n * `args.paginationOpts` for function args, `result` for the page shape.\n * Pair with `include(...).paginate(opts)` (cvx-kit/auth). The result object\n * is deliberately non-strict — Convex may attach engine fields (splitCursor,\n * pageStatus); the page items themselves are validated against the dto.\n * Note: under row-level security, pages may be SHORTER than numItems when\n * rules reject rows mid-page; cursors remain correct.\n */\nexport function paginated<Dto extends z.ZodType>(dto: Dto) {\n\treturn {\n\t\targs: { paginationOpts: convexToZod(paginationOptsValidator) },\n\t\tresult: z.object({\n\t\t\tpage: z.array(dto),\n\t\t\tisDone: z.boolean(),\n\t\t\tcontinueCursor: z.string(),\n\t\t\tsplitCursor: z.string().nullable().optional(),\n\t\t\tpageStatus: z.enum(['SplitRecommended', 'SplitRequired']).nullable().optional(),\n\t\t}),\n\t}\n}\n\n/** Preserves discriminated-union storage while retaining the same table owner. */\nexport function zodVariantTable<Table extends string, Schema extends z.ZodType>(\n\ttableName: Table,\n\tstorage: Schema,\n) {\n\treturn {\n\t\ttableName,\n\t\tstorage,\n\t\tinsertSchema: storage,\n\t\ttable: defineTable(zodToConvex(storage)),\n\t}\n}\n\n/**\n * A zid that presents as a plain string in generated JSON schemas so\n * LLM-tool inputs stay primitive, while keeping the Id type at compile time.\n */\nexport function jsonSafeZid<Table extends string>(\n\ttableName: Table,\n): z.ZodType<GenericId<Table>, string> {\n\tconst id = zid(tableName)\n\treturn z\n\t\t.string()\n\t\t.refine((value): value is GenericId<Table> => id.safeParse(value).success)\n\t\t.describe(`Convex document id for table \"${tableName}\"; validated against the table by Convex`)\n}\n\ntype ZodObjectOmit<\n\tSchema extends z.ZodObject<any, any>,\n\tOmitMask extends z.util.Mask<keyof Schema['shape']>,\n> =\n\tSchema extends z.ZodObject<infer ObjectFields, infer Config>\n\t\t? z.ZodObject<\n\t\t\t\tz.util.Flatten<Omit<ObjectFields, Extract<keyof ObjectFields, keyof OmitMask>>>,\n\t\t\t\tConfig\n\t\t\t>\n\t\t: never\n\nfunction documentSchema<Fields extends FieldSchemas, Table extends string>(\n\tstorage: StrictObject<Fields>,\n\ttableName: Table,\n) {\n\treturn storage.extend({ _id: zid(tableName), _creationTime: z.number() })\n}\n\nfunction pickFields<Fields extends FieldSchemas, const Keys extends readonly FieldKey<Fields>[]>(\n\tfields: Fields,\n\tkeys: Keys,\n) {\n\t// SAFETY: each entry uses a requested key and its original validator, preserving Pick.\n\treturn Object.fromEntries(keys.map((key) => [key, fields[key]])) as Pick<Fields, Keys[number]>\n}\n\nfunction omitFields<Fields extends FieldSchemas, const Keys extends readonly FieldKey<Fields>[]>(\n\tfields: Fields,\n\tkeys: Keys,\n) {\n\tconst omitted = new Set<string>(keys)\n\t// SAFETY: filtering removes exactly the supplied keys and keeps every remaining validator.\n\treturn Object.fromEntries(Object.entries(fields).filter(([key]) => !omitted.has(key))) as Omit<\n\t\tFields,\n\t\tKeys[number]\n\t>\n}\n\ntype DtoPropertiesCheck = z.core.$ZodCheck & {\n\treadonly _zod: {\n\t\treadonly def: { readonly check: 'properties' } & Record<\n\t\t\t'shape',\n\t\t\tRecord<string, z.core.$ZodType>\n\t\t>\n\t}\n}\n\n/** Inspect Zod 4's discriminated definitions, failing closed for unsupported nodes.\n * This restriction applies only to new DTO extensions; storage/publicDto are unchanged.\n */\nfunction assertDtoValidationSchema(\n\tschema: z.core.$ZodType,\n\tseen = new Set<z.core.$ZodType>(),\n): void {\n\tif (seen.has(schema)) return\n\tseen.add(schema)\n\t// SAFETY: Zod 4 exposes its built-in schema definitions as this discriminated union.\n\tconst def = (schema as z.core.$ZodTypes)._zod.def\n\tconst reject = () => {\n\t\tthrow new Error('DTO extensions require supported value-preserving validation schemas')\n\t}\n\tif ('coerce' in def && def.coerce) reject()\n\tif ('normalize' in def && def.normalize) reject()\n\tconst visit = (child: z.core.$ZodType) => assertDtoValidationSchema(child, seen)\n\tfor (const check of def.checks ?? []) {\n\t\tif (check._zod.def.check === 'overwrite') reject()\n\t\tif ('normalize' in check._zod.def && check._zod.def.normalize) reject()\n\t\tif (check._zod.def.check === 'property') {\n\t\t\t// SAFETY: the property discriminator owns this embedded schema in Zod 4.\n\t\t\tvisit((check as z.core.$ZodCheckProperty)._zod.def.schema)\n\t\t}\n\t\tif (check._zod.def.check === 'properties') {\n\t\t\t// SAFETY: the properties discriminator owns this schema shape in Zod 4.\n\t\t\tfor (const child of Object.values((check as DtoPropertiesCheck)._zod.def.shape)) {\n\t\t\t\tvisit(child)\n\t\t\t}\n\t\t}\n\t}\n\tswitch (def.type) {\n\t\tcase 'string':\n\t\tcase 'number':\n\t\tcase 'boolean':\n\t\tcase 'bigint':\n\t\tcase 'date':\n\t\tcase 'symbol':\n\t\tcase 'null':\n\t\tcase 'undefined':\n\t\tcase 'void':\n\t\tcase 'never':\n\t\tcase 'literal':\n\t\tcase 'enum':\n\t\t\treturn\n\t\tcase 'object':\n\t\t\tfor (const child of Object.values(def.shape)) visit(child)\n\t\t\tif (def.catchall) visit(def.catchall)\n\t\t\treturn\n\t\tcase 'array':\n\t\t\tvisit(def.element)\n\t\t\treturn\n\t\tcase 'optional':\n\t\tcase 'nullable':\n\t\tcase 'nonoptional':\n\t\t\tvisit(def.innerType)\n\t\t\treturn\n\t\tcase 'union':\n\t\t\tfor (const child of def.options) visit(child)\n\t\t\treturn\n\t\tcase 'intersection':\n\t\t\tvisit(def.left)\n\t\t\tvisit(def.right)\n\t\t\treturn\n\t\tcase 'tuple':\n\t\t\tfor (const child of def.items) visit(child)\n\t\t\tif (def.rest) visit(def.rest)\n\t\t\treturn\n\t\tcase 'record':\n\t\t\tvisit(def.keyType)\n\t\t\tvisit(def.valueType)\n\t\t\treturn\n\t\tdefault:\n\t\t\treject()\n\t}\n}\n\nfunction assertOwnedFields(fields: FieldSchemas, reserved: readonly string[]): void {\n\tfor (const key of reserved) {\n\t\tif (Object.hasOwn(fields, key))\n\t\t\tthrow new Error(`Reserved table field \"${key}\" cannot be declared`)\n\t}\n}\n\nfunction assertFieldLists(\n\tfields: FieldSchemas,\n\tserver: readonly string[],\n\tcommand: readonly string[],\n\tpublicKeys: readonly string[],\n): void {\n\tfor (const key of [...server, ...command, ...publicKeys]) {\n\t\tif (!Object.hasOwn(fields, key)) throw new Error(`Unknown table field \"${key}\"`)\n\t}\n\tconst managed = new Set<string>([...TIMESTAMP_FIELDS, ...server])\n\tfor (const key of command) {\n\t\tif (managed.has(key)) throw new Error(`Server-owned field \"${key}\" cannot be a command input`)\n\t}\n}\n"],"mappings":"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"}
|
package/docs/conventions.md
CHANGED
|
@@ -20,6 +20,7 @@ convex/
|
|
|
20
20
|
functions.ts
|
|
21
21
|
triggers.ts
|
|
22
22
|
foundation.ts
|
|
23
|
+
relations.ts querying.ts (optional declarative relation/engine facades)
|
|
23
24
|
<component>.ts (one per mounted component: audit.ts, approvals.ts, ...)
|
|
24
25
|
auth.ts auth.config.ts http.ts crons.ts
|
|
25
26
|
api/
|
|
@@ -59,6 +60,17 @@ Three zones, three privileges:
|
|
|
59
60
|
Dependency direction is one-way: `api → domain → shared → kit`. Anything
|
|
60
61
|
pointing the other way is a violation.
|
|
61
62
|
|
|
63
|
+
### Relational query facades
|
|
64
|
+
|
|
65
|
+
When using the querying engine, root `relations.ts` exports one declarative
|
|
66
|
+
`defineRelations(...)` registry and root `querying.ts` exports one configured
|
|
67
|
+
`createQueryEngine(...)` facade. Establish native indexes before compiling the
|
|
68
|
+
registry. Keep database execution and business predicates in domain Query
|
|
69
|
+
handlers, and derive pagination/result validators in domain `contracts.ts`.
|
|
70
|
+
Root facades contain configuration, not handlers or business branches.
|
|
71
|
+
See [querying.md](./querying.md) for complete usage and [oxlint.md](./oxlint.md)
|
|
72
|
+
for facade ownership and pagination diagnostics.
|
|
73
|
+
|
|
62
74
|
### Module grain
|
|
63
75
|
|
|
64
76
|
`domain/<entity>/` is really `domain/<module>/`: the directory is a **bounded
|
package/docs/llms.md
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
> Paste this file (installed at `node_modules/cvx-kit/docs/`) into an agent's
|
|
4
4
|
> context when it works on a cvx-kit application. Detailed docs:
|
|
5
|
-
> `architecture.md`, `conventions.md`, `zod-table.md`, `auth.md`,
|
|
5
|
+
> `architecture.md`, `conventions.md`, `zod-table.md`, `querying.md`, `auth.md`,
|
|
6
6
|
> `commands.md`, `effect.md`, `domain-operations.md`, `triggers.md`, `approvals.md`, `maintainability.md`,
|
|
7
7
|
> `migration.md` (same directory).
|
|
8
8
|
> Structure, file anatomy, and naming rules → `conventions.md` is
|
|
9
9
|
> authoritative. Restructuring an existing raw project → `migration.md`.
|
|
10
10
|
> Upgrading from 0.0.x → `upgrading.md`. New 0.1.0 helpers: `crud.md`,
|
|
11
|
-
> `webhooks.md`, `agent-tools.md`; pagination in `
|
|
11
|
+
> `webhooks.md`, `agent-tools.md`; stream/relationship pagination in `querying.md`,
|
|
12
|
+
> table DTO and pagination helpers in `zod-table.md`.
|
|
12
13
|
> Consumer lint configuration and coverage → `oxlint.md`. The optional
|
|
13
14
|
> `cvx-kit/oxlint` plugin checks the statically detectable conventions;
|
|
14
15
|
> behavioral guarantees still need architecture and runtime tests.
|
|
@@ -41,11 +42,20 @@ copy that API into new implementations.
|
|
|
41
42
|
built once with `createEffectAuthFunctions(config, policies)` from `cvx-kit/effect`
|
|
42
43
|
for shared Effect handles, or individually wrapped with `effectZodApiBuilder`.
|
|
43
44
|
This is what guarantees auth, triggers, and bounded reads.
|
|
44
|
-
3. **Every public query returns DTOs** via `<table>.toPublicDto(row)`
|
|
45
|
-
|
|
45
|
+
3. **Every public query returns DTOs** via `<table>.toPublicDto(row)` or the
|
|
46
|
+
querying engine. For IDs use `toPublicDto(row, true)` with `dto(true)`; for
|
|
47
|
+
computed fields use matching `extend` schemas and explicit `values`.
|
|
48
|
+
`publicDto` remains the default schema. Never manually append IDs to a DTO.
|
|
46
49
|
4. **Every read is bounded**: `ctx.include(ctx.db.query('t')).matching(...)
|
|
47
50
|
.execute(limit)` with `1 ≤ limit ≤ 100`. `.resolve()` falls back to a full
|
|
48
|
-
table scan — avoid it.
|
|
51
|
+
table scan — avoid it. Alternatively use the independent querying engine
|
|
52
|
+
with table/registry `paginationOpts(selection)` and matching `pageDto`.
|
|
53
|
+
Declare relationships in root `relations.ts`, configure `createQueryEngine`
|
|
54
|
+
in root `querying.ts`, and paginate inside authenticated domain Query handlers.
|
|
55
|
+
Apply eligibility before limits; many relationships return bounded pages.
|
|
56
|
+
`order` reverses native index order; `.when(value, index)` selects an index
|
|
57
|
+
without binding fields. Unknown contexts fail closed and budget exhaustion
|
|
58
|
+
throws. See `querying.md` and the DTO/pagination rules in `oxlint.md`.
|
|
49
59
|
5. **State changes are Effect commands**: declare
|
|
50
60
|
`Command({ context, operations: ({ command }) => ({ ... }), defaults })`.
|
|
51
61
|
Each `command({ input, result, classification, handler, audit })` owns its
|
package/docs/oxlint.md
CHANGED
|
@@ -11,7 +11,8 @@ library authoring. The five architecture rules below were added in 0.1.6. See `u
|
|
|
11
11
|
## Configuration
|
|
12
12
|
|
|
13
13
|
Install Oxlint as a development dependency and load the plugin in
|
|
14
|
-
`.oxlintrc.json`. The root README includes a configuration enabling all rules.
|
|
14
|
+
`.oxlintrc.json`. The root README includes a configuration enabling all rules. The coverage table
|
|
15
|
+
below lists all 23 rule names.
|
|
15
16
|
Loading a plugin does not enable its rules. For example:
|
|
16
17
|
|
|
17
18
|
```json
|
|
@@ -44,28 +45,31 @@ offer automatic fixes for architectural moves.
|
|
|
44
45
|
|
|
45
46
|
Every name below has the `cvx/` prefix in lint configuration.
|
|
46
47
|
|
|
47
|
-
| Rule | Detects | Documentation
|
|
48
|
-
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
49
|
-
| `project-structure` | Root folder allowlist, domain ownership, colocated runnable tests, misplaced tests, symlinks, parse failures, runtime domain-module cycles, and shared implementations with only one domain consumer. | Folder structure and Tests
|
|
50
|
-
| `root-wiring-only` | Unknown root files without a component facade, root function declarations, nondelegating callbacks, branches, loops, direct writes/network calls, and inline root schema construction. | Root files
|
|
51
|
-
| `application-orchestration` | Files without two domain dependencies, provider construction, external runtime dependencies, database access, validator construction, and business branches. | Module grain
|
|
52
|
-
| `internal-function-ownership` | Internal/system registrations at root, in public API adapters, or in domain/shared. | Folder structure
|
|
53
|
-
| `domain-file-responsibilities` | Impure rules, query writes/mutations, database access in declarative files, misplaced direct external I/O, and imports that violate these roles. | File anatomy
|
|
54
|
-
| `root-facade-ownership` | `createAuthFunctions`, `createTriggers`, and `
|
|
55
|
-
| `no-raw-builders` | Runtime imports of raw Convex builders outside `functions.ts`; namespace calls to raw generic builders. | `auth.md`; `llms.md`, Non-negotiable rules
|
|
56
|
-
| `no-handwritten-references` | Calls to Convex `makeFunctionReference`, including import aliases. | `conventions.md`, Import rules
|
|
57
|
-
| `domain-import-boundaries` | Reverse dependencies into `api/`, domain imports of `application/`, forbidden sibling imports, and API adapters importing another domain module. | `conventions.md`, Module grain and Import rules
|
|
58
|
-
| `public-functions-in-api` | Calls to root `auth*`, `role*`, or `admin*` constructors outside `api/`. | `conventions.md`, Folder structure
|
|
59
|
-
| `thin-api-adapters` | `if`, `switch`, ternary branches, top-level private helpers, and direct `ctx.db` writes in `api/`. | `conventions.md`, The adapter file
|
|
60
|
-
| `no-inline-enums` | Literal arrays passed to imported Zod `enum`. Use named vocabulary tuples. | `conventions.md`, Vocabularies
|
|
61
|
-
| `no-unbounded-reads` |
|
|
62
|
-
| `no-manual-timestamps` | Explicit `createdAt`/`updatedAt` fields in object literals passed to direct `ctx.db` writes. | `triggers.md`
|
|
63
|
-
| `component-boundaries` | Local portable components importing host/sibling files; host imports bypassing component client facades; private package component subpaths. | `architecture.md`, Injection, not import
|
|
64
|
-
| `no-component-env` | Direct `process.env` access, process import aliases, environment destructuring, and named env imports inside local portable components. | `conventions.md`, Import rules
|
|
65
|
-
| `schema-file-boundaries` | Host `defineTable`, misplaced `defineSchema` or kit table constructors, and misplaced `.table.index/searchIndex/vectorIndex` chains. | `conventions.md`, Shapes and topology
|
|
66
|
-
| `no-internal-reexports` | Re-exports and import-then-export aliases inside `convex/`. | `conventions.md`, Universal file order
|
|
67
|
-
| `public-api-first` | Private function declarations before the last export. | `conventions.md`, Universal file order
|
|
68
|
-
| `named-private-helpers` | Top-level private arrow/function-expression variables. | `conventions.md`, Universal file order
|
|
48
|
+
| Rule | Detects | Documentation |
|
|
49
|
+
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
|
|
50
|
+
| `project-structure` | Root folder allowlist, domain ownership, colocated runnable tests, misplaced tests, symlinks, parse failures, runtime domain-module cycles, and shared implementations with only one domain consumer. | Folder structure and Tests |
|
|
51
|
+
| `root-wiring-only` | Unknown root files without a component facade, root function declarations, nondelegating callbacks, branches, loops, direct writes/network calls, and inline root schema construction. | Root files |
|
|
52
|
+
| `application-orchestration` | Files without two domain dependencies, provider construction, external runtime dependencies, database access, validator construction, and business branches. | Module grain |
|
|
53
|
+
| `internal-function-ownership` | Internal/system registrations at root, in public API adapters, or in domain/shared. | Folder structure |
|
|
54
|
+
| `domain-file-responsibilities` | Impure rules, query writes/mutations, database access in declarative files, misplaced direct external I/O, and imports that violate these roles. | File anatomy |
|
|
55
|
+
| `root-facade-ownership` | `createAuthFunctions`, foundation factories, `createTriggers`, `defineRelations`, and `createQueryEngine` outside their root facades, or repeated in one file. | `conventions.md`, Root files |
|
|
56
|
+
| `no-raw-builders` | Runtime imports of raw Convex builders outside `functions.ts`; namespace calls to raw generic builders. | `auth.md`; `llms.md`, Non-negotiable rules |
|
|
57
|
+
| `no-handwritten-references` | Calls to Convex `makeFunctionReference`, including import aliases. | `conventions.md`, Import rules |
|
|
58
|
+
| `domain-import-boundaries` | Reverse dependencies into `api/`, domain imports of `application/`, forbidden sibling imports, and API adapters importing another domain module. | `conventions.md`, Module grain and Import rules |
|
|
59
|
+
| `public-functions-in-api` | Calls to root `auth*`, `role*`, or `admin*` constructors outside `api/`. | `conventions.md`, Folder structure |
|
|
60
|
+
| `thin-api-adapters` | `if`, `switch`, ternary branches, top-level private helpers, and direct `ctx.db` writes in `api/`. | `conventions.md`, The adapter file |
|
|
61
|
+
| `no-inline-enums` | Literal arrays passed to imported Zod `enum`. Use named vocabulary tuples. | `conventions.md`, Vocabularies |
|
|
62
|
+
| `no-unbounded-reads` | Recognized unbounded database/stream/relationship reads and eligibility `.filter()` calls after bounded take/execute/paginate results. | `llms.md`, Bounded reads; `zod-table.md` |
|
|
63
|
+
| `no-manual-timestamps` | Explicit `createdAt`/`updatedAt` fields in object literals passed to direct `ctx.db` writes. | `triggers.md` |
|
|
64
|
+
| `component-boundaries` | Local portable components importing host/sibling files; host imports bypassing component client facades; private package component subpaths. | `architecture.md`, Injection, not import |
|
|
65
|
+
| `no-component-env` | Direct `process.env` access, process import aliases, environment destructuring, and named env imports inside local portable components. | `conventions.md`, Import rules |
|
|
66
|
+
| `schema-file-boundaries` | Host `defineTable`, misplaced `defineSchema` or kit table constructors, and misplaced `.table.index/searchIndex/vectorIndex` chains. | `conventions.md`, Shapes and topology |
|
|
67
|
+
| `no-internal-reexports` | Re-exports and import-then-export aliases inside `convex/`. | `conventions.md`, Universal file order |
|
|
68
|
+
| `public-api-first` | Private function declarations before the last export. | `conventions.md`, Universal file order |
|
|
69
|
+
| `named-private-helpers` | Top-level private arrow/function-expression variables. | `conventions.md`, Universal file order |
|
|
70
|
+
| `no-manual-id-projection` | Adding `id`/`_id` to a recognized table DTO through object spreads or property assignments. | Public DTO parameters in `zod-table.md` |
|
|
71
|
+
| `no-copied-pagination` | Zod/native validator objects copying `numItems` + `cursor` or `page` + `isDone` + `continueCursor`. | Pagination options in `zod-table.md`; `querying.md` |
|
|
72
|
+
| `reserved-domain-file-anatomy` | Reserved domain files without their expected declarative schema/contracts/table or Command/Query registry shape. | Reserved domain files below |
|
|
69
73
|
|
|
70
74
|
## Allowed patterns
|
|
71
75
|
|
|
@@ -87,14 +91,120 @@ Every name below has the `cvx/` prefix in lint configuration.
|
|
|
87
91
|
- `archivedAt` remains application-controlled. Singular `db.get` reads and
|
|
88
92
|
bounded `execute`/`paginate` calls are allowed.
|
|
89
93
|
|
|
94
|
+
## DTO and pagination diagnostics
|
|
95
|
+
|
|
96
|
+
Enable the new diagnostics alongside the existing architecture rules:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"rules": {
|
|
101
|
+
"cvx/no-manual-id-projection": "error",
|
|
102
|
+
"cvx/no-copied-pagination": "error",
|
|
103
|
+
"cvx/no-unbounded-reads": "error",
|
|
104
|
+
"cvx/root-facade-ownership": "error",
|
|
105
|
+
"cvx/root-wiring-only": "error",
|
|
106
|
+
"cvx/reserved-domain-file-anatomy": "error"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### `no-manual-id-projection`
|
|
112
|
+
|
|
113
|
+
Recognizes a table DTO projection/parse, including supported binding aliases,
|
|
114
|
+
then reports adding `id` or `_id` by a spread or property assignment:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
// Reported
|
|
118
|
+
const output = { ...tasks.toPublicDto(row), _id: row._id }
|
|
119
|
+
const parsed = tasks.publicDto.parse(input)
|
|
120
|
+
parsed.id = row._id
|
|
121
|
+
|
|
122
|
+
// Use the matching projection and return boundary
|
|
123
|
+
const outputWithId = tasks.toPublicDto(row, true)
|
|
124
|
+
const returns = tasks.dto(true)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The helper returns `_id`, not an `id` alias. For computed fields use the
|
|
128
|
+
validated `extend`/`values` API described in [zod-table.md](./zod-table.md).
|
|
129
|
+
The rule depends on recognized table/DTO provenance; it does not treat every
|
|
130
|
+
object named `dto` as a kit boundary or prove all runtime projection paths safe.
|
|
131
|
+
|
|
132
|
+
### `no-copied-pagination`
|
|
133
|
+
|
|
134
|
+
Reports recognized `z.object`/Convex `v.object` shapes containing both `numItems`
|
|
135
|
+
and `cursor`, or all three of `page`, `isDone`, and `continueCursor`:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
// Reported
|
|
139
|
+
const options = z.object({ numItems: z.number(), cursor: z.string().nullable() })
|
|
140
|
+
const result = z.object({
|
|
141
|
+
page: z.array(tasks.publicDto),
|
|
142
|
+
isDone: z.boolean(),
|
|
143
|
+
continueCursor: z.string(),
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
// Table query input and relation-aware output
|
|
147
|
+
const options = tasks.paginationOpts({ status: true })
|
|
148
|
+
const result = relations.tables.tasks.pageDto({})
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
For ordinary native pagination, retain `paginationOptsValidator` (bridge through
|
|
152
|
+
`convexToZod` when needed) or `paginated(tasks.dto())`. These preserve native
|
|
153
|
+
validators instead of copying their current fields. Application-specific fields
|
|
154
|
+
can extend the derived Zod object. Use the engine's `paginationOpts` for its
|
|
155
|
+
selected filters and order; a native options validator alone supplies neither.
|
|
156
|
+
|
|
157
|
+
### `no-unbounded-reads`
|
|
158
|
+
|
|
159
|
+
The existing rule now follows recognized native, kit `include`, stream, and
|
|
160
|
+
relationship read chains. It reports unbounded `collect()`/`resolve()` where
|
|
161
|
+
supported provenance is known, and `.filter()` on recognized bounded results:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
// Reported: eligible rows outside the first 25 can be missed
|
|
165
|
+
const rows = await ctx.db.query('tasks').take(25)
|
|
166
|
+
const eligible = rows.filter((task) => task.status === 'open')
|
|
167
|
+
|
|
168
|
+
// Put eligibility into the query before page filling
|
|
169
|
+
const page = await querying(ctx).tasks.where({ status: 'open' }).paginate(args)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Choose an indexed native constraint where appropriate, or express the predicate
|
|
173
|
+
through the querying engine. Many relationships use bounded pages and separate
|
|
174
|
+
continuation endpoints. There is no automatic predicate-movement fix: moving a
|
|
175
|
+
predicate can change authorization, ordering, and continuation semantics.
|
|
176
|
+
|
|
177
|
+
### Relation and engine facade ownership
|
|
178
|
+
|
|
179
|
+
`root-facade-ownership` requires `defineRelations` in `convex/relations.ts` and
|
|
180
|
+
`createQueryEngine` in `convex/querying.ts`, each configured once. The same rule
|
|
181
|
+
continues to enforce auth, triggers, and foundation facades. Declare one global
|
|
182
|
+
engine as `createQueryEngine({ schema, relations })`, then bind it with
|
|
183
|
+
`querying(ctx)` inside handlers. Avoid rebuilding engines per request or table.
|
|
184
|
+
Cursor key parsing belongs to the engine's server environment loader; advanced
|
|
185
|
+
application-wide overrides use the optional second argument. This setup guidance
|
|
186
|
+
is a convention; the rule currently checks facade ownership and repeated factory
|
|
187
|
+
calls, rather than proving module-scope placement or environment parsing.
|
|
188
|
+
|
|
189
|
+
`root-wiring-only` and the whole-project checker permit these root files only
|
|
190
|
+
as narrow declarative factory configurations. `relations.ts` can import domain
|
|
191
|
+
schema/table declarations (including the domain table assembly); `querying.ts`
|
|
192
|
+
can import domain query/contract modules where permitted. Neither file is a
|
|
193
|
+
place for endpoint handlers, business branches, or direct database operations.
|
|
194
|
+
Domain contracts derive validators, domain Query registries own handlers, and
|
|
195
|
+
`api/` adapters delegate through authenticated constructors. See
|
|
196
|
+
[querying.md](./querying.md) and [conventions.md](./conventions.md).
|
|
197
|
+
|
|
90
198
|
## What still needs review and tests
|
|
91
199
|
|
|
92
200
|
These are syntax and lexical-binding checks, not whole-program validation.
|
|
93
201
|
Import-boundary rules resolve relative imports and tsconfig aliases (including
|
|
94
202
|
extended configs) using the package resolver. Symlinked source is rejected by
|
|
95
203
|
the project scan. Computed import paths are not resolved. Named import aliases and namespaces are recognized where documented;
|
|
96
|
-
|
|
97
|
-
methods
|
|
204
|
+
General variable reassignment, arbitrary wrappers, and destructured database
|
|
205
|
+
methods are not whole-program traced. DTO/read checks do follow supported local
|
|
206
|
+
initializers, aliases, awaited results, and recognized helper chains; unrelated
|
|
207
|
+
user functions and unknown provenance remain outside that bounded analysis. Database checks
|
|
98
208
|
recognize the documented `ctx.db` member shape, not its runtime type.
|
|
99
209
|
|
|
100
210
|
The plugin does not prove DTO redaction, complete command/audit coverage,
|