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.
Files changed (65) hide show
  1. package/AGENTS.md +47 -0
  2. package/CHANGELOG.md +33 -0
  3. package/README.md +142 -0
  4. package/dist/auth.d.mts +3 -1
  5. package/dist/auth.mjs +1 -1
  6. package/dist/auth.mjs.map +1 -1
  7. package/dist/index.d.mts +5 -2
  8. package/dist/index.mjs +1 -1
  9. package/dist/modules/effect/api-zod.d.mts +1 -1
  10. package/dist/oxlint/architecture.d.mts +2 -0
  11. package/dist/oxlint/architecture.mjs +1 -1
  12. package/dist/oxlint/architecture.mjs.map +1 -1
  13. package/dist/oxlint/ast.d.mts +10 -1
  14. package/dist/oxlint/ast.mjs +1 -1
  15. package/dist/oxlint/ast.mjs.map +1 -1
  16. package/dist/oxlint.d.mts +1 -1
  17. package/dist/oxlint.mjs +1 -1
  18. package/dist/oxlint.mjs.map +1 -1
  19. package/dist/querying/cursors.d.mts +50 -0
  20. package/dist/querying/cursors.mjs +2 -0
  21. package/dist/querying/cursors.mjs.map +1 -0
  22. package/dist/querying/execution.d.mts +30 -0
  23. package/dist/querying/execution.mjs +2 -0
  24. package/dist/querying/execution.mjs.map +1 -0
  25. package/dist/querying/filters.d.mts +136 -0
  26. package/dist/querying/filters.mjs +2 -0
  27. package/dist/querying/filters.mjs.map +1 -0
  28. package/dist/querying/planner.d.mts +50 -0
  29. package/dist/querying/planner.mjs +2 -0
  30. package/dist/querying/planner.mjs.map +1 -0
  31. package/dist/querying/relationships.d.mts +41 -0
  32. package/dist/querying/relationships.mjs +2 -0
  33. package/dist/querying/relationships.mjs.map +1 -0
  34. package/dist/querying/security.d.mts +107 -0
  35. package/dist/querying/security.mjs +2 -0
  36. package/dist/querying/security.mjs.map +1 -0
  37. package/dist/querying.d.mts +53 -0
  38. package/dist/querying.mjs +2 -0
  39. package/dist/querying.mjs.map +1 -0
  40. package/dist/relations-public.d.mts +2 -0
  41. package/dist/relations-public.mjs +1 -0
  42. package/dist/relations.d.mts +155 -0
  43. package/dist/relations.mjs +2 -0
  44. package/dist/relations.mjs.map +1 -0
  45. package/dist/zod-convex.d.mts +10 -0
  46. package/dist/zod-convex.mjs +2 -0
  47. package/dist/zod-convex.mjs.map +1 -0
  48. package/dist/zod-registration.d.mts +10 -0
  49. package/dist/zod-registration.mjs +2 -0
  50. package/dist/zod-registration.mjs.map +1 -0
  51. package/dist/zod-table.d.mts +63 -8
  52. package/dist/zod-table.mjs +1 -1
  53. package/dist/zod-table.mjs.map +1 -1
  54. package/docs/conventions.md +12 -0
  55. package/docs/llms.md +15 -5
  56. package/docs/oxlint.md +135 -25
  57. package/docs/querying.md +329 -0
  58. package/docs/zod-table.md +93 -13
  59. package/package.json +11 -1
  60. package/scripts/owned-process.mjs +140 -0
  61. package/scripts/owned-process.test.mjs +107 -0
  62. package/scripts/smoke-packed-querying.mjs +298 -0
  63. package/scripts/test-packed-effect.mjs +8 -1
  64. package/scripts/test-packed-oxlint.mjs +5 -1
  65. package/scripts/test-packed-querying.mjs +185 -0
package/AGENTS.md ADDED
@@ -0,0 +1,47 @@
1
+ # Orchestration
2
+
3
+ <!-- Orchestration -->
4
+
5
+ Use T3 Code to orchestrate the agents the new orchestrator V2 allows you to orchestrate multiple models for working together, I have some recommendations for model selection and orchestration:
6
+
7
+ ## Models
8
+
9
+ | Provider | Heavy | Medium | Light |
10
+ | -------- | ----------- | ----------- | ---------------------- |
11
+ | OpenAI | gpt-6-astra | gpt-6.1-sol | gpt-6-luna |
12
+ | Claude | flabel-5.1 | opus-5.5 | sonnet-5.5 / haiku-5.5 |
13
+
14
+ Haiku and Luna are very special models for short tasks such as research file reader and those tasks that wont consume more than 100K tokens.
15
+
16
+ ## Workflow
17
+
18
+ I have a workflow for implementing features and working which contains some skills within the process they don't fallow an order but depending how deep we are on context we use it but those skills are:
19
+
20
+ - **ce-explain**: This I used it to verify if the AI is aligned with the user's intent, then got a back and forth conversation with the user to explain the feature and the context of the feature including research on the libraries internet and packages.
21
+ - **ce-plan**: This I used it to plan the feature implementation.
22
+ - **ce-work**: This I used it to work on the feature implementation.
23
+
24
+ These are goated skills that are used to implement features and work on them, but the real workflow for implementation itself is:
25
+
26
+ | Planners | workers | low workers | researcher | reviewers |
27
+ | ----------- | ----------- | ----------- | ---------------------- | ----------- |
28
+ | gpt-6-astra | gpt-6.1-sol | gpt-6-luna | gpt-6-luna | gpt-6-astra |
29
+ | flabel-5.1 | opus-5.5 | sonnet-5.5 | sonnet-5.5 / haiku-5.5 | N/A |
30
+
31
+ Invoke **reviewers** for code review, implementation review, and security review on changed code. Use **gpt-6-astra** at **Low** effort for that role.
32
+
33
+ After the planning and research session I sent the `ce-work` message to the main thread with the fallowing context:
34
+ "Work on this feature into a goal with multiple agents and make sure you test every phase. And you do a code review and an implementation review as well. Make sure every phase is tested before go to the other one And commit it as well. Make a security review to make sure everything is working correctly. Use a browser or any test you need to make sure you cover the needed and everything is working correctly."
35
+
36
+ with the fallowing effort:
37
+
38
+ | Model | Recommended Effort |
39
+ | ----------- | --------------------------------------------- |
40
+ | gpt-6-astra | Medium / Low worker; Low reviewer |
41
+ | flabel-5.1 | low |
42
+ | opus-5.5 | low |
43
+ | gpt-6-luna | low(fast) |
44
+ | gpt-6.1-sol | medium(fast) |
45
+ | sonnet-5.5 | medium |
46
+
47
+ <!-- Orchestration -->
package/CHANGELOG.md CHANGED
@@ -12,6 +12,39 @@ are this file's matching section.
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [0.1.15] - 2026-10-11
16
+
17
+ ### Added
18
+
19
+ - Declare a global query engine with only `createQueryEngine({ schema, relations })`.
20
+ - Load cursor secrets internally from server environment configuration on first pagination.
21
+ - Configure optional engine overrides in a separate second argument, while preserving existing declarations.
22
+
23
+ ### Changed
24
+
25
+ - Document global engine setup, server cursor secrets, and the purpose of advanced options.
26
+ - Publish releases after successful CI for the exact main commit, without repeating tests in the release workflow.
27
+
28
+ ## [0.1.14] - 2026-10-11
29
+
30
+ ### Added
31
+
32
+ - Typed relational queries with scalar and relationship filters, conditional indexes, global ascending or descending order, and bounded stream pagination.
33
+ - Explicit relationship declarations for direct, self, and junction relationships, with paginated child continuations.
34
+ - Managed query authorization, shared read budgets, and encrypted cursors bound to the query, principal, tenant, and parent relationship.
35
+ - Table DTO options for validated IDs and extensions, including `toPublicDto(row, true)`, plus native-derived `paginationOpts` and page DTO helpers.
36
+ - Oxlint enforcement for manual ID projections, copied pagination contracts, and unsafe relational query boundaries.
37
+ - Dedicated querying documentation and updated table and rule documentation in `src/docs`.
38
+
39
+ ### Fixed
40
+
41
+ - Preserve shared validator graphs during native auth registration to avoid repeated validator expansion.
42
+
43
+ ### Verified
44
+
45
+ - Source, type, rule, and packed-consumer checks; 308 independently audited relational stress sequences across installed and minimum peer versions.
46
+ - 33 HTTP responses exercised encrypted cursor pagination and rejection paths on a local Convex backend. Hosted production performance remains unmeasured.
47
+
15
48
  ## [0.1.13] - 2026-10-10
16
49
 
17
50
  ### Added
package/README.md CHANGED
@@ -83,6 +83,148 @@ under application control.
83
83
  `zodVariantTable` covers discriminated-union tables; `jsonSafeZid` keeps
84
84
  LLM-tool JSON schemas primitive while preserving the `Id<...>` type.
85
85
 
86
+ ### `cvx-kit/relations` and `cvx-kit/querying`
87
+
88
+ See the [querying guide](./src/docs/querying.md),
89
+ [DTO parameters](./src/docs/zod-table.md#public-dto-parameters), and
90
+ [consumer rule reference](./src/docs/oxlint.md#dto-and-pagination-diagnostics).
91
+
92
+ Declare relationships once in `convex/relations.ts`, then configure the engine
93
+ once in `convex/querying.ts`. Both factories are also available from the package
94
+ root. The relations subpath exposes declarations and public types; executor
95
+ metadata and managed reader capabilities are internal.
96
+
97
+ ```ts
98
+ // convex/relations.ts
99
+ import { defineRelations } from 'cvx-kit/relations'
100
+ import './domain/tasks/table' // Establish native indexes before compiling the registry.
101
+ import { tasks, labels, edges } from './domain/tasks/schema'
102
+
103
+ export const relations = defineRelations({ tasks, labels, edges }, (r) => ({
104
+ tasks: {
105
+ referenceTask: r.one.tasks({ from: r.tasks.reference, to: r.tasks._id }),
106
+ labels: r.many.labels({
107
+ from: r.tasks._id.through(r.edges.task),
108
+ to: r.labels._id.through(r.edges.label),
109
+ }),
110
+ },
111
+ }))
112
+ ```
113
+
114
+ The tables must be object-shaped `zodTable` or `tenantTable` boundaries, with
115
+ indexes supporting each declared lookup. Names can distinguish multiple edges
116
+ to the same table; references can point to the same table. A one edge returns a
117
+ public DTO or `null`. A many edge returns a bounded native page. Junction output
118
+ preserves duplicate edge occurrences, even when target reads are memoized.
119
+ Missing and unreadable targets have the same public result.
120
+
121
+ ```ts
122
+ // convex/querying.ts
123
+ import { createQueryEngine } from 'cvx-kit/querying'
124
+ import schema from './schema'
125
+ import { relations } from './relations'
126
+
127
+ export const querying = createQueryEngine({ schema, relations })
128
+ ```
129
+
130
+ One engine serves every registered table. Cursor keys load internally on first
131
+ pagination from server environment variables `CVX_QUERY_CURSOR_KEYS_JSON`
132
+ (key IDs mapped to 128-character hex secrets) and `CVX_QUERY_CURSOR_CURRENT_KEY_ID`.
133
+ Keep key material out of arguments, client code, and logs. Optional overrides
134
+ belong in a separate second argument; see [querying](src/docs/querying.md). The default codec
135
+ uses authenticated AES-SIV encryption without a plaintext fallback; identical
136
+ payloads under the same binding reveal equality. Tokens bind filters, selected
137
+ output, index, order, relation ancestry, principal/tenant/security scope, and
138
+ registry/policy/engine versions. Page-size changes alone remain valid. Retain
139
+ old keys during rotation when existing cursors should continue working.
140
+
141
+ Use the registry's validators in domain contracts and an authenticated Query
142
+ registry handler:
143
+
144
+ ```ts
145
+ const options = relations.tables.tasks.paginationOpts({
146
+ department: true,
147
+ status: true,
148
+ labels: { name: true },
149
+ })
150
+ const result = relations.tables.tasks.pageDto({ labels: true })
151
+
152
+ // In the Query operation's handler, ctx comes from createAuthFunctions.authQuery.
153
+ const page = await querying(ctx)
154
+ .tasks.where({ tenant: ctx.tenant })
155
+ .with({ labels: true })
156
+ .paginate(args)
157
+
158
+ // A separate authenticated child endpoint validates parent with tasks.schema.shape._id.
159
+ const nextLabels = await querying(ctx)
160
+ .tasks.relation(parent, 'labels')
161
+ .paginate({ numItems: 25, cursor: page.page[0].labels.continueCursor })
162
+ ```
163
+
164
+ Select filterable stored fields explicitly, including server-owned fields when
165
+ needed. Public DTO fields do not determine filter eligibility. Scalar predicates
166
+ support equality, ranges, `and`, `or`, and `not`; many predicates use `some` or
167
+ `none`. Eligibility and composed read authorization run before parents fill a
168
+ page. Parent, junction, target, and continuation reads all recheck authorization.
169
+ `include` and `paginated(dto)` retain their existing behavior independently.
170
+
171
+ Chain `.when(value, 'index_name')` to choose the first present condition: `false`,
172
+ `0`, and `''` are present; `null` and `undefined` are absent. The gate does not
173
+ bind an index field; validated filters and server `.where(...)` constraints do.
174
+ An incompatible explicit choice fails. Automatic selection uses the longest
175
+ usable equality prefix, then stable index-name order, with creation-time fallback.
176
+ `order: 'desc'` reverses the chosen native index order. Native continuation,
177
+ split/end cursors, and page status are preserved, with external tokens protected.
178
+
179
+ Page sizes are positive integers up to 100; central `maxPageSize` may lower that
180
+ ceiling. Central budgets bound read operations, index ranges, raw candidates,
181
+ observed/reserved document bytes, fan-out, depth, and memo entries across a
182
+ request, including policy reads. Native request read limits may lower ceilings.
183
+ An incomplete eligibility proof throws `MANAGED_READ_BUDGET`; it is never an
184
+ empty exhausted page or a negative predicate. Output relationship depth is at
185
+ most four. Traversal executes in the caller's query transaction and may require
186
+ many reads. Application/helper accounting does not measure physical platform I/O.
187
+
188
+ Kit auth query contexts carry the engine's private read capability. Unknown
189
+ contexts fail closed unless a centrally configured trusted adapter attests their
190
+ query reader, effective policy, and security scope. Mutation/action contexts,
191
+ variant tables, search/vector optimization, arbitrary predicates/projections,
192
+ and custom sorts are outside this API.
193
+
194
+ For manual projection, `tasks.toPublicDto(row)` still omits IDs and unselected
195
+ storage fields. `tasks.toPublicDto(row, true)` or `{ _id: true }` adds the branded
196
+ ID only. Computed extensions use matching schemas:
197
+
198
+ ```ts
199
+ const extend = { url: z.url() }
200
+ const dto = tasks.toPublicDto(row, {
201
+ _id: true,
202
+ extend,
203
+ values: { url: `https://example.test/tasks/${row._id}` },
204
+ })
205
+ const returnSchema = tasks.dto({ _id: true, extend })
206
+ ```
207
+
208
+ To migrate, replace manual ID spreads with the shorthand and copied pagination
209
+ objects with `paginationOpts`. Move eligibility filters before bounded reads by
210
+ expressing them in the engine. Replace recognized unbounded relationship
211
+ collections with declared many pages and authenticated continuation endpoints.
212
+ Consumer diagnostics identify these patterns when kit/native provenance is known;
213
+ predicate movement has no automatic fix. Keep declarations in root
214
+ `relations.ts`/`querying.ts` and contracts and Query registries in their domain.
215
+
216
+ The [packed executable example](./test/fixture-packed-querying/convex/) includes
217
+ domain tables, contracts, authenticated API adapters, one/self/named/many/junction
218
+ edges, and child continuation. Its `.template` files become ordinary TypeScript
219
+ in isolated consumers; `bun run test:package:querying` installs the tarball on
220
+ installed and minimum peers, checks strict declarations and consumer rules, and
221
+ runs the full independent stress oracle and focused tie, output, replay, and
222
+ authorization-revocation probes. For a bounded anonymous local native proof,
223
+ run `node scripts/smoke-packed-querying.mjs <retained-packed-proof-directory>`
224
+ after the package check. It uses the same tarball and central default budgets.
225
+ Package publishing and application
226
+ migration remain separate work.
227
+
86
228
  ### `cvx-kit/auth`
87
229
 
88
230
  `createAuthFunctions` builds the six auth-aware constructors from injected
package/dist/auth.d.mts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ErrorFactory } from "./errors.mjs";
2
2
  import { RLSConfig, Rules } from "./tenancy.mjs";
3
+ import { SecurityPolicyContext } from "./querying/security.mjs";
3
4
  import { CustomBuilder } from "convex-helpers/server/zod4";
4
5
  import { ActionBuilder, DocumentByInfo, GenericActionCtx, GenericDataModel, GenericMutationCtx, GenericQueryCtx, GenericTableInfo, IndexNames, IndexRange, IndexRangeBuilder, MutationBuilder, NamedIndex, PaginationOptions, PaginationResult, Query, QueryBuilder, QueryInitializer, TableNamesInDataModel, UserIdentity } from "convex/server";
5
6
  import { Triggers } from "convex-helpers/server/triggers";
@@ -46,7 +47,7 @@ export type SecurityConfig<DataModel extends GenericDataModel, Role extends stri
46
47
  * composed AND-wise with tenant isolation. E.g. gate `modify` on a table
47
48
  * to admin roles.
48
49
  */
49
- rules?: (bundle: AuthBundle<Role>) => Rules<unknown, DataModel>;
50
+ rules?: (bundle: AuthBundle<Role>) => Rules<SecurityPolicyContext<DataModel>, DataModel>;
50
51
  /**
51
52
  * Policy for tables with no rule. Defaults to 'deny' when tenancy is
52
53
  * configured (unlisted tables are unreachable — fail closed), 'allow'
@@ -184,4 +185,5 @@ export declare function createInclude(options?: {
184
185
  */
185
186
  export declare function createAuthFunctions<DataModel extends GenericDataModel, const Role extends string = DefaultRole>(config: AuthFunctionsConfig<DataModel, Role>): AuthFunctions<DataModel, Role>;
186
187
  //#endregion
188
+ export type { SecurityPolicyContext };
187
189
  //# sourceMappingURL=auth.d.mts.map
package/dist/auth.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{defaultErrors as e}from"./errors.mjs";import{composeRules as t,createTenantRules as n,wrapDatabaseReader as r,wrapDatabaseWriter as i}from"./tenancy.mjs";import{zCustomAction as a,zCustomMutation as o,zCustomQuery as s}from"convex-helpers/server/zod4";import{z as c}from"zod";function l(e){let t=e?.trim().toLowerCase();return t===`member`?`writer`:t===`reader`||t===`writer`||t===`admin`?t:null}function u(t){let n=t?.errors??e,r=t?.maxRows??100;function i(e,t){async function a(i,a){if(!Number.isSafeInteger(i)||i<1||i>r)return n.throw({code:`INVALID_REQUEST_BOUNDARY`,message:`A bounded query limit must be from 1 to ${r}`});let o=await(t??e.fullTableScan()).take(i);return a?a(o):o}async function o(i,a){if(!Number.isSafeInteger(i.numItems)||i.numItems<1||i.numItems>r)return n.throw({code:`INVALID_REQUEST_BOUNDARY`,message:`A page size must be from 1 to ${r}`});let o=await(t??e.fullTableScan()).paginate(i);return a?{...o,page:await a(o.page)}:o}return{when:(n,r)=>t||n==null?i(e,t):i(e,r(e,n)),matching:(n,r,a=!0)=>t||!a?i(e,t):i(e,e.withIndex(n,r)),otherwise:n=>t??n(e),resolve:()=>t??e.fullTableScan(),execute:a,paginate:o}}return e=>i(e)}function d(c){let l=c.errors??e,d=c.triggers,p=d?e=>d.wrapDB(e):c.wrapDB??(e=>e),m=u({errors:l,maxRows:c.maxIncludedQueryRows});async function h(e){let t=await e.auth.getUserIdentity(),n=t?await c.getAuthUser(e):null;if(!t||!n)return l.throw({code:`UNAUTHENTICATED`});let r,i;if(c.resolveOrganization){let a=null;try{a=await c.resolveOrganization({ctx:e,identity:t,user:n})}catch{}if(!a)return l.throw({code:`FORBIDDEN`});r=a.organizationId,i=a.roleSlug}else{let e=f.parse(t),n=e.organization?.organizationId??e.org_id;if(!n)return l.throw({code:`UNAUTHENTICATED`});r=n,i=e.organization?.role??e.role}let a=c.mapRole(i);if(!a)return l.throw({code:`FORBIDDEN`});let o={identity:t,user:Object.freeze({id:n.id}),org:Object.freeze({organizationId:r,role:a}),role:a,actor:Object.freeze({userId:n.id,organizationId:r,role:a})};return Object.freeze({...o,tenant:c.security?.tenancy?.resolve?.(o)??r})}let g=c.security?{defaultPolicy:c.security.defaultPolicy??(c.security.tenancy?`deny`:`allow`)}:void 0;function _(e){let r=c.security;if(!r)return;let i=[];return r.tenancy&&i.push(n(e.tenant,r.tenancy.tables)),r.rules&&i.push(r.rules(e)),i.length===1?i[0]:t(...i)}function v(e,t){let n=_(t);return n?{...e,db:r({},e.db,n,g)}:e}function y(e,t){let n=_(t);return n?{...e,db:i({},e.db,n,g)}:e}async function b(e,t){if(!c.verifyMembership)return null;try{let n=await c.verifyMembership({userId:e,organizationId:t});if(n&&n.organizationId===t)return n}catch{}return l.throw({code:`FORBIDDEN`})}function x(e,t){if(!t.includes(e))return l.throw({code:`FORBIDDEN`})}async function S(e){let t=await h(e);return{ctx:{...v(e,t),include:m,...t},args:{}}}async function C(e){let t=await h(e);return{ctx:{...y(p(e),t),include:m,...t},args:{}}}async function w(e){let t=await h(e),n=await b(t.user.id,t.org.organizationId);if(!n)return{ctx:{...e,...t},args:{}};let r=c.mapRole(n.roleSlug);if(!r)return l.throw({code:`FORBIDDEN`});let i={identity:t.identity,user:t.user,org:Object.freeze({organizationId:n.organizationId,role:r}),role:r,actor:Object.freeze({userId:t.user.id,organizationId:n.organizationId,role:r})};return{ctx:{...e,...i,tenant:c.security?.tenancy?.resolve?.(i)??n.organizationId},args:{}}}function T(...e){return s(c.query,{args:{},input:async t=>{let n=await S(t);return x(n.ctx.actor.role,e),n}})}function E(...e){return o(c.mutation,{args:{},input:async t=>{let n=await C(t);return x(n.ctx.actor.role,e),n}})}function D(...e){return a(c.action,{args:{},input:async t=>{let n=await w(t);return x(n.ctx.actor.role,e),n}})}return{include:m,authenticatedUser:h,roleQuery:T,roleMutation:E,roleAction:D,authQuery:s(c.query,{args:{},input:S}),authMutation:o(c.mutation,{args:{},input:C}),authAction:a(c.action,{args:{},input:w}),adminQuery:T(...c.adminRoles),adminMutation:E(...c.adminRoles),adminAction:D(...c.adminRoles),systemQuery:s(c.internalQuery,{args:{},input:async e=>({ctx:{...e,include:m},args:{}})}),systemMutation:o(c.internalMutation,{args:{},input:async e=>({ctx:{...p(e),include:m},args:{}})}),systemAction:a(c.internalAction,{args:{},input:async e=>({ctx:e,args:{}})})}}const f=c.object({organization:c.object({organizationId:c.string().optional().catch(void 0),role:c.string().optional().catch(void 0)}).optional().catch(void 0),org_id:c.string().optional().catch(void 0),role:c.string().optional().catch(void 0)});export{d as createAuthFunctions,u as createInclude,l as defaultRoleMap};
1
+ import{zCustomAction as e,zCustomMutation as t,zCustomQuery as n}from"./zod-registration.mjs";import{defaultErrors as r}from"./errors.mjs";import{registerQueryReader as i,suppliedPolicyRules as a}from"./querying/security.mjs";import{composeRules as o,createTenantRules as s,wrapDatabaseReader as c,wrapDatabaseWriter as l}from"./tenancy.mjs";import{z as u}from"zod";function d(e){let t=e?.trim().toLowerCase();return t===`member`?`writer`:t===`reader`||t===`writer`||t===`admin`?t:null}function f(e){let t=e?.errors??r,n=e?.maxRows??100;function i(e,r){async function a(i,a){if(!Number.isSafeInteger(i)||i<1||i>n)return t.throw({code:`INVALID_REQUEST_BOUNDARY`,message:`A bounded query limit must be from 1 to ${n}`});let o=await(r??e.fullTableScan()).take(i);return a?a(o):o}async function o(i,a){if(!Number.isSafeInteger(i.numItems)||i.numItems<1||i.numItems>n)return t.throw({code:`INVALID_REQUEST_BOUNDARY`,message:`A page size must be from 1 to ${n}`});let o=await(r??e.fullTableScan()).paginate(i);return a?{...o,page:await a(o.page)}:o}return{when:(t,n)=>r||t==null?i(e,r):i(e,n(e,t)),matching:(t,n,a=!0)=>r||!a?i(e,r):i(e,e.withIndex(t,n)),otherwise:t=>r??t(e),resolve:()=>r??e.fullTableScan(),execute:a,paginate:o}}return e=>i(e)}function p(u){let d=u.errors??r,p=u.triggers,h=p?e=>p.wrapDB(e):u.wrapDB??(e=>e),g=f({errors:d,maxRows:u.maxIncludedQueryRows});async function _(e){let t=await e.auth.getUserIdentity(),n=t?await u.getAuthUser(e):null;if(!t||!n)return d.throw({code:`UNAUTHENTICATED`});let r,i;if(u.resolveOrganization){let a=null;try{a=await u.resolveOrganization({ctx:e,identity:t,user:n})}catch{}if(!a)return d.throw({code:`FORBIDDEN`});r=a.organizationId,i=a.roleSlug}else{let e=m.parse(t),n=e.organization?.organizationId??e.org_id;if(!n)return d.throw({code:`UNAUTHENTICATED`});r=n,i=e.organization?.role??e.role}let a=u.mapRole(i);if(!a)return d.throw({code:`FORBIDDEN`});let o={identity:t,user:Object.freeze({id:n.id}),org:Object.freeze({organizationId:r,role:a}),role:a,actor:Object.freeze({userId:n.id,organizationId:r,role:a})};return Object.freeze({...o,tenant:u.security?.tenancy?.resolve?.(o)??r})}let v=u.security?{defaultPolicy:u.security.defaultPolicy??(u.security.tenancy?`deny`:`allow`)}:void 0;function y(e){let t=u.security;if(!t)return;let n=[];return t.tenancy&&n.push(s(e.tenant,t.tenancy.tables)),t.rules&&n.push(t.rules(e)),n.length===1?n[0]:o(...n)}function b(e,t){let n=y(t),r=v?.defaultPolicy??`allow`,o=n?c({db:e.db},e.db,a(e.db,n,r),v):e.db;return{...e,db:i(e.db,o,{scope:{mode:`authenticated`,principal:t.user.id,organization:t.org.organizationId,tenant:t.tenant,role:t.role},rules:n??{},defaultPolicy:r})}}function x(e,t){let n=y(t);return n?{...e,db:l({db:e.db},e.db,a(e.db,n,v?.defaultPolicy??`allow`),v)}:e}async function S(e,t){if(!u.verifyMembership)return null;try{let n=await u.verifyMembership({userId:e,organizationId:t});if(n&&n.organizationId===t)return n}catch{}return d.throw({code:`FORBIDDEN`})}function C(e,t){if(!t.includes(e))return d.throw({code:`FORBIDDEN`})}async function w(e){let t=await _(e);return{ctx:{...b(e,t),include:g,...t},args:{}}}async function T(e){let t=await _(e);return{ctx:{...x(h(e),t),include:g,...t},args:{}}}async function E(e){let t=await _(e),n=await S(t.user.id,t.org.organizationId);if(!n)return{ctx:{...e,...t},args:{}};let r=u.mapRole(n.roleSlug);if(!r)return d.throw({code:`FORBIDDEN`});let i={identity:t.identity,user:t.user,org:Object.freeze({organizationId:n.organizationId,role:r}),role:r,actor:Object.freeze({userId:t.user.id,organizationId:n.organizationId,role:r})};return{ctx:{...e,...i,tenant:u.security?.tenancy?.resolve?.(i)??n.organizationId},args:{}}}function D(...e){return n(u.query,{args:{},input:async t=>{let n=await w(t);return C(n.ctx.actor.role,e),n}})}function O(...e){return t(u.mutation,{args:{},input:async t=>{let n=await T(t);return C(n.ctx.actor.role,e),n}})}function k(...t){return e(u.action,{args:{},input:async e=>{let n=await E(e);return C(n.ctx.actor.role,t),n}})}return{include:g,authenticatedUser:_,roleQuery:D,roleMutation:O,roleAction:k,authQuery:n(u.query,{args:{},input:w}),authMutation:t(u.mutation,{args:{},input:T}),authAction:e(u.action,{args:{},input:E}),adminQuery:D(...u.adminRoles),adminMutation:O(...u.adminRoles),adminAction:k(...u.adminRoles),systemQuery:n(u.internalQuery,{args:{},input:async e=>({ctx:{...e,include:g,db:i(e.db,e.db,{scope:{mode:`system`,principal:null,organization:null,tenant:null,role:null},rules:{},defaultPolicy:`allow`})},args:{}})}),systemMutation:t(u.internalMutation,{args:{},input:async e=>({ctx:{...h(e),include:g},args:{}})}),systemAction:e(u.internalAction,{args:{},input:async e=>({ctx:e,args:{}})})}}const m=u.object({organization:u.object({organizationId:u.string().optional().catch(void 0),role:u.string().optional().catch(void 0)}).optional().catch(void 0),org_id:u.string().optional().catch(void 0),role:u.string().optional().catch(void 0)});export{p as createAuthFunctions,f as createInclude,d as defaultRoleMap};
2
2
  //# sourceMappingURL=auth.mjs.map
package/dist/auth.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"auth.mjs","names":["defaultRoleMap","normalized","roleSlug","createInclude","errors","options","defaultErrors","maxRows","attach","execute","limit","data","selected","query","transform","paginate","opts","result","value","select","shouldMatch","indexName","indexRange","createAuthFunctions","config","triggers","wrapDB","ctx","include","authenticatedUser","identity","user","organizationId","organizationRole","resolved","claims","organizationClaims","claimedOrganizationId","role","base","securityConfig","securityRules","security","sets","createTenantRules","bundle","composeRules","secureReader","rules","wrapDatabaseReader","secureWriter","wrapDatabaseWriter","verifiedMembership","membership","requireRole","allowed","queryInput","mutationInput","actionInput","authenticated","verified","roleQuery","zCustomQuery","roleMutation","zCustomMutation","roleAction","zCustomAction","z"],"sources":["../src/auth.ts"],"sourcesContent":["import { z } from 'zod'\nimport { zCustomAction, zCustomMutation, zCustomQuery } from 'convex-helpers/server/zod4'\nimport type { CustomBuilder } from 'convex-helpers/server/zod4'\nimport type {\n\tActionBuilder,\n\tDocumentByInfo,\n\tPaginationOptions,\n\tPaginationResult,\n\tGenericActionCtx,\n\tGenericDataModel,\n\tGenericMutationCtx,\n\tGenericQueryCtx,\n\tGenericTableInfo,\n\tIndexNames,\n\tIndexRange,\n\tIndexRangeBuilder,\n\tMutationBuilder,\n\tNamedIndex,\n\tQuery,\n\tQueryBuilder,\n\tQueryInitializer,\n\tUserIdentity,\n} from 'convex/server'\nimport type { Triggers } from 'convex-helpers/server/triggers'\nimport type { TableNamesInDataModel } from 'convex/server'\nimport { defaultErrors, type ErrorFactory } from './errors'\nimport {\n\tcomposeRules,\n\tcreateTenantRules,\n\twrapDatabaseReader,\n\twrapDatabaseWriter,\n\ttype RLSConfig,\n\ttype Rules,\n} from './tenancy'\n\n/** The default vocabulary; every app can supply its own via the Role generic. */\nexport type DefaultRole = 'reader' | 'writer' | 'admin'\n\nexport type Actor<Role extends string> = Readonly<{\n\tuserId: string\n\torganizationId: string\n\trole: Role\n}>\n\nexport type AuthBundle<Role extends string> = Readonly<{\n\tidentity: UserIdentity\n\tuser: Readonly<{ id: string }>\n\torg: Readonly<{ organizationId: string; role: Role }>\n\trole: Role\n\tactor: Actor<Role>\n\t/** The resolved tenant — security.tenancy.resolve, or the organization id. */\n\ttenant: string\n}>\n\n/**\n * Row-level security configuration. Tenant isolation and role-level rules\n * compose (AND) onto the same wrapped database: queries get a wrapped\n * reader, mutations get triggers first, then a wrapped writer.\n */\nexport type SecurityConfig<DataModel extends GenericDataModel, Role extends string> = {\n\t/**\n\t * Tenant isolation. `tables` is THE registry: every listed table gets\n\t * read/insert/modify gated on row.tenant === ctx.tenant. Pair with\n\t * tenantTable (schema) and tenantOwnership (triggers) on the same list.\n\t */\n\ttenancy?: {\n\t\ttables: readonly TableNamesInDataModel<DataModel>[]\n\t\t/** Maps the auth bundle to the tenant; defaults to org.organizationId. */\n\t\tresolve?: (bundle: Omit<AuthBundle<Role>, 'tenant'>) => string\n\t}\n\t/**\n\t * Role-level (or any policy) rules derived from the authenticated bundle,\n\t * composed AND-wise with tenant isolation. E.g. gate `modify` on a table\n\t * to admin roles.\n\t */\n\trules?: (bundle: AuthBundle<Role>) => Rules<unknown, DataModel>\n\t/**\n\t * Policy for tables with no rule. Defaults to 'deny' when tenancy is\n\t * configured (unlisted tables are unreachable — fail closed), 'allow'\n\t * otherwise.\n\t */\n\tdefaultPolicy?: RLSConfig['defaultPolicy']\n}\n\ntype AnyAuthContext<DataModel extends GenericDataModel> =\n\t| GenericQueryCtx<DataModel>\n\t| GenericMutationCtx<DataModel>\n\t| GenericActionCtx<DataModel>\n\nexport type AuthFunctionsConfig<DataModel extends GenericDataModel, Role extends string> = {\n\t/** The consumer's generated function builders (from ./_generated/server). */\n\tquery: QueryBuilder<DataModel, 'public'>\n\tmutation: MutationBuilder<DataModel, 'public'>\n\taction: ActionBuilder<DataModel, 'public'>\n\tinternalQuery: QueryBuilder<DataModel, 'internal'>\n\tinternalMutation: MutationBuilder<DataModel, 'internal'>\n\tinternalAction: ActionBuilder<DataModel, 'internal'>\n\t/** Resolves the synchronized principal, e.g. (ctx) => authKit.getAuthUser(ctx). */\n\tgetAuthUser: (ctx: AnyAuthContext<DataModel>) => Promise<{ id: string } | null>\n\t/**\n\t * Maps an identity role slug onto the app's role vocabulary. Returning\n\t * null rejects the caller with FORBIDDEN — unknown roles never pass.\n\t */\n\tmapRole: (roleSlug?: string) => Role | null\n\t/** Roles allowed through the admin* constructors. */\n\tadminRoles: readonly NoInfer<Role>[]\n\t/**\n\t * Live membership check for actions (queries/mutations trust the JWT).\n\t * Authentication fails closed with FORBIDDEN when verification errors.\n\t * Omit to let actions trust the JWT like queries do.\n\t */\n\tverifyMembership?: (input: {\n\t\tuserId: string\n\t\torganizationId: string\n\t}) => Promise<{ organizationId: string; roleSlug: string } | null>\n\t/**\n\t * Resolves organization authority from the app's own data for identity\n\t * providers whose tokens carry no org claims. When configured, its result\n\t * overrides claim-based resolution; null or an error rejects with FORBIDDEN\n\t * (fail closed). In actions the ctx has no db — resolve via ctx.runQuery.\n\t */\n\tresolveOrganization?: (input: {\n\t\tctx: AnyAuthContext<DataModel>\n\t\tidentity: UserIdentity\n\t\tuser: { id: string }\n\t}) => Promise<{ organizationId: string; roleSlug: string } | null>\n\t/** Host error policy; defaults to throwing KitError. */\n\terrors?: ErrorFactory\n\t/**\n\t * Application trigger registry. When set, every authMutation,\n\t * roleMutation, and systemMutation write runs through triggers.wrapDB —\n\t * evaluations registered on tables fire structurally.\n\t */\n\ttriggers?: Triggers<DataModel>\n\t/** Lower-level alternative to `triggers`: a custom ctx wrapper. */\n\twrapDB?: (ctx: GenericMutationCtx<DataModel>) => GenericMutationCtx<DataModel>\n\t/** Upper bound for include() reads. */\n\tmaxIncludedQueryRows?: number\n\t/**\n\t * Row-level security: tenant isolation (from a table registry) and\n\t * role-level rules, applied structurally inside every auth*, role*, and\n\t * admin* constructor. system* constructors stay unwrapped (trusted).\n\t */\n\tsecurity?: SecurityConfig<DataModel, Role>\n}\n\n/** WorkOS-style default: `member` writes; reader/writer/admin pass through. */\nexport function defaultRoleMap(roleSlug?: string): DefaultRole | null {\n\tconst normalized = roleSlug?.trim().toLowerCase()\n\tif (normalized === 'member') return 'writer'\n\treturn normalized === 'reader' || normalized === 'writer' || normalized === 'admin'\n\t\t? normalized\n\t\t: null\n}\n\nexport type IncludedQuery<Table extends GenericTableInfo> = {\n\twhen<Value>(\n\t\tvalue: Value | null | undefined,\n\t\tselect: (query: QueryInitializer<Table>, value: Value) => Query<Table>,\n\t): IncludedQuery<Table>\n\tmatching<IndexName extends IndexNames<Table>>(\n\t\tindexName: IndexName,\n\t\tindexRange?: (\n\t\t\tquery: IndexRangeBuilder<DocumentByInfo<Table>, NamedIndex<Table, IndexName>>,\n\t\t) => IndexRange,\n\t\tshouldMatch?: boolean,\n\t): IncludedQuery<Table>\n\totherwise(select: (query: QueryInitializer<Table>) => Query<Table>): Query<Table>\n\tresolve(): Query<Table>\n\texecute(limit: number): Promise<DocumentByInfo<Table>[]>\n\texecute<Result>(\n\t\tlimit: number,\n\t\ttransform: (data: Awaited<ReturnType<Query<Table>['take']>>) => Result | Promise<Result>,\n\t): Promise<Result>\n\t/**\n\t * Cursor pagination over the selected indexed query. numItems is bounded\n\t * by the same maxRows policy as execute(). Under row-level security a\n\t * page may be shorter than numItems (rejected rows are filtered after\n\t * pagination); cursors remain correct. Pair with paginated(dto) from\n\t * cvx-kit/zod-table for the boundary schemas.\n\t */\n\tpaginate(opts: PaginationOptions): Promise<PaginationResult<DocumentByInfo<Table>>>\n\tpaginate<Item>(\n\t\topts: PaginationOptions,\n\t\ttransform: (page: DocumentByInfo<Table>[]) => Item[] | Promise<Item[]>,\n\t): Promise<PaginationResult<Item>>\n}\n\nexport type Include = <Table extends GenericTableInfo>(\n\tquery: QueryInitializer<Table>,\n) => IncludedQuery<Table>\n\n/**\n * Keep native contexts symbolic in published declarations. Inferred spreads\n * copy build-time Convex methods and override the consumer's native context.\n */\nexport type AuthFunctions<DataModel extends GenericDataModel, Role extends string = DefaultRole> = {\n\tinclude: Include\n\tauthenticatedUser: (ctx: AnyAuthContext<DataModel>) => Promise<AuthBundle<Role>>\n\tauthQuery: CustomBuilder<\n\t\t'query',\n\t\t{},\n\t\tGenericQueryCtx<DataModel> & AuthBundle<Role> & { include: Include },\n\t\t{},\n\t\tGenericQueryCtx<DataModel>,\n\t\t'public',\n\t\tobject\n\t>\n\tauthMutation: CustomBuilder<\n\t\t'mutation',\n\t\t{},\n\t\tGenericMutationCtx<DataModel> & AuthBundle<Role> & { include: Include },\n\t\t{},\n\t\tGenericMutationCtx<DataModel>,\n\t\t'public',\n\t\tobject\n\t>\n\tauthAction: CustomBuilder<\n\t\t'action',\n\t\t{},\n\t\tGenericActionCtx<DataModel> & AuthBundle<Role>,\n\t\t{},\n\t\tGenericActionCtx<DataModel>,\n\t\t'public',\n\t\tobject\n\t>\n\troleQuery: (...allowed: readonly Role[]) => AuthFunctions<DataModel, Role>['authQuery']\n\troleMutation: (...allowed: readonly Role[]) => AuthFunctions<DataModel, Role>['authMutation']\n\troleAction: (...allowed: readonly Role[]) => AuthFunctions<DataModel, Role>['authAction']\n\tadminQuery: AuthFunctions<DataModel, Role>['authQuery']\n\tadminMutation: AuthFunctions<DataModel, Role>['authMutation']\n\tadminAction: AuthFunctions<DataModel, Role>['authAction']\n\tsystemQuery: CustomBuilder<\n\t\t'query',\n\t\t{},\n\t\tGenericQueryCtx<DataModel> & { include: Include },\n\t\t{},\n\t\tGenericQueryCtx<DataModel>,\n\t\t'internal',\n\t\tobject\n\t>\n\tsystemMutation: CustomBuilder<\n\t\t'mutation',\n\t\t{},\n\t\tGenericMutationCtx<DataModel> & { include: Include },\n\t\t{},\n\t\tGenericMutationCtx<DataModel>,\n\t\t'internal',\n\t\tobject\n\t>\n\tsystemAction: CustomBuilder<\n\t\t'action',\n\t\t{},\n\t\tGenericActionCtx<DataModel>,\n\t\t{},\n\t\tGenericActionCtx<DataModel>,\n\t\t'internal',\n\t\tobject\n\t>\n}\n\n/** Builds an include() that selects the first matching indexed query and bounds the read. */\nexport function createInclude(options?: { errors?: ErrorFactory; maxRows?: number }): Include {\n\tconst errors = options?.errors ?? defaultErrors\n\tconst maxRows = options?.maxRows ?? 100\n\n\tfunction attach<Table extends GenericTableInfo>(\n\t\tquery: QueryInitializer<Table>,\n\t\tselected?: Query<Table>,\n\t): IncludedQuery<Table> {\n\t\tfunction execute(limit: number): Promise<DocumentByInfo<Table>[]>\n\t\tfunction execute<Result>(\n\t\t\tlimit: number,\n\t\t\ttransform: (data: DocumentByInfo<Table>[]) => Result | Promise<Result>,\n\t\t): Promise<Result>\n\t\tasync function execute<Result>(\n\t\t\tlimit: number,\n\t\t\ttransform?: (data: DocumentByInfo<Table>[]) => Result | Promise<Result>,\n\t\t) {\n\t\t\tif (!Number.isSafeInteger(limit) || limit < 1 || limit > maxRows) {\n\t\t\t\treturn errors.throw({\n\t\t\t\t\tcode: 'INVALID_REQUEST_BOUNDARY',\n\t\t\t\t\tmessage: `A bounded query limit must be from 1 to ${maxRows}`,\n\t\t\t\t})\n\t\t\t}\n\t\t\tconst data = await (selected ?? query.fullTableScan()).take(limit)\n\t\t\treturn transform ? transform(data) : data\n\t\t}\n\t\tfunction paginate(opts: PaginationOptions): Promise<PaginationResult<DocumentByInfo<Table>>>\n\t\tfunction paginate<Item>(\n\t\t\topts: PaginationOptions,\n\t\t\ttransform: (page: DocumentByInfo<Table>[]) => Item[] | Promise<Item[]>,\n\t\t): Promise<PaginationResult<Item>>\n\t\tasync function paginate<Item>(\n\t\t\topts: PaginationOptions,\n\t\t\ttransform?: (page: DocumentByInfo<Table>[]) => Item[] | Promise<Item[]>,\n\t\t) {\n\t\t\tif (!Number.isSafeInteger(opts.numItems) || opts.numItems < 1 || opts.numItems > maxRows) {\n\t\t\t\treturn errors.throw({\n\t\t\t\t\tcode: 'INVALID_REQUEST_BOUNDARY',\n\t\t\t\t\tmessage: `A page size must be from 1 to ${maxRows}`,\n\t\t\t\t})\n\t\t\t}\n\t\t\tconst result = await (selected ?? query.fullTableScan()).paginate(opts)\n\t\t\treturn transform ? { ...result, page: await transform(result.page) } : result\n\t\t}\n\t\treturn {\n\t\t\twhen: (value, select) =>\n\t\t\t\tselected || value === undefined || value === null\n\t\t\t\t\t? attach(query, selected)\n\t\t\t\t\t: attach(query, select(query, value)),\n\t\t\tmatching: (indexName, indexRange, shouldMatch = true) =>\n\t\t\t\tselected || !shouldMatch\n\t\t\t\t\t? attach(query, selected)\n\t\t\t\t\t: attach(query, query.withIndex(indexName, indexRange)),\n\t\t\totherwise: (select) => selected ?? select(query),\n\t\t\tresolve: () => selected ?? query.fullTableScan(),\n\t\t\texecute,\n\t\t\tpaginate,\n\t\t}\n\t}\n\n\treturn (query) => attach(query)\n}\n\n/**\n * Builds the auth-aware function constructors from the host's injected\n * policy: identity resolution, the app's own role vocabulary, live\n * membership verification for actions, trigger wrapping for mutations,\n * and errors. Role-based access is first-class — every constructor family\n * has a role-gated variant (roleQuery/roleMutation/roleAction), and the\n * admin* constructors are just that gate applied to config.adminRoles.\n */\nexport function createAuthFunctions<\n\tDataModel extends GenericDataModel,\n\tconst Role extends string = DefaultRole,\n>(config: AuthFunctionsConfig<DataModel, Role>): AuthFunctions<DataModel, Role> {\n\tconst errors = config.errors ?? defaultErrors\n\tconst triggers = config.triggers\n\tconst wrapDB = triggers\n\t\t? (ctx: GenericMutationCtx<DataModel>) => triggers.wrapDB(ctx)\n\t\t: (config.wrapDB ?? ((ctx) => ctx))\n\tconst include = createInclude({\n\t\terrors,\n\t\tmaxRows: config.maxIncludedQueryRows,\n\t})\n\n\tasync function authenticatedUser(ctx: AnyAuthContext<DataModel>): Promise<AuthBundle<Role>> {\n\t\tconst identity = await ctx.auth.getUserIdentity()\n\t\tconst user = identity ? await config.getAuthUser(ctx) : null\n\t\tif (!identity || !user) return errors.throw({ code: 'UNAUTHENTICATED' })\n\t\tlet organizationId: string\n\t\tlet organizationRole: string | undefined\n\t\tif (config.resolveOrganization) {\n\t\t\t// The hook is the organization authority — claims are skipped\n\t\t\t// entirely. Authentication fails closed when it errors or abstains.\n\t\t\tlet resolved: { organizationId: string; roleSlug: string } | null = null\n\t\t\ttry {\n\t\t\t\tresolved = await config.resolveOrganization({ ctx, identity, user })\n\t\t\t} catch {\n\t\t\t\t// Authentication fails closed when the hook cannot resolve.\n\t\t\t}\n\t\t\tif (!resolved) return errors.throw({ code: 'FORBIDDEN' })\n\t\t\torganizationId = resolved.organizationId\n\t\t\torganizationRole = resolved.roleSlug\n\t\t} else {\n\t\t\tconst claims = organizationClaims.parse(identity)\n\t\t\tconst claimedOrganizationId = claims.organization?.organizationId ?? claims.org_id\n\t\t\tif (!claimedOrganizationId) return errors.throw({ code: 'UNAUTHENTICATED' })\n\t\t\torganizationId = claimedOrganizationId\n\t\t\torganizationRole = claims.organization?.role ?? claims.role\n\t\t}\n\t\tconst role = config.mapRole(organizationRole)\n\t\tif (!role) return errors.throw({ code: 'FORBIDDEN' })\n\t\tconst base = {\n\t\t\tidentity,\n\t\t\tuser: Object.freeze({ id: user.id }),\n\t\t\torg: Object.freeze({ organizationId, role }),\n\t\t\trole,\n\t\t\tactor: Object.freeze({ userId: user.id, organizationId, role }),\n\t\t}\n\t\treturn Object.freeze({\n\t\t\t...base,\n\t\t\ttenant: config.security?.tenancy?.resolve?.(base) ?? organizationId,\n\t\t})\n\t}\n\n\tconst securityConfig: RLSConfig | undefined = config.security\n\t\t? {\n\t\t\t\tdefaultPolicy:\n\t\t\t\t\tconfig.security.defaultPolicy ?? (config.security.tenancy ? 'deny' : 'allow'),\n\t\t\t}\n\t\t: undefined\n\n\tfunction securityRules(bundle: AuthBundle<Role>): Rules<unknown, DataModel> | undefined {\n\t\tconst security = config.security\n\t\tif (!security) return undefined\n\t\tconst sets: Rules<unknown, DataModel>[] = []\n\t\tif (security.tenancy) {\n\t\t\tsets.push(createTenantRules<DataModel>(bundle.tenant, security.tenancy.tables))\n\t\t}\n\t\tif (security.rules) sets.push(security.rules(bundle))\n\t\treturn sets.length === 1 ? sets[0] : composeRules(...sets)\n\t}\n\n\tfunction secureReader(\n\t\tctx: GenericQueryCtx<DataModel>,\n\t\tbundle: AuthBundle<Role>,\n\t): GenericQueryCtx<DataModel> {\n\t\tconst rules = securityRules(bundle)\n\t\tif (!rules) return ctx\n\t\treturn {\n\t\t\t...ctx,\n\t\t\tdb: wrapDatabaseReader({}, ctx.db, rules, securityConfig),\n\t\t}\n\t}\n\n\tfunction secureWriter(\n\t\tctx: GenericMutationCtx<DataModel>,\n\t\tbundle: AuthBundle<Role>,\n\t): GenericMutationCtx<DataModel> {\n\t\tconst rules = securityRules(bundle)\n\t\tif (!rules) return ctx\n\t\t// Triggers wrap first (ctx already trigger-wrapped by the caller),\n\t\t// then row-level security wraps the triggered db — order matters.\n\t\treturn {\n\t\t\t...ctx,\n\t\t\tdb: wrapDatabaseWriter({}, ctx.db, rules, securityConfig),\n\t\t}\n\t}\n\n\tasync function verifiedMembership(userId: string, organizationId: string) {\n\t\tif (!config.verifyMembership) return null\n\t\ttry {\n\t\t\tconst membership = await config.verifyMembership({\n\t\t\t\tuserId,\n\t\t\t\torganizationId,\n\t\t\t})\n\t\t\tif (membership && membership.organizationId === organizationId) {\n\t\t\t\treturn membership\n\t\t\t}\n\t\t} catch {\n\t\t\t// Authentication fails closed when membership cannot be verified.\n\t\t}\n\t\treturn errors.throw({ code: 'FORBIDDEN' })\n\t}\n\n\tfunction requireRole(role: Role, allowed: readonly Role[]) {\n\t\tif (!allowed.includes(role)) return errors.throw({ code: 'FORBIDDEN' })\n\t}\n\n\tasync function queryInput(ctx: GenericQueryCtx<DataModel>) {\n\t\tconst bundle = await authenticatedUser(ctx)\n\t\treturn {\n\t\t\tctx: { ...secureReader(ctx, bundle), include, ...bundle },\n\t\t\targs: {},\n\t\t}\n\t}\n\n\tasync function mutationInput(ctx: GenericMutationCtx<DataModel>) {\n\t\tconst bundle = await authenticatedUser(ctx)\n\t\treturn {\n\t\t\tctx: { ...secureWriter(wrapDB(ctx), bundle), include, ...bundle },\n\t\t\targs: {},\n\t\t}\n\t}\n\n\tasync function actionInput(ctx: GenericActionCtx<DataModel>) {\n\t\tconst authenticated = await authenticatedUser(ctx)\n\t\tconst membership = await verifiedMembership(\n\t\t\tauthenticated.user.id,\n\t\t\tauthenticated.org.organizationId,\n\t\t)\n\t\tif (!membership) return { ctx: { ...ctx, ...authenticated }, args: {} }\n\t\tconst role = config.mapRole(membership.roleSlug)\n\t\tif (!role) return errors.throw({ code: 'FORBIDDEN' })\n\t\tconst verified = {\n\t\t\tidentity: authenticated.identity,\n\t\t\tuser: authenticated.user,\n\t\t\torg: Object.freeze({\n\t\t\t\torganizationId: membership.organizationId,\n\t\t\t\trole,\n\t\t\t}),\n\t\t\trole,\n\t\t\tactor: Object.freeze({\n\t\t\t\tuserId: authenticated.user.id,\n\t\t\t\torganizationId: membership.organizationId,\n\t\t\t\trole,\n\t\t\t}),\n\t\t}\n\t\treturn {\n\t\t\tctx: {\n\t\t\t\t...ctx,\n\t\t\t\t...verified,\n\t\t\t\t// Tenant re-derives from the live-verified organization.\n\t\t\t\ttenant: config.security?.tenancy?.resolve?.(verified) ?? membership.organizationId,\n\t\t\t},\n\t\t\targs: {},\n\t\t}\n\t}\n\n\t/** Query constructor allowing only the given roles. */\n\tfunction roleQuery(...allowed: readonly Role[]) {\n\t\treturn zCustomQuery(config.query, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => {\n\t\t\t\tconst authenticated = await queryInput(ctx)\n\t\t\t\trequireRole(authenticated.ctx.actor.role, allowed)\n\t\t\t\treturn authenticated\n\t\t\t},\n\t\t})\n\t}\n\n\t/** Mutation constructor allowing only the given roles. */\n\tfunction roleMutation(...allowed: readonly Role[]) {\n\t\treturn zCustomMutation(config.mutation, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => {\n\t\t\t\tconst authenticated = await mutationInput(ctx)\n\t\t\t\trequireRole(authenticated.ctx.actor.role, allowed)\n\t\t\t\treturn authenticated\n\t\t\t},\n\t\t})\n\t}\n\n\t/** Action constructor allowing only the given roles (post live verification). */\n\tfunction roleAction(...allowed: readonly Role[]) {\n\t\treturn zCustomAction(config.action, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => {\n\t\t\t\tconst authenticated = await actionInput(ctx)\n\t\t\t\trequireRole(authenticated.ctx.actor.role, allowed)\n\t\t\t\treturn authenticated\n\t\t\t},\n\t\t})\n\t}\n\n\treturn {\n\t\tinclude,\n\t\tauthenticatedUser,\n\t\troleQuery,\n\t\troleMutation,\n\t\troleAction,\n\t\tauthQuery: zCustomQuery(config.query, { args: {}, input: queryInput }),\n\t\tauthMutation: zCustomMutation(config.mutation, {\n\t\t\targs: {},\n\t\t\tinput: mutationInput,\n\t\t}),\n\t\tauthAction: zCustomAction(config.action, {\n\t\t\targs: {},\n\t\t\tinput: actionInput,\n\t\t}),\n\t\tadminQuery: roleQuery(...config.adminRoles),\n\t\tadminMutation: roleMutation(...config.adminRoles),\n\t\tadminAction: roleAction(...config.adminRoles),\n\t\tsystemQuery: zCustomQuery(config.internalQuery, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => ({ ctx: { ...ctx, include }, args: {} }),\n\t\t}),\n\t\tsystemMutation: zCustomMutation(config.internalMutation, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => ({ ctx: { ...wrapDB(ctx), include }, args: {} }),\n\t\t}),\n\t\tsystemAction: zCustomAction(config.internalAction, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => ({ ctx, args: {} }),\n\t\t}),\n\t}\n}\n\nconst organizationClaims = z.object({\n\torganization: z\n\t\t.object({\n\t\t\torganizationId: z.string().optional().catch(undefined),\n\t\t\trole: z.string().optional().catch(undefined),\n\t\t})\n\t\t.optional()\n\t\t.catch(undefined),\n\torg_id: z.string().optional().catch(undefined),\n\trole: z.string().optional().catch(undefined),\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"auth.mjs","names":["defaultRoleMap","normalized","roleSlug","createInclude","errors","options","defaultErrors","maxRows","attach","execute","limit","data","selected","query","transform","paginate","opts","result","value","select","shouldMatch","indexName","indexRange","createAuthFunctions","config","triggers","wrapDB","ctx","include","authenticatedUser","identity","user","organizationId","organizationRole","resolved","claims","organizationClaims","claimedOrganizationId","role","base","securityConfig","securityRules","security","sets","createTenantRules","bundle","composeRules","secureReader","rules","defaultPolicy","ordinary","wrapDatabaseReader","suppliedPolicyRules","registerQueryReader","secureWriter","wrapDatabaseWriter","verifiedMembership","membership","requireRole","allowed","queryInput","mutationInput","actionInput","authenticated","verified","roleQuery","zCustomQuery","roleMutation","zCustomMutation","roleAction","zCustomAction","z"],"sources":["../src/auth.ts"],"sourcesContent":["import { z } from 'zod'\nimport { zCustomAction, zCustomMutation, zCustomQuery } from './zod-registration'\nimport type { CustomBuilder } from 'convex-helpers/server/zod4'\nimport type {\n\tActionBuilder,\n\tDocumentByInfo,\n\tPaginationOptions,\n\tPaginationResult,\n\tGenericActionCtx,\n\tGenericDataModel,\n\tGenericMutationCtx,\n\tGenericQueryCtx,\n\tGenericTableInfo,\n\tIndexNames,\n\tIndexRange,\n\tIndexRangeBuilder,\n\tMutationBuilder,\n\tNamedIndex,\n\tQuery,\n\tQueryBuilder,\n\tQueryInitializer,\n\tUserIdentity,\n} from 'convex/server'\nimport type { Triggers } from 'convex-helpers/server/triggers'\nimport type { TableNamesInDataModel } from 'convex/server'\nimport { defaultErrors, type ErrorFactory } from './errors'\nimport {\n\tregisterQueryReader,\n\tsuppliedPolicyRules,\n\ttype SecurityPolicyContext,\n} from './querying/security'\nexport type { SecurityPolicyContext } from './querying/security'\nimport {\n\tcomposeRules,\n\tcreateTenantRules,\n\twrapDatabaseReader,\n\twrapDatabaseWriter,\n\ttype RLSConfig,\n\ttype Rules,\n} from './tenancy'\n\n/** The default vocabulary; every app can supply its own via the Role generic. */\nexport type DefaultRole = 'reader' | 'writer' | 'admin'\n\nexport type Actor<Role extends string> = Readonly<{\n\tuserId: string\n\torganizationId: string\n\trole: Role\n}>\n\nexport type AuthBundle<Role extends string> = Readonly<{\n\tidentity: UserIdentity\n\tuser: Readonly<{ id: string }>\n\torg: Readonly<{ organizationId: string; role: Role }>\n\trole: Role\n\tactor: Actor<Role>\n\t/** The resolved tenant — security.tenancy.resolve, or the organization id. */\n\ttenant: string\n}>\n\n/**\n * Row-level security configuration. Tenant isolation and role-level rules\n * compose (AND) onto the same wrapped database: queries get a wrapped\n * reader, mutations get triggers first, then a wrapped writer.\n */\nexport type SecurityConfig<DataModel extends GenericDataModel, Role extends string> = {\n\t/**\n\t * Tenant isolation. `tables` is THE registry: every listed table gets\n\t * read/insert/modify gated on row.tenant === ctx.tenant. Pair with\n\t * tenantTable (schema) and tenantOwnership (triggers) on the same list.\n\t */\n\ttenancy?: {\n\t\ttables: readonly TableNamesInDataModel<DataModel>[]\n\t\t/** Maps the auth bundle to the tenant; defaults to org.organizationId. */\n\t\tresolve?: (bundle: Omit<AuthBundle<Role>, 'tenant'>) => string\n\t}\n\t/**\n\t * Role-level (or any policy) rules derived from the authenticated bundle,\n\t * composed AND-wise with tenant isolation. E.g. gate `modify` on a table\n\t * to admin roles.\n\t */\n\trules?: (bundle: AuthBundle<Role>) => Rules<SecurityPolicyContext<DataModel>, DataModel>\n\t/**\n\t * Policy for tables with no rule. Defaults to 'deny' when tenancy is\n\t * configured (unlisted tables are unreachable — fail closed), 'allow'\n\t * otherwise.\n\t */\n\tdefaultPolicy?: RLSConfig['defaultPolicy']\n}\n\ntype AnyAuthContext<DataModel extends GenericDataModel> =\n\t| GenericQueryCtx<DataModel>\n\t| GenericMutationCtx<DataModel>\n\t| GenericActionCtx<DataModel>\n\nexport type AuthFunctionsConfig<DataModel extends GenericDataModel, Role extends string> = {\n\t/** The consumer's generated function builders (from ./_generated/server). */\n\tquery: QueryBuilder<DataModel, 'public'>\n\tmutation: MutationBuilder<DataModel, 'public'>\n\taction: ActionBuilder<DataModel, 'public'>\n\tinternalQuery: QueryBuilder<DataModel, 'internal'>\n\tinternalMutation: MutationBuilder<DataModel, 'internal'>\n\tinternalAction: ActionBuilder<DataModel, 'internal'>\n\t/** Resolves the synchronized principal, e.g. (ctx) => authKit.getAuthUser(ctx). */\n\tgetAuthUser: (ctx: AnyAuthContext<DataModel>) => Promise<{ id: string } | null>\n\t/**\n\t * Maps an identity role slug onto the app's role vocabulary. Returning\n\t * null rejects the caller with FORBIDDEN — unknown roles never pass.\n\t */\n\tmapRole: (roleSlug?: string) => Role | null\n\t/** Roles allowed through the admin* constructors. */\n\tadminRoles: readonly NoInfer<Role>[]\n\t/**\n\t * Live membership check for actions (queries/mutations trust the JWT).\n\t * Authentication fails closed with FORBIDDEN when verification errors.\n\t * Omit to let actions trust the JWT like queries do.\n\t */\n\tverifyMembership?: (input: {\n\t\tuserId: string\n\t\torganizationId: string\n\t}) => Promise<{ organizationId: string; roleSlug: string } | null>\n\t/**\n\t * Resolves organization authority from the app's own data for identity\n\t * providers whose tokens carry no org claims. When configured, its result\n\t * overrides claim-based resolution; null or an error rejects with FORBIDDEN\n\t * (fail closed). In actions the ctx has no db — resolve via ctx.runQuery.\n\t */\n\tresolveOrganization?: (input: {\n\t\tctx: AnyAuthContext<DataModel>\n\t\tidentity: UserIdentity\n\t\tuser: { id: string }\n\t}) => Promise<{ organizationId: string; roleSlug: string } | null>\n\t/** Host error policy; defaults to throwing KitError. */\n\terrors?: ErrorFactory\n\t/**\n\t * Application trigger registry. When set, every authMutation,\n\t * roleMutation, and systemMutation write runs through triggers.wrapDB —\n\t * evaluations registered on tables fire structurally.\n\t */\n\ttriggers?: Triggers<DataModel>\n\t/** Lower-level alternative to `triggers`: a custom ctx wrapper. */\n\twrapDB?: (ctx: GenericMutationCtx<DataModel>) => GenericMutationCtx<DataModel>\n\t/** Upper bound for include() reads. */\n\tmaxIncludedQueryRows?: number\n\t/**\n\t * Row-level security: tenant isolation (from a table registry) and\n\t * role-level rules, applied structurally inside every auth*, role*, and\n\t * admin* constructor. system* constructors stay unwrapped (trusted).\n\t */\n\tsecurity?: SecurityConfig<DataModel, Role>\n}\n\n/** WorkOS-style default: `member` writes; reader/writer/admin pass through. */\nexport function defaultRoleMap(roleSlug?: string): DefaultRole | null {\n\tconst normalized = roleSlug?.trim().toLowerCase()\n\tif (normalized === 'member') return 'writer'\n\treturn normalized === 'reader' || normalized === 'writer' || normalized === 'admin'\n\t\t? normalized\n\t\t: null\n}\n\nexport type IncludedQuery<Table extends GenericTableInfo> = {\n\twhen<Value>(\n\t\tvalue: Value | null | undefined,\n\t\tselect: (query: QueryInitializer<Table>, value: Value) => Query<Table>,\n\t): IncludedQuery<Table>\n\tmatching<IndexName extends IndexNames<Table>>(\n\t\tindexName: IndexName,\n\t\tindexRange?: (\n\t\t\tquery: IndexRangeBuilder<DocumentByInfo<Table>, NamedIndex<Table, IndexName>>,\n\t\t) => IndexRange,\n\t\tshouldMatch?: boolean,\n\t): IncludedQuery<Table>\n\totherwise(select: (query: QueryInitializer<Table>) => Query<Table>): Query<Table>\n\tresolve(): Query<Table>\n\texecute(limit: number): Promise<DocumentByInfo<Table>[]>\n\texecute<Result>(\n\t\tlimit: number,\n\t\ttransform: (data: Awaited<ReturnType<Query<Table>['take']>>) => Result | Promise<Result>,\n\t): Promise<Result>\n\t/**\n\t * Cursor pagination over the selected indexed query. numItems is bounded\n\t * by the same maxRows policy as execute(). Under row-level security a\n\t * page may be shorter than numItems (rejected rows are filtered after\n\t * pagination); cursors remain correct. Pair with paginated(dto) from\n\t * cvx-kit/zod-table for the boundary schemas.\n\t */\n\tpaginate(opts: PaginationOptions): Promise<PaginationResult<DocumentByInfo<Table>>>\n\tpaginate<Item>(\n\t\topts: PaginationOptions,\n\t\ttransform: (page: DocumentByInfo<Table>[]) => Item[] | Promise<Item[]>,\n\t): Promise<PaginationResult<Item>>\n}\n\nexport type Include = <Table extends GenericTableInfo>(\n\tquery: QueryInitializer<Table>,\n) => IncludedQuery<Table>\n\n/**\n * Keep native contexts symbolic in published declarations. Inferred spreads\n * copy build-time Convex methods and override the consumer's native context.\n */\nexport type AuthFunctions<DataModel extends GenericDataModel, Role extends string = DefaultRole> = {\n\tinclude: Include\n\tauthenticatedUser: (ctx: AnyAuthContext<DataModel>) => Promise<AuthBundle<Role>>\n\tauthQuery: CustomBuilder<\n\t\t'query',\n\t\t{},\n\t\tGenericQueryCtx<DataModel> & AuthBundle<Role> & { include: Include },\n\t\t{},\n\t\tGenericQueryCtx<DataModel>,\n\t\t'public',\n\t\tobject\n\t>\n\tauthMutation: CustomBuilder<\n\t\t'mutation',\n\t\t{},\n\t\tGenericMutationCtx<DataModel> & AuthBundle<Role> & { include: Include },\n\t\t{},\n\t\tGenericMutationCtx<DataModel>,\n\t\t'public',\n\t\tobject\n\t>\n\tauthAction: CustomBuilder<\n\t\t'action',\n\t\t{},\n\t\tGenericActionCtx<DataModel> & AuthBundle<Role>,\n\t\t{},\n\t\tGenericActionCtx<DataModel>,\n\t\t'public',\n\t\tobject\n\t>\n\troleQuery: (...allowed: readonly Role[]) => AuthFunctions<DataModel, Role>['authQuery']\n\troleMutation: (...allowed: readonly Role[]) => AuthFunctions<DataModel, Role>['authMutation']\n\troleAction: (...allowed: readonly Role[]) => AuthFunctions<DataModel, Role>['authAction']\n\tadminQuery: AuthFunctions<DataModel, Role>['authQuery']\n\tadminMutation: AuthFunctions<DataModel, Role>['authMutation']\n\tadminAction: AuthFunctions<DataModel, Role>['authAction']\n\tsystemQuery: CustomBuilder<\n\t\t'query',\n\t\t{},\n\t\tGenericQueryCtx<DataModel> & { include: Include },\n\t\t{},\n\t\tGenericQueryCtx<DataModel>,\n\t\t'internal',\n\t\tobject\n\t>\n\tsystemMutation: CustomBuilder<\n\t\t'mutation',\n\t\t{},\n\t\tGenericMutationCtx<DataModel> & { include: Include },\n\t\t{},\n\t\tGenericMutationCtx<DataModel>,\n\t\t'internal',\n\t\tobject\n\t>\n\tsystemAction: CustomBuilder<\n\t\t'action',\n\t\t{},\n\t\tGenericActionCtx<DataModel>,\n\t\t{},\n\t\tGenericActionCtx<DataModel>,\n\t\t'internal',\n\t\tobject\n\t>\n}\n\n/** Builds an include() that selects the first matching indexed query and bounds the read. */\nexport function createInclude(options?: { errors?: ErrorFactory; maxRows?: number }): Include {\n\tconst errors = options?.errors ?? defaultErrors\n\tconst maxRows = options?.maxRows ?? 100\n\n\tfunction attach<Table extends GenericTableInfo>(\n\t\tquery: QueryInitializer<Table>,\n\t\tselected?: Query<Table>,\n\t): IncludedQuery<Table> {\n\t\tfunction execute(limit: number): Promise<DocumentByInfo<Table>[]>\n\t\tfunction execute<Result>(\n\t\t\tlimit: number,\n\t\t\ttransform: (data: DocumentByInfo<Table>[]) => Result | Promise<Result>,\n\t\t): Promise<Result>\n\t\tasync function execute<Result>(\n\t\t\tlimit: number,\n\t\t\ttransform?: (data: DocumentByInfo<Table>[]) => Result | Promise<Result>,\n\t\t) {\n\t\t\tif (!Number.isSafeInteger(limit) || limit < 1 || limit > maxRows) {\n\t\t\t\treturn errors.throw({\n\t\t\t\t\tcode: 'INVALID_REQUEST_BOUNDARY',\n\t\t\t\t\tmessage: `A bounded query limit must be from 1 to ${maxRows}`,\n\t\t\t\t})\n\t\t\t}\n\t\t\tconst data = await (selected ?? query.fullTableScan()).take(limit)\n\t\t\treturn transform ? transform(data) : data\n\t\t}\n\t\tfunction paginate(opts: PaginationOptions): Promise<PaginationResult<DocumentByInfo<Table>>>\n\t\tfunction paginate<Item>(\n\t\t\topts: PaginationOptions,\n\t\t\ttransform: (page: DocumentByInfo<Table>[]) => Item[] | Promise<Item[]>,\n\t\t): Promise<PaginationResult<Item>>\n\t\tasync function paginate<Item>(\n\t\t\topts: PaginationOptions,\n\t\t\ttransform?: (page: DocumentByInfo<Table>[]) => Item[] | Promise<Item[]>,\n\t\t) {\n\t\t\tif (!Number.isSafeInteger(opts.numItems) || opts.numItems < 1 || opts.numItems > maxRows) {\n\t\t\t\treturn errors.throw({\n\t\t\t\t\tcode: 'INVALID_REQUEST_BOUNDARY',\n\t\t\t\t\tmessage: `A page size must be from 1 to ${maxRows}`,\n\t\t\t\t})\n\t\t\t}\n\t\t\tconst result = await (selected ?? query.fullTableScan()).paginate(opts)\n\t\t\treturn transform ? { ...result, page: await transform(result.page) } : result\n\t\t}\n\t\treturn {\n\t\t\twhen: (value, select) =>\n\t\t\t\tselected || value === undefined || value === null\n\t\t\t\t\t? attach(query, selected)\n\t\t\t\t\t: attach(query, select(query, value)),\n\t\t\tmatching: (indexName, indexRange, shouldMatch = true) =>\n\t\t\t\tselected || !shouldMatch\n\t\t\t\t\t? attach(query, selected)\n\t\t\t\t\t: attach(query, query.withIndex(indexName, indexRange)),\n\t\t\totherwise: (select) => selected ?? select(query),\n\t\t\tresolve: () => selected ?? query.fullTableScan(),\n\t\t\texecute,\n\t\t\tpaginate,\n\t\t}\n\t}\n\n\treturn (query) => attach(query)\n}\n\n/**\n * Builds the auth-aware function constructors from the host's injected\n * policy: identity resolution, the app's own role vocabulary, live\n * membership verification for actions, trigger wrapping for mutations,\n * and errors. Role-based access is first-class — every constructor family\n * has a role-gated variant (roleQuery/roleMutation/roleAction), and the\n * admin* constructors are just that gate applied to config.adminRoles.\n */\nexport function createAuthFunctions<\n\tDataModel extends GenericDataModel,\n\tconst Role extends string = DefaultRole,\n>(config: AuthFunctionsConfig<DataModel, Role>): AuthFunctions<DataModel, Role> {\n\tconst errors = config.errors ?? defaultErrors\n\tconst triggers = config.triggers\n\tconst wrapDB = triggers\n\t\t? (ctx: GenericMutationCtx<DataModel>) => triggers.wrapDB(ctx)\n\t\t: (config.wrapDB ?? ((ctx) => ctx))\n\tconst include = createInclude({\n\t\terrors,\n\t\tmaxRows: config.maxIncludedQueryRows,\n\t})\n\n\tasync function authenticatedUser(ctx: AnyAuthContext<DataModel>): Promise<AuthBundle<Role>> {\n\t\tconst identity = await ctx.auth.getUserIdentity()\n\t\tconst user = identity ? await config.getAuthUser(ctx) : null\n\t\tif (!identity || !user) return errors.throw({ code: 'UNAUTHENTICATED' })\n\t\tlet organizationId: string\n\t\tlet organizationRole: string | undefined\n\t\tif (config.resolveOrganization) {\n\t\t\t// The hook is the organization authority — claims are skipped\n\t\t\t// entirely. Authentication fails closed when it errors or abstains.\n\t\t\tlet resolved: { organizationId: string; roleSlug: string } | null = null\n\t\t\ttry {\n\t\t\t\tresolved = await config.resolveOrganization({ ctx, identity, user })\n\t\t\t} catch {\n\t\t\t\t// Authentication fails closed when the hook cannot resolve.\n\t\t\t}\n\t\t\tif (!resolved) return errors.throw({ code: 'FORBIDDEN' })\n\t\t\torganizationId = resolved.organizationId\n\t\t\torganizationRole = resolved.roleSlug\n\t\t} else {\n\t\t\tconst claims = organizationClaims.parse(identity)\n\t\t\tconst claimedOrganizationId = claims.organization?.organizationId ?? claims.org_id\n\t\t\tif (!claimedOrganizationId) return errors.throw({ code: 'UNAUTHENTICATED' })\n\t\t\torganizationId = claimedOrganizationId\n\t\t\torganizationRole = claims.organization?.role ?? claims.role\n\t\t}\n\t\tconst role = config.mapRole(organizationRole)\n\t\tif (!role) return errors.throw({ code: 'FORBIDDEN' })\n\t\tconst base = {\n\t\t\tidentity,\n\t\t\tuser: Object.freeze({ id: user.id }),\n\t\t\torg: Object.freeze({ organizationId, role }),\n\t\t\trole,\n\t\t\tactor: Object.freeze({ userId: user.id, organizationId, role }),\n\t\t}\n\t\treturn Object.freeze({\n\t\t\t...base,\n\t\t\ttenant: config.security?.tenancy?.resolve?.(base) ?? organizationId,\n\t\t})\n\t}\n\n\tconst securityConfig: RLSConfig | undefined = config.security\n\t\t? {\n\t\t\t\tdefaultPolicy:\n\t\t\t\t\tconfig.security.defaultPolicy ?? (config.security.tenancy ? 'deny' : 'allow'),\n\t\t\t}\n\t\t: undefined\n\n\tfunction securityRules(\n\t\tbundle: AuthBundle<Role>,\n\t): Rules<SecurityPolicyContext<DataModel>, DataModel> | undefined {\n\t\tconst security = config.security\n\t\tif (!security) return undefined\n\t\tconst sets: Rules<SecurityPolicyContext<DataModel>, DataModel>[] = []\n\t\tif (security.tenancy) {\n\t\t\tsets.push(createTenantRules<DataModel>(bundle.tenant, security.tenancy.tables))\n\t\t}\n\t\tif (security.rules) sets.push(security.rules(bundle))\n\t\treturn sets.length === 1 ? sets[0] : composeRules(...sets)\n\t}\n\n\tfunction secureReader(\n\t\tctx: GenericQueryCtx<DataModel>,\n\t\tbundle: AuthBundle<Role>,\n\t): GenericQueryCtx<DataModel> {\n\t\tconst rules = securityRules(bundle)\n\t\tconst defaultPolicy = securityConfig?.defaultPolicy ?? 'allow'\n\t\tconst ordinary = rules\n\t\t\t? wrapDatabaseReader(\n\t\t\t\t\t{ db: ctx.db },\n\t\t\t\t\tctx.db,\n\t\t\t\t\tsuppliedPolicyRules(ctx.db, rules, defaultPolicy),\n\t\t\t\t\tsecurityConfig,\n\t\t\t\t)\n\t\t\t: ctx.db\n\t\treturn {\n\t\t\t...ctx,\n\t\t\tdb: registerQueryReader(ctx.db, ordinary, {\n\t\t\t\tscope: {\n\t\t\t\t\tmode: 'authenticated',\n\t\t\t\t\tprincipal: bundle.user.id,\n\t\t\t\t\torganization: bundle.org.organizationId,\n\t\t\t\t\ttenant: bundle.tenant,\n\t\t\t\t\trole: bundle.role,\n\t\t\t\t},\n\t\t\t\trules: rules ?? {},\n\t\t\t\tdefaultPolicy,\n\t\t\t}),\n\t\t}\n\t}\n\n\tfunction secureWriter(\n\t\tctx: GenericMutationCtx<DataModel>,\n\t\tbundle: AuthBundle<Role>,\n\t): GenericMutationCtx<DataModel> {\n\t\tconst rules = securityRules(bundle)\n\t\tif (!rules) return ctx\n\t\t// Triggers wrap first (ctx already trigger-wrapped by the caller),\n\t\t// then row-level security wraps the triggered db — order matters.\n\t\treturn {\n\t\t\t...ctx,\n\t\t\tdb: wrapDatabaseWriter(\n\t\t\t\t{ db: ctx.db },\n\t\t\t\tctx.db,\n\t\t\t\tsuppliedPolicyRules(ctx.db, rules, securityConfig?.defaultPolicy ?? 'allow'),\n\t\t\t\tsecurityConfig,\n\t\t\t),\n\t\t}\n\t}\n\n\tasync function verifiedMembership(userId: string, organizationId: string) {\n\t\tif (!config.verifyMembership) return null\n\t\ttry {\n\t\t\tconst membership = await config.verifyMembership({\n\t\t\t\tuserId,\n\t\t\t\torganizationId,\n\t\t\t})\n\t\t\tif (membership && membership.organizationId === organizationId) {\n\t\t\t\treturn membership\n\t\t\t}\n\t\t} catch {\n\t\t\t// Authentication fails closed when membership cannot be verified.\n\t\t}\n\t\treturn errors.throw({ code: 'FORBIDDEN' })\n\t}\n\n\tfunction requireRole(role: Role, allowed: readonly Role[]) {\n\t\tif (!allowed.includes(role)) return errors.throw({ code: 'FORBIDDEN' })\n\t}\n\n\tasync function queryInput(ctx: GenericQueryCtx<DataModel>) {\n\t\tconst bundle = await authenticatedUser(ctx)\n\t\treturn {\n\t\t\tctx: { ...secureReader(ctx, bundle), include, ...bundle },\n\t\t\targs: {},\n\t\t}\n\t}\n\n\tasync function mutationInput(ctx: GenericMutationCtx<DataModel>) {\n\t\tconst bundle = await authenticatedUser(ctx)\n\t\treturn {\n\t\t\tctx: { ...secureWriter(wrapDB(ctx), bundle), include, ...bundle },\n\t\t\targs: {},\n\t\t}\n\t}\n\n\tasync function actionInput(ctx: GenericActionCtx<DataModel>) {\n\t\tconst authenticated = await authenticatedUser(ctx)\n\t\tconst membership = await verifiedMembership(\n\t\t\tauthenticated.user.id,\n\t\t\tauthenticated.org.organizationId,\n\t\t)\n\t\tif (!membership) return { ctx: { ...ctx, ...authenticated }, args: {} }\n\t\tconst role = config.mapRole(membership.roleSlug)\n\t\tif (!role) return errors.throw({ code: 'FORBIDDEN' })\n\t\tconst verified = {\n\t\t\tidentity: authenticated.identity,\n\t\t\tuser: authenticated.user,\n\t\t\torg: Object.freeze({\n\t\t\t\torganizationId: membership.organizationId,\n\t\t\t\trole,\n\t\t\t}),\n\t\t\trole,\n\t\t\tactor: Object.freeze({\n\t\t\t\tuserId: authenticated.user.id,\n\t\t\t\torganizationId: membership.organizationId,\n\t\t\t\trole,\n\t\t\t}),\n\t\t}\n\t\treturn {\n\t\t\tctx: {\n\t\t\t\t...ctx,\n\t\t\t\t...verified,\n\t\t\t\t// Tenant re-derives from the live-verified organization.\n\t\t\t\ttenant: config.security?.tenancy?.resolve?.(verified) ?? membership.organizationId,\n\t\t\t},\n\t\t\targs: {},\n\t\t}\n\t}\n\n\t/** Query constructor allowing only the given roles. */\n\tfunction roleQuery(...allowed: readonly Role[]) {\n\t\treturn zCustomQuery(config.query, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => {\n\t\t\t\tconst authenticated = await queryInput(ctx)\n\t\t\t\trequireRole(authenticated.ctx.actor.role, allowed)\n\t\t\t\treturn authenticated\n\t\t\t},\n\t\t})\n\t}\n\n\t/** Mutation constructor allowing only the given roles. */\n\tfunction roleMutation(...allowed: readonly Role[]) {\n\t\treturn zCustomMutation(config.mutation, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => {\n\t\t\t\tconst authenticated = await mutationInput(ctx)\n\t\t\t\trequireRole(authenticated.ctx.actor.role, allowed)\n\t\t\t\treturn authenticated\n\t\t\t},\n\t\t})\n\t}\n\n\t/** Action constructor allowing only the given roles (post live verification). */\n\tfunction roleAction(...allowed: readonly Role[]) {\n\t\treturn zCustomAction(config.action, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => {\n\t\t\t\tconst authenticated = await actionInput(ctx)\n\t\t\t\trequireRole(authenticated.ctx.actor.role, allowed)\n\t\t\t\treturn authenticated\n\t\t\t},\n\t\t})\n\t}\n\n\treturn {\n\t\tinclude,\n\t\tauthenticatedUser,\n\t\troleQuery,\n\t\troleMutation,\n\t\troleAction,\n\t\tauthQuery: zCustomQuery(config.query, { args: {}, input: queryInput }),\n\t\tauthMutation: zCustomMutation(config.mutation, {\n\t\t\targs: {},\n\t\t\tinput: mutationInput,\n\t\t}),\n\t\tauthAction: zCustomAction(config.action, {\n\t\t\targs: {},\n\t\t\tinput: actionInput,\n\t\t}),\n\t\tadminQuery: roleQuery(...config.adminRoles),\n\t\tadminMutation: roleMutation(...config.adminRoles),\n\t\tadminAction: roleAction(...config.adminRoles),\n\t\tsystemQuery: zCustomQuery(config.internalQuery, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => ({\n\t\t\t\tctx: {\n\t\t\t\t\t...ctx,\n\t\t\t\t\tinclude,\n\t\t\t\t\tdb: registerQueryReader(ctx.db, ctx.db, {\n\t\t\t\t\t\tscope: {\n\t\t\t\t\t\t\tmode: 'system',\n\t\t\t\t\t\t\tprincipal: null,\n\t\t\t\t\t\t\torganization: null,\n\t\t\t\t\t\t\ttenant: null,\n\t\t\t\t\t\t\trole: null,\n\t\t\t\t\t\t},\n\t\t\t\t\t\trules: {},\n\t\t\t\t\t\tdefaultPolicy: 'allow',\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t\targs: {},\n\t\t\t}),\n\t\t}),\n\t\tsystemMutation: zCustomMutation(config.internalMutation, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => ({ ctx: { ...wrapDB(ctx), include }, args: {} }),\n\t\t}),\n\t\tsystemAction: zCustomAction(config.internalAction, {\n\t\t\targs: {},\n\t\t\tinput: async (ctx) => ({ ctx, args: {} }),\n\t\t}),\n\t}\n}\n\nconst organizationClaims = z.object({\n\torganization: z\n\t\t.object({\n\t\t\torganizationId: z.string().optional().catch(undefined),\n\t\t\trole: z.string().optional().catch(undefined),\n\t\t})\n\t\t.optional()\n\t\t.catch(undefined),\n\torg_id: z.string().optional().catch(undefined),\n\trole: z.string().optional().catch(undefined),\n})\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -5,10 +5,13 @@ import { Actor, AuthBundle, AuthFunctions, AuthFunctionsConfig, DefaultRole, Inc
5
5
  import { AnyQueryMiddleware, QueryMiddleware } from "./components/foundation/modules/query/query.mjs";
6
6
  import { AnyCommandMiddleware, ApplicationCommand, AuditEntryInput, AuditWriter, AuditedOperation, AuditedRegistry, CommandConstructor, CommandDefaults, CommandMiddleware, Foundation, FoundationOptions, PermissionChecker } from "./components/foundation/client.mjs";
7
7
  import { CrudConfig, createCrudCommands } from "./crud.mjs";
8
- import { TIMESTAMP_FIELDS, TableBoundaryOptions, createModule, jsonSafeZid, paginated, tenantTable, zodTable, zodVariantTable } from "./zod-table.mjs";
8
+ import { ScalarFilters, ScalarSelection, ServerFilter } from "./querying/filters.mjs";
9
+ import { DtoOptions, TIMESTAMP_FIELDS, TableBoundaryOptions, createModule, jsonSafeZid, paginated, tenantTable, zodTable, zodVariantTable } from "./zod-table.mjs";
9
10
  import { RateLimiterLike, rateLimit } from "./middleware.mjs";
10
11
  import { WebhookBoundaryConfig, createWebhookBoundary, recordWebhookEvent, webhookEventsTable } from "./webhooks.mjs";
11
12
  import { createStateMachine } from "./state-machine.mjs";
12
13
  import { Change, Trigger, Triggers, appendOnly, createTriggers, noDelete, tenantOwnership, timestamps } from "./triggers.mjs";
13
14
  import { FoundationRegistry, FoundationRegistryOptions, createFoundation } from "./modules/foundation/foundation.mjs";
14
- export { type Actor, type AgentToolHandlers, type AgentToolRecord, type AnyCommandMiddleware, type AnyQueryMiddleware, type ApplicationCommand, type AuditEntryInput, type AuditWriter, type AuditedOperation, type AuditedRegistry, type AuthBundle, type AuthFunctions, type AuthFunctionsConfig, type Change, type CommandConstructor, type CommandDefaults, type CommandMiddleware, type CrudConfig, type DefaultRole, type ErrorFactory, Foundation, type FoundationOptions, type FoundationRegistry, type FoundationRegistryOptions, type Include, type IncludedQuery, KitError, type PermissionChecker, type QueryMiddleware, type RLSConfig, type RateLimiterLike, type Rules, type SecurityConfig, TENANT_FIELD, TIMESTAMP_FIELDS, type TableBoundaryOptions, type Trigger, Triggers, type WebhookBoundaryConfig, appendOnly, assertTenantOwned, composeRules, createAgentTools, createAuthFunctions, createCrudCommands, createFoundation, createInclude, createModule, createStateMachine, createTenantRules, createTriggers, createWebhookBoundary, defaultErrors, defaultRoleMap, jsonSafeZid, noDelete, paginated, rateLimit, recordWebhookEvent, requireTenantReference, tenantOwnership, tenantTable, timestamps, webhookEventsTable, zodTable, zodVariantTable };
15
+ import { FieldReference, RelationBuilder, RelationDeclarations, RelationEdge, RelationFilterSelection, RelationFilters, RelationRegistry, RelationRegistryIdentity, RelationRow, RelationSelection, RelationTable, RelationTableHelpers, RelationTables, ThroughReference, defineRelations } from "./relations.mjs";
16
+ import { QueryEngineConfig, createQueryEngine } from "./querying.mjs";
17
+ export { type Actor, type AgentToolHandlers, type AgentToolRecord, type AnyCommandMiddleware, type AnyQueryMiddleware, type ApplicationCommand, type AuditEntryInput, type AuditWriter, type AuditedOperation, type AuditedRegistry, type AuthBundle, type AuthFunctions, type AuthFunctionsConfig, type Change, type CommandConstructor, type CommandDefaults, type CommandMiddleware, type CrudConfig, type DefaultRole, type DtoOptions, type ErrorFactory, type FieldReference, Foundation, type FoundationOptions, type FoundationRegistry, type FoundationRegistryOptions, type Include, type IncludedQuery, KitError, type PermissionChecker, type QueryEngineConfig, type QueryMiddleware, type RLSConfig, type RateLimiterLike, type RelationBuilder, type RelationDeclarations, type RelationEdge, type RelationFilterSelection, type RelationFilters, type RelationRegistry, type RelationRegistryIdentity, type RelationRow, type RelationSelection, type RelationTable, type RelationTableHelpers, type RelationTables, type Rules, type ScalarFilters, type ScalarSelection, type SecurityConfig, type ServerFilter, TENANT_FIELD, TIMESTAMP_FIELDS, type TableBoundaryOptions, type ThroughReference, type Trigger, Triggers, type WebhookBoundaryConfig, appendOnly, assertTenantOwned, composeRules, createAgentTools, createAuthFunctions, createCrudCommands, createFoundation, createInclude, createModule, createQueryEngine, createStateMachine, createTenantRules, createTriggers, createWebhookBoundary, defaultErrors, defaultRoleMap, defineRelations, jsonSafeZid, noDelete, paginated, rateLimit, recordWebhookEvent, requireTenantReference, tenantOwnership, tenantTable, timestamps, webhookEventsTable, zodTable, zodVariantTable };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createAgentTools as e}from"./agent-tools.mjs";import{KitError as t,defaultErrors as n}from"./errors.mjs";import{TENANT_FIELD as r,assertTenantOwned as i,composeRules as a,createTenantRules as o,requireTenantReference as s}from"./tenancy.mjs";import{createAuthFunctions as c,createInclude as l,defaultRoleMap as u}from"./auth.mjs";import{createCrudCommands as d}from"./crud.mjs";import{TIMESTAMP_FIELDS as f,createModule as p,jsonSafeZid as m,paginated as h,tenantTable as g,zodTable as _,zodVariantTable as v}from"./zod-table.mjs";import{rateLimit as y}from"./middleware.mjs";import{createWebhookBoundary as b,recordWebhookEvent as x,webhookEventsTable as S}from"./webhooks.mjs";import{createStateMachine as C}from"./state-machine.mjs";import{Triggers as w,appendOnly as T,createTriggers as E,noDelete as D,tenantOwnership as O,timestamps as k}from"./triggers.mjs";import{Foundation as A}from"./components/foundation/client.mjs";import{createFoundation as j}from"./modules/foundation/foundation.mjs";export{A as Foundation,t as KitError,r as TENANT_FIELD,f as TIMESTAMP_FIELDS,w as Triggers,T as appendOnly,i as assertTenantOwned,a as composeRules,e as createAgentTools,c as createAuthFunctions,d as createCrudCommands,j as createFoundation,l as createInclude,p as createModule,C as createStateMachine,o as createTenantRules,E as createTriggers,b as createWebhookBoundary,n as defaultErrors,u as defaultRoleMap,m as jsonSafeZid,D as noDelete,h as paginated,y as rateLimit,x as recordWebhookEvent,s as requireTenantReference,O as tenantOwnership,g as tenantTable,k as timestamps,S as webhookEventsTable,_ as zodTable,v as zodVariantTable};
1
+ import{createAgentTools as e}from"./agent-tools.mjs";import{KitError as t,defaultErrors as n}from"./errors.mjs";import{TENANT_FIELD as r,assertTenantOwned as i,composeRules as a,createTenantRules as o,requireTenantReference as s}from"./tenancy.mjs";import{createAuthFunctions as c,createInclude as l,defaultRoleMap as u}from"./auth.mjs";import{createCrudCommands as d}from"./crud.mjs";import{TIMESTAMP_FIELDS as f,createModule as p,jsonSafeZid as m,paginated as h,tenantTable as g,zodTable as _,zodVariantTable as v}from"./zod-table.mjs";import{rateLimit as y}from"./middleware.mjs";import{createWebhookBoundary as b,recordWebhookEvent as x,webhookEventsTable as S}from"./webhooks.mjs";import{createStateMachine as C}from"./state-machine.mjs";import{Triggers as w,appendOnly as T,createTriggers as E,noDelete as D,tenantOwnership as O,timestamps as k}from"./triggers.mjs";import{Foundation as A}from"./components/foundation/client.mjs";import{createFoundation as j}from"./modules/foundation/foundation.mjs";import{defineRelations as M}from"./relations.mjs";import{createQueryEngine as N}from"./querying.mjs";export{A as Foundation,t as KitError,r as TENANT_FIELD,f as TIMESTAMP_FIELDS,w as Triggers,T as appendOnly,i as assertTenantOwned,a as composeRules,e as createAgentTools,c as createAuthFunctions,d as createCrudCommands,j as createFoundation,l as createInclude,p as createModule,N as createQueryEngine,C as createStateMachine,o as createTenantRules,E as createTriggers,b as createWebhookBoundary,n as defaultErrors,u as defaultRoleMap,M as defineRelations,m as jsonSafeZid,D as noDelete,h as paginated,y as rateLimit,x as recordWebhookEvent,s as requireTenantReference,O as tenantOwnership,g as tenantTable,k as timestamps,S as webhookEventsTable,_ as zodTable,v as zodVariantTable};
@@ -3,9 +3,9 @@ import { EffectApiContext, EffectApiHandlerValue, EffectApiOptions, EffectApiSer
3
3
  import { CustomBuilder } from "convex-helpers/server/zod4";
4
4
  import { ArgsArrayToObject, DefaultFunctionArgs, FunctionVisibility } from "convex/server";
5
5
  import { ObjectType, PropertyValidators } from "convex/values";
6
- import { Scope } from "effect";
7
6
  import { Registration } from "convex-helpers/server/customFunctions";
8
7
  import * as zCore from "zod/v4/core";
8
+ import { Scope } from "effect";
9
9
  //#region src/modules/effect/api-zod.d.ts
10
10
  type Fields = Record<string, zCore.$ZodType>;
11
11
  type ArgumentsValidator = Fields | zCore.$ZodObject<Fields> | void;
@@ -15,5 +15,7 @@ export declare function createResolver(): ResolverFactory;
15
15
  export declare function resolveImport(resolver: ResolverFactory, filename: string, source: string): string | null;
16
16
  /** The lint rule runs the project scan on one deterministic source file. */
17
17
  export declare function architectureAnchor(root: string): string | null;
18
+ /** Only these two factory declarations may omit root business ownership. */
19
+ export declare function narrowRootDeclaration(file: string, source: string): boolean;
18
20
  //#endregion
19
21
  //# sourceMappingURL=architecture.d.mts.map
@@ -1,2 +1,2 @@
1
- import{dirname as e,relative as t,resolve as n,sep as r}from"node:path";import{existsSync as i,readFileSync as a,readdirSync as o,realpathSync as s}from"node:fs";import{Visitor as c,parseSync as l}from"oxc-parser";import{ResolverFactory as u}from"oxc-resolver";function d(o={}){let c=n(o.cwd??process.cwd(),o.convexDir??`convex`),u=i(c)?s(c):c,d=[];if(!i(u))return[{code:`missing-root`,file:`.`,message:`Convex directory does not exist: ${u}`}];let m=h(u,d),y=/* @__PURE__ */ new Set,w=/* @__PURE__ */ new Set,T=/* @__PURE__ */ new Map,E=/* @__PURE__ */ new Map,D=f();for(let i of m){let o=n(u,i),s=l(o,a(o,`utf8`));if(s.errors.length){d.push({code:`parse`,file:i,message:`Cannot validate ${i}: ${s.errors[0].message}`});continue}if(g(i)){i.includes(`__tests__/`)?e(i).endsWith(`__tests__`)&&v(s.program)&&w.add(e(e(i))):d.push({code:`test-location`,file:i,message:`Place tests in a sibling __tests__/ directory.`});continue}if(i.split(`/`).includes(`__tests__`))continue;/^domain\/[^/]+$/.test(i)&&i!==`domain/table.ts`&&d.push({code:`domain-entry`,file:i,message:`Domain source belongs in domain/<module>/ or domain/shared/. Only domain/table.ts assembles module tables.`}),_(s.program)&&i!==`domain/table.ts`&&y.add(e(i));let c=x(i)??``,f=/* @__PURE__ */ new Set;E.set(i,f);for(let e of b(s.program)){let n=p(D,o,e);if(!n)continue;let i=t(u,n).split(r).join(`/`);f.add(i);let a=x(i);if(c&&a&&c!==a){let e=T.get(c)??/* @__PURE__ */ new Set;T.set(c,e),e.add(a)}}}for(let e of y)w.has(e)||d.push({code:`tests`,file:e,message:`${e}/__tests__/ must contain a .test or .spec file with a nonempty, non-skipped test. Parent or child suites do not count.`});d.push(...C(E));for(let e of S(T))d.push({code:`cycle`,file:e[0],message:`Module dependency cycle: ${e.join(` → `)}.`});return d.sort((e,t)=>e.file.localeCompare(t.file)||e.code.localeCompare(t.code))}function f(){return new u({tsconfig:`auto`,extensions:[`.ts`,`.tsx`,`.mts`,`.cts`,`.js`,`.jsx`,`.mjs`,`.cjs`,`.json`],extensionAlias:{".js":[`.ts`,`.tsx`,`.js`],".mjs":[`.mts`,`.mjs`],".cjs":[`.cts`,`.cjs`]},conditionNames:[`types`,`import`,`node`,`default`],symlinks:!0})}function p(t,r,i){return t.resolveFileSync(r,i).path||(i.startsWith(`.`)?n(e(r),i):null)}function m(e){if(!i(e))return null;if(i(n(e,`convex.config.ts`)))return s(n(e,`convex.config.ts`));e=s(e);for(let t of h(e,[]))if(!t.includes(`__tests__/`)&&!g(t))return n(e,t);return null}function*h(e,i){let a=/* @__PURE__ */ new Set([`_generated`,`__tests__`,`api`,`domain`,`application`,`migrations`,`components`]);function*s(c){for(let l of o(c,{withFileTypes:!0}).sort((e,t)=>e.name.localeCompare(t.name))){let o=n(c,l.name),u=t(e,o).split(r).join(`/`);if(l.isSymbolicLink()){i.push({code:`symlink`,file:u,message:`Keep application source inside its owning directory; symlinked files and folders are not allowed.`});continue}l.isDirectory()?(c===e&&!a.has(l.name)&&i.push({code:`root-directory`,file:u,message:`Root folder ${l.name}/ is not allowed. Use api/, domain/, application/, migrations/, components/, __tests__/, or _generated/.`}),l.name!==`_generated`&&l.name!==`node_modules`&&!l.name.startsWith(`.`)&&(yield*s(o))):/\.[cm]?[jt]sx?$/.test(l.name)&&!/\.d\.[cm]?ts$/.test(l.name)&&(yield u)}}yield*s(e)}function g(e){return/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(e)}function _(e){return e.body.some(e=>{let t=e.type===`ExportNamedDeclaration`?e.declaration:e;return t&&`declare`in t&&t.declare||e.type===`ImportDeclaration`||e.type===`EmptyStatement`?!1:e.type===`ExportNamedDeclaration`?e.exportKind===`type`||!e.declaration?!1:![`TSInterfaceDeclaration`,`TSTypeAliasDeclaration`,`TSDeclareFunction`].includes(e.declaration.type):![`TSInterfaceDeclaration`,`TSTypeAliasDeclaration`,`TSDeclareFunction`,`ExportAllDeclaration`].includes(e.type)})}function v(e){let t=/* @__PURE__ */ new Map([[`test`,`test`],[`it`,`it`],[`describe`,`describe`],[`suite`,`suite`]]);for(let n of e.body){let e=n.type===`ExportNamedDeclaration`?n.declaration:n;if(e?.type===`FunctionDeclaration`&&e.id&&t.delete(e.id.name),e?.type===`VariableDeclaration`)for(let n of e.declarations)n.id.type===`Identifier`&&t.delete(n.id.name);if(n.type===`ImportDeclaration`)for(let e of n.specifiers)t.delete(e.local.name),[`vitest`,`vite-plus/test`,`bun:test`,`node:test`,`@jest/globals`].includes(n.source.value)&&(e.type===`ImportNamespaceSpecifier`?t.set(e.local.name,`*`):e.type===`ImportDefaultSpecifier`?t.set(e.local.name,`test`):e.imported.type===`Identifier`&&t.set(e.local.name,e.imported.name))}let n=[];new c({CallExpression(e){n.push(e)}}).visit(e);let r=n.filter(e=>{let n=y(e.callee,t);return[`test`,`it`,`describe`,`suite`].includes(n[0])&&(n.some(e=>[`skip`,`todo`,`skipIf`,`runIf`].includes(e))||e.arguments.some(e=>e.type===`ObjectExpression`&&e.properties.some(e=>e.type===`Property`&&[`skip`,`todo`].includes(e.key.type===`Identifier`?e.key.name:e.key.type===`Literal`?String(e.key.value):``)&&(e.value.type!==`Literal`||e.value.value!==!1))))});return n.some(e=>{let n=y(e.callee,t);return![`test`,`it`].includes(n[0])||r.some(t=>e.start>=t.start&&e.end<=t.end)?!1:e.arguments.some(e=>(e.type===`ArrowFunctionExpression`||e.type===`FunctionExpression`)&&e.body&&(e.body.type!==`BlockStatement`||e.body.body.some(e=>e.type!==`EmptyStatement`)))})}function y(e,t){if(e.type===`Identifier`){let n=t.get(e.name);return n?[n]:[]}if(e.type===`CallExpression`)return y(e.callee,t);if(e.type===`MemberExpression`){let n=y(e.object,t),r=!e.computed&&e.property.type===`Identifier`?e.property.name:e.property.type===`Literal`?String(e.property.value):``;return!n.length||!r?[]:n[0]===`*`?[r]:[...n,r]}return[]}function b(e){let t=[];return new c({ImportDeclaration(e){e.importKind!==`type`&&(e.specifiers.length===0||e.specifiers.some(e=>e.type!==`ImportSpecifier`||e.importKind!==`type`))&&t.push(e.source.value)},ExportNamedDeclaration(e){e.source&&e.exportKind!==`type`&&e.specifiers.some(e=>e.exportKind!==`type`)&&t.push(e.source.value)},ExportAllDeclaration(e){e.exportKind!==`type`&&t.push(e.source.value)},ImportExpression(e){e.source.type===`Literal`&&String(e.source.value)===e.source.value&&t.push(String(e.source.value))}}).visit(e),t}function x(e){let t=e.split(`/`);return t[0]===`domain`&&t.length>=3&&t[1]!==`__tests__`?`domain/${t[1]}`:null}function S(e){let t=/* @__PURE__ */ new Set,n=[],r=[];function i(a){let o=n.indexOf(a);if(o>=0){r.push([...n.slice(o),a]);return}if(!t.has(a)){n.push(a);for(let t of e.get(a)??[])i(t);n.pop(),t.add(a)}}for(let t of e.keys())i(t);return r}function C(e){let t=/* @__PURE__ */ new Map;for(let[n,r]of e){let i=x(n)??``;if(!i||i===`domain/shared`)continue;let a=/* @__PURE__ */ new Set;function o(n){if(!n.startsWith(`domain/shared/`)||a.has(n))return;a.add(n);let r=t.get(n)??/* @__PURE__ */ new Set;r.add(i),t.set(n,r);for(let t of e.get(n)??[])o(t)}for(let e of r)o(e)}let n=[];for(let[e,r]of t)r.size===1&&n.push({code:`shared-owner`,file:e,message:`This shared implementation is used only by ${[...r][0]}. Move it into that module until another module needs it.`});return n}export{m as architectureAnchor,f as createResolver,d as inspectArchitecture,p as resolveImport};
1
+ import{dirname as e,relative as t,resolve as n,sep as r}from"node:path";import{existsSync as i,readFileSync as a,readdirSync as o,realpathSync as s}from"node:fs";import{Visitor as c,parseSync as l}from"oxc-parser";import{ResolverFactory as u}from"oxc-resolver";function d(o={}){let c=n(o.cwd??process.cwd(),o.convexDir??`convex`),u=i(c)?s(c):c,d=[];if(!i(u))return[{code:`missing-root`,file:`.`,message:`Convex directory does not exist: ${u}`}];let m=g(u,d),b=/* @__PURE__ */ new Set,T=/* @__PURE__ */ new Set,E=/* @__PURE__ */ new Map,D=/* @__PURE__ */ new Map,O=f();for(let i of m){let o=n(u,i),s=l(o,a(o,`utf8`));if(s.errors.length){d.push({code:`parse`,file:i,message:`Cannot validate ${i}: ${s.errors[0].message}`});continue}if(_(i)){i.includes(`__tests__/`)?e(i).endsWith(`__tests__`)&&y(s.program)&&T.add(e(e(i))):d.push({code:`test-location`,file:i,message:`Place tests in a sibling __tests__/ directory.`});continue}if(i.split(`/`).includes(`__tests__`))continue;/^domain\/[^/]+$/.test(i)&&i!==`domain/table.ts`&&d.push({code:`domain-entry`,file:i,message:`Domain source belongs in domain/<module>/ or domain/shared/. Only domain/table.ts assembles module tables.`}),v(s.program)&&i!==`domain/table.ts`&&!h(i,a(o,`utf8`))&&b.add(e(i));let c=S(i)??``,f=/* @__PURE__ */ new Set;D.set(i,f);for(let e of x(s.program)){let n=p(O,o,e);if(!n)continue;let i=t(u,n).split(r).join(`/`);f.add(i);let a=S(i);if(c&&a&&c!==a){let e=E.get(c)??/* @__PURE__ */ new Set;E.set(c,e),e.add(a)}}}for(let e of b)T.has(e)||d.push({code:`tests`,file:e,message:`${e}/__tests__/ must contain a .test or .spec file with a nonempty, non-skipped test. Parent or child suites do not count.`});d.push(...w(D));for(let e of C(E))d.push({code:`cycle`,file:e[0],message:`Module dependency cycle: ${e.join(` → `)}.`});return d.sort((e,t)=>e.file.localeCompare(t.file)||e.code.localeCompare(t.code))}function f(){return new u({tsconfig:`auto`,extensions:[`.ts`,`.tsx`,`.mts`,`.cts`,`.js`,`.jsx`,`.mjs`,`.cjs`,`.json`],extensionAlias:{".js":[`.ts`,`.tsx`,`.js`],".mjs":[`.mts`,`.mjs`],".cjs":[`.cts`,`.cjs`]},conditionNames:[`types`,`import`,`node`,`default`],symlinks:!0})}function p(t,r,i){return t.resolveFileSync(r,i).path||(i.startsWith(`.`)?n(e(r),i):null)}function m(e){if(!i(e))return null;if(i(n(e,`convex.config.ts`)))return s(n(e,`convex.config.ts`));e=s(e);for(let t of g(e,[]))if(!t.includes(`__tests__/`)&&!_(t))return n(e,t);return null}function h(e,t){let n=e===`relations.ts`?`defineRelations`:e===`querying.ts`?`createQueryEngine`:null;if(!n)return!1;let r=l(e,t);if(r.errors.length)return!1;let i=/* @__PURE__ */ new Map,a=/* @__PURE__ */ new Map,o=0,s=!1;function c(e,t=/* @__PURE__ */ new Set){if(e.type===`Identifier`){if(t.has(e.name))return null;let n=a.get(e.name);return n?c(n,new Set(t).add(e.name)):i.get(e.name)??null}return e.type===`MemberExpression`&&!e.computed&&e.property.type===`Identifier`&&c(e.object,t)===`*`?e.property.name:null}function u(e,t,n=!1){if(e.type===`Identifier`||e.type===`Literal`)return!0;if(e.type===`TSAsExpression`||e.type===`TSSatisfiesExpression`||e.type===`ParenthesizedExpression`)return u(e.expression,t,n);if(e.type===`MemberExpression`)return!e.computed&&u(e.object,t);if(e.type===`ArrayExpression`)return e.elements.every(e=>e!==null&&u(e,t));if(e.type===`ObjectExpression`)return e.properties.every(e=>e.type===`Property`&&!e.computed&&e.kind===`init`&&!e.method&&u(e.value,t));if(e.type===`ArrowFunctionExpression`&&n&&!t&&e.params.length===1&&e.params[0].type===`Identifier`&&!e.async){let t=e.body.type===`ParenthesizedExpression`?e.body.expression:e.body;return t.type===`ObjectExpression`&&u(t,e.params[0].name)}if(e.type===`CallExpression`&&t){let n=e.callee,r=[];for(;n.type===`MemberExpression`&&!n.computed&&n.property.type===`Identifier`;)r.unshift(n.property.name),n=n.object;return n.type===`Identifier`&&n.name===t&&(r.length===2&&[`one`,`many`].includes(r[0])||r.length===3&&r[2]===`through`)&&e.arguments.every(e=>u(e,t))}return!1}for(let t of r.program.body){if(t.type===`ImportDeclaration`){if(t.importKind===`type`)continue;for(let n of t.specifiers)(n.type!==`ImportSpecifier`||n.importKind!==`type`)&&[`cvx-kit`,`cvx-kit/${e.slice(0,-3)}`].includes(t.source.value)&&i.set(n.local.name,n.type===`ImportSpecifier`?n.imported.type===`Identifier`?n.imported.name:``:n.type===`ImportNamespaceSpecifier`?`*`:``);continue}if(t.type===`EmptyStatement`||t.type===`TSTypeAliasDeclaration`||t.type===`TSInterfaceDeclaration`)continue;let r=t.type===`ExportNamedDeclaration`?t.declaration:t;if(r?.type!==`VariableDeclaration`||r.kind!==`const`)return!1;for(let e of r.declarations){if(e.id.type!==`Identifier`||!e.init)return!1;if(e.init.type===`CallExpression`&&c(e.init.callee)===n){if(!e.init.arguments.every((e,t)=>u(e,void 0,n===`defineRelations`&&t===1)))return!1;o++,s||=t.type===`ExportNamedDeclaration`}else if(t.type===`ExportNamedDeclaration`||!u(e.init))return!1;a.set(e.id.name,e.init)}}return o===1&&s}function*g(e,i){let a=/* @__PURE__ */ new Set([`_generated`,`__tests__`,`api`,`domain`,`application`,`migrations`,`components`]);function*s(c){for(let l of o(c,{withFileTypes:!0}).sort((e,t)=>e.name.localeCompare(t.name))){let o=n(c,l.name),u=t(e,o).split(r).join(`/`);if(l.isSymbolicLink()){i.push({code:`symlink`,file:u,message:`Keep application source inside its owning directory; symlinked files and folders are not allowed.`});continue}l.isDirectory()?(c===e&&!a.has(l.name)&&i.push({code:`root-directory`,file:u,message:`Root folder ${l.name}/ is not allowed. Use api/, domain/, application/, migrations/, components/, __tests__/, or _generated/.`}),l.name!==`_generated`&&l.name!==`node_modules`&&!l.name.startsWith(`.`)&&(yield*s(o))):/\.[cm]?[jt]sx?$/.test(l.name)&&!/\.d\.[cm]?ts$/.test(l.name)&&(yield u)}}yield*s(e)}function _(e){return/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(e)}function v(e){return e.body.some(e=>{let t=e.type===`ExportNamedDeclaration`?e.declaration:e;return t&&`declare`in t&&t.declare||e.type===`ImportDeclaration`||e.type===`EmptyStatement`?!1:e.type===`ExportNamedDeclaration`?e.exportKind===`type`||!e.declaration?!1:![`TSInterfaceDeclaration`,`TSTypeAliasDeclaration`,`TSDeclareFunction`].includes(e.declaration.type):![`TSInterfaceDeclaration`,`TSTypeAliasDeclaration`,`TSDeclareFunction`,`ExportAllDeclaration`].includes(e.type)})}function y(e){let t=/* @__PURE__ */ new Map([[`test`,`test`],[`it`,`it`],[`describe`,`describe`],[`suite`,`suite`]]);for(let n of e.body){let e=n.type===`ExportNamedDeclaration`?n.declaration:n;if(e?.type===`FunctionDeclaration`&&e.id&&t.delete(e.id.name),e?.type===`VariableDeclaration`)for(let n of e.declarations)n.id.type===`Identifier`&&t.delete(n.id.name);if(n.type===`ImportDeclaration`)for(let e of n.specifiers)t.delete(e.local.name),[`vitest`,`vite-plus/test`,`bun:test`,`node:test`,`@jest/globals`].includes(n.source.value)&&(e.type===`ImportNamespaceSpecifier`?t.set(e.local.name,`*`):e.type===`ImportDefaultSpecifier`?t.set(e.local.name,`test`):e.imported.type===`Identifier`&&t.set(e.local.name,e.imported.name))}let n=[];new c({CallExpression(e){n.push(e)}}).visit(e);let r=n.filter(e=>{let n=b(e.callee,t);return[`test`,`it`,`describe`,`suite`].includes(n[0])&&(n.some(e=>[`skip`,`todo`,`skipIf`,`runIf`].includes(e))||e.arguments.some(e=>e.type===`ObjectExpression`&&e.properties.some(e=>e.type===`Property`&&[`skip`,`todo`].includes(e.key.type===`Identifier`?e.key.name:e.key.type===`Literal`?String(e.key.value):``)&&(e.value.type!==`Literal`||e.value.value!==!1))))});return n.some(e=>{let n=b(e.callee,t);return![`test`,`it`].includes(n[0])||r.some(t=>e.start>=t.start&&e.end<=t.end)?!1:e.arguments.some(e=>(e.type===`ArrowFunctionExpression`||e.type===`FunctionExpression`)&&e.body&&(e.body.type!==`BlockStatement`||e.body.body.some(e=>e.type!==`EmptyStatement`)))})}function b(e,t){if(e.type===`Identifier`){let n=t.get(e.name);return n?[n]:[]}if(e.type===`CallExpression`)return b(e.callee,t);if(e.type===`MemberExpression`){let n=b(e.object,t),r=!e.computed&&e.property.type===`Identifier`?e.property.name:e.property.type===`Literal`?String(e.property.value):``;return!n.length||!r?[]:n[0]===`*`?[r]:[...n,r]}return[]}function x(e){let t=[];return new c({ImportDeclaration(e){e.importKind!==`type`&&(e.specifiers.length===0||e.specifiers.some(e=>e.type!==`ImportSpecifier`||e.importKind!==`type`))&&t.push(e.source.value)},ExportNamedDeclaration(e){e.source&&e.exportKind!==`type`&&e.specifiers.some(e=>e.exportKind!==`type`)&&t.push(e.source.value)},ExportAllDeclaration(e){e.exportKind!==`type`&&t.push(e.source.value)},ImportExpression(e){e.source.type===`Literal`&&String(e.source.value)===e.source.value&&t.push(String(e.source.value))}}).visit(e),t}function S(e){let t=e.split(`/`);return t[0]===`domain`&&t.length>=3&&t[1]!==`__tests__`?`domain/${t[1]}`:null}function C(e){let t=/* @__PURE__ */ new Set,n=[],r=[];function i(a){let o=n.indexOf(a);if(o>=0){r.push([...n.slice(o),a]);return}if(!t.has(a)){n.push(a);for(let t of e.get(a)??[])i(t);n.pop(),t.add(a)}}for(let t of e.keys())i(t);return r}function w(e){let t=/* @__PURE__ */ new Map;for(let[n,r]of e){let i=S(n)??``;if(!i||i===`domain/shared`)continue;let a=/* @__PURE__ */ new Set;function o(n){if(!n.startsWith(`domain/shared/`)||a.has(n))return;a.add(n);let r=t.get(n)??/* @__PURE__ */ new Set;r.add(i),t.set(n,r);for(let t of e.get(n)??[])o(t)}for(let e of r)o(e)}let n=[];for(let[e,r]of t)r.size===1&&n.push({code:`shared-owner`,file:e,message:`This shared implementation is used only by ${[...r][0]}. Move it into that module until another module needs it.`});return n}export{m as architectureAnchor,f as createResolver,d as inspectArchitecture,h as narrowRootDeclaration,p as resolveImport};
2
2
  //# sourceMappingURL=architecture.mjs.map