@fitzzero/quickdraw-core 5.0.0 → 5.0.1

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 (47) hide show
  1. package/README.md +40 -17
  2. package/dist/{admin-BiU4LRZS.d.ts → admin-CBlTY0do.d.ts} +24 -9
  3. package/dist/{adminSchemas-k5a1Ov7b.d.ts → adminSchemas-zji11V74.d.ts} +14 -8
  4. package/dist/{caller-DHzK3oiI.d.ts → caller-BqCYnpmC.d.ts} +1 -1
  5. package/dist/{chunk-5IC5VZ7T.js → chunk-3JNMVB2V.js} +3 -3
  6. package/dist/{chunk-DNCAI3KY.js → chunk-4DIQHASK.js} +1 -1
  7. package/dist/chunk-4DIQHASK.js.map +1 -0
  8. package/dist/{chunk-B2UQ27GH.js → chunk-CDKG4TFA.js} +25 -5
  9. package/dist/{chunk-B2UQ27GH.js.map → chunk-CDKG4TFA.js.map} +1 -1
  10. package/dist/{chunk-XFDOGTMC.js → chunk-HQ6WIRE2.js} +3 -3
  11. package/dist/{chunk-LLLYILEP.js → chunk-NS5PUHIZ.js} +283 -22
  12. package/dist/chunk-NS5PUHIZ.js.map +1 -0
  13. package/dist/{chunk-XUSN5WKU.js → chunk-O2PWC2YW.js} +3 -3
  14. package/dist/{chunk-7QIY2DDX.js → chunk-WVIRXYI4.js} +2 -2
  15. package/dist/{chunk-7QIY2DDX.js.map → chunk-WVIRXYI4.js.map} +1 -1
  16. package/dist/{chunk-IRGAGRVG.js → chunk-ZACRRYWL.js} +3 -3
  17. package/dist/client/index.d.ts +3 -3
  18. package/dist/client/index.js +4 -4
  19. package/dist/{clientTypes-B3o_99Xq.d.ts → clientTypes-DuZW_8mb.d.ts} +1 -1
  20. package/dist/{createServer-DUacdfKl.d.ts → createServer-CGsJlxM8.d.ts} +2 -2
  21. package/dist/{createTestApp-DFLXoTKm.d.ts → createTestApp-Cg2CZa1g.d.ts} +3 -3
  22. package/dist/{http--uoooIH5.d.ts → http-D3fSFQ57.d.ts} +1 -1
  23. package/dist/index.d.ts +4 -4
  24. package/dist/index.js +3 -3
  25. package/dist/prisma/index.d.ts +1 -1
  26. package/dist/prisma/index.js +1 -1
  27. package/dist/server/auth/index.d.ts +11 -2
  28. package/dist/server/auth/index.js +0 -5
  29. package/dist/server/auth/index.js.map +1 -1
  30. package/dist/server/index.d.ts +83 -17
  31. package/dist/server/index.js +87 -160
  32. package/dist/server/index.js.map +1 -1
  33. package/dist/server/mcp/index.d.ts +2 -2
  34. package/dist/{sharing-n4ss-ONM.d.ts → sharing-CoreezAq.d.ts} +2 -2
  35. package/dist/testing/client.d.ts +7 -7
  36. package/dist/testing/client.js +5 -5
  37. package/dist/testing/index.d.ts +6 -6
  38. package/dist/testing/index.js +3 -3
  39. package/dist/testing/mock.d.ts +3 -3
  40. package/dist/testing/mock.js +4 -4
  41. package/package.json +1 -1
  42. package/dist/chunk-DNCAI3KY.js.map +0 -1
  43. package/dist/chunk-LLLYILEP.js.map +0 -1
  44. /package/dist/{chunk-5IC5VZ7T.js.map → chunk-3JNMVB2V.js.map} +0 -0
  45. /package/dist/{chunk-XFDOGTMC.js.map → chunk-HQ6WIRE2.js.map} +0 -0
  46. /package/dist/{chunk-XUSN5WKU.js.map → chunk-O2PWC2YW.js.map} +0 -0
  47. /package/dist/{chunk-IRGAGRVG.js.map → chunk-ZACRRYWL.js.map} +0 -0
@@ -1,16 +1,16 @@
1
1
  import { J as AnyContract, at as ProjectionName, ai as FullProjectionOf, ac as CollectionName, ap as MethodName, d as StandardSchemaV1, c as InferOutput, N as NullableProjection, P as ProjectionList, aq as MethodOf, as as ParsedInputOf, ao as KindOf, A as AccessLevel, af as EntityOf, ar as OutputOf } from '../infer-Dw21kmLw.js';
2
- import { M as ModelName, v as PolicyFor, w as RowForms, x as AccessFor, H as HandlerContext, y as ModelColumn, z as WatchAccess, B as PublicAccess, E as HandlerArgs, G as CustomAccess, I as ShareMode, J as ChannelOptions, K as StreamOptions, u as RoomLeaveHandler, L as ChannelsRequired, f as Service, N as BaseContext, O as CallerFor, l as RunContext, m as RunOptions, o as StreamHandle, P as Presence, n as ServerRooms, Q as AccessPolicy, T as AnyAccessPolicy, V as PolicyColumns, X as PolicyForeign, Y as PolicyTools, k as AccessFilter, Z as RowAccess, q as AccessEngine, b as StorageWhere } from '../caller-DHzK3oiI.js';
3
- export { aI as ANY_FIELD, aA as AccessForm, aB as AccessRequest, _ as AffectsLink, p as AnyContext, A as AnyService, r as AppRooms, aC as AuthenticatedAccess, aq as CallOptions, i as Caller, a9 as ChannelAccess, aa as ChannelContext, ab as ChannelHandler, ac as ChannelImplementation, ad as ChannelRoomOf, a4 as CollectionScope, a6 as ContextRooms, a7 as ContextServices, C as CountArgs, a3 as DEFAULT_BULK_THRESHOLD, g as DevWarning, D as DevWarningError, h as DevWarningKind, aD as EntryAccess, F as FindManyArgs, e as FlushInfo, d as FlushSink, at as ForeignColumns, au as ForeignColumnsOf, aE as IdKeyOf, aF as IdSelector, av as MembershipRead, ar as MethodCaller, aM as N_PLUS_ONE_STATEMENTS, aw as ParentLink, ax as PolicyKind, ay as PolicyReads, $ as ProjectedOutput, a0 as Projection, ae as RoomLeave, af as RoomLeaveReason, ag as RoomLeft, ah as RoomTarget, az as RowLevel, R as RowLevels, aG as ScopeAccess, aH as ServiceAccess, as as ServiceCaller, ai as ServiceChannel, a5 as ServiceCollection, j as ServiceMethod, aj as ServiceStream, c as StatementCount, S as StorageAdapter, a as StorageRow, ak as StreamImplementation, al as StreamPushArgs, am as StreamPushManyArgs, an as StreamSeed, ao as StreamSeedContext, t as StreamSubscriptions, ap as StreamValidation, a1 as TierGroup, a2 as Tiers, a8 as TouchOptions, aJ as UnitOfWork, U as UnitOfWorkFactory, aK as UnitOfWorkScope, W as WriteRecord, aL as formatDevWarning, s as storageOf } from '../caller-DHzK3oiI.js';
4
- import { S as ServerOptions, Q as QuickdrawServer } from '../createServer-DUacdfKl.js';
5
- export { C as ClusterClient, t as ClusterOptions, D as DEFAULT_STALL_INTERVAL_MS, e as DEFAULT_STALL_MAX_MS, f as DEFAULT_STALL_THRESHOLD_MS, p as DisconnectUserOptions, E as EntitySubscription, a as EntitySubscriptions, H as HttpApp, I as IoRedisLike, M as MIN_STALL_INTERVAL_MS, m as QuickdrawIo, n as QuickdrawServerSocket, o as QuickdrawSocketData, x as RateLimitOptions, y as RateLimiter, R as RotateOptions, g as STALL_RESOLUTION_MS, b as ScopeSubscription, c as ScopeSubscriptions, l as ServerOnlyOptions, h as SlowMethod, q as SocketCors, r as SocketOptions, s as SocketRateLimitOptions, i as StallReport, j as StallWatchdogOptions, T as TopicWatch, d as TopicWatches, v as applyRateLimitMiddleware, u as createRateLimiter, k as createServer, w as createTieredRateLimiter } from '../createServer-DUacdfKl.js';
2
+ import { M as ModelName, v as PolicyFor, w as RowForms, x as AccessFor, H as HandlerContext, y as ModelColumn, z as WatchAccess, B as PublicAccess, E as HandlerArgs, G as CustomAccess, I as ShareMode, J as ChannelOptions, K as StreamOptions, u as RoomLeaveHandler, L as ChannelsRequired, f as Service, N as BaseContext, O as CallerFor, l as RunContext, m as RunOptions, o as StreamHandle, P as Presence, n as ServerRooms, Q as AccessPolicy, T as AnyAccessPolicy, V as PolicyColumns, X as PolicyForeign, Y as PolicyTools, k as AccessFilter, Z as RowAccess, q as AccessEngine, b as StorageWhere } from '../caller-BqCYnpmC.js';
3
+ export { aI as ANY_FIELD, aA as AccessForm, aB as AccessRequest, _ as AffectsLink, p as AnyContext, A as AnyService, r as AppRooms, aC as AuthenticatedAccess, aq as CallOptions, i as Caller, a9 as ChannelAccess, aa as ChannelContext, ab as ChannelHandler, ac as ChannelImplementation, ad as ChannelRoomOf, a4 as CollectionScope, a6 as ContextRooms, a7 as ContextServices, C as CountArgs, a3 as DEFAULT_BULK_THRESHOLD, g as DevWarning, D as DevWarningError, h as DevWarningKind, aD as EntryAccess, F as FindManyArgs, e as FlushInfo, d as FlushSink, at as ForeignColumns, au as ForeignColumnsOf, aE as IdKeyOf, aF as IdSelector, av as MembershipRead, ar as MethodCaller, aM as N_PLUS_ONE_STATEMENTS, aw as ParentLink, ax as PolicyKind, ay as PolicyReads, $ as ProjectedOutput, a0 as Projection, ae as RoomLeave, af as RoomLeaveReason, ag as RoomLeft, ah as RoomTarget, az as RowLevel, R as RowLevels, aG as ScopeAccess, aH as ServiceAccess, as as ServiceCaller, ai as ServiceChannel, a5 as ServiceCollection, j as ServiceMethod, aj as ServiceStream, c as StatementCount, S as StorageAdapter, a as StorageRow, ak as StreamImplementation, al as StreamPushArgs, am as StreamPushManyArgs, an as StreamSeed, ao as StreamSeedContext, t as StreamSubscriptions, ap as StreamValidation, a1 as TierGroup, a2 as Tiers, a8 as TouchOptions, aJ as UnitOfWork, U as UnitOfWorkFactory, aK as UnitOfWorkScope, W as WriteRecord, aL as formatDevWarning, s as storageOf } from '../caller-BqCYnpmC.js';
4
+ import { S as ServerOptions, Q as QuickdrawServer } from '../createServer-CGsJlxM8.js';
5
+ export { C as ClusterClient, t as ClusterOptions, D as DEFAULT_STALL_INTERVAL_MS, e as DEFAULT_STALL_MAX_MS, f as DEFAULT_STALL_THRESHOLD_MS, p as DisconnectUserOptions, E as EntitySubscription, a as EntitySubscriptions, H as HttpApp, I as IoRedisLike, M as MIN_STALL_INTERVAL_MS, m as QuickdrawIo, n as QuickdrawServerSocket, o as QuickdrawSocketData, x as RateLimitOptions, y as RateLimiter, R as RotateOptions, g as STALL_RESOLUTION_MS, b as ScopeSubscription, c as ScopeSubscriptions, l as ServerOnlyOptions, h as SlowMethod, q as SocketCors, r as SocketOptions, s as SocketRateLimitOptions, i as StallReport, j as StallWatchdogOptions, T as TopicWatch, d as TopicWatches, v as applyRateLimitMiddleware, u as createRateLimiter, k as createServer, w as createTieredRateLimiter } from '../createServer-CGsJlxM8.js';
6
6
  import { ad as Version } from '../envelope-nf5eGB-a.js';
7
7
  import { Q as QuickdrawTypes, D as DbOf, c as PrincipalOf, M as MaybePromise, b as McpContextOf, C as ContextExtensionOf, P as Principal } from '../types-Bwg3kIEc.js';
8
8
  export { a as McpContext, T as Transport } from '../types-Bwg3kIEc.js';
9
- import { a as DispatcherOptions, D as Dispatcher, c as DispatcherCollections } from '../http--uoooIH5.js';
10
- export { o as AccessChange, p as AccessChangeListener, A as AccessOptions, d as ChangeLogOptions, C as ContractOfServices, j as DEFAULT_LIMITS, f as DbOfServices, h as DispatchRequest, i as DispatchResult, n as DispatcherAccess, k as DispatcherLimits, s as HttpMiddleware, H as HttpRouter, u as HttpRouterOptions, b as HttpTransportOptions, L as LimitsOptions, l as PipelineOptions, P as PrincipalOfServices, R as RegisteredMethod, g as Registry, S as SharedData, m as SubscriptionLimits, T as TrackingOptions, V as VersionRequest, q as VersionSource, e as createDispatcher, r as createHttpRouter, t as toCallReply } from '../http--uoooIH5.js';
11
- import { l as CrudMethodName, d as CrudContractOptions, S as ScalarFieldOf, N as NumberFieldOf, m as CrudMethods, H as AdminMethodsOf, w as AdminContractOptions, G as AdminMethods } from '../admin-BiU4LRZS.js';
12
- import { S as SearchContractOptions, T as TextFieldOf, b as SearchMethods, n as SharingMethodName, f as SharingByNameMethod, g as SharingContractOptions, o as SharingMethods } from '../sharing-n4ss-ONM.js';
13
- import { l as AdminFieldConfig } from '../adminSchemas-k5a1Ov7b.js';
9
+ import { a as DispatcherOptions, D as Dispatcher, c as DispatcherCollections } from '../http-D3fSFQ57.js';
10
+ export { o as AccessChange, p as AccessChangeListener, A as AccessOptions, d as ChangeLogOptions, C as ContractOfServices, j as DEFAULT_LIMITS, f as DbOfServices, h as DispatchRequest, i as DispatchResult, n as DispatcherAccess, k as DispatcherLimits, s as HttpMiddleware, H as HttpRouter, u as HttpRouterOptions, b as HttpTransportOptions, L as LimitsOptions, l as PipelineOptions, P as PrincipalOfServices, R as RegisteredMethod, g as Registry, S as SharedData, m as SubscriptionLimits, T as TrackingOptions, V as VersionRequest, q as VersionSource, e as createDispatcher, r as createHttpRouter, t as toCallReply } from '../http-D3fSFQ57.js';
11
+ import { l as CrudMethodName, d as CrudContractOptions, S as ScalarFieldOf, N as NumberFieldOf, m as CrudMethods, H as AdminMethodsOf, w as AdminContractOptions, G as AdminMethods } from '../admin-CBlTY0do.js';
12
+ import { S as SearchContractOptions, T as TextFieldOf, b as SearchMethods, n as SharingMethodName, f as SharingByNameMethod, g as SharingContractOptions, o as SharingMethods } from '../sharing-CoreezAq.js';
13
+ import { x as AdminNeverWritable, l as AdminFieldConfig } from '../adminSchemas-zji11V74.js';
14
14
  export { a as CallOutcome, C as CallRecord } from '../metrics-C0WvB7-I.js';
15
15
  export { b as AuthenticateRequest, A as AuthenticateResult, c as HttpAuthenticateRequest, d as HttpCredentialSource, S as ServerAuth, e as ServiceAccessSource, a as ServiceGrants, f as SocketAuthenticateRequest, r as recordSocketSession } from '../auth-CMyRkfjZ.js';
16
16
  import { Server } from 'socket.io';
@@ -616,8 +616,25 @@ declare function inherit<const Via extends string>(options: InheritOptions<Via>)
616
616
  */
617
617
  declare function anyOf<const P extends readonly AnyAccessPolicy[]>(...policies: P): AccessPolicy<PolicyColumns<P[number]>, PolicyForeign<P[number]>>;
618
618
 
619
+ /** A membership table a resolver's levels depend on: `{ model, entry, user, level, levels? }`, as `members` takes it. */
620
+ type ResolverMembership = MembersOptions;
621
+ /** What a resolver's levels depend on: `resolver({ reads })`. Name at least one column or table. */
622
+ interface ResolverReads<Column extends string = string, Membership extends ResolverMembership = ResolverMembership> {
623
+ /**
624
+ * Columns of the service's own model. A tracked write that sets one (or
625
+ * creates or deletes a row) re-checks that row, for every user. `tools.rows`
626
+ * reads them.
627
+ */
628
+ readonly columns?: readonly Column[];
629
+ /**
630
+ * Membership tables, one row per user and row of this service: `entry`
631
+ * holds the id of this service's row, as for `members`. A tracked write to
632
+ * one re-checks that user on that row.
633
+ */
634
+ readonly memberships?: readonly Membership[];
635
+ }
619
636
  /** Options of {@link resolver}. */
620
- interface ResolverOptions<P extends Principal = Principal> {
637
+ interface ResolverOptions<P extends Principal = Principal, Column extends string = string, Membership extends ResolverMembership = ResolverMembership> {
621
638
  /**
622
639
  * The principal's level on each of `ids`, as a map or a record by id. An id
623
640
  * left out, or given anything but an access level, has no level. Read
@@ -631,21 +648,52 @@ interface ResolverOptions<P extends Principal = Principal> {
631
648
  * no row.
632
649
  */
633
650
  readonly where?: (principal: P, level: AccessLevel, tools: PolicyTools) => MaybePromise<AccessFilter>;
651
+ /**
652
+ * What the levels depend on, so tracked writes re-check what they
653
+ * decided: live rows, collection scopes and change topics.
654
+ * `{ columns, memberships }` names the service's columns and the membership
655
+ * tables `levelsFor` and `where` read; `"none"` says nothing a tracked
656
+ * write changes can change a level (the levels come from the principal's
657
+ * grants alone, say). Without it nothing a write does re-checks this
658
+ * policy, and the server says so in development (`resolver-without-reads`).
659
+ */
660
+ readonly reads?: ResolverReads<Column, Membership> | "none";
634
661
  }
662
+ /** The other models and columns the membership tables `M` read: what `defineService` checks. */
663
+ type MembershipColumns<M> = M extends {
664
+ readonly model: infer Model extends string;
665
+ readonly entry: infer Entry extends string;
666
+ readonly user: infer User extends string;
667
+ readonly level: infer Level extends string;
668
+ } ? {
669
+ readonly model: Model;
670
+ readonly columns: Entry | User | Level;
671
+ } : never;
635
672
  /**
636
673
  * A policy written in app code: `levelsFor` answers the level per row and
637
674
  * `where` the list filter. A resolver's own results are memoized for the
638
- * request and never cached across requests, because the framework cannot
639
- * know which writes change them; what it reads through `tools` is cached as
640
- * usual.
675
+ * request and never cached across requests; what it reads through `tools`
676
+ * is: `tools.rows(ids)` reads `id` and the declared columns in one query,
677
+ * and `tools.memberships(table, userId, ids)` a member's levels, both kept
678
+ * with `cacheMs` and evicted by tracked writes when `reads` declares them.
679
+ *
680
+ * `reads` declares what the levels depend on, so a tracked write to it
681
+ * re-checks the live rows, collection scopes and change topics the policy
682
+ * authorized, as for the other builders: a user removed from a membership
683
+ * table stops receiving the rows it gave them. `defineService` checks the
684
+ * declared columns and tables against the app's database client.
641
685
  *
642
686
  * @example
643
687
  * access: resolver({
644
- * levelsFor: async (principal, ids) => levelsFromMyTable(principal.userId, ids),
688
+ * levelsFor: (principal, ids, tools) => levelsFromMyTables(principal.userId, ids, tools),
645
689
  * where: (principal) => ({ visibility: "public" }),
690
+ * reads: {
691
+ * columns: ["visibility"],
692
+ * memberships: [{ model: "teamMember", entry: "projectId", user: "userId", level: "role" }],
693
+ * },
646
694
  * }),
647
695
  */
648
- declare function resolver<P extends Principal = Principal>(options: ResolverOptions<P>): AccessPolicy<never, never>;
696
+ declare function resolver<P extends Principal = Principal, const Column extends string = never, const Membership extends ResolverMembership = never>(options: ResolverOptions<P, Column, Membership>): AccessPolicy<Column, MembershipColumns<Membership>>;
649
697
 
650
698
  /**
651
699
  * Every signed-in user has `level` on every row. Anonymous callers have no
@@ -1184,6 +1232,18 @@ interface AdminHandlersOptions<C extends AnyContract, A, Db = unknown> {
1184
1232
  * an entity with such a field. Default `false`.
1185
1233
  */
1186
1234
  readonly grants?: [Extract<AdminFieldOf<C>, "serviceAccess" | "service_access">] extends [never] ? never : boolean;
1235
+ /**
1236
+ * The only fields `adminCreate` and `adminUpdate` write (finding R1.4 of
1237
+ * the 5.0.0 review): `adminMeta` reports every other field `editable:
1238
+ * false`, so a generic form leaves it read-only, and a write naming one is
1239
+ * refused with `VALIDATION` ("is not editable"). Each must be a field the
1240
+ * kit shows, never `id` or a timestamp, and a `fieldOverrides` entry may
1241
+ * not say otherwise about it. When `admin.contract` was given a list, this
1242
+ * one must name the same fields, and without this one the contract's
1243
+ * applies. Without either, the kit writes every field but `id`, the
1244
+ * timestamps, the hidden ones and those an override made read-only.
1245
+ */
1246
+ readonly editable?: readonly Exclude<AdminFieldOf<C>, AdminNeverWritable>[];
1187
1247
  /** Changes to the configuration `adminMeta` derives, per field. */
1188
1248
  readonly fieldOverrides?: {
1189
1249
  readonly [Field in AdminFieldOf<C>]?: AdminFieldOverride;
@@ -1253,7 +1313,7 @@ type Empty = Record<never, never>;
1253
1313
  declare function handlers<C extends AnyContract, const A extends AdminAccess<C> = Empty, Db = unknown>(contract: C & NoInfer<AdminContract<C>>, options?: AdminHandlersOptions<C, A, Db>): AdminImplementations<C, A, Db>;
1254
1314
  /**
1255
1315
  * The admin kit: `admin.handlers(contract, { access?, displayName?,
1256
- * hiddenFields?, fieldOverrides?, rowless?, grants? })` implements the
1316
+ * hiddenFields?, editable?, fieldOverrides?, rowless?, grants? })` implements the
1257
1317
  * methods `admin.contract` made in `contract`, each open to a service-wide
1258
1318
  * `Admin` grant unless `access` gives another form. `admin.contract` is here
1259
1319
  * too, for server code; a shared package imports it from the root export.
@@ -1273,6 +1333,12 @@ declare const admin: Readonly<{
1273
1333
  readonly sort: `admin.contract: sort: "${Exclude<O extends infer T_3 ? T_3 extends O ? T_3 extends {
1274
1334
  readonly sort: readonly (infer Field extends string)[];
1275
1335
  } ? Field : never : never : never, ScalarFieldOf<Entity extends StandardSchemaV1<unknown, unknown> ? InferOutput<Entity> : never>> & string}" is not a field of the entity holding strings, numbers or booleans`;
1336
+ }) & ([Exclude<O extends infer T_4 ? T_4 extends O ? T_4 extends {
1337
+ readonly editable: readonly (infer Field extends string)[];
1338
+ } ? Field : never : never : never, Exclude<keyof (Entity extends StandardSchemaV1<unknown, unknown> ? InferOutput<Entity> : never), AdminNeverWritable>>] extends [never] ? unknown : {
1339
+ readonly editable: `admin.contract: editable: "${Exclude<O extends infer T_5 ? T_5 extends O ? T_5 extends {
1340
+ readonly editable: readonly (infer Field extends string)[];
1341
+ } ? Field : never : never : never, Exclude<keyof (Entity extends StandardSchemaV1<unknown, unknown> ? InferOutput<Entity> : never), AdminNeverWritable>> & string}" is not a field of the entity the kit may write`;
1276
1342
  }) : unknown>) => AdminMethods<O>;
1277
1343
  handlers: typeof handlers;
1278
1344
  }>;
@@ -1491,4 +1557,4 @@ declare function decryptIfEncrypted(value: string): string;
1491
1557
  /** Safe wrapper that returns `null` instead of throwing on invalid/stale ciphertext. */
1492
1558
  declare function tryDecrypt(ciphertext: string, logger?: Logger): string | null;
1493
1559
 
1494
- export { ADMIN_HIDDEN_FIELDS, AccessEngine, AccessFilter, AccessFor, type AccessMap, AccessPolicy, type AdminAccess, type AdminContract, type AdminDefaultAccess, type AdminFieldOf, type AdminFieldOverride, type AdminHandlersOptions, type AdminImplementations, type AdminOnCommitted, type AdminOnWrite, type AdminOutputOf, type AdminWrite, type AffectsOption, AnyAccessPolicy, BaseContext, type BasicAccessEngineOptions, CHANNEL_ABUSE_MULTIPLIER, CHANNEL_ABUSE_WINDOW_MS, CallerFor, ChannelOptions, ChannelsRequired, type CollectionOption, type CollectionOptions, type CollectionsRequired, ContextExtensionOf, type ContextFactory, type CrudAccess, type CrudCreateOf, type CrudHandlersOptions, type CrudImplementations, type CrudKindOf, type CrudMethodsOf, type CrudPrepare, CustomAccess, DbOf, type DefineService, Dispatcher, DispatcherCollections, DispatcherOptions, type EnvValidationResult, HandlerArgs, HandlerContext, type HandlerOutputOf, type HandlerRow, INDEX_MAX_ROWS, type InheritOptions, type InitArgs, type InitOptions, type JsonAclOptions, type JsonColumnValue, type KitContext, type KitHandler, type KitHandlerArgs, type LegacyReply, MAX_APP_ROOMS, MAX_ITEM_IDS, MAX_STREAMS_PER_SOCKET, MaybePromise, McpContextOf, type MembersOptions, type MethodAccess, type MethodImplementation, ModelColumn, ModelName, type NextOrdinalOptions, ORDINAL_STEP, PRESENCE_MAX_LAST_SEEN, PolicyFor, PolicyTools, Presence, Principal, type PrincipalFor, PrincipalOf, type ProjectCheck, type ProjectionOption, PublicAccess, type Quickdraw, QuickdrawServer, QuickdrawTypes, RESUME_MAX_AGE_MS, RESUME_MAX_DELTAS, type RedisAdapterOptions, type RedisAdapterResult, type ResolverOptions, RoomLeaveHandler, RowAccess, type RowFor, RowForms, type RowFormsOf, RunContext, RunOptions, STREAM_MAX_SCOPES, type SearchAccess, type SearchContract, type SearchHandlersOptions, type SearchIdsOptions, type SearchImplementations, type SearchMethodsOf, type SearchStrategy, type SearchStrategyContext, ServerOptions, ServerRooms, Service, type ServiceDefinition, ShareMode, type SharingAccess, type SharingByNameOf, type SharingChange, type SharingChangeKind, type SharingContract, type SharingDefaultAccess, type SharingHandlersOptions, type SharingImplementations, type SharingKindOf, type SharingMethodsOf, type SharingOnChange, type SharingOptionsArgs, type SharingResolveUser, type SharingUserLookup, StorageWhere, StreamHandle, StreamOptions, type ValidateEnvOptions, WatchAccess, admin, anyOf, checkEnv, createBasicAccessEngine, crud, custom, decrypt, decryptIfEncrypted, displayNameOf, encrypt, everyone, inherit, initQuickdraw, isEncrypted, isRedisAdapterAvailable, jsonAcl, labelOf, meetsLevel, members, nextOrdinal, owner, requireEnv, requireRow, resolver, search, serviceGrant, setupRedisAdapter, sharing, tryDecrypt, validateEnv };
1560
+ export { ADMIN_HIDDEN_FIELDS, AccessEngine, AccessFilter, AccessFor, type AccessMap, AccessPolicy, type AdminAccess, type AdminContract, type AdminDefaultAccess, type AdminFieldOf, type AdminFieldOverride, type AdminHandlersOptions, type AdminImplementations, type AdminOnCommitted, type AdminOnWrite, type AdminOutputOf, type AdminWrite, type AffectsOption, AnyAccessPolicy, BaseContext, type BasicAccessEngineOptions, CHANNEL_ABUSE_MULTIPLIER, CHANNEL_ABUSE_WINDOW_MS, CallerFor, ChannelOptions, ChannelsRequired, type CollectionOption, type CollectionOptions, type CollectionsRequired, ContextExtensionOf, type ContextFactory, type CrudAccess, type CrudCreateOf, type CrudHandlersOptions, type CrudImplementations, type CrudKindOf, type CrudMethodsOf, type CrudPrepare, CustomAccess, DbOf, type DefineService, Dispatcher, DispatcherCollections, DispatcherOptions, type EnvValidationResult, HandlerArgs, HandlerContext, type HandlerOutputOf, type HandlerRow, INDEX_MAX_ROWS, type InheritOptions, type InitArgs, type InitOptions, type JsonAclOptions, type JsonColumnValue, type KitContext, type KitHandler, type KitHandlerArgs, type LegacyReply, MAX_APP_ROOMS, MAX_ITEM_IDS, MAX_STREAMS_PER_SOCKET, MaybePromise, McpContextOf, type MembersOptions, type MethodAccess, type MethodImplementation, ModelColumn, ModelName, type NextOrdinalOptions, ORDINAL_STEP, PRESENCE_MAX_LAST_SEEN, PolicyFor, PolicyTools, Presence, Principal, type PrincipalFor, PrincipalOf, type ProjectCheck, type ProjectionOption, PublicAccess, type Quickdraw, QuickdrawServer, QuickdrawTypes, RESUME_MAX_AGE_MS, RESUME_MAX_DELTAS, type RedisAdapterOptions, type RedisAdapterResult, type ResolverMembership, type ResolverOptions, type ResolverReads, RoomLeaveHandler, RowAccess, type RowFor, RowForms, type RowFormsOf, RunContext, RunOptions, STREAM_MAX_SCOPES, type SearchAccess, type SearchContract, type SearchHandlersOptions, type SearchIdsOptions, type SearchImplementations, type SearchMethodsOf, type SearchStrategy, type SearchStrategyContext, ServerOptions, ServerRooms, Service, type ServiceDefinition, ShareMode, type SharingAccess, type SharingByNameOf, type SharingChange, type SharingChangeKind, type SharingContract, type SharingDefaultAccess, type SharingHandlersOptions, type SharingImplementations, type SharingKindOf, type SharingMethodsOf, type SharingOnChange, type SharingOptionsArgs, type SharingResolveUser, type SharingUserLookup, StorageWhere, StreamHandle, StreamOptions, type ValidateEnvOptions, WatchAccess, admin, anyOf, checkEnv, createBasicAccessEngine, crud, custom, decrypt, decryptIfEncrypted, displayNameOf, encrypt, everyone, inherit, initQuickdraw, isEncrypted, isRedisAdapterAvailable, jsonAcl, labelOf, meetsLevel, members, nextOrdinal, owner, requireEnv, requireRow, resolver, search, serviceGrant, setupRedisAdapter, sharing, tryDecrypt, validateEnv };
@@ -20,6 +20,8 @@ import {
20
20
  accessFormProblem,
21
21
  afterCursor,
22
22
  allowedScopes,
23
+ anyOf,
24
+ anyOfPolicies,
23
25
  applyRateLimitMiddleware,
24
26
  checkName,
25
27
  checkWhenDefined,
@@ -36,7 +38,6 @@ import {
36
38
  definePolicy,
37
39
  encodeCursor,
38
40
  handlerProblem,
39
- idFilter,
40
41
  inputKeys,
41
42
  isAccessPolicy,
42
43
  isCustomAccess,
@@ -47,16 +48,19 @@ import {
47
48
  markGrantsEditor,
48
49
  maxLevel,
49
50
  meetsLevel,
51
+ members,
50
52
  membersWhere,
53
+ membershipOf,
51
54
  orderByOf,
52
55
  projectRow,
53
56
  projectedOutput,
54
57
  registerRuntime,
58
+ resolver,
55
59
  selectWith,
56
60
  serviceGrant,
57
61
  strip,
58
62
  watchAdapterClients
59
- } from "../chunk-LLLYILEP.js";
63
+ } from "../chunk-NS5PUHIZ.js";
60
64
  import {
61
65
  DevWarningError,
62
66
  N_PLUS_ONE_STATEMENTS,
@@ -64,13 +68,13 @@ import {
64
68
  currentRev,
65
69
  formatDevWarning,
66
70
  runBeforeAnyDispatcher
67
- } from "../chunk-DNCAI3KY.js";
71
+ } from "../chunk-4DIQHASK.js";
68
72
  import {
69
73
  crud,
70
74
  crudSpecOf,
71
75
  sharing,
72
76
  sharingSpecOf
73
- } from "../chunk-5IC5VZ7T.js";
77
+ } from "../chunk-3JNMVB2V.js";
74
78
  import "../chunk-QEKVYG5F.js";
75
79
  import {
76
80
  abortError,
@@ -98,13 +102,13 @@ import {
98
102
  adminSpecOf,
99
103
  search,
100
104
  searchSpecOf
101
- } from "../chunk-B2UQ27GH.js";
105
+ } from "../chunk-CDKG4TFA.js";
102
106
  import {
103
107
  ACCESS_LEVELS,
104
108
  entityRoom,
105
109
  isAccessLevel,
106
110
  reservedRoomPrefix
107
- } from "../chunk-7QIY2DDX.js";
111
+ } from "../chunk-WVIRXYI4.js";
108
112
  import {
109
113
  CHANNEL_DEFAULT_RATE,
110
114
  DEFAULT_COLLECTION_LIMIT,
@@ -1084,60 +1088,6 @@ function jsonAcl(field, options = {}) {
1084
1088
  return policy;
1085
1089
  }
1086
1090
 
1087
- // src/server/access/policies/members.ts
1088
- function membershipOf(policy) {
1089
- return policy.kind === "members" ? policy.reads.memberships[0] : void 0;
1090
- }
1091
- function checkLevels(levels) {
1092
- if (levels === void 0) {
1093
- return void 0;
1094
- }
1095
- const valid = typeof levels === "object" && levels !== null && !Array.isArray(levels) && Object.values(levels).every(isAccessLevel);
1096
- if (!valid) {
1097
- throw new TypeError(
1098
- 'members({ levels }): levels must map stored roles to access levels, as in { editor: "Moderate" }'
1099
- );
1100
- }
1101
- return Object.freeze({ ...levels });
1102
- }
1103
- function rolesAtLeast(read, level) {
1104
- const enough = new Set(levelsAtLeast(level));
1105
- if (read.levels === void 0) {
1106
- return [...enough];
1107
- }
1108
- return Object.entries(read.levels).filter(([, mapped]) => enough.has(mapped)).map(([role]) => role);
1109
- }
1110
- function members(options) {
1111
- if (typeof options !== "object" || options === null) {
1112
- throw new TypeError("members(options): options must be { model, entry, user, level }");
1113
- }
1114
- const read = Object.freeze({
1115
- model: checkName("members({ model })", "model", options.model),
1116
- entry: checkName("members({ entry })", "entry", options.entry),
1117
- user: checkName("members({ user })", "user", options.user),
1118
- level: checkName("members({ level })", "level", options.level),
1119
- levels: checkLevels(options.levels)
1120
- });
1121
- return definePolicy({
1122
- kind: "members",
1123
- membership: read,
1124
- reads: { columns: [], memberships: [read], inherits: [], storage: true },
1125
- levelsFor: (principal, ids, tools) => tools.memberships(read, principal.userId, ids),
1126
- async accessWhere(principal, level, tools) {
1127
- const roles = rolesAtLeast(read, level);
1128
- if (roles.length === 0) {
1129
- return "none";
1130
- }
1131
- const rows = await tools.storage.findMany(read.model, {
1132
- where: { [read.user]: principal.userId, [read.level]: { in: roles } },
1133
- select: { [read.entry]: true }
1134
- });
1135
- const entries = rows.map((row) => row[read.entry]);
1136
- return idFilter(entries.filter((entry) => typeof entry === "string"));
1137
- }
1138
- });
1139
- }
1140
-
1141
1091
  // src/server/access/policies/inherit.ts
1142
1092
  function isContract3(value) {
1143
1093
  return typeof value === "object" && value !== null && typeof value.name === "string" && Object.isFrozen(value);
@@ -1185,101 +1135,6 @@ function inherit(options) {
1185
1135
  });
1186
1136
  }
1187
1137
 
1188
- // src/server/access/policies/anyOf.ts
1189
- function unionReads(policies) {
1190
- const memberships = /* @__PURE__ */ new Set();
1191
- for (const policy of policies) {
1192
- for (const read of policy.reads.memberships) {
1193
- memberships.add(read);
1194
- }
1195
- }
1196
- return {
1197
- columns: [...new Set(policies.flatMap((policy) => policy.reads.columns))],
1198
- memberships: [...memberships],
1199
- inherits: [...new Set(policies.flatMap((policy) => policy.reads.inherits))],
1200
- parents: policies.flatMap((policy) => policy.reads.parents ?? []),
1201
- storage: policies.some((policy) => policy.reads.storage)
1202
- };
1203
- }
1204
- var MEMBERS = /* @__PURE__ */ new WeakMap();
1205
- function anyOfPolicies(policy) {
1206
- return MEMBERS.get(policy);
1207
- }
1208
- function anyOf(...policies) {
1209
- if (policies.length === 0 || !policies.every(isAccessPolicy)) {
1210
- throw new TypeError("anyOf(...policies): pass one or more access policies");
1211
- }
1212
- const all = Object.freeze([...policies]);
1213
- const combined = definePolicy({
1214
- kind: "anyOf",
1215
- policies: all,
1216
- reads: unionReads(all),
1217
- async levelsFor(principal, ids, tools) {
1218
- const each = await Promise.all(all.map((policy) => policy.levelsFor(principal, ids, tools)));
1219
- const levels = /* @__PURE__ */ new Map();
1220
- for (const id of ids) {
1221
- levels.set(
1222
- id,
1223
- each.reduce((level, granted) => maxLevel(level, granted.get(id)), null)
1224
- );
1225
- }
1226
- return levels;
1227
- },
1228
- async accessWhere(principal, level, tools) {
1229
- const filters = await Promise.all(
1230
- all.map((policy) => policy.accessWhere(principal, level, tools))
1231
- );
1232
- const matching = filters.filter((filter) => filter !== "none");
1233
- if (matching.some((filter) => Object.keys(filter).length === 0)) {
1234
- return {};
1235
- }
1236
- if (matching.length <= 1) {
1237
- return matching[0] ?? "none";
1238
- }
1239
- return { OR: matching };
1240
- }
1241
- });
1242
- MEMBERS.set(combined, all);
1243
- return combined;
1244
- }
1245
-
1246
- // src/server/access/policies/resolver.ts
1247
- function levelIn(levels, id) {
1248
- let level;
1249
- if (levels instanceof Map) {
1250
- level = levels.get(id);
1251
- } else if (Object.hasOwn(levels, id)) {
1252
- level = levels[id];
1253
- }
1254
- return isAccessLevel(level) ? level : null;
1255
- }
1256
- function resolver(options) {
1257
- const valid = typeof options === "object" && options !== null && typeof options.levelsFor === "function" && (options.where === void 0 || typeof options.where === "function");
1258
- if (!valid) {
1259
- throw new TypeError("resolver({ levelsFor, where? }): levelsFor and where must be functions");
1260
- }
1261
- const { levelsFor, where } = options;
1262
- return definePolicy({
1263
- kind: "resolver",
1264
- reads: { columns: [], memberships: [], inherits: [], storage: false },
1265
- async levelsFor(principal, ids, tools) {
1266
- const levels = await levelsFor(principal, ids, tools);
1267
- if (typeof levels !== "object" || levels === null) {
1268
- return levelsById(ids, () => null);
1269
- }
1270
- return levelsById(ids, (id) => levelIn(levels, id));
1271
- },
1272
- async accessWhere(principal, level, tools) {
1273
- if (where === void 0) {
1274
- return "none";
1275
- }
1276
- const filter = await where(principal, level, tools);
1277
- const isFilter = typeof filter === "object" && filter !== null && !Array.isArray(filter);
1278
- return isFilter ? filter : "none";
1279
- }
1280
- });
1281
- }
1282
-
1283
1138
  // src/server/access/policies/everyone.ts
1284
1139
  function everyone(level) {
1285
1140
  if (!isAccessLevel(level) || level === "Public") {
@@ -2483,7 +2338,7 @@ function parseList(value) {
2483
2338
  }
2484
2339
  return Array.isArray(value) && value.every(isEntry) ? value : void 0;
2485
2340
  }
2486
- function levelIn2(entries, userId) {
2341
+ function levelIn(entries, userId) {
2487
2342
  return entries.filter((entry) => entry.userId === userId).reduce((level, entry) => maxLevel(level, entry.level), null);
2488
2343
  }
2489
2344
  function entriesOf(entries, userId) {
@@ -2792,7 +2647,7 @@ function refusal2(listed, change, model) {
2792
2647
  `That user owns this ${model}: an owner's access cannot be shared, changed or taken away`
2793
2648
  );
2794
2649
  }
2795
- if (change.listed && levelIn2(listed.entries, change.userId) === null) {
2650
+ if (change.listed && levelIn(listed.entries, change.userId) === null) {
2796
2651
  return new QuickdrawError("NOT_FOUND", `This ${model} is not shared with that user`);
2797
2652
  }
2798
2653
  return void 0;
@@ -2819,7 +2674,7 @@ async function changeList(call, context, change) {
2819
2674
  throw problem;
2820
2675
  }
2821
2676
  await checkLastAdmin(call, tx, listed, change);
2822
- const before = levelIn2(listed.entries, change.userId);
2677
+ const before = levelIn(listed.entries, change.userId);
2823
2678
  if (before === change.level && entriesOf(listed.entries, change.userId) === 1) {
2824
2679
  return sharesOf(listed.entries);
2825
2680
  }
@@ -3335,6 +3190,53 @@ function checkOverrides(fieldOverrides, names, hidden, fail5) {
3335
3190
  }
3336
3191
  return fieldOverrides;
3337
3192
  }
3193
+ function quoted(names) {
3194
+ return names.map((name) => `"${name}"`).join(", ");
3195
+ }
3196
+ function checkEditable(editable, declared, shown, fail5) {
3197
+ const named = (label, list2) => {
3198
+ for (const name of list2) {
3199
+ if (ADMIN_NEVER_WRITABLE.includes(name)) {
3200
+ fail5(`${label}: "${name}" is never editable; the database sets it`);
3201
+ }
3202
+ if (!shown(name)) {
3203
+ fail5(`${label}: "${name}" is not a field of the entity the kit shows`);
3204
+ }
3205
+ }
3206
+ return new Set(list2);
3207
+ };
3208
+ if (editable === void 0) {
3209
+ return declared === void 0 ? void 0 : named("admin.contract's editable", declared);
3210
+ }
3211
+ const valid = Array.isArray(editable) && editable.every((name) => typeof name === "string") && new Set(editable).size === editable.length;
3212
+ if (!valid) {
3213
+ fail5("editable must be a list of distinct field names");
3214
+ }
3215
+ const list = named("editable", editable);
3216
+ if (declared !== void 0 && (declared.length !== list.size || !declared.every((name) => list.has(name)))) {
3217
+ fail5(
3218
+ `editable names ${quoted(editable)}, but admin.contract was given ${quoted(declared)}: give both halves the same fields`
3219
+ );
3220
+ }
3221
+ return list;
3222
+ }
3223
+ function checkAgreement(overrides, editable, fail5) {
3224
+ if (editable === void 0) {
3225
+ return;
3226
+ }
3227
+ for (const [name, override] of Object.entries(overrides)) {
3228
+ if (override.editable === true && !editable.has(name)) {
3229
+ fail5(
3230
+ `fieldOverrides: "${name}" is editable: true, which editable leaves out; name it in editable instead`
3231
+ );
3232
+ }
3233
+ if (override.editable === false && editable.has(name)) {
3234
+ fail5(
3235
+ `fieldOverrides: "${name}" is editable: false, which editable names; leave it out of editable instead`
3236
+ );
3237
+ }
3238
+ }
3239
+ }
3338
3240
  function derived(field, grants) {
3339
3241
  const fixed = Object.hasOwn(DEFAULT_FIELDS, field.name) ? DEFAULT_FIELDS[field.name] : void 0;
3340
3242
  if (fixed !== void 0) {
@@ -3372,10 +3274,18 @@ function adminFieldsOf(serviceName, spec, options, fail5) {
3372
3274
  fail5(`"${listed}" is hidden, so adminList may not filter or sort on it`);
3373
3275
  }
3374
3276
  const overrides = checkOverrides(options.fieldOverrides, names, hidden, fail5);
3277
+ const editable = checkEditable(
3278
+ options.editable,
3279
+ spec.editable,
3280
+ (name) => names.includes(name) && !hidden.has(name),
3281
+ fail5
3282
+ );
3283
+ checkAgreement(overrides, editable, fail5);
3375
3284
  const fields = ordered(spec.fields).filter((field) => !hidden.has(field.name)).map(
3376
3285
  (field) => frozenConfig({
3377
3286
  ...derived(field, grants),
3378
3287
  ...overrides[field.name],
3288
+ ...editable === void 0 ? {} : { editable: editable.has(field.name) },
3379
3289
  sortable: spec.sort.includes(field.name),
3380
3290
  filterable: spec.filter.includes(field.name)
3381
3291
  })
@@ -3701,6 +3611,7 @@ var OPTION_KEYS4 = [
3701
3611
  "access",
3702
3612
  "displayName",
3703
3613
  "hiddenFields",
3614
+ "editable",
3704
3615
  "fieldOverrides",
3705
3616
  "rowless",
3706
3617
  "grants",
@@ -3741,7 +3652,7 @@ function checkOptions3(options) {
3741
3652
  }
3742
3653
  if (!isRecord11(options)) {
3743
3654
  fail4(
3744
- "options must be { access?, displayName?, hiddenFields?, fieldOverrides?, rowless?, grants?, onWrite?, onCommitted? }"
3655
+ "options must be { access?, displayName?, hiddenFields?, editable?, fieldOverrides?, rowless?, grants?, onWrite?, onCommitted? }"
3745
3656
  );
3746
3657
  }
3747
3658
  const unknownKey = Object.keys(options).find((key) => !OPTION_KEYS4.includes(key));
@@ -3768,13 +3679,28 @@ function checkAccess3(access, names) {
3768
3679
  }
3769
3680
  return access;
3770
3681
  }
3682
+ function contractEditable(contract, specs) {
3683
+ const lists = /* @__PURE__ */ new Map();
3684
+ for (const { editable } of specs) {
3685
+ if (editable !== void 0) {
3686
+ lists.set([...editable].sort().join("\0"), editable);
3687
+ }
3688
+ }
3689
+ if (lists.size > 1) {
3690
+ fail4(
3691
+ `admin.contract was given two editable lists for ${contract.name}: give its write methods one`
3692
+ );
3693
+ }
3694
+ return [...lists.values()][0];
3695
+ }
3771
3696
  function fieldsOf(contract, kit, options) {
3772
3697
  const specs = kit.map(([, spec2]) => spec2);
3773
3698
  const [first] = specs;
3774
3699
  const spec = {
3775
3700
  fields: first?.fields ?? [],
3776
3701
  filter: [...new Set(specs.flatMap((one) => one.filter))],
3777
- sort: [...new Set(specs.flatMap((one) => one.sort))]
3702
+ sort: [...new Set(specs.flatMap((one) => one.sort))],
3703
+ editable: contractEditable(contract, specs)
3778
3704
  };
3779
3705
  return adminFieldsOf(
3780
3706
  contract.name,
@@ -3782,6 +3708,7 @@ function fieldsOf(contract, kit, options) {
3782
3708
  {
3783
3709
  displayName: options.displayName,
3784
3710
  hiddenFields: options.hiddenFields,
3711
+ editable: options.editable,
3785
3712
  fieldOverrides: options.fieldOverrides,
3786
3713
  grants: options.grants
3787
3714
  },