cvx-kit 0.1.3 → 0.1.4

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.
Files changed (82) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +69 -24
  3. package/dist/agent-tools.d.mts +28 -12
  4. package/dist/agent-tools.mjs +1 -1
  5. package/dist/agent-tools.mjs.map +1 -1
  6. package/dist/auth.d.mts +4 -2
  7. package/dist/auth.mjs +1 -1
  8. package/dist/auth.mjs.map +1 -1
  9. package/dist/components/approvals/_generated/api.d.mts +1 -1
  10. package/dist/components/approvals/audit.mjs +1 -1
  11. package/dist/components/approvals/audit.mjs.map +1 -1
  12. package/dist/components/approvals/client.d.mts +1 -1
  13. package/dist/components/approvals/client.mjs +1 -1
  14. package/dist/components/approvals/client.mjs.map +1 -1
  15. package/dist/components/approvals/constants.mjs.map +1 -1
  16. package/dist/components/approvals/decisions.d.mts +6 -7
  17. package/dist/components/approvals/decisions.mjs +1 -1
  18. package/dist/components/approvals/decisions.mjs.map +1 -1
  19. package/dist/components/approvals/functions.mjs +1 -1
  20. package/dist/components/approvals/functions.mjs.map +1 -1
  21. package/dist/components/approvals/health.mjs.map +1 -1
  22. package/dist/components/approvals/requests.mjs.map +1 -1
  23. package/dist/components/approvals/validators.mjs.map +1 -1
  24. package/dist/components/approvals/workflow.mjs +1 -1
  25. package/dist/components/approvals/workflow.mjs.map +1 -1
  26. package/dist/components/approvals/workflow_steps.d.mts +3 -3
  27. package/dist/components/approvals/workflow_steps.mjs +1 -1
  28. package/dist/components/approvals/workflow_steps.mjs.map +1 -1
  29. package/dist/components/foundation/client.d.mts +17 -24
  30. package/dist/components/foundation/client.mjs +1 -1
  31. package/dist/components/foundation/client.mjs.map +1 -1
  32. package/dist/components/foundation/modules/command/command.d.mts +4 -4
  33. package/dist/components/foundation/modules/command/command.mjs.map +1 -1
  34. package/dist/components/foundation/modules/observability/observability.d.mts +1 -1
  35. package/dist/components/foundation/modules/observability/observability.mjs +1 -1
  36. package/dist/components/foundation/modules/observability/observability.mjs.map +1 -1
  37. package/dist/components/foundation/modules/query/query.d.mts +8 -8
  38. package/dist/components/foundation/modules/query/query.mjs +1 -1
  39. package/dist/components/foundation/modules/query/query.mjs.map +1 -1
  40. package/dist/components/foundation/operation.d.mts +1 -1
  41. package/dist/components/foundation/operation.mjs.map +1 -1
  42. package/dist/components/foundation/result.d.mts +1 -1
  43. package/dist/components/foundation/result.mjs.map +1 -1
  44. package/dist/components/foundation/telemetry.mjs +1 -1
  45. package/dist/components/foundation/telemetry.mjs.map +1 -1
  46. package/dist/crud.d.mts +177 -16
  47. package/dist/crud.mjs +1 -1
  48. package/dist/crud.mjs.map +1 -1
  49. package/dist/middleware.d.mts +7 -7
  50. package/dist/middleware.mjs +1 -1
  51. package/dist/middleware.mjs.map +1 -1
  52. package/dist/oxlint.d.mts +9 -0
  53. package/dist/oxlint.mjs +2 -0
  54. package/dist/oxlint.mjs.map +1 -0
  55. package/dist/tenancy.mjs +1 -1
  56. package/dist/tenancy.mjs.map +1 -1
  57. package/dist/triggers.mjs +1 -1
  58. package/dist/triggers.mjs.map +1 -1
  59. package/dist/webhooks.d.mts +17 -106
  60. package/dist/webhooks.mjs.map +1 -1
  61. package/dist/zod-table.d.mts +46 -90
  62. package/dist/zod-table.mjs +1 -1
  63. package/dist/zod-table.mjs.map +1 -1
  64. package/docs/approvals.md +57 -56
  65. package/docs/architecture.md +1 -1
  66. package/docs/auth.md +57 -33
  67. package/docs/commands.md +53 -53
  68. package/docs/conventions.md +40 -40
  69. package/docs/crud.md +5 -5
  70. package/docs/llms.md +117 -71
  71. package/docs/maintainability.md +4 -4
  72. package/docs/migration.md +33 -33
  73. package/docs/tenancy.md +5 -8
  74. package/docs/triggers.md +4 -2
  75. package/docs/upgrading.md +32 -7
  76. package/docs/zod-table.md +48 -45
  77. package/package.json +33 -22
  78. package/scripts/convex-config-bridge.mjs +7 -18
  79. package/scripts/smoke-packed-components.mjs +20 -61
  80. package/scripts/test-packed-ids.mjs +16 -24
  81. package/scripts/test-packed-oxlint.mjs +121 -0
  82. package/src/test.ts +8 -17
@@ -1,4 +1,6 @@
1
1
  import { ErrorFactory } from "./errors.mjs";
2
+ import { TableBoundary } from "./zod-table.mjs";
3
+ import { FunctionReference, GenericDataModel, GenericMutationCtx } from "convex/server";
2
4
  import { z } from "zod";
3
5
  //#region src/webhooks.d.ts
4
6
  /**
@@ -11,108 +13,12 @@ import { z } from "zod";
11
13
  * longer than the provider's redelivery horizon (a cleanup cron over
12
14
  * `receivedAt` — see docs/webhooks.md).
13
15
  */
14
- declare function webhookEventsTable(): {
15
- tableName: "webhookEvents";
16
- schema: z.ZodObject<{
17
- createdAt: z.ZodOptional<z.ZodNumber>;
18
- updatedAt: z.ZodOptional<z.ZodNumber>;
19
- archivedAt: z.ZodOptional<z.ZodNumber>;
20
- _id: import("convex-helpers/server/zod4").Zid<"webhookEvents">;
21
- _creationTime: z.ZodNumber;
22
- eventKey: z.ZodString;
23
- source: z.ZodString;
24
- receivedAt: z.ZodNumber;
25
- }, z.core.$strict>;
26
- storage: z.ZodObject<{
27
- createdAt: z.ZodOptional<z.ZodNumber>;
28
- updatedAt: z.ZodOptional<z.ZodNumber>;
29
- archivedAt: z.ZodOptional<z.ZodNumber>;
30
- eventKey: z.ZodString;
31
- source: z.ZodString;
32
- receivedAt: z.ZodNumber;
33
- }, z.core.$strict>;
34
- insertSchema: z.ZodObject<Omit<{
35
- eventKey: z.ZodString;
36
- source: z.ZodString;
37
- receivedAt: z.ZodNumber;
38
- }, "archivedAt" | "createdAt" | "updatedAt">, z.core.$strip>;
39
- updateSchema: z.ZodObject<{
40
- eventKey: z.ZodOptional<z.ZodString>;
41
- source: z.ZodOptional<z.ZodString>;
42
- receivedAt: z.ZodOptional<z.ZodNumber>;
43
- }, z.core.$strip>;
44
- commandInput: z.ZodObject<{}, z.core.$strict>;
45
- publicDto: z.ZodObject<{}, z.core.$strict>;
46
- toPublicDto: (row: {
47
- createdAt?: number | undefined;
48
- updatedAt?: number | undefined;
49
- archivedAt?: number | undefined;
50
- eventKey: string;
51
- source: string;
52
- receivedAt: number;
53
- }) => Record<string, never>;
54
- table: import("convex/server").TableDefinition<import("convex/values").VObject<{
55
- createdAt?: number | undefined;
56
- updatedAt?: number | undefined;
57
- archivedAt?: number | undefined;
58
- eventKey: string;
59
- source: string;
60
- receivedAt: number;
61
- }, {
62
- createdAt: import("convex/values").VFloat64<number | undefined, "optional">;
63
- updatedAt: import("convex/values").VFloat64<number | undefined, "optional">;
64
- archivedAt: import("convex/values").VFloat64<number | undefined, "optional">;
65
- eventKey: import("convex/values").VString<string, "required">;
66
- source: import("convex/values").VString<string, "required">;
67
- receivedAt: import("convex/values").VFloat64<number, "required">;
68
- }, "required", "archivedAt" | "createdAt" | "eventKey" | "receivedAt" | "source" | "updatedAt">, {}, {}, {}>;
69
- insert: {
70
- (): z.ZodObject<Omit<{
71
- eventKey: z.ZodString;
72
- source: z.ZodString;
73
- receivedAt: z.ZodNumber;
74
- }, "archivedAt" | "createdAt" | "updatedAt">, z.core.$strip>;
75
- <OmitMask extends z.util.Mask<"eventKey" | "receivedAt" | "source">>(omit: OmitMask): z.ZodObject<Omit<Omit<{
76
- eventKey: z.ZodString;
77
- source: z.ZodString;
78
- receivedAt: z.ZodNumber;
79
- }, "archivedAt" | "createdAt" | "updatedAt">, Extract<"eventKey", keyof OmitMask> | Extract<"receivedAt", keyof OmitMask> | Extract<"source", keyof OmitMask>> extends (infer T) ? { [k in keyof T]: T[k]; } : never, z.core.$strip>;
80
- };
81
- update: {
82
- (): z.ZodObject<{
83
- eventKey: z.ZodOptional<z.ZodString>;
84
- source: z.ZodOptional<z.ZodString>;
85
- receivedAt: z.ZodOptional<z.ZodNumber>;
86
- }, z.core.$strip>;
87
- <OmitMask extends z.util.Mask<"eventKey" | "receivedAt" | "source">>(omit: OmitMask): z.ZodObject<Omit<{
88
- eventKey: z.ZodOptional<z.ZodString>;
89
- source: z.ZodOptional<z.ZodString>;
90
- receivedAt: z.ZodOptional<z.ZodNumber>;
91
- }, Extract<"eventKey", keyof OmitMask> | Extract<"receivedAt", keyof OmitMask> | Extract<"source", keyof OmitMask>> extends (infer T) ? { [k in keyof T]: T[k]; } : never, z.core.$strip>;
92
- };
93
- tools: {
94
- insert: z.ZodObject<{}, z.core.$strict>;
95
- update: z.ZodObject<{
96
- data: z.ZodObject<{}, z.core.$strict>;
97
- id: z.ZodType<import("convex/values").GenericId<"webhookEvents">, string, z.core.$ZodTypeInternals<import("convex/values").GenericId<"webhookEvents">, string>>;
98
- }, z.core.$strict>;
99
- id: z.ZodObject<{
100
- id: z.ZodType<import("convex/values").GenericId<"webhookEvents">, string, z.core.$ZodTypeInternals<import("convex/values").GenericId<"webhookEvents">, string>>;
101
- }, z.core.$strict>;
102
- };
103
- };
104
- type DedupContext = {
105
- db: {
106
- query: (table: never) => {
107
- withIndex: (index: never, range: (builder: {
108
- eq: (field: never, value: never) => unknown;
109
- }) => unknown) => {
110
- first: () => Promise<unknown | null>;
111
- };
112
- };
113
- insert: (table: never, value: never) => Promise<unknown>;
114
- };
115
- };
16
+ declare function webhookEventsTable(): TableBoundary<"webhookEvents", {
17
+ eventKey: z.ZodString;
18
+ source: z.ZodString;
19
+ receivedAt: z.ZodNumber;
20
+ }, readonly [], readonly [], readonly []>;
21
+ type DedupContext = Pick<GenericMutationCtx<GenericDataModel>, 'db'>;
116
22
  /**
117
23
  * Transactional insert-if-absent dedup guard. Call this FIRST inside the
118
24
  * internal mutation the boundary delegates to — dedup must live in the
@@ -155,10 +61,15 @@ type WebhookBoundaryConfig = {
155
61
  * boundary.handle(ctx, request, internal.domain.vendor.functions.applyEvent)) })
156
62
  */
157
63
  declare function createWebhookBoundary(config: WebhookBoundaryConfig): {
158
- handle: (ctx: {
159
- runMutation: (target: never, args: never) => Promise<unknown>;
160
- }, request: Request, target: unknown) => Promise<Response>;
64
+ handle: <Result>(ctx: {
65
+ runMutation: (target: FunctionReference<'mutation', 'internal', WebhookDelivery, Result>, args: WebhookDelivery) => Promise<Result>;
66
+ }, request: Request, target: FunctionReference<'mutation', 'internal', WebhookDelivery, Result>) => Promise<Response>;
67
+ };
68
+ type WebhookDelivery = {
69
+ eventKey: string;
70
+ payload: string;
71
+ source: string;
161
72
  };
162
73
  //#endregion
163
- export { WebhookBoundaryConfig, createWebhookBoundary, recordWebhookEvent, webhookEventsTable };
74
+ export { WebhookBoundaryConfig, WebhookDelivery, createWebhookBoundary, recordWebhookEvent, webhookEventsTable };
164
75
  //# sourceMappingURL=webhooks.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"webhooks.mjs","names":[],"sources":["../src/webhooks.ts"],"sourcesContent":["import { z } from 'zod'\nimport { defaultErrors, type ErrorFactory } from './errors'\nimport { zodTable } from './zod-table'\n\n/**\n * The host-owned dedup table (the kit gains no tables). Register it in the\n * schema with the by_eventKey index:\n *\n * webhookEvents: webhookEventsTable().table.index('by_eventKey', ['eventKey'])\n *\n * Rows are host-owned data: prune them on a retention window comfortably\n * longer than the provider's redelivery horizon (a cleanup cron over\n * `receivedAt` — see docs/webhooks.md).\n */\nexport function webhookEventsTable() {\n\treturn zodTable('webhookEvents', () => ({\n\t\teventKey: z.string(),\n\t\tsource: z.string(),\n\t\treceivedAt: z.number(),\n\t}))\n}\n\ntype DedupContext = {\n\tdb: {\n\t\tquery: (table: never) => {\n\t\t\twithIndex: (\n\t\t\t\tindex: never,\n\t\t\t\trange: (builder: { eq: (field: never, value: never) => unknown }) => unknown,\n\t\t\t) => { first: () => Promise<unknown | null> }\n\t\t}\n\t\tinsert: (table: never, value: never) => Promise<unknown>\n\t}\n}\n\n/**\n * Transactional insert-if-absent dedup guard. Call this FIRST inside the\n * internal mutation the boundary delegates to — dedup must live in the\n * mutation (transactional), never in the action. Returns duplicate: true\n * when the natural key was already recorded; the caller then no-ops.\n */\nexport async function recordWebhookEvent(\n\tctx: DedupContext,\n\tinput: { key: string; source?: string; table?: string },\n): Promise<{ duplicate: boolean }> {\n\tconst table = (input.table ?? 'webhookEvents') as never\n\tconst existing = await ctx.db\n\t\t.query(table)\n\t\t.withIndex('by_eventKey' as never, (builder) =>\n\t\t\tbuilder.eq('eventKey' as never, input.key as never),\n\t\t)\n\t\t.first()\n\tif (existing) return { duplicate: true }\n\tawait ctx.db.insert(table, {\n\t\teventKey: input.key,\n\t\tsource: input.source ?? 'unknown',\n\t\treceivedAt: Date.now(),\n\t} as never)\n\treturn { duplicate: false }\n}\n\nexport type WebhookBoundaryConfig = {\n\t/**\n\t * Verifies the provider signature over the RAW body bytes — the helper\n\t * hands it `request.text()` BEFORE any parsing. Implementations must use\n\t * a constant-time comparison (e.g. crypto.subtle HMAC verify) and read\n\t * the secret from a Convex environment variable. Return false or throw\n\t * to reject.\n\t */\n\tverify: (raw: string, request: Request) => boolean | Promise<boolean>\n\t/**\n\t * Derives the natural dedup key — the `${event}:${id}:${updatedAt}`\n\t * pattern. Receives the raw body and the request; parse inside if needed.\n\t */\n\teventKey: (raw: string, request: Request) => string | Promise<string>\n\tsource?: string\n\terrors?: ErrorFactory\n}\n\n/**\n * The sanctioned webhook pattern as code: verify (raw body, fail closed) →\n * derive natural key → delegate to a host internal mutation whose FIRST act\n * is recordWebhookEvent (transactional dedup). Use inside the host's\n * httpAction:\n *\n * const boundary = createWebhookBoundary({ verify, eventKey })\n * http.route({ path: '/webhooks/vendor', method: 'POST',\n * handler: httpAction((ctx, request) =>\n * boundary.handle(ctx, request, internal.domain.vendor.functions.applyEvent)) })\n */\nexport function createWebhookBoundary(config: WebhookBoundaryConfig) {\n\tconst errors = config.errors ?? defaultErrors\n\n\tasync function handle(\n\t\tctx: {\n\t\t\trunMutation: (target: never, args: never) => Promise<unknown>\n\t\t},\n\t\trequest: Request,\n\t\ttarget: unknown,\n\t): Promise<Response> {\n\t\tconst raw = await request.text()\n\t\tlet verified = false\n\t\ttry {\n\t\t\tverified = await config.verify(raw, request)\n\t\t} catch {\n\t\t\tverified = false\n\t\t}\n\t\tif (!verified) {\n\t\t\terrors.throw({\n\t\t\t\tcode: 'WEBHOOK_SIGNATURE_INVALID',\n\t\t\t\tmessage: 'Webhook signature verification failed',\n\t\t\t})\n\t\t}\n\t\tconst key = await config.eventKey(raw, request)\n\t\tawait ctx.runMutation(target as never, {\n\t\t\teventKey: key,\n\t\t\tpayload: raw,\n\t\t\tsource: config.source ?? 'unknown',\n\t\t} as never)\n\t\treturn new Response(null, { status: 200 })\n\t}\n\n\treturn { handle }\n}\n"],"mappings":"gHAcA,SAAgB,GAAqB,CACpC,OAAO,EAAS,qBAAwB,CACvC,SAAU,EAAE,OAAO,EACnB,OAAQ,EAAE,OAAO,EACjB,WAAY,EAAE,OAAO,CACtB,EAAE,CACH,CAoBA,eAAsB,EACrB,EACA,EACkC,CAClC,IAAM,EAAS,EAAM,OAAS,gBAa9B,OANI,MANmB,EAAI,GACzB,MAAM,CAAK,CAAC,CACZ,UAAU,cAAyB,GACnC,EAAQ,GAAG,WAAqB,EAAM,GAAY,CACnD,CAAC,CACA,MAAM,EACa,CAAE,UAAW,EAAK,GACvC,MAAM,EAAI,GAAG,OAAO,EAAO,CAC1B,SAAU,EAAM,IAChB,OAAQ,EAAM,QAAU,UACxB,WAAY,KAAK,IAAI,CACtB,CAAU,EACH,CAAE,UAAW,EAAM,EAC3B,CA+BA,SAAgB,EAAsB,EAA+B,CACpE,IAAM,EAAS,EAAO,QAAU,EAEhC,eAAe,EACd,EAGA,EACA,EACoB,CACpB,IAAM,EAAM,MAAM,EAAQ,KAAK,EAC3B,EAAW,GACf,GAAI,CACH,EAAW,MAAM,EAAO,OAAO,EAAK,CAAO,CAC5C,MAAQ,CACP,EAAW,EACZ,CACK,GACJ,EAAO,MAAM,CACZ,KAAM,4BACN,QAAS,uCACV,CAAC,EAEF,IAAM,EAAM,MAAM,EAAO,SAAS,EAAK,CAAO,EAM9C,OALA,MAAM,EAAI,YAAY,EAAiB,CACtC,SAAU,EACV,QAAS,EACT,OAAQ,EAAO,QAAU,SAC1B,CAAU,EACH,IAAI,SAAS,KAAM,CAAE,OAAQ,GAAI,CAAC,CAC1C,CAEA,MAAO,CAAE,QAAO,CACjB"}
1
+ {"version":3,"file":"webhooks.mjs","names":[],"sources":["../src/webhooks.ts"],"sourcesContent":["import type { GenericMutationCtx, GenericDataModel, FunctionReference } from 'convex/server'\nimport { z } from 'zod'\nimport { defaultErrors, type ErrorFactory } from './errors'\nimport { zodTable } from './zod-table'\n\n/**\n * The host-owned dedup table (the kit gains no tables). Register it in the\n * schema with the by_eventKey index:\n *\n * webhookEvents: webhookEventsTable().table.index('by_eventKey', ['eventKey'])\n *\n * Rows are host-owned data: prune them on a retention window comfortably\n * longer than the provider's redelivery horizon (a cleanup cron over\n * `receivedAt` — see docs/webhooks.md).\n */\nexport function webhookEventsTable() {\n\treturn zodTable('webhookEvents', () => ({\n\t\teventKey: z.string(),\n\t\tsource: z.string(),\n\t\treceivedAt: z.number(),\n\t}))\n}\n\ntype DedupContext = Pick<GenericMutationCtx<GenericDataModel>, 'db'>\n\n/**\n * Transactional insert-if-absent dedup guard. Call this FIRST inside the\n * internal mutation the boundary delegates to — dedup must live in the\n * mutation (transactional), never in the action. Returns duplicate: true\n * when the natural key was already recorded; the caller then no-ops.\n */\nexport async function recordWebhookEvent(\n\tctx: DedupContext,\n\tinput: { key: string; source?: string; table?: string },\n): Promise<{ duplicate: boolean }> {\n\tconst table = input.table ?? 'webhookEvents'\n\tconst existing = await ctx.db\n\t\t.query(table)\n\t\t.withIndex('by_eventKey', (builder) => builder.eq('eventKey', input.key))\n\t\t.first()\n\tif (existing) return { duplicate: true }\n\tawait ctx.db.insert(table, {\n\t\teventKey: input.key,\n\t\tsource: input.source ?? 'unknown',\n\t\treceivedAt: Date.now(),\n\t})\n\treturn { duplicate: false }\n}\n\nexport type WebhookBoundaryConfig = {\n\t/**\n\t * Verifies the provider signature over the RAW body bytes — the helper\n\t * hands it `request.text()` BEFORE any parsing. Implementations must use\n\t * a constant-time comparison (e.g. crypto.subtle HMAC verify) and read\n\t * the secret from a Convex environment variable. Return false or throw\n\t * to reject.\n\t */\n\tverify: (raw: string, request: Request) => boolean | Promise<boolean>\n\t/**\n\t * Derives the natural dedup key — the `${event}:${id}:${updatedAt}`\n\t * pattern. Receives the raw body and the request; parse inside if needed.\n\t */\n\teventKey: (raw: string, request: Request) => string | Promise<string>\n\tsource?: string\n\terrors?: ErrorFactory\n}\n\n/**\n * The sanctioned webhook pattern as code: verify (raw body, fail closed) →\n * derive natural key → delegate to a host internal mutation whose FIRST act\n * is recordWebhookEvent (transactional dedup). Use inside the host's\n * httpAction:\n *\n * const boundary = createWebhookBoundary({ verify, eventKey })\n * http.route({ path: '/webhooks/vendor', method: 'POST',\n * handler: httpAction((ctx, request) =>\n * boundary.handle(ctx, request, internal.domain.vendor.functions.applyEvent)) })\n */\nexport function createWebhookBoundary(config: WebhookBoundaryConfig) {\n\tconst errors = config.errors ?? defaultErrors\n\n\tasync function handle<Result>(\n\t\tctx: {\n\t\t\trunMutation: (\n\t\t\t\ttarget: FunctionReference<'mutation', 'internal', WebhookDelivery, Result>,\n\t\t\t\targs: WebhookDelivery,\n\t\t\t) => Promise<Result>\n\t\t},\n\t\trequest: Request,\n\t\ttarget: FunctionReference<'mutation', 'internal', WebhookDelivery, Result>,\n\t): Promise<Response> {\n\t\tconst raw = await request.text()\n\t\tlet verified = false\n\t\ttry {\n\t\t\tverified = await config.verify(raw, request)\n\t\t} catch {\n\t\t\tverified = false\n\t\t}\n\t\tif (!verified) {\n\t\t\terrors.throw({\n\t\t\t\tcode: 'WEBHOOK_SIGNATURE_INVALID',\n\t\t\t\tmessage: 'Webhook signature verification failed',\n\t\t\t})\n\t\t}\n\t\tconst key = await config.eventKey(raw, request)\n\t\tawait ctx.runMutation(target, {\n\t\t\teventKey: key,\n\t\t\tpayload: raw,\n\t\t\tsource: config.source ?? 'unknown',\n\t\t})\n\t\treturn new Response(null, { status: 200 })\n\t}\n\n\treturn { handle }\n}\n\nexport type WebhookDelivery = { eventKey: string; payload: string; source: string }\n"],"mappings":"gHAeA,SAAgB,GAAqB,CACpC,OAAO,EAAS,qBAAwB,CACvC,SAAU,EAAE,OAAO,EACnB,OAAQ,EAAE,OAAO,EACjB,WAAY,EAAE,OAAO,CACtB,EAAE,CACH,CAUA,eAAsB,EACrB,EACA,EACkC,CAClC,IAAM,EAAQ,EAAM,OAAS,gBAW7B,OANI,MAJmB,EAAI,GACzB,MAAM,CAAK,CAAC,CACZ,UAAU,cAAgB,GAAY,EAAQ,GAAG,WAAY,EAAM,GAAG,CAAC,CAAC,CACxE,MAAM,EACa,CAAE,UAAW,EAAK,GACvC,MAAM,EAAI,GAAG,OAAO,EAAO,CAC1B,SAAU,EAAM,IAChB,OAAQ,EAAM,QAAU,UACxB,WAAY,KAAK,IAAI,CACtB,CAAC,EACM,CAAE,UAAW,EAAM,EAC3B,CA+BA,SAAgB,EAAsB,EAA+B,CACpE,IAAM,EAAS,EAAO,QAAU,EAEhC,eAAe,EACd,EAMA,EACA,EACoB,CACpB,IAAM,EAAM,MAAM,EAAQ,KAAK,EAC3B,EAAW,GACf,GAAI,CACH,EAAW,MAAM,EAAO,OAAO,EAAK,CAAO,CAC5C,MAAQ,CACP,EAAW,EACZ,CACK,GACJ,EAAO,MAAM,CACZ,KAAM,4BACN,QAAS,uCACV,CAAC,EAEF,IAAM,EAAM,MAAM,EAAO,SAAS,EAAK,CAAO,EAM9C,OALA,MAAM,EAAI,YAAY,EAAQ,CAC7B,SAAU,EACV,QAAS,EACT,OAAQ,EAAO,QAAU,SAC1B,CAAC,EACM,IAAI,SAAS,KAAM,CAAE,OAAQ,GAAI,CAAC,CAC1C,CAEA,MAAO,CAAE,QAAO,CACjB"}
@@ -3,113 +3,60 @@ import { TableDefinition } from "convex/server";
3
3
  import { z } from "zod";
4
4
  import { GenericId } from "convex/values";
5
5
  //#region src/zod-table.d.ts
6
- type Shape = Record<string, z.ZodType>;
7
- type ShapeKey<Fields extends Shape> = Extract<keyof Fields, string>;
6
+ type FieldSchemas = Record<string, z.ZodType>;
7
+ type FieldKey<Fields extends FieldSchemas> = Extract<keyof Fields, string>;
8
8
  /**
9
9
  * Opinionated lifecycle timestamps — every table has them, server-owned,
10
10
  * always excluded from insert/command boundaries. Maintained by the
11
11
  * `timestamps` trigger helper (see cvx-kit/triggers).
12
12
  */
13
- declare const timestampShape: {
13
+ declare const timestampFields: {
14
14
  createdAt: z.ZodOptional<z.ZodNumber>;
15
15
  updatedAt: z.ZodOptional<z.ZodNumber>;
16
16
  archivedAt: z.ZodOptional<z.ZodNumber>;
17
17
  };
18
- type TimestampShape = typeof timestampShape;
18
+ type TimestampFields = typeof timestampFields;
19
19
  declare const TIMESTAMP_FIELDS: readonly ['createdAt', 'updatedAt', 'archivedAt'];
20
- type TableBoundaryOptions<Fields extends Shape, ServerFields extends readonly ShapeKey<Fields>[], CommandFields extends readonly ShapeKey<Fields>[], PublicFields extends readonly ShapeKey<Fields & TimestampShape>[]> = {
20
+ type TableBoundaryOptions<Fields extends FieldSchemas, ServerFields extends readonly FieldKey<Fields>[], CommandFields extends readonly FieldKey<Fields>[], PublicFields extends readonly FieldKey<Fields & TimestampFields>[]> = {
21
21
  serverFields?: ServerFields;
22
22
  commandFields?: CommandFields;
23
23
  publicFields?: PublicFields;
24
24
  };
25
- /** Defines one table and derives its storage, document, write, and DTO boundaries. */
26
- declare function zodTable<Table extends string, Fields extends Shape, const ServerFields extends readonly ShapeKey<Fields>[] = readonly [], const CommandFields extends readonly ShapeKey<Fields>[] = readonly [], const PublicFields extends readonly ShapeKey<Fields & TimestampShape>[] = readonly []>(tableName: Table, fields: (id: typeof zid$1) => Fields, options?: TableBoundaryOptions<Fields, ServerFields, CommandFields, PublicFields>): {
25
+ type StrictObject<Fields extends FieldSchemas> = z.ZodObject<Fields, z.core.$strict>;
26
+ type WriteFields<Fields extends FieldSchemas, ServerFields extends readonly FieldKey<Fields>[]> = Omit<Fields & TimestampFields, ServerFields[number] | (typeof TIMESTAMP_FIELDS)[number]>;
27
+ type PartialFields<Fields extends FieldSchemas> = { [Key in keyof Fields]: z.ZodOptional<Fields[Key]>; };
28
+ /** Named schema relationships keep generated declarations bounded and consumers fully typed. */
29
+ type TableBoundary<Table extends string, Fields extends FieldSchemas, ServerFields extends readonly FieldKey<Fields>[], CommandFields extends readonly FieldKey<Fields>[], PublicFields extends readonly FieldKey<Fields & TimestampFields>[]> = {
27
30
  tableName: Table;
28
- schema: z.ZodObject<((keyof Fields & "_creationTime") | (keyof Fields & "_id") extends never ? (Fields & {
29
- createdAt: z.ZodOptional<z.ZodNumber>;
30
- updatedAt: z.ZodOptional<z.ZodNumber>;
31
- archivedAt: z.ZodOptional<z.ZodNumber>;
32
- } extends (infer T_1) ? { -readonly [P in keyof T_1]: T_1[P]; } : never) & {
33
- _id: import("convex-helpers/server/zod4").Zid<Table>;
34
- _creationTime: z.ZodNumber;
35
- } : ((Fields & {
36
- createdAt: z.ZodOptional<z.ZodNumber>;
37
- updatedAt: z.ZodOptional<z.ZodNumber>;
38
- archivedAt: z.ZodOptional<z.ZodNumber>;
39
- } extends (infer T_1) ? { -readonly [P in keyof T_1]: T_1[P]; } : never) extends (infer T_2 extends z.util.SomeObject) ? { [K in keyof T_2 as K extends "_creationTime" | "_id" ? never : K]: T_2[K]; } : never) & {
40
- _id: import("convex-helpers/server/zod4").Zid<Table>;
41
- _creationTime: z.ZodNumber;
42
- }) extends (infer T) ? { [k in keyof T]: T[k]; } : never, z.core.$strict>;
43
- storage: z.ZodObject<Fields & {
44
- createdAt: z.ZodOptional<z.ZodNumber>;
45
- updatedAt: z.ZodOptional<z.ZodNumber>;
46
- archivedAt: z.ZodOptional<z.ZodNumber>;
47
- } extends (infer T_1) ? { -readonly [P in keyof T_1]: T_1[P]; } : never, z.core.$strict>;
48
- insertSchema: z.ZodObject<Omit<Fields, "archivedAt" | "createdAt" | "updatedAt" | ServerFields[number]>, z.core.$strip>;
49
- updateSchema: z.ZodObject<Omit<Fields, "archivedAt" | "createdAt" | "updatedAt" | ServerFields[number]> extends (infer T_3 extends Readonly<{
50
- [k: string]: z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
51
- }>) ? { -readonly [k in keyof T_3]: z.ZodOptional<T_3[k]>; } : never, z.core.$strip>;
52
- commandInput: z.ZodObject<Pick<Fields & {
53
- createdAt: z.ZodOptional<z.ZodNumber>;
54
- updatedAt: z.ZodOptional<z.ZodNumber>;
55
- archivedAt: z.ZodOptional<z.ZodNumber>;
56
- }, CommandFields[number]> extends (infer T_4) ? { -readonly [P in keyof T_4]: T_4[P]; } : never, z.core.$strict>;
57
- publicDto: z.ZodObject<Pick<Fields & {
58
- createdAt: z.ZodOptional<z.ZodNumber>;
59
- updatedAt: z.ZodOptional<z.ZodNumber>;
60
- archivedAt: z.ZodOptional<z.ZodNumber>;
61
- }, PublicFields[number]> extends (infer T_5) ? { -readonly [P in keyof T_5]: T_5[P]; } : never, z.core.$strict>;
62
- toPublicDto: (row: z.input<z.ZodObject<Fields & {
63
- createdAt: z.ZodOptional<z.ZodNumber>;
64
- updatedAt: z.ZodOptional<z.ZodNumber>;
65
- archivedAt: z.ZodOptional<z.ZodNumber>;
66
- } extends (infer T_1) ? { -readonly [P in keyof T_1]: T_1[P]; } : never, z.core.$strict>>) => z.core.$InferObjectOutput<Pick<Fields & {
67
- createdAt: z.ZodOptional<z.ZodNumber>;
68
- updatedAt: z.ZodOptional<z.ZodNumber>;
69
- archivedAt: z.ZodOptional<z.ZodNumber>;
70
- }, PublicFields[number]> extends (infer T_5) ? { -readonly [P in keyof T_5]: T_5[P]; } : never, {}>;
71
- table: TableDefinition<ConvexValidatorFromZod<z.ZodObject<Fields & {
72
- createdAt: z.ZodOptional<z.ZodNumber>;
73
- updatedAt: z.ZodOptional<z.ZodNumber>;
74
- archivedAt: z.ZodOptional<z.ZodNumber>;
75
- } extends (infer T_1) ? { -readonly [P in keyof T_1]: T_1[P]; } : never, z.core.$strict>, 'required'>>;
76
- insert: {
77
- (): z.ZodObject<Omit<Fields, "archivedAt" | "createdAt" | "updatedAt" | ServerFields[number]>, z.core.$strip>;
78
- <OmitMask extends z.util.Mask<Exclude<keyof Fields, "archivedAt" | "createdAt" | "updatedAt" | ServerFields[number]>>>(omit: OmitMask): ZodObjectOmit<z.ZodObject<Omit<Fields, "archivedAt" | "createdAt" | "updatedAt" | ServerFields[number]>, z.core.$strip>, OmitMask>;
79
- };
80
- update: {
81
- (): z.ZodObject<Omit<Fields, "archivedAt" | "createdAt" | "updatedAt" | ServerFields[number]> extends (infer T_3 extends Readonly<{
82
- [k: string]: z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
83
- }>) ? { -readonly [k in keyof T_3]: z.ZodOptional<T_3[k]>; } : never, z.core.$strip>;
84
- <OmitMask extends z.util.Mask<Exclude<keyof Fields, "archivedAt" | "createdAt" | "updatedAt" | ServerFields[number]>>>(omit: OmitMask): ZodObjectOmit<z.ZodObject<Omit<Fields, "archivedAt" | "createdAt" | "updatedAt" | ServerFields[number]> extends (infer T_3 extends Readonly<{
85
- [k: string]: z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
86
- }>) ? { -readonly [k in keyof T_3]: z.ZodOptional<T_3[k]>; } : never, z.core.$strip>, OmitMask>;
87
- };
31
+ storage: StrictObject<Fields & TimestampFields>;
32
+ schema: ReturnType<typeof documentSchema<Fields & TimestampFields, Table>>;
33
+ insertSchema: StrictObject<WriteFields<Fields, ServerFields>>;
34
+ updateSchema: StrictObject<PartialFields<WriteFields<Fields, ServerFields>>>;
35
+ commandInput: StrictObject<Pick<Fields & TimestampFields, CommandFields[number]>>;
36
+ publicDto: StrictObject<Pick<Fields & TimestampFields, PublicFields[number]>>;
37
+ toPublicDto: (row: z.input<StrictObject<Fields & TimestampFields>>) => z.output<StrictObject<Pick<Fields & TimestampFields, PublicFields[number]>>>;
38
+ table: TableDefinition<ConvexValidatorFromZod<StrictObject<Fields & TimestampFields>, 'required'>>;
39
+ insert(): StrictObject<WriteFields<Fields, ServerFields>>;
40
+ insert<Mask extends z.util.Mask<keyof WriteFields<Fields, ServerFields>>>(omit: Mask): ZodObjectOmit<StrictObject<WriteFields<Fields, ServerFields>>, Mask>;
41
+ update(): StrictObject<PartialFields<WriteFields<Fields, ServerFields>>>;
42
+ update<Mask extends z.util.Mask<keyof WriteFields<Fields, ServerFields>>>(omit: Mask): ZodObjectOmit<StrictObject<PartialFields<WriteFields<Fields, ServerFields>>>, Mask>;
88
43
  tools: {
89
- insert: z.ZodObject<Pick<Fields & {
90
- createdAt: z.ZodOptional<z.ZodNumber>;
91
- updatedAt: z.ZodOptional<z.ZodNumber>;
92
- archivedAt: z.ZodOptional<z.ZodNumber>;
93
- }, CommandFields[number]> extends (infer T_4) ? { -readonly [P in keyof T_4]: T_4[P]; } : never, z.core.$strict>;
94
- update: z.ZodObject<{
95
- data: z.ZodObject<(Pick<Fields & {
96
- createdAt: z.ZodOptional<z.ZodNumber>;
97
- updatedAt: z.ZodOptional<z.ZodNumber>;
98
- archivedAt: z.ZodOptional<z.ZodNumber>;
99
- }, CommandFields[number]> extends (infer T_4) ? { -readonly [P in keyof T_4]: T_4[P]; } : never) extends (infer T_6 extends Readonly<{
100
- [k: string]: z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
101
- }>) ? { -readonly [k in keyof T_6]: z.ZodOptional<T_6[k]>; } : never, z.core.$strict>;
102
- id: z.ZodType<GenericId<Table>, string, z.core.$ZodTypeInternals<GenericId<Table>, string>>;
103
- }, z.core.$strict>;
104
- id: z.ZodObject<{
105
- id: z.ZodType<GenericId<Table>, string, z.core.$ZodTypeInternals<GenericId<Table>, string>>;
106
- }, z.core.$strict>;
44
+ insert: StrictObject<Pick<Fields & TimestampFields, CommandFields[number]>>;
45
+ update: StrictObject<{
46
+ data: StrictObject<PartialFields<Pick<Fields & TimestampFields, CommandFields[number]>>>;
47
+ id: z.ZodType<GenericId<Table>, string>;
48
+ }>;
49
+ id: StrictObject<{
50
+ id: z.ZodType<GenericId<Table>, string>;
51
+ }>;
107
52
  };
108
53
  };
109
- declare const tenantShape: {
54
+ /** Defines one table and derives its storage, document, write, and DTO boundaries. */
55
+ 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, options?: TableBoundaryOptions<Fields, ServerFields, CommandFields, PublicFields>): TableBoundary<Table, Fields, ServerFields, CommandFields, PublicFields>;
56
+ declare const tenantFields: {
110
57
  tenant: z.ZodString;
111
58
  };
112
- type TenantShape = typeof tenantShape;
59
+ type TenantFields = typeof tenantFields;
113
60
  /**
114
61
  * A zodTable whose rows are tenant-owned: injects the server-owned `tenant`
115
62
  * field (the tenancy boundary — see cvx-kit/tenancy). Like timestamps, it is
@@ -118,7 +65,9 @@ type TenantShape = typeof tenantShape;
118
65
  * trigger forbids reassigning it. Expose it via publicFields only when a DTO
119
66
  * genuinely needs it.
120
67
  */
121
- declare function tenantTable<Table extends string, Fields extends Shape, const ServerFields extends readonly ShapeKey<Fields>[] = readonly [], const CommandFields extends readonly ShapeKey<Fields>[] = readonly [], const PublicFields extends readonly ShapeKey<Fields & TenantShape & TimestampShape>[] = readonly []>(tableName: Table, fields: (id: typeof zid$1) => Fields, options?: TableBoundaryOptions<Fields & TenantShape, readonly (ServerFields[number] | 'tenant')[], CommandFields, PublicFields>): ReturnType<typeof zodTable<Table, Fields & TenantShape, readonly (ServerFields[number] | 'tenant')[], CommandFields, PublicFields>>;
68
+ 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 & {
69
+ tenant: z.ZodString;
70
+ }, readonly ("tenant" | ServerFields[number])[], CommandFields, PublicFields>;
122
71
  /**
123
72
  * Merges per-module table maps into the application schema map, rejecting
124
73
  * duplicate table names across modules — the module-registry combinator for
@@ -169,7 +118,14 @@ declare function zodVariantTable<Table extends string, Schema extends z.ZodType>
169
118
  * LLM-tool inputs stay primitive, while keeping the Id type at compile time.
170
119
  */
171
120
  declare function jsonSafeZid<Table extends string>(tableName: Table): z.ZodType<GenericId<Table>, string>;
172
- type ZodObjectOmit<Schema extends z.ZodObject<any, any>, OmitMask extends z.util.Mask<keyof Schema['shape']>> = Schema extends z.ZodObject<infer ObjectShape, infer Config> ? z.ZodObject<z.util.Flatten<Omit<ObjectShape, Extract<keyof ObjectShape, keyof OmitMask>>>, Config> : never;
121
+ 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
+ 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: import("convex-helpers/server/zod4").Zid<Table>;
124
+ _creationTime: z.ZodNumber;
125
+ } : { [K in keyof Fields as K extends "_creationTime" | "_id" ? never : K]: Fields[K]; } & {
126
+ _id: import("convex-helpers/server/zod4").Zid<Table>;
127
+ _creationTime: z.ZodNumber;
128
+ }) extends (infer T) ? { [k in keyof T]: T[k]; } : never, z.core.$strict>;
173
129
  //#endregion
174
- export { TIMESTAMP_FIELDS, TableBoundaryOptions, convexToZod, createModule, jsonSafeZid, paginated, tenantTable, zid, zodTable, zodToConvex, zodVariantTable };
130
+ export { TIMESTAMP_FIELDS, TableBoundary, TableBoundaryOptions, convexToZod, createModule, jsonSafeZid, paginated, tenantTable, zid, zodTable, zodToConvex, zodVariantTable };
175
131
  //# sourceMappingURL=zod-table.d.mts.map
@@ -1,2 +1,2 @@
1
- import{convexToZod as e,convexToZod as t,zid as n,zid as r,zodToConvex as i,zodToConvex 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(e,t,n={}){let i={...t(r),...l},s=c.object(i).strict(),d=s.extend({_id:r(e),_creationTime:c.number()}),f=c.object(y(i,[...u,...n.serverFields??[]])).strict(),p=f.partial(),m=c.object(v(i,n.commandFields??[])).strict(),h=c.object(v(i,n.publicFields??[])).strict(),g=e=>h.parse(Object.fromEntries(Object.keys(h.shape).map(t=>[t,e[t]])));function b(e){return e?f.omit(e):f}function x(e){return e?p.omit(e):p}return{tableName:e,schema:d,storage:s,insertSchema:f,updateSchema:p,commandInput:m,publicDto:h,toPublicDto:g,table:o(a(s)),insert:b,update:x,tools:{insert:m,update:c.object({data:m.partial(),id:_(e)}).strict(),id:c.object({id:_(e)}).strict()}}}const f={tenant:c.string().min(1)};function p(e,t,n={}){return d(e,e=>({...t(e),...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:t(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(e,t){return{tableName:e,storage:t,insertSchema:t,table:o(a(t))}}function _(e){return c.string().describe(`Convex document id for table "${e}"`)}function v(e,t){return Object.fromEntries(t.map(t=>[t,e[t]]))}function y(e,t){let n=new Set(t);return Object.fromEntries(Object.entries(e).filter(([e])=>!n.has(e)))}export{u as TIMESTAMP_FIELDS,e as convexToZod,m as createModule,_ as jsonSafeZid,h as paginated,p as tenantTable,n as zid,d as zodTable,i as zodToConvex,g as zodVariantTable};
1
+ import{convexToZod as e,convexToZod as t,zid as n,zid as r,zodToConvex as i,zodToConvex 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(e,t,n={}){let i={...t(r),...l},s=c.object(i).strict(),d=v(s,e),f=[...u,...n.serverFields??[]],p=n.commandFields??[],m=n.publicFields??[],h=c.object(b(i,f)).strict(),g=h.partial(),x=c.object(y(i,p)).strict(),S=c.object(y(i,m)).strict(),C=e=>S.parse(Object.fromEntries(Object.keys(S.shape).map(t=>[t,e[t]])));function w(e){return e?h.omit(e):h}function T(e){return e?g.omit(e):g}return{tableName:e,schema:d,storage:s,insertSchema:h,updateSchema:g,commandInput:x,publicDto:S,toPublicDto:C,table:o(a(s)),insert:w,update:T,tools:{insert:x,update:c.object({data:x.partial(),id:_(e)}).strict(),id:c.object({id:_(e)}).strict()}}}const f={tenant:c.string().min(1)};function p(e,t,n={}){return d(e,e=>({...t(e),...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:t(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(e,t){return{tableName:e,storage:t,insertSchema:t,table:o(a(t))}}function _(e){let t=r(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:r(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)))}export{u as TIMESTAMP_FIELDS,e as convexToZod,m as createModule,_ as jsonSafeZid,h as paginated,p as tenantTable,n as zid,d as zodTable,i as zodToConvex,g as zodVariantTable};
2
2
  //# sourceMappingURL=zod-table.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"zod-table.mjs","names":["zid","zodToConvex","convexToZod"],"sources":["../src/zod-table.ts"],"sourcesContent":["import {\n\ttype ConvexValidatorFromZod,\n\tconvexToZod,\n\tzid,\n\tzodToConvex,\n} from 'convex-helpers/server/zod4'\nimport {\n\tdefineTable,\n\tpaginationOptsValidator,\n\ttype TableDefinition,\n} 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 Shape = Record<string, z.ZodType>\ntype ShapeKey<Fields extends Shape> = 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 timestampShape = {\n\tcreatedAt: z.number().optional(),\n\tupdatedAt: z.number().optional(),\n\tarchivedAt: z.number().optional(),\n} satisfies Shape\n\ntype TimestampShape = typeof timestampShape\n\nexport const TIMESTAMP_FIELDS = [\n\t'createdAt',\n\t'updatedAt',\n\t'archivedAt',\n] as const\n\nexport type TableBoundaryOptions<\n\tFields extends Shape,\n\tServerFields extends readonly ShapeKey<Fields>[],\n\tCommandFields extends readonly ShapeKey<Fields>[],\n\tPublicFields extends readonly ShapeKey<Fields & TimestampShape>[],\n> = {\n\tserverFields?: ServerFields\n\tcommandFields?: CommandFields\n\tpublicFields?: PublicFields\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 Shape,\n\tconst ServerFields extends readonly ShapeKey<Fields>[] = readonly [],\n\tconst CommandFields extends readonly ShapeKey<Fields>[] = readonly [],\n\tconst PublicFields extends readonly ShapeKey<\n\t\tFields & TimestampShape\n\t>[] = readonly [],\n>(\n\ttableName: Table,\n\tfields: (id: typeof zid) => Fields,\n\toptions: TableBoundaryOptions<\n\t\tFields,\n\t\tServerFields,\n\t\tCommandFields,\n\t\tPublicFields\n\t> = {},\n) {\n\tconst shape = { ...fields(zid), ...timestampShape } as Fields &\n\t\tTimestampShape\n\tconst storage = z.object(shape).strict()\n\tconst schema = storage.extend({\n\t\t_id: zid(tableName),\n\t\t_creationTime: z.number(),\n\t})\n\tconst insertSchema = z\n\t\t.object(\n\t\t\tomitShape(shape, [\n\t\t\t\t...TIMESTAMP_FIELDS,\n\t\t\t\t...(options.serverFields ?? ([] as never)),\n\t\t\t] as never),\n\t\t)\n\t\t.strict() as z.ZodObject<\n\t\tOmit<Fields, ServerFields[number] | (typeof TIMESTAMP_FIELDS)[number]>\n\t>\n\tconst updateSchema = insertSchema.partial()\n\tconst commandInput = z\n\t\t.object(pickShape(shape, options.commandFields ?? ([] as never)))\n\t\t.strict()\n\tconst publicDto = z\n\t\t.object(pickShape(shape, options.publicFields ?? ([] as never)))\n\t\t.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\tkey,\n\t\t\t\t\t(row as Record<string, unknown>)[key],\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\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\treturn omit ? updateSchema.omit(omit as never) : updateSchema\n\t}\n\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: defineTable(zodToConvex(storage)) as TableDefinition<\n\t\t\tConvexValidatorFromZod<typeof storage, 'required'>\n\t\t>,\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\n\t\t\t\t.object({ data: commandInput.partial(), id: jsonSafeZid(tableName) })\n\t\t\t\t.strict(),\n\t\t\tid: z.object({ id: jsonSafeZid(tableName) }).strict(),\n\t\t},\n\t}\n}\n\nconst tenantShape = {\n\ttenant: z.string().min(1),\n} satisfies Shape\n\ntype TenantShape = typeof tenantShape\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 Shape,\n\tconst ServerFields extends readonly ShapeKey<Fields>[] = readonly [],\n\tconst CommandFields extends readonly ShapeKey<Fields>[] = readonly [],\n\tconst PublicFields extends readonly ShapeKey<\n\t\tFields & TenantShape & TimestampShape\n\t>[] = readonly [],\n>(\n\ttableName: Table,\n\tfields: (id: typeof zid) => Fields,\n\toptions: TableBoundaryOptions<\n\t\tFields & TenantShape,\n\t\treadonly (ServerFields[number] | 'tenant')[],\n\t\tCommandFields,\n\t\tPublicFields\n\t> = {},\n) {\n\treturn zodTable(\n\t\ttableName,\n\t\t(id) => ({ ...fields(id), ...tenantShape }),\n\t\t{\n\t\t\t...options,\n\t\t\tserverFields: [\n\t\t\t\t...((options.serverFields ?? []) as readonly (\n\t\t\t\t\t| ServerFields[number]\n\t\t\t\t\t| 'tenant'\n\t\t\t\t)[]),\n\t\t\t\t'tenant',\n\t\t\t] as never,\n\t\t} as never,\n\t) as ReturnType<\n\t\ttypeof zodTable<\n\t\t\tTable,\n\t\t\tFields & TenantShape,\n\t\t\treadonly (ServerFields[number] | 'tenant')[],\n\t\t\tCommandFields,\n\t\t\tPublicFields\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<\n\tconst Maps extends readonly Record<string, TableDefinition<any>>[],\n>(...maps: Maps): 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(\n\t\t\t\t\t`Table \"${tableName}\" is declared by more than one module`,\n\t\t\t\t)\n\t\t\t}\n\t\t\tcombined[tableName] = definition\n\t\t}\n\t}\n\treturn combined as UnionToIntersection<Maps[number]>\n}\n\ntype UnionToIntersection<Union> = (\n\tUnion extends unknown\n\t\t? (member: Union) => void\n\t\t: 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\n\t\t\t\t.enum(['SplitRecommended', 'SplitRequired'])\n\t\t\t\t.nullable()\n\t\t\t\t.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>(tableName: Table) {\n\treturn z\n\t\t.string()\n\t\t.describe(\n\t\t\t`Convex document id for table \"${tableName}\"`,\n\t\t) as unknown as z.ZodType<GenericId<Table>, string>\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 ObjectShape, infer Config>\n\t\t? z.ZodObject<\n\t\t\t\tz.util.Flatten<\n\t\t\t\t\tOmit<ObjectShape, Extract<keyof ObjectShape, keyof OmitMask>>\n\t\t\t\t>,\n\t\t\t\tConfig\n\t\t\t>\n\t\t: never\n\nfunction pickShape<\n\tFields extends Shape,\n\tconst Keys extends readonly ShapeKey<Fields>[],\n>(fields: Fields, keys: Keys) {\n\treturn Object.fromEntries(keys.map((key) => [key, fields[key]])) as Pick<\n\t\tFields,\n\t\tKeys[number]\n\t>\n}\n\nfunction omitShape<\n\tFields extends Shape,\n\tconst Keys extends readonly ShapeKey<Fields>[],\n>(fields: Fields, keys: Keys) {\n\tconst omitted = new Set<string>(keys)\n\treturn Object.fromEntries(\n\t\tObject.entries(fields).filter(([key]) => !omitted.has(key)),\n\t) as Omit<Fields, Keys[number]>\n}\n"],"mappings":"+NA0BA,MAAM,EAAiB,CACtB,UAAW,EAAE,OAAO,CAAC,CAAC,SAAS,EAC/B,UAAW,EAAE,OAAO,CAAC,CAAC,SAAS,EAC/B,WAAY,EAAE,OAAO,CAAC,CAAC,SAAS,CACjC,EAIa,EAAmB,CAC/B,YACA,YACA,YACD,EAcA,SAAgB,EASf,EACA,EACA,EAKI,CAAC,EACJ,CACD,IAAM,EAAQ,CAAE,GAAG,EAAOA,CAAG,EAAG,GAAG,CAAe,EAE5C,EAAU,EAAE,OAAO,CAAK,CAAC,CAAC,OAAO,EACjC,EAAS,EAAQ,OAAO,CAC7B,IAAKA,EAAI,CAAS,EAClB,cAAe,EAAE,OAAO,CACzB,CAAC,EACK,EAAe,EACnB,OACA,EAAU,EAAO,CAChB,GAAG,EACH,GAAI,EAAQ,cAAiB,CAAC,CAC/B,CAAU,CACX,CAAC,CACA,OAAO,EAGH,EAAe,EAAa,QAAQ,EACpC,EAAe,EACnB,OAAO,EAAU,EAAO,EAAQ,eAAkB,CAAC,CAAW,CAAC,CAAC,CAChE,OAAO,EACH,EAAY,EAChB,OAAO,EAAU,EAAO,EAAQ,cAAiB,CAAC,CAAW,CAAC,CAAC,CAC/D,OAAO,EACH,EAAe,GACpB,EAAU,MACT,OAAO,YACN,OAAO,KAAK,EAAU,KAAK,CAAC,CAAC,IAAK,GAAQ,CACzC,EACC,EAAgC,EAClC,CAAC,CACF,CACD,EAWD,SAAS,EAAO,EAAmB,CAClC,OAAO,EAAO,EAAa,KAAK,CAAa,EAAI,CAClD,CAMA,SAAS,EAAO,EAAmB,CAClC,OAAO,EAAO,EAAa,KAAK,CAAa,EAAI,CAClD,CAEA,MAAO,CACN,YACA,SACA,UACA,eACA,eACA,eACA,YACA,cACA,MAAO,EAAYC,EAAY,CAAO,CAAC,EAGvC,SACA,SACA,MAAO,CAGN,OAAQ,EACR,OAAQ,EACN,OAAO,CAAE,KAAM,EAAa,QAAQ,EAAG,GAAI,EAAY,CAAS,CAAE,CAAC,CAAC,CACpE,OAAO,EACT,GAAI,EAAE,OAAO,CAAE,GAAI,EAAY,CAAS,CAAE,CAAC,CAAC,CAAC,OAAO,CACrD,CACD,CACD,CAEA,MAAM,EAAc,CACnB,OAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CACzB,EAYA,SAAgB,EASf,EACA,EACA,EAKI,CAAC,EACJ,CACD,OAAO,EACN,EACC,IAAQ,CAAE,GAAG,EAAO,CAAE,EAAG,GAAG,CAAY,GACzC,CACC,GAAG,EACH,aAAc,CACb,GAAK,EAAQ,cAAgB,CAAC,EAI9B,QACD,CACD,CACD,CASD,CAOA,SAAgB,EAEd,GAAG,EAA+C,CACnD,IAAM,EAAiD,CAAC,EACxD,IAAK,IAAM,KAAO,EACjB,IAAK,GAAM,CAAC,EAAW,KAAe,OAAO,QAAQ,CAAG,EAAG,CAC1D,GAAI,KAAa,EAChB,MAAU,MACT,UAAU,EAAU,sCACrB,EAED,EAAS,GAAa,CACvB,CAED,OAAO,CACR,CAmBA,SAAgB,EAAiC,EAAU,CAC1D,MAAO,CACN,KAAM,CAAE,eAAgBC,EAAY,CAAuB,CAAE,EAC7D,OAAQ,EAAE,OAAO,CAChB,KAAM,EAAE,MAAM,CAAG,EACjB,OAAQ,EAAE,QAAQ,EAClB,eAAgB,EAAE,OAAO,EACzB,YAAa,EAAE,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,SAAS,EAC5C,WAAY,EACV,KAAK,CAAC,mBAAoB,eAAe,CAAC,CAAC,CAC3C,SAAS,CAAC,CACV,SAAS,CACZ,CAAC,CACF,CACD,CAGA,SAAgB,EACf,EACA,EACC,CACD,MAAO,CACN,YACA,UACA,aAAc,EACd,MAAO,EAAYD,EAAY,CAAO,CAAC,CACxC,CACD,CAMA,SAAgB,EAAkC,EAAkB,CACnE,OAAO,EACL,OAAO,CAAC,CACR,SACA,iCAAiC,EAAU,EAC5C,CACF,CAeA,SAAS,EAGP,EAAgB,EAAY,CAC7B,OAAO,OAAO,YAAY,EAAK,IAAK,GAAQ,CAAC,EAAK,EAAO,EAAI,CAAC,CAAC,CAIhE,CAEA,SAAS,EAGP,EAAgB,EAAY,CAC7B,IAAM,EAAU,IAAI,IAAY,CAAI,EACpC,OAAO,OAAO,YACb,OAAO,QAAQ,CAAM,CAAC,CAAC,QAAQ,CAAC,KAAS,CAAC,EAAQ,IAAI,CAAG,CAAC,CAC3D,CACD"}
1
+ {"version":3,"file":"zod-table.mjs","names":["zid","zodToConvex","convexToZod"],"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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"}
package/docs/approvals.md CHANGED
@@ -45,7 +45,7 @@ export const approvals = new Approvals(components.approvals)
45
45
 
46
46
  ## Defining a workflow — in the owning domain
47
47
 
48
- Workflow *definitions* are business logic and live in the domain that owns the
48
+ Workflow _definitions_ are business logic and live in the domain that owns the
49
49
  resource, not in the root facade:
50
50
 
51
51
  ```ts
@@ -54,38 +54,38 @@ import { approvals } from '../../approvals'
54
54
  import { internal } from '../../_generated/api'
55
55
 
56
56
  export const publishApproval = approvals.define({
57
- name: 'documentPublish',
58
- steps: [
59
- approvals.decision('managerDecision', {
60
- decisions: ['approved', 'rejected'],
61
- quorum: { kind: 'count', approvals: 1 },
62
- makerChecker: true, // requester cannot decide
63
- expiresAfterMs: 7 * 24 * 60 * 60 * 1000,
64
- }),
65
- approvals.branch('applyDecision', {
66
- approvedStepKey: 'publishDocument',
67
- rejectedStepKey: 'notifyRejected',
68
- }),
69
- approvals.mutation('publishDocument', {
70
- handler: internal.domain.documents.approval_functions.applyDecision,
71
- }),
72
- approvals.notify('notifyRejected', {
73
- handler: internal.domain.documents.approval_functions.notifyRejected,
74
- retry: true,
75
- }),
76
- ],
57
+ name: 'documentPublish',
58
+ steps: [
59
+ approvals.decision('managerDecision', {
60
+ decisions: ['approved', 'rejected'],
61
+ quorum: { kind: 'count', approvals: 1 },
62
+ makerChecker: true, // requester cannot decide
63
+ expiresAfterMs: 7 * 24 * 60 * 60 * 1000,
64
+ }),
65
+ approvals.branch('applyDecision', {
66
+ approvedStepKey: 'publishDocument',
67
+ rejectedStepKey: 'notifyRejected',
68
+ }),
69
+ approvals.mutation('publishDocument', {
70
+ handler: internal.domain.documents.approval_functions.applyDecision,
71
+ }),
72
+ approvals.notify('notifyRejected', {
73
+ handler: internal.domain.documents.approval_functions.notifyRejected,
74
+ retry: true,
75
+ }),
76
+ ],
77
77
  })
78
78
  ```
79
79
 
80
80
  ### Step kinds
81
81
 
82
- | Step | Runs | Notes |
83
- |---|---|---|
84
- | `decision(key, { decisions, quorum, makerChecker?, expiresAfterMs? })` | waits for humans | quorum is `{ kind: 'count', approvals: n }`; expiry resolves the run as `expired` |
85
- | `branch(key, { approvedStepKey, rejectedStepKey })` | routing | jumps by the preceding decision's outcome |
86
- | `mutation(key, { handler, retry? })` | internal mutation handle | transactional apply step |
87
- | `action(key, { handler, retry? })` | internal action handle | side effects (third parties) |
88
- | `notify(key, { handler, retry? })` | internal action handle | semantically a notification; same contract as action |
82
+ | Step | Runs | Notes |
83
+ | ---------------------------------------------------------------------- | ------------------------ | --------------------------------------------------------------------------------- |
84
+ | `decision(key, { decisions, quorum, makerChecker?, expiresAfterMs? })` | waits for humans | quorum is `{ kind: 'count', approvals: n }`; expiry resolves the run as `expired` |
85
+ | `branch(key, { approvedStepKey, rejectedStepKey })` | routing | jumps by the preceding decision's outcome |
86
+ | `mutation(key, { handler, retry? })` | internal mutation handle | transactional apply step |
87
+ | `action(key, { handler, retry? })` | internal action handle | side effects (third parties) |
88
+ | `notify(key, { handler, retry? })` | internal action handle | semantically a notification; same contract as action |
89
89
 
90
90
  Limits (enforced by the component's validators): ≤ 32 steps per workflow,
91
91
  ≤ 8 decisions per decision step, names ≤ 96 chars, references ≤ 256 chars,
@@ -103,23 +103,23 @@ workflow's steps in a breaking way, change the key.
103
103
  ```ts
104
104
  // start — from an authMutation, with ctx.actor as the requester
105
105
  const { runId } = await publishApproval.start(ctx, {
106
- scopeRef: ctx.org.organizationId, // opaque tenancy
107
- resourceType: 'document',
108
- resourceRef: documentId, // opaque to the component
109
- requester: ctx.actor,
110
- metadata: { title }, // bounded string map
106
+ scopeRef: ctx.org.organizationId, // opaque tenancy
107
+ resourceType: 'document',
108
+ resourceRef: documentId, // opaque to the component
109
+ requester: ctx.actor,
110
+ metadata: { title }, // bounded string map
111
111
  })
112
112
 
113
113
  // decide — a manager approves or rejects
114
114
  await publishApproval.decide(ctx, { runId, decision: 'approved', reason }, ctx.actor)
115
115
 
116
116
  // observe from a host query
117
- await publishApproval.status(ctx, runId) // pending | approved | rejected | expired | canceled
117
+ await publishApproval.status(ctx, runId) // pending | approved | rejected | expired | canceled
118
118
  await publishApproval.evidence(ctx, runId) // per-step decision evidence
119
119
  await publishApproval.list(ctx, {
120
- scopeRef,
121
- state: 'pending',
122
- paginationOpts: { cursor: null, numItems: 20 },
120
+ scopeRef,
121
+ state: 'pending',
122
+ paginationOpts: { cursor: null, numItems: 20 },
123
123
  })
124
124
 
125
125
  // manage
@@ -140,16 +140,16 @@ trust:
140
140
  import { approvalCallbackArgs } from 'cvx-kit/components/approvals'
141
141
 
142
142
  export const applyDecision = systemMutation({
143
- args: approvalCallbackArgs,
144
- handler: async (ctx, input) => {
145
- if (input.resourceType !== 'document') throw new Error('WRONG_RESOURCE')
146
- const id = ctx.db.normalizeId('documents', input.resourceRef)
147
- if (!id) throw new Error('BAD_REF')
148
- const document = await ctx.db.get(id)
149
- // guard against stale runs: the doc must still point at this run
150
- if (document?.approvalRunId !== input.runId) return
151
- await ctx.db.patch(id, { reviewState: 'published' })
152
- },
143
+ args: approvalCallbackArgs,
144
+ handler: async (ctx, input) => {
145
+ if (input.resourceType !== 'document') throw new Error('WRONG_RESOURCE')
146
+ const id = ctx.db.normalizeId('documents', input.resourceRef)
147
+ if (!id) throw new Error('BAD_REF')
148
+ const document = await ctx.db.get(id)
149
+ // guard against stale runs: the doc must still point at this run
150
+ if (document?.approvalRunId !== input.runId) return
151
+ await ctx.db.patch(id, { reviewState: 'published' })
152
+ },
153
153
  })
154
154
  ```
155
155
 
@@ -168,14 +168,15 @@ available inside components:
168
168
 
169
169
  ```ts
170
170
  export const approvalInbox = authQuery({
171
- args: {
172
- state: z.enum(['pending', 'approved', 'rejected', 'expired', 'canceled']).optional(),
173
- paginationOpts: convexToZod(paginationOptsValidator),
174
- },
175
- handler: (ctx, args) => publishApproval.list(ctx, {
176
- scopeRef: ctx.org.organizationId,
177
- ...args,
178
- }),
171
+ args: {
172
+ state: z.enum(['pending', 'approved', 'rejected', 'expired', 'canceled']).optional(),
173
+ paginationOpts: convexToZod(paginationOptsValidator),
174
+ },
175
+ handler: (ctx, args) =>
176
+ publishApproval.list(ctx, {
177
+ scopeRef: ctx.org.organizationId,
178
+ ...args,
179
+ }),
179
180
  })
180
181
  ```
181
182
 
@@ -187,7 +188,7 @@ scope/state query.
187
188
  ## Rules and footguns
188
189
 
189
190
  1. Root facade (`convex/approvals.ts`) instantiates the client; workflow
190
- *definitions* live in the owning domain.
191
+ _definitions_ live in the owning domain.
191
192
  2. Handlers get opaque strings — always `normalizeId`, re-check
192
193
  `resourceType`, and verify the resource still references the run.
193
194
  3. `makerChecker: true` means the requester's decisions are rejected — design
@@ -3,7 +3,7 @@
3
3
  cvx-kit was extracted from a production Convex application (~12k LOC backend).
4
4
  This document describes the architecture that application follows and that the
5
5
  kit is designed to reproduce: strict ownership boundaries, injected policy,
6
- portable components, and conventions that are *executable* (enforced by tests)
6
+ portable components, and conventions that are _executable_ (enforced by tests)
7
7
  rather than aspirational.
8
8
 
9
9
  ## The layers