@fitzzero/quickdraw-core 5.1.1 → 5.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1308 -219
- package/dist/{admin-DKwkIh5i.d.ts → admin-CS7JB_UH.d.ts} +2 -2
- package/dist/{adminSchemas-2DUA_hZ8.d.ts → adminSchemas-29cGgrg5.d.ts} +6 -4
- package/dist/{auth-BhWqdyb-.d.ts → auth-DtdYjrZ9.d.ts} +51 -21
- package/dist/{caller-CXTFswBO.d.ts → caller-BTwGl7vm.d.ts} +296 -33
- package/dist/chunk-2FDSKLBO.js +82 -0
- package/dist/chunk-2FDSKLBO.js.map +1 -0
- package/dist/{chunk-RT6OILOI.js → chunk-4D2DOGAW.js} +2 -14
- package/dist/chunk-4D2DOGAW.js.map +1 -0
- package/dist/{chunk-OECZPRPW.js → chunk-64I6MYBC.js} +162 -775
- package/dist/chunk-64I6MYBC.js.map +1 -0
- package/dist/chunk-7473HK24.js +17 -0
- package/dist/chunk-7473HK24.js.map +1 -0
- package/dist/chunk-7Y5ROPXL.js +33 -0
- package/dist/chunk-7Y5ROPXL.js.map +1 -0
- package/dist/chunk-A2RBQD66.js +7 -0
- package/dist/chunk-A2RBQD66.js.map +1 -0
- package/dist/chunk-AK23XSWD.js +16 -0
- package/dist/chunk-AK23XSWD.js.map +1 -0
- package/dist/chunk-ASAMUGHG.js +651 -0
- package/dist/chunk-ASAMUGHG.js.map +1 -0
- package/dist/{chunk-B2P7AZ25.js → chunk-CYE4UBOB.js} +6 -4
- package/dist/chunk-CYE4UBOB.js.map +1 -0
- package/dist/chunk-DKE6QO26.js +136 -0
- package/dist/chunk-DKE6QO26.js.map +1 -0
- package/dist/chunk-DNP65TD5.js +832 -0
- package/dist/chunk-DNP65TD5.js.map +1 -0
- package/dist/{chunk-H23KEFBA.js → chunk-DUBU3BB3.js} +30 -1
- package/dist/chunk-DUBU3BB3.js.map +1 -0
- package/dist/chunk-GYLWEJHW.js +19 -0
- package/dist/chunk-GYLWEJHW.js.map +1 -0
- package/dist/{chunk-SHEGLZEP.js → chunk-I3JCFS7H.js} +34 -13
- package/dist/chunk-I3JCFS7H.js.map +1 -0
- package/dist/{chunk-X5CWPX7H.js → chunk-IIQ3WMJK.js} +13 -2
- package/dist/chunk-IIQ3WMJK.js.map +1 -0
- package/dist/{chunk-RZTP4IEW.js → chunk-JVTONQFI.js} +6339 -4226
- package/dist/chunk-JVTONQFI.js.map +1 -0
- package/dist/{chunk-QEKVYG5F.js → chunk-KKQANRLG.js} +2 -2
- package/dist/chunk-KKQANRLG.js.map +1 -0
- package/dist/{chunk-CDKG4TFA.js → chunk-MPQAT55F.js} +38 -205
- package/dist/chunk-MPQAT55F.js.map +1 -0
- package/dist/chunk-NO4M6PO5.js +101 -0
- package/dist/chunk-NO4M6PO5.js.map +1 -0
- package/dist/{chunk-TBMKJ26R.js → chunk-NW6U45HX.js} +3 -1
- package/dist/chunk-NW6U45HX.js.map +1 -0
- package/dist/chunk-NX2V6SIL.js +202 -0
- package/dist/chunk-NX2V6SIL.js.map +1 -0
- package/dist/{chunk-ZIVPHYRG.js → chunk-NZHA4TZD.js} +72 -175
- package/dist/chunk-NZHA4TZD.js.map +1 -0
- package/dist/{chunk-2PWMUVC7.js → chunk-OCZJ4EYV.js} +518 -27
- package/dist/chunk-OCZJ4EYV.js.map +1 -0
- package/dist/{chunk-REX5YPHX.js → chunk-PHCURHC7.js} +76 -21
- package/dist/chunk-PHCURHC7.js.map +1 -0
- package/dist/{chunk-4YO5H5ON.js → chunk-Q4OGQM5I.js} +4 -10
- package/dist/chunk-Q4OGQM5I.js.map +1 -0
- package/dist/chunk-QSU47XEX.js +13 -0
- package/dist/chunk-QSU47XEX.js.map +1 -0
- package/dist/{chunk-DD4SKCNO.js → chunk-RZXJTDH5.js} +13 -2
- package/dist/chunk-RZXJTDH5.js.map +1 -0
- package/dist/{chunk-Z2LZ7IZ7.js → chunk-SN3OZVEQ.js} +5 -31
- package/dist/chunk-SN3OZVEQ.js.map +1 -0
- package/dist/{chunk-4DIQHASK.js → chunk-SQ5VUG77.js} +28 -8
- package/dist/chunk-SQ5VUG77.js.map +1 -0
- package/dist/chunk-STH5DLQP.js +1192 -0
- package/dist/chunk-STH5DLQP.js.map +1 -0
- package/dist/{chunk-QCHKWIRS.js → chunk-VZFJTKIW.js} +13 -13
- package/dist/{chunk-QCHKWIRS.js.map → chunk-VZFJTKIW.js.map} +1 -1
- package/dist/{chunk-VSQBHPWH.js → chunk-X4ZU4QH5.js} +54 -22
- package/dist/chunk-X4ZU4QH5.js.map +1 -0
- package/dist/chunk-XD7SRDUY.js +188 -0
- package/dist/chunk-XD7SRDUY.js.map +1 -0
- package/dist/chunk-XH2IZVLT.js +62 -0
- package/dist/chunk-XH2IZVLT.js.map +1 -0
- package/dist/chunk-Z7I6SONO.js +825 -0
- package/dist/chunk-Z7I6SONO.js.map +1 -0
- package/dist/cli/quickdraw-docs.js +132 -112
- package/dist/cli/quickdraw-docs.js.map +1 -1
- package/dist/client/core.d.ts +387 -0
- package/dist/client/core.js +109 -0
- package/dist/client/index.d.ts +52 -203
- package/dist/client/index.js +59 -218
- package/dist/client/index.js.map +1 -1
- package/dist/{clientTypes-Bc-G8n-U.d.ts → clientTypes-CUUNJwWs.d.ts} +3 -3
- package/dist/{connection-Cr1BU07W.d.ts → connection-2EeQ_Et0.d.ts} +22 -11
- package/dist/{createServer-CP9eDHZZ.d.ts → createServer-Bx5HOcYK.d.ts} +67 -17
- package/dist/{createTestApp-UKVGEmOf.d.ts → createTestApp-DAMANT79.d.ts} +46 -13
- package/dist/{envelope-Cp3YcVxC.d.ts → envelope-BMBaxppV.d.ts} +216 -10
- package/dist/{http-Djj2Inuu.d.ts → http-YzLAar3w.d.ts} +76 -60
- package/dist/index.d.ts +8 -7
- package/dist/index.js +82 -25
- package/dist/index.js.map +1 -1
- package/dist/{infer-Teo1xpsd.d.ts → infer-Br62cnyW.d.ts} +82 -11
- package/dist/logger-Yz93KzdV.d.ts +30 -0
- package/dist/{metrics-BdBu5o8G.d.ts → metrics-CTVpw0gu.d.ts} +2 -2
- package/dist/origins-kvpxnVcW.d.ts +8 -0
- package/dist/parser.js +2 -1
- package/dist/prisma/index.d.ts +14 -7
- package/dist/prisma/index.js +279 -70
- package/dist/prisma/index.js.map +1 -1
- package/dist/{rateLimit-ZMIZJUZF.js → rateLimit-5RKBFLUR.js} +2 -2
- package/dist/rateLimit-5RKBFLUR.js.map +1 -0
- package/dist/remote-N7OUAGOW.js +15 -0
- package/dist/remote-N7OUAGOW.js.map +1 -0
- package/dist/server/auth/index.d.ts +82 -33
- package/dist/server/auth/index.js +277 -270
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/express/index.d.ts +9 -2
- package/dist/server/express/index.js +1 -1
- package/dist/server/index.d.ts +268 -84
- package/dist/server/index.js +664 -418
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/index.d.ts +89 -27
- package/dist/server/mcp/index.js +281 -62
- package/dist/server/mcp/index.js.map +1 -1
- package/dist/server/otel.d.ts +3 -3
- package/dist/{sharing-DVa8W-yR.d.ts → sharing-BrsmlDv_.d.ts} +3 -3
- package/dist/testing/client.d.ts +14 -14
- package/dist/testing/client.js +19 -10
- package/dist/testing/client.js.map +1 -1
- package/dist/testing/index.d.ts +50 -13
- package/dist/testing/index.js +113 -21
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/mock.d.ts +5 -5
- package/dist/testing/mock.js +17 -9
- package/dist/tracked-BR5b2O-U.d.ts +43 -0
- package/dist/{types-Cu0vmx7c.d.ts → types-CLRCQGnO.d.ts} +4 -3
- package/dist/types-tMagHbjy.d.ts +151 -0
- package/dist/utils/index.d.ts +16 -4
- package/dist/utils/index.js +6 -3
- package/package.json +5 -1
- package/dist/chunk-2PWMUVC7.js.map +0 -1
- package/dist/chunk-4DIQHASK.js.map +0 -1
- package/dist/chunk-4YO5H5ON.js.map +0 -1
- package/dist/chunk-B2P7AZ25.js.map +0 -1
- package/dist/chunk-CDKG4TFA.js.map +0 -1
- package/dist/chunk-DD4SKCNO.js.map +0 -1
- package/dist/chunk-H23KEFBA.js.map +0 -1
- package/dist/chunk-OECZPRPW.js.map +0 -1
- package/dist/chunk-QEKVYG5F.js.map +0 -1
- package/dist/chunk-REX5YPHX.js.map +0 -1
- package/dist/chunk-RT6OILOI.js.map +0 -1
- package/dist/chunk-RZTP4IEW.js.map +0 -1
- package/dist/chunk-SHEGLZEP.js.map +0 -1
- package/dist/chunk-TBMKJ26R.js.map +0 -1
- package/dist/chunk-VSQBHPWH.js.map +0 -1
- package/dist/chunk-X5CWPX7H.js.map +0 -1
- package/dist/chunk-YSWH776Y.js +0 -13
- package/dist/chunk-YSWH776Y.js.map +0 -1
- package/dist/chunk-Z2LZ7IZ7.js.map +0 -1
- package/dist/chunk-ZIVPHYRG.js.map +0 -1
- package/dist/logger-BY28acW-.d.ts +0 -16
- /package/dist/{rateLimit-ZMIZJUZF.js.map → client/core.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/contract/kits/crud.ts","../src/contract/kits/sharingSchemas.ts","../src/contract/kits/sharing.ts"],"sourcesContent":["// The read/write kit's contract half (RFC 0003 section 12.1). `crud.contract`\n// returns ordinary `query` and `mutation` entries to spread into a contract's\n// `methods`, one per method it names and no others:\n//\n// export const task = defineContract(\"taskService\", {\n// entity: taskSchema,\n// projections: { card: cardSchema },\n// methods: {\n// ...crud.contract({\n// entity: taskSchema,\n// get: true,\n// list: { item: cardSchema, filter: [\"projectId\", \"status\"], sort: [\"ordinal\"] },\n// update: { input: taskPatch },\n// }),\n// rename: mutation({ ... }),\n// },\n// });\n//\n// The server half (`crud.handlers` on `./server`) finds the kit's methods in\n// the contract and implements exactly those: each entry carries what it\n// needs (`crudSpecOf`), and its type carries which kit method it is\n// (`CrudTag`), so the two halves cannot disagree. A client sees ordinary\n// members. About 270 of the 579 methods in the seven smaller 4.1 apps are\n// these shapes, each with its own paging and guards.\n\nimport type { RowSchema } from \"../defineContract\";\nimport {\n listOf,\n mutation,\n query,\n type MethodDef,\n type MutationDef,\n type ProjectionList,\n type QueryDef,\n} from \"../methods\";\nimport { isStandardSchema, type InferOutput, type StandardSchemaV1 } from \"../standardSchema\";\nimport { listInput, pageOutput, type ListPage, type ListQuery, type ListSort } from \"./crudList\";\nimport {\n bulkPatch,\n countOutput,\n CRUD_MAX_IDS,\n idInput,\n idsInput,\n nullOutput,\n reorderInput,\n withId,\n type BulkPatch,\n type BulkResult,\n type IdInput,\n type IdsInput,\n type ReorderInput,\n type WithId,\n} from \"./crudSchemas\";\nimport type { KitSchema } from \"./schemas\";\n\n/** The methods the read/write kit can add, each opt-in by name. */\nexport type CrudMethodName =\n | \"get\"\n | \"getMany\"\n | \"list\"\n | \"create\"\n | \"update\"\n | \"delete\"\n | \"reorder\"\n | \"bulkUpdate\"\n | \"bulkDelete\";\n\n/** Every method of the read/write kit, in the order of RFC 0003 section 12.1. */\nexport const CRUD_METHODS: readonly CrudMethodName[] = Object.freeze([\n \"get\",\n \"getMany\",\n \"list\",\n \"create\",\n \"update\",\n \"delete\",\n \"reorder\",\n \"bulkUpdate\",\n \"bulkDelete\",\n]);\n\n/** Type-only: marks a method the read/write kit made, and which one. Never set. */\nexport interface CrudTag<Kind extends CrudMethodName> {\n readonly \"~crud\"?: Kind;\n}\n\ntype RowOf<Entity> = Entity extends StandardSchemaV1 ? InferOutput<Entity> : never;\n\n/** The entity's fields holding strings, numbers or booleans: what `list` filters and sorts on. */\nexport type ScalarFieldOf<Row> = {\n [Field in keyof Row]-?: NonNullable<Row[Field]> extends string | number | boolean ? Field : never;\n}[keyof Row] &\n string;\n\n/** The entity's fields holding numbers: a `reorder` column. */\nexport type NumberFieldOf<Row> = {\n [Field in keyof Row]-?: NonNullable<Row[Field]> extends number ? Field : never;\n}[keyof Row] &\n string;\n\ninterface Described {\n /** The method's description, for people and agents; the kit's own when absent. */\n readonly describe?: string;\n}\n\n/** Turns a method without options on: `true`, or `{ describe }`. */\nexport type CrudToggle = boolean | Described;\n\n/** `list`'s options. */\nexport interface CrudListOptions<Field extends string = string> extends Described {\n /**\n * The schema of each item: the contract's entity schema (the default) or\n * one of its projections' schemas, the same object.\n */\n readonly item?: RowSchema;\n /** The fields a caller may filter on, by equality. */\n readonly filter?: readonly Field[];\n /** The fields a caller may sort by; the first is the default order. */\n readonly sort?: readonly Field[];\n}\n\n/** `create`'s, `update`'s and `bulkUpdate`'s options: the app's schema of the fields written. */\nexport interface CrudInputOptions extends Described {\n /**\n * `create`: the new row's fields. `update` and `bulkUpdate`: the fields a\n * call may change, every one optional (`z.object({...}).partial()`); the\n * kit adds `id`, or `ids`.\n */\n readonly input: StandardSchemaV1;\n}\n\n/** `reorder`'s options. */\nexport interface CrudReorderOptions<\n Column extends string = string,\n Scope extends string = string,\n> extends Described {\n /** The numeric column rows are ordered by, ascending. */\n readonly column: Column;\n /** The columns whose values make one ordered list (`\"projectId\"`); the whole table when absent. */\n readonly within?: Scope | readonly Scope[];\n}\n\n/** `crud.contract`'s options: the entity, and each method to add. */\nexport interface CrudContractOptions {\n /** The contract's entity schema: the fields `list` and `reorder` name are its fields. */\n readonly entity: RowSchema;\n readonly get?: CrudToggle;\n readonly getMany?: CrudToggle;\n readonly list?: CrudListOptions;\n readonly create?: CrudInputOptions;\n readonly update?: CrudInputOptions;\n readonly delete?: CrudToggle;\n readonly reorder?: CrudReorderOptions;\n readonly bulkUpdate?: CrudInputOptions;\n readonly bulkDelete?: CrudToggle;\n}\n\ntype Quoted<Names> = `\"${Names & string}\"`;\n\ntype NamesIn<Value> = Value extends string\n ? Value\n : Value extends readonly (infer Name)[]\n ? Name\n : never;\n\ntype OptionNames<O, Option extends string, Key extends string> = O extends {\n readonly [K in Option]: { readonly [P in Key]: infer Value };\n}\n ? NamesIn<Value>\n : never;\n\n/** A broken option becomes a message: replacing the whole option keeps TypeScript from printing `never`. */\ntype Problem<Option extends string, Wrong, Text extends string> = [Wrong] extends [never]\n ? unknown\n : { readonly [K in Option]: `crud.contract: ${Option}: ${Quoted<Wrong>} ${Text}` };\n\n/** The checks against the entity's fields that the option types alone cannot state. */\ntype CrudChecks<O> = O extends { readonly entity: infer Entity }\n ? Problem<\n \"list\",\n Exclude<\n OptionNames<O, \"list\", \"filter\"> | OptionNames<O, \"list\", \"sort\">,\n ScalarFieldOf<RowOf<Entity>>\n >,\n \"is not a field of the entity holding strings, numbers or booleans\"\n > &\n Problem<\n \"reorder\",\n Exclude<OptionNames<O, \"reorder\", \"column\">, NumberFieldOf<RowOf<Entity>>>,\n \"is not a field of the entity holding numbers\"\n > &\n Problem<\n \"reorder\",\n Exclude<OptionNames<O, \"reorder\", \"within\">, ScalarFieldOf<RowOf<Entity>>>,\n \"is not a field of the entity holding strings, numbers or booleans\"\n >\n : unknown;\n\n/** What a `list` call passes: a filter and a sort over the declared fields, and paging. */\nexport type ListInput<Row, Filter extends string, Sort extends string> = {\n readonly filter?: [Filter] extends [never]\n ? undefined\n : { readonly [Field in Filter]?: Field extends keyof Row ? Row[Field] : never };\n readonly sort?: [Sort] extends [never] ? undefined : ListSort<Sort>;\n /** The `nextCursor` of the page before; the first page when absent. */\n readonly cursor?: string;\n /** The page size: default 50, at most 200 (a larger one is clamped). */\n readonly limit?: number;\n /** Also count every matching row (a second statement). */\n readonly totalCount?: boolean;\n};\n\nexport type CrudGet = QueryDef<KitSchema<IdInput>, \"entity\"> & CrudTag<\"get\">;\nexport type CrudGetMany = QueryDef<\n KitSchema<IdsInput, { readonly ids: string[] }>,\n ProjectionList<\"entity\">\n> &\n CrudTag<\"getMany\">;\nexport type CrudList<Row, Item, Filter extends string, Sort extends string> = QueryDef<\n KitSchema<ListInput<Row, Filter, Sort> | undefined, ListQuery<Filter, Sort>>,\n KitSchema<ListPage<Item>>\n> &\n CrudTag<\"list\">;\nexport type CrudCreate<Input extends StandardSchemaV1> = MutationDef<Input, \"entity\"> &\n CrudTag<\"create\">;\nexport type CrudUpdate<Patch extends StandardSchemaV1> = MutationDef<WithId<Patch>, \"entity\"> &\n CrudTag<\"update\">;\nexport type CrudDelete = MutationDef<KitSchema<IdInput>, KitSchema<null>> & CrudTag<\"delete\">;\nexport type CrudReorder = MutationDef<KitSchema<ReorderInput>, \"entity\"> & CrudTag<\"reorder\">;\nexport type CrudBulkUpdate<Patch extends StandardSchemaV1> = MutationDef<\n BulkPatch<Patch>,\n KitSchema<BulkResult>\n> &\n CrudTag<\"bulkUpdate\">;\nexport type CrudBulkDelete = MutationDef<\n KitSchema<IdsInput, { readonly ids: string[] }>,\n KitSchema<BulkResult>\n> &\n CrudTag<\"bulkDelete\">;\n\ntype EntityIn<O> = O extends { readonly entity: infer Entity } ? Entity : never;\n\ntype ListFieldsIn<O, Key extends \"filter\" | \"sort\"> = O extends {\n readonly list: { readonly [K in Key]: readonly (infer Field extends string)[] };\n}\n ? Field\n : never;\n\ntype ListItemIn<O> = O extends { readonly list: { readonly item: infer Item } }\n ? RowOf<Item>\n : RowOf<EntityIn<O>>;\n\ntype InputIn<O, Name extends \"create\" | \"update\" | \"bulkUpdate\"> = O extends {\n readonly [K in Name]: { readonly input: infer Input extends StandardSchemaV1 };\n}\n ? Input\n : never;\n\ntype CrudMethodOf<O, Name extends CrudMethodName> = {\n get: CrudGet;\n getMany: CrudGetMany;\n list: CrudList<\n RowOf<EntityIn<O>>,\n ListItemIn<O>,\n ListFieldsIn<O, \"filter\">,\n ListFieldsIn<O, \"sort\">\n >;\n create: CrudCreate<InputIn<O, \"create\">>;\n update: CrudUpdate<InputIn<O, \"update\">>;\n delete: CrudDelete;\n reorder: CrudReorder;\n bulkUpdate: CrudBulkUpdate<InputIn<O, \"bulkUpdate\">>;\n bulkDelete: CrudBulkDelete;\n}[Name];\n\ntype Enabled<O, Name extends CrudMethodName> = Name extends keyof O\n ? [O[Name]] extends [false | undefined]\n ? never\n : Name\n : never;\n\n/** The entries `crud.contract(options)` returns: one per method the options turn on. */\nexport type CrudMethods<O> = {\n readonly [Name in CrudMethodName as Enabled<O, Name>]: CrudMethodOf<O, Name>;\n};\n\n/** What the server half needs to know about one of the kit's methods. */\nexport type CrudSpec =\n | { readonly method: Exclude<CrudMethodName, \"list\" | \"reorder\"> }\n | {\n readonly method: \"list\";\n readonly filter: readonly string[];\n readonly sort: readonly string[];\n /** The item's schema; `undefined` for the entity. */\n readonly item: RowSchema | undefined;\n }\n | { readonly method: \"reorder\"; readonly column: string; readonly within: readonly string[] };\n\nconst SPECS = new WeakMap<object, CrudSpec>();\n\n/** What the read/write kit made `method` for, or `undefined` for any other method. */\nexport function crudSpecOf(method: unknown): CrudSpec | undefined {\n return typeof method === \"object\" && method !== null ? SPECS.get(method) : undefined;\n}\n\nconst DESCRIBE: Readonly<Record<CrudMethodName, string>> = Object.freeze({\n get: \"Reads one row by id.\",\n getMany: `Reads up to ${CRUD_MAX_IDS} rows by id, in the order asked; ids the caller cannot read, or with no row, are left out.`,\n list: \"Lists the rows the caller can read, a page at a time, filtered and sorted by the declared fields. Pass a page's nextCursor back as cursor for the next page.\",\n create: \"Creates a row.\",\n update: \"Changes the given fields of one row.\",\n delete: \"Deletes one row.\",\n reorder:\n \"Moves a row between two others: beforeId is the row that will come right before it, afterId the row right after; one is enough.\",\n bulkUpdate: `Changes the same fields on up to ${CRUD_MAX_IDS} rows, skipping rows the caller cannot change; returns how many changed.`,\n bulkDelete: `Deletes up to ${CRUD_MAX_IDS} rows, skipping rows the caller cannot delete; returns how many were deleted.`,\n});\n\ntype UnknownRecord = Readonly<Record<string, unknown>>;\n\nfunction isRecord(value: unknown): value is UnknownRecord {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction fail(message: string): never {\n throw new TypeError(`crud.contract: ${message}`);\n}\n\nfunction checkKeys(owner: string, value: UnknownRecord, allowed: readonly string[]): void {\n const unknownKey = Object.keys(value).find((key) => !allowed.includes(key));\n if (unknownKey !== undefined) {\n fail(`${owner} has an unknown option \"${unknownKey}\"; the options are ${allowed.join(\", \")}`);\n }\n}\n\n/** The method's description: its own `describe`, or the kit's. */\nfunction describeOf(name: CrudMethodName, option: unknown): string {\n const own = isRecord(option) ? option.describe : undefined;\n if (own !== undefined && (typeof own !== \"string\" || own.length === 0)) {\n fail(`${name}: describe must be a non-empty string`);\n }\n return own ?? DESCRIBE[name];\n}\n\nfunction checkToggle(name: CrudMethodName, option: unknown): void {\n if (typeof option !== \"boolean\" && !isRecord(option)) {\n fail(`${name} must be true, false or { describe }`);\n }\n if (isRecord(option)) {\n checkKeys(name, option, [\"describe\"]);\n }\n}\n\nfunction checkFields(owner: string, fields: unknown): readonly string[] {\n if (fields === undefined) {\n return [];\n }\n const valid =\n Array.isArray(fields) &&\n fields.every((field) => typeof field === \"string\" && field.length > 0) &&\n new Set(fields).size === fields.length;\n if (!valid) {\n fail(`${owner} must be a list of distinct field names`);\n }\n return Object.freeze([...(fields as string[])]);\n}\n\nfunction listSpec(option: unknown): CrudSpec {\n if (!isRecord(option)) {\n fail(\"list must be { item?, filter?, sort?, describe? }\");\n }\n checkKeys(\"list\", option, [\"item\", \"filter\", \"sort\", \"describe\"]);\n if (option.item !== undefined && !isStandardSchema(option.item)) {\n fail(\"list.item must be the entity schema or one of the contract's projection schemas\");\n }\n return {\n method: \"list\",\n filter: checkFields(\"list.filter\", option.filter),\n sort: checkFields(\"list.sort\", option.sort),\n item: option.item as RowSchema | undefined,\n };\n}\n\nfunction inputOf(name: CrudMethodName, option: unknown): StandardSchemaV1 {\n if (!isRecord(option) || !isStandardSchema(option.input)) {\n fail(`${name} must be { input: <Standard Schema>, describe? }`);\n }\n checkKeys(name, option, [\"input\", \"describe\"]);\n return option.input;\n}\n\nfunction reorderSpec(option: unknown): CrudSpec {\n if (!isRecord(option) || typeof option.column !== \"string\" || option.column.length === 0) {\n fail(\"reorder must be { column, within?, describe? } with the name of a numeric column\");\n }\n checkKeys(\"reorder\", option, [\"column\", \"within\", \"describe\"]);\n const { column } = option;\n const within = checkFields(\n \"reorder.within\",\n typeof option.within === \"string\" ? [option.within] : option.within,\n );\n if (column === \"id\" || within.includes(column)) {\n fail(\"reorder.column must be a numeric column other than id and the within columns\");\n }\n return { method: \"reorder\", column, within };\n}\n\n/** One method of the kit: its entry, and what the server half needs to know about it. */\nfunction build(\n name: CrudMethodName,\n options: UnknownRecord,\n entity: StandardSchemaV1,\n): { def: MethodDef; spec: CrudSpec } {\n const option = options[name];\n const describe = describeOf(name, option);\n switch (name) {\n case \"list\": {\n const spec = listSpec(option) as Extract<CrudSpec, { method: \"list\" }>;\n const input = listInput(spec, entity);\n return { def: query({ input, output: pageOutput(spec.item ?? entity), describe }), spec };\n }\n case \"create\":\n return {\n def: mutation({ input: inputOf(name, option), output: \"entity\", describe }),\n spec: { method: name },\n };\n case \"update\":\n return {\n def: mutation({ input: withId(inputOf(name, option)), output: \"entity\", describe }),\n spec: { method: name },\n };\n case \"bulkUpdate\":\n return {\n def: mutation({ input: bulkPatch(inputOf(name, option)), output: countOutput(), describe }),\n spec: { method: name },\n };\n case \"reorder\":\n return {\n def: mutation({ input: reorderInput(), output: \"entity\", describe }),\n spec: reorderSpec(option),\n };\n default:\n checkToggle(name, option);\n return { def: toggled(name, describe), spec: { method: name } };\n }\n}\n\n/** The entry of a method without options. */\nfunction toggled(name: CrudMethodName, describe: string): MethodDef {\n switch (name) {\n case \"get\":\n return query({ input: idInput(), output: \"entity\", describe });\n case \"getMany\":\n return query({ input: idsInput(), output: listOf(\"entity\"), describe });\n case \"delete\":\n return mutation({ input: idInput(), output: nullOutput(), describe });\n default:\n return mutation({ input: idsInput(), output: countOutput(), describe });\n }\n}\n\nfunction checkOptions(options: unknown): UnknownRecord {\n if (!isRecord(options)) {\n fail(\"options must be { entity, ...methods }\");\n }\n checkKeys(\"the options\", options, [\"entity\", ...CRUD_METHODS]);\n if (!isStandardSchema(options.entity)) {\n fail(\"entity must be the contract's entity schema\");\n }\n return options;\n}\n\nfunction contract<const O extends CrudContractOptions>(\n options: O & NoInfer<CrudChecks<O>>,\n): CrudMethods<O> {\n const checked = checkOptions(options);\n const entity = checked.entity as StandardSchemaV1;\n const methods: Record<string, MethodDef> = {};\n for (const name of CRUD_METHODS) {\n const option = checked[name];\n if (option === undefined || option === false) {\n continue;\n }\n const { def, spec } = build(name, checked, entity);\n SPECS.set(def, Object.freeze(spec));\n methods[name] = def;\n }\n return Object.freeze(methods) as unknown as CrudMethods<O>;\n}\n\n/**\n * The read/write kit's contract half: `crud.contract(options)`. Each method is\n * opt-in by name (`get: true`, `list: { filter, sort }`, `update: { input }`),\n * and the server half (`crud.handlers` on `./server`) implements exactly the\n * methods named.\n */\nexport const crud = Object.freeze({ contract });\n","// The inputs and outputs the sharing and membership kit generates (RFC 0003\n// section 12.3). An access list method names its row as `id` and its user as\n// `userId`, and grants one of the levels a list entry can usefully hold\n// (`Read`, `Moderate` or `Admin`); a membership method names its row as\n// `entryId` and a stored role as `role`, which the server checks against the\n// service's `members` policy (the contract cannot know its roles). The\n// by-name methods take `name` or `email` instead of `userId`, for the\n// server's `resolveUser`. `listMembers` pages like `list` (`crudList.ts`):\n// 50 members by default, at most 200, by keyset cursor.\n//\n// Outputs: an access list as `ACL` (`[{ userId, level }]`, one entry per\n// user), a member as `{ userId, role, level }`, a page of members, or `null`.\n\nimport { ACCESS_LEVELS, isAccessLevel, type ACL, type AccessLevel } from \"../access\";\nimport type { StandardSchemaV1 } from \"../standardSchema\";\nimport { LIST_DEFAULT_LIMIT, LIST_MAX_LIMIT, MAX_CURSOR_LENGTH, pagingIssues } from \"./crudList\";\nimport {\n idJson,\n isId,\n isRecord,\n kitSchema,\n objectJson,\n unknownKeys,\n type JsonSchema,\n type KitSchema,\n} from \"./schemas\";\n\n/** A level an access list entry is given: `Public` grants nothing, so it is not one. */\nexport type ShareLevel = Exclude<AccessLevel, \"Public\">;\n\n/** The levels `share` and `setLevel` grant, lowest first. */\nexport const SHARE_LEVELS: readonly ShareLevel[] = Object.freeze([\"Read\", \"Moderate\", \"Admin\"]);\n\n/** The longest role, name or email the kit accepts. */\nconst MAX_TEXT_LENGTH = 256;\n\n/** `share` and `setLevel`: give `userId` `level` on the row `id`. */\nexport interface ShareInput {\n readonly id: string;\n readonly userId: string;\n readonly level: ShareLevel;\n}\n\n/** `unshare`: take `userId`'s level on the row `id` away. */\nexport interface UnshareInput {\n readonly id: string;\n readonly userId: string;\n}\n\n/** Who a by-name method means: their name or their email, at least one. */\nexport interface UserLookupInput {\n readonly name?: string;\n readonly email?: string;\n}\n\n/** `shareByName`: `share`, with the user found by name or email. */\nexport interface ShareByNameInput extends UserLookupInput {\n readonly id: string;\n readonly level: ShareLevel;\n}\n\n/** `shareByName`'s input as its handler receives it. */\nexport interface ShareByNameQuery {\n readonly id: string;\n readonly name: string | undefined;\n readonly email: string | undefined;\n readonly level: ShareLevel;\n}\n\n/** `invite`: make `userId` a member of the row `entryId`, with `role` (the lowest by default). */\nexport interface InviteInput {\n readonly entryId: string;\n readonly userId: string;\n readonly role?: string;\n}\n\n/** `invite`'s input as its handler receives it. */\nexport interface InviteQuery {\n readonly entryId: string;\n readonly userId: string;\n readonly role: string | undefined;\n}\n\n/** `inviteByName`: `invite`, with the user found by name or email. */\nexport interface InviteByNameInput extends UserLookupInput {\n readonly entryId: string;\n readonly role?: string;\n}\n\n/** `inviteByName`'s input as its handler receives it. */\nexport interface InviteByNameQuery {\n readonly entryId: string;\n readonly name: string | undefined;\n readonly email: string | undefined;\n readonly role: string | undefined;\n}\n\n/** `remove`: end `userId`'s membership of the row `entryId`. */\nexport interface MemberInput {\n readonly entryId: string;\n readonly userId: string;\n}\n\n/** `leave`: end the caller's own membership of the row `entryId`. */\nexport interface LeaveInput {\n readonly entryId: string;\n}\n\n/** `setRole`: change `userId`'s role on the row `entryId`. */\nexport interface SetRoleInput {\n readonly entryId: string;\n readonly userId: string;\n readonly role: string;\n}\n\n/** `listMembers`: one page of the members of the row `entryId`. */\nexport interface ListMembersInput {\n readonly entryId: string;\n /** The `nextCursor` of the page before; the first page when absent. */\n readonly cursor?: string;\n /** The page size: default 50, at most 200 (a larger one is clamped). */\n readonly limit?: number;\n}\n\n/** `listMembers`' input as its handler receives it: the limit defaulted and clamped. */\nexport interface ListMembersQuery {\n readonly entryId: string;\n readonly cursor: string | undefined;\n readonly limit: number;\n}\n\n/** One member of a row. */\nexport interface Member {\n readonly userId: string;\n /** The role the membership table stores. */\n readonly role: string;\n /** The access level the role gives, or `null` for a role that gives none. */\n readonly level: AccessLevel | null;\n}\n\n/** One page of `listMembers`. */\nexport interface MembersPage {\n readonly items: Member[];\n /** Passed back as `cursor` for the next page; `null` on the last page. */\n readonly nextCursor: string | null;\n}\n\ntype Issues = StandardSchemaV1.Issue[];\n\n/** What one input key holds: a row or user id, a level, a role, or a name or email. */\ntype FieldKind = \"id\" | \"level\" | \"role\" | \"text\";\n\ninterface Field {\n readonly kind: FieldKind;\n readonly optional?: boolean;\n}\n\nconst MESSAGES: Readonly<Record<FieldKind, string>> = Object.freeze({\n id: \"Expected a non-empty string\",\n level: 'Expected \"Read\", \"Moderate\" or \"Admin\"',\n role: `Expected a role: a non-empty string of at most ${MAX_TEXT_LENGTH} characters`,\n text: `Expected a non-empty string of at most ${MAX_TEXT_LENGTH} characters`,\n});\n\nfunction isText(value: unknown): boolean {\n return typeof value === \"string\" && value.length > 0 && value.length <= MAX_TEXT_LENGTH;\n}\n\nfunction fits(kind: FieldKind, value: unknown): boolean {\n if (kind === \"id\") {\n return isId(value);\n }\n return kind === \"level\" ? SHARE_LEVELS.some((level) => level === value) : isText(value);\n}\n\nfunction fieldJson(kind: FieldKind): JsonSchema {\n if (kind === \"id\") {\n return idJson();\n }\n return kind === \"level\"\n ? { type: \"string\", enum: [...SHARE_LEVELS] }\n : { type: \"string\", minLength: 1, maxLength: MAX_TEXT_LENGTH };\n}\n\n/** How one input is checked and parsed. */\ninterface InputShape {\n readonly fields: Readonly<Record<string, Field>>;\n /** Keys checked by `issues` rather than by a field: `listMembers`' paging. */\n readonly more?: Readonly<Record<string, JsonSchema>>;\n /** Issues no single field shows: a by-name method needs a name or an email. */\n readonly issues?: (value: Readonly<Record<string, unknown>>) => Issues;\n /** The parsed input, when it is not the known keys as given. */\n readonly parse?: (value: Readonly<Record<string, unknown>>) => unknown;\n}\n\nfunction fieldIssues(shape: InputShape, value: Readonly<Record<string, unknown>>): Issues {\n return Object.entries(shape.fields).flatMap(([key, field]): Issues => {\n const given = value[key];\n const missing = given === undefined && field.optional !== true;\n const wrong = given !== undefined && !fits(field.kind, given);\n return missing || wrong ? [{ message: MESSAGES[field.kind], path: [key] }] : [];\n });\n}\n\nfunction inputJson(shape: InputShape): JsonSchema {\n const properties = Object.fromEntries(\n Object.entries(shape.fields).map(([key, field]) => [key, fieldJson(field.kind)]),\n );\n const required = Object.entries(shape.fields)\n .filter(([, field]) => field.optional !== true)\n .map(([key]) => key);\n return objectJson({ ...properties, ...shape.more }, required);\n}\n\n/** An input made of `shape`'s keys and nothing else. */\nfunction objectInput<Input, Parsed = Input>(shape: InputShape): KitSchema<Input, Parsed> {\n const keys = [...Object.keys(shape.fields), ...Object.keys(shape.more ?? {})];\n return kitSchema<Input, Parsed>(\n (input) => {\n if (!isRecord(input)) {\n return { issues: [{ message: \"Expected an object\", path: [] }] };\n }\n const issues = [\n ...unknownKeys(input, keys),\n ...fieldIssues(shape, input),\n ...(shape.issues?.(input) ?? []),\n ];\n if (issues.length > 0) {\n return { issues };\n }\n const parsed =\n shape.parse?.(input) ?? Object.fromEntries(keys.map((key) => [key, input[key]]));\n return { value: parsed as Parsed };\n },\n { input: () => inputJson(shape) },\n );\n}\n\nconst ID: Field = { kind: \"id\" };\nconst LEVEL: Field = { kind: \"level\" };\nconst ROLE: Field = { kind: \"role\" };\nconst OPTIONAL_ROLE: Field = { kind: \"role\", optional: true };\nconst OPTIONAL_TEXT: Field = { kind: \"text\", optional: true };\n\nfunction lookupIssues(value: Readonly<Record<string, unknown>>): Issues {\n return value.name === undefined && value.email === undefined\n ? [{ message: \"Give name or email\", path: [] }]\n : [];\n}\n\n/** `{ id, userId, level }`: `share`'s and `setLevel`'s input. */\nexport function shareInput(): KitSchema<ShareInput> {\n return objectInput<ShareInput>({ fields: { id: ID, userId: ID, level: LEVEL } });\n}\n\n/** `{ id, name?, email?, level }`: `shareByName`'s input. */\nexport function shareByNameInput(): KitSchema<ShareByNameInput, ShareByNameQuery> {\n return objectInput<ShareByNameInput, ShareByNameQuery>({\n fields: { id: ID, name: OPTIONAL_TEXT, email: OPTIONAL_TEXT, level: LEVEL },\n issues: lookupIssues,\n });\n}\n\n/** `{ id, userId }`: `unshare`'s input. */\nexport function unshareInput(): KitSchema<UnshareInput> {\n return objectInput<UnshareInput>({ fields: { id: ID, userId: ID } });\n}\n\n/** `{ entryId, userId, role? }`: `invite`'s input. */\nexport function inviteInput(): KitSchema<InviteInput, InviteQuery> {\n return objectInput<InviteInput, InviteQuery>({\n fields: { entryId: ID, userId: ID, role: OPTIONAL_ROLE },\n });\n}\n\n/** `{ entryId, name?, email?, role? }`: `inviteByName`'s input. */\nexport function inviteByNameInput(): KitSchema<InviteByNameInput, InviteByNameQuery> {\n return objectInput<InviteByNameInput, InviteByNameQuery>({\n fields: { entryId: ID, name: OPTIONAL_TEXT, email: OPTIONAL_TEXT, role: OPTIONAL_ROLE },\n issues: lookupIssues,\n });\n}\n\n/** `{ entryId, userId }`: `remove`'s input. */\nexport function memberInput(): KitSchema<MemberInput> {\n return objectInput<MemberInput>({ fields: { entryId: ID, userId: ID } });\n}\n\n/** `{ entryId }`: `leave`'s input. */\nexport function leaveInput(): KitSchema<LeaveInput> {\n return objectInput<LeaveInput>({ fields: { entryId: ID } });\n}\n\n/** `{ entryId, userId, role }`: `setRole`'s input. */\nexport function setRoleInput(): KitSchema<SetRoleInput> {\n return objectInput<SetRoleInput>({ fields: { entryId: ID, userId: ID, role: ROLE } });\n}\n\n/** `{ entryId, cursor?, limit? }`: `listMembers`' input. */\nexport function listMembersInput(): KitSchema<ListMembersInput, ListMembersQuery> {\n return objectInput<ListMembersInput, ListMembersQuery>({\n fields: { entryId: ID },\n more: {\n cursor: { type: \"string\", minLength: 1, maxLength: MAX_CURSOR_LENGTH },\n limit: { type: \"integer\", minimum: 1, maximum: LIST_MAX_LIMIT, default: LIST_DEFAULT_LIMIT },\n },\n issues: (value) => pagingIssues({ cursor: value.cursor, limit: value.limit }),\n parse: (value): ListMembersQuery => ({\n entryId: value.entryId as string,\n cursor: value.cursor as string | undefined,\n limit: Math.min((value.limit as number | undefined) ?? LIST_DEFAULT_LIMIT, LIST_MAX_LIMIT),\n }),\n });\n}\n\nfunction shareIssues(entry: unknown, path: readonly PropertyKey[]): Issues {\n const valid =\n isRecord(entry) &&\n isId(entry.userId) &&\n isAccessLevel(entry.level) &&\n Object.keys(entry).length === 2;\n return valid ? [] : [{ message: \"Expected { userId, level }\", path: [...path] }];\n}\n\nfunction shareJson(): JsonSchema {\n return objectJson({ userId: idJson(), level: { type: \"string\", enum: [...ACCESS_LEVELS] } }, [\n \"userId\",\n \"level\",\n ]);\n}\n\n/** `[{ userId, level }]`: an access list, as `share`, `unshare`, `setLevel` and `listShares` return it. */\nexport function aclOutput(): KitSchema<ACL> {\n return kitSchema<ACL>(\n (value) => {\n if (!Array.isArray(value)) {\n return { issues: [{ message: \"Expected an access list\", path: [] }] };\n }\n const issues = value.flatMap((entry: unknown, index) => shareIssues(entry, [index]));\n return issues.length > 0 ? { issues } : { value: value as ACL };\n },\n { input: () => ({ type: \"array\", items: shareJson() }) },\n );\n}\n\nfunction memberIssues(member: unknown, path: readonly PropertyKey[]): Issues {\n const valid =\n isRecord(member) &&\n isId(member.userId) &&\n typeof member.role === \"string\" &&\n (member.level === null || isAccessLevel(member.level)) &&\n Object.keys(member).length === 3;\n return valid ? [] : [{ message: \"Expected { userId, role, level }\", path: [...path] }];\n}\n\nfunction memberJson(): JsonSchema {\n return objectJson(\n { userId: idJson(), role: { type: \"string\" }, level: { enum: [...ACCESS_LEVELS, null] } },\n [\"userId\", \"role\", \"level\"],\n );\n}\n\n/** `{ userId, role, level }`: a member, as `invite` and `setRole` return it. */\nexport function memberOutput(): KitSchema<Member> {\n return kitSchema<Member>(\n (value) => {\n const issues = memberIssues(value, []);\n return issues.length > 0 ? { issues } : { value: value as Member };\n },\n { input: memberJson },\n );\n}\n\n/** `{ items, nextCursor }`: one page of `listMembers`. */\nexport function membersPageOutput(): KitSchema<MembersPage> {\n return kitSchema<MembersPage>(\n (value) => {\n if (!isRecord(value) || !Array.isArray(value.items)) {\n return { issues: [{ message: \"Expected a page of members\", path: [] }] };\n }\n const issues = [\n ...unknownKeys(value, [\"items\", \"nextCursor\"]),\n ...value.items.flatMap((member: unknown, index) => memberIssues(member, [\"items\", index])),\n ];\n if (value.nextCursor !== null && typeof value.nextCursor !== \"string\") {\n issues.push({ message: \"Expected a cursor or null\", path: [\"nextCursor\"] });\n }\n return issues.length > 0 ? { issues } : { value: value as unknown as MembersPage };\n },\n {\n input: () =>\n objectJson(\n {\n items: { type: \"array\", items: memberJson() },\n nextCursor: { type: [\"string\", \"null\"] },\n },\n [\"items\", \"nextCursor\"],\n ),\n },\n );\n}\n","// The sharing and membership kit's contract half (RFC 0003 section 12.3).\n// `sharing.contract` returns ordinary `query` and `mutation` entries to\n// spread into a contract's `methods`, for one of two ways a service's rows\n// are shared:\n//\n// export const project = defineContract(\"projectService\", {\n// entity: projectSchema,\n// methods: {\n// ...sharing.contract({ mode: \"acl\" }), // share, unshare, setLevel, listShares\n// ...sharing.contract({ mode: \"members\", methods: [\"invite\", \"remove\", \"leave\", \"listMembers\"] }),\n// },\n// });\n//\n// `\"acl\"` edits the JSON access list a `jsonAcl` policy reads; `\"members\"`\n// edits the membership table a `members` policy reads. `methods` picks the\n// methods to add, by name; without it a mode adds all of its methods but the\n// by-name ones (`shareByName`, `inviteByName`), which find the user by name\n// or email through the server's `resolveUser`. The server half\n// (`sharing.handlers` on `./server`) finds the kit's entries in the contract\n// by what they were made for (`sharingSpecOf`), and its types by their tag\n// (`SharingTag`), so the two halves name the same methods.\n\nimport type { ACL } from \"../access\";\nimport { mutation, query, type MethodDef, type MutationDef, type QueryDef } from \"../methods\";\nimport { idInput, nullOutput, type IdInput } from \"./crudSchemas\";\nimport type { KitSchema } from \"./schemas\";\nimport {\n aclOutput,\n inviteByNameInput,\n inviteInput,\n leaveInput,\n listMembersInput,\n memberInput,\n memberOutput,\n membersPageOutput,\n setRoleInput,\n shareByNameInput,\n shareInput,\n unshareInput,\n type InviteByNameInput,\n type InviteByNameQuery,\n type InviteInput,\n type InviteQuery,\n type LeaveInput,\n type ListMembersInput,\n type ListMembersQuery,\n type Member,\n type MemberInput,\n type MembersPage,\n type SetRoleInput,\n type ShareByNameInput,\n type ShareByNameQuery,\n type ShareInput,\n type UnshareInput,\n} from \"./sharingSchemas\";\n\n/** How a service's rows are shared: a JSON access list (`jsonAcl`) or a membership table (`members`). */\nexport type SharingMode = \"acl\" | \"members\";\n\n/** The methods of `mode: \"acl\"`. */\nexport type AclMethodName = \"share\" | \"shareByName\" | \"unshare\" | \"setLevel\" | \"listShares\";\n\n/** The methods of `mode: \"members\"`. */\nexport type MembersMethodName =\n | \"invite\"\n | \"inviteByName\"\n | \"remove\"\n | \"leave\"\n | \"setRole\"\n | \"listMembers\";\n\n/** Every method the sharing kit can add. */\nexport type SharingMethodName = AclMethodName | MembersMethodName;\n\n/** The methods that find their user by name or email: opt-in, since they need `resolveUser`. */\nexport type SharingByNameMethod = \"shareByName\" | \"inviteByName\";\n\n/** Every method of each mode: those of RFC 0003 section 12.3, then the by-name one. */\nexport const SHARING_METHODS: {\n readonly acl: readonly AclMethodName[];\n readonly members: readonly MembersMethodName[];\n} = Object.freeze({\n acl: Object.freeze<AclMethodName[]>([\n \"share\",\n \"unshare\",\n \"setLevel\",\n \"listShares\",\n \"shareByName\",\n ]),\n members: Object.freeze<MembersMethodName[]>([\n \"invite\",\n \"remove\",\n \"leave\",\n \"setRole\",\n \"listMembers\",\n \"inviteByName\",\n ]),\n});\n\n/** Type-only: marks a method the sharing kit made, and which one. Never set. */\nexport interface SharingTag<Kind extends SharingMethodName> {\n readonly \"~sharing\"?: Kind;\n}\n\n/** `sharing.contract`'s options, for one mode. */\nexport type SharingContractOptions =\n | {\n readonly mode: \"acl\";\n /** The methods to add; all but `shareByName` when absent. */\n readonly methods?: readonly AclMethodName[];\n /** Descriptions for people and agents, per method; the kit's own for the rest. */\n readonly describe?: { readonly [Name in AclMethodName]?: string };\n }\n | {\n readonly mode: \"members\";\n /** The methods to add; all but `inviteByName` when absent. */\n readonly methods?: readonly MembersMethodName[];\n /** Descriptions for people and agents, per method; the kit's own for the rest. */\n readonly describe?: { readonly [Name in MembersMethodName]?: string };\n };\n\nexport type SharingShare = MutationDef<KitSchema<ShareInput>, KitSchema<ACL>> & SharingTag<\"share\">;\nexport type SharingShareByName = MutationDef<\n KitSchema<ShareByNameInput, ShareByNameQuery>,\n KitSchema<ACL>\n> &\n SharingTag<\"shareByName\">;\nexport type SharingUnshare = MutationDef<KitSchema<UnshareInput>, KitSchema<ACL>> &\n SharingTag<\"unshare\">;\nexport type SharingSetLevel = MutationDef<KitSchema<ShareInput>, KitSchema<ACL>> &\n SharingTag<\"setLevel\">;\nexport type SharingListShares = QueryDef<KitSchema<IdInput>, KitSchema<ACL>> &\n SharingTag<\"listShares\">;\nexport type SharingInvite = MutationDef<KitSchema<InviteInput, InviteQuery>, KitSchema<Member>> &\n SharingTag<\"invite\">;\nexport type SharingInviteByName = MutationDef<\n KitSchema<InviteByNameInput, InviteByNameQuery>,\n KitSchema<Member>\n> &\n SharingTag<\"inviteByName\">;\nexport type SharingRemove = MutationDef<KitSchema<MemberInput>, KitSchema<null>> &\n SharingTag<\"remove\">;\nexport type SharingLeave = MutationDef<KitSchema<LeaveInput>, KitSchema<null>> &\n SharingTag<\"leave\">;\nexport type SharingSetRole = MutationDef<KitSchema<SetRoleInput>, KitSchema<Member>> &\n SharingTag<\"setRole\">;\nexport type SharingListMembers = QueryDef<\n KitSchema<ListMembersInput, ListMembersQuery>,\n KitSchema<MembersPage>\n> &\n SharingTag<\"listMembers\">;\n\n/** The entry `sharing.contract` makes for method `Name`. */\nexport type SharingDefOf<Name extends SharingMethodName> = {\n share: SharingShare;\n shareByName: SharingShareByName;\n unshare: SharingUnshare;\n setLevel: SharingSetLevel;\n listShares: SharingListShares;\n invite: SharingInvite;\n inviteByName: SharingInviteByName;\n remove: SharingRemove;\n leave: SharingLeave;\n setRole: SharingSetRole;\n listMembers: SharingListMembers;\n}[Name];\n\ntype ModeIn<O> = O extends { readonly mode: infer Mode } ? Mode : never;\n\ntype DefaultNames<Mode> = Mode extends \"acl\"\n ? Exclude<AclMethodName, SharingByNameMethod>\n : Mode extends \"members\"\n ? Exclude<MembersMethodName, SharingByNameMethod>\n : never;\n\ntype NamesIn<O> = O extends { readonly methods: readonly (infer Name extends SharingMethodName)[] }\n ? Name\n : DefaultNames<ModeIn<O>>;\n\n/** The entries `sharing.contract(options)` returns: one per method it adds. */\nexport type SharingMethods<O> = { readonly [Name in NamesIn<O>]: SharingDefOf<Name> };\n\n/** What the server half needs to know about a method the sharing kit made. */\nexport interface SharingSpec {\n readonly method: SharingMethodName;\n readonly mode: SharingMode;\n}\n\nconst SPECS = new WeakMap<object, SharingSpec>();\n\n/** What the sharing kit made `method` for, or `undefined` for any other method. */\nexport function sharingSpecOf(method: unknown): SharingSpec | undefined {\n return typeof method === \"object\" && method !== null ? SPECS.get(method) : undefined;\n}\n\nconst DESCRIBE: Readonly<Record<SharingMethodName, string>> = Object.freeze({\n share:\n \"Gives a user a level on one row: Read, Moderate or Admin. A user the row is shared with already gets the new level.\",\n shareByName:\n \"Gives the user with this name or email a level on one row: Read, Moderate or Admin.\",\n unshare: \"Takes a user's level on one row away. The owner's cannot be taken away.\",\n setLevel: \"Changes the level of a user one row is already shared with.\",\n listShares: \"Lists the users one row is shared with, and their levels.\",\n invite: \"Makes a user a member of one row, with a role (the lowest role when none is given).\",\n inviteByName:\n \"Makes the user with this name or email a member of one row, with a role (the lowest role when none is given).\",\n remove: \"Ends a user's membership of one row. The last Admin member cannot be removed.\",\n leave: \"Ends the caller's own membership of one row. The last Admin member cannot leave.\",\n setRole: \"Changes a member's role on one row.\",\n listMembers:\n \"Lists the members of one row and their roles, a page at a time. Pass a page's nextCursor back as cursor for the next page.\",\n});\n\nconst BUILD: Readonly<Record<SharingMethodName, (describe: string) => MethodDef>> = Object.freeze({\n share: (describe) => mutation({ input: shareInput(), output: aclOutput(), describe }),\n shareByName: (describe) => mutation({ input: shareByNameInput(), output: aclOutput(), describe }),\n unshare: (describe) => mutation({ input: unshareInput(), output: aclOutput(), describe }),\n setLevel: (describe) => mutation({ input: shareInput(), output: aclOutput(), describe }),\n listShares: (describe) => query({ input: idInput(), output: aclOutput(), describe }),\n invite: (describe) => mutation({ input: inviteInput(), output: memberOutput(), describe }),\n inviteByName: (describe) =>\n mutation({ input: inviteByNameInput(), output: memberOutput(), describe }),\n remove: (describe) => mutation({ input: memberInput(), output: nullOutput(), describe }),\n leave: (describe) => mutation({ input: leaveInput(), output: nullOutput(), describe }),\n setRole: (describe) => mutation({ input: setRoleInput(), output: memberOutput(), describe }),\n listMembers: (describe) =>\n query({ input: listMembersInput(), output: membersPageOutput(), describe }),\n});\n\ntype UnknownRecord = Readonly<Record<string, unknown>>;\n\nfunction isRecord(value: unknown): value is UnknownRecord {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction fail(message: string): never {\n throw new TypeError(`sharing.contract: ${message}`);\n}\n\nfunction quoted(names: readonly string[]): string {\n return names.map((name) => `\"${name}\"`).join(\", \");\n}\n\nfunction modeOf(options: unknown): SharingMode {\n if (!isRecord(options)) {\n fail('options must be { mode: \"acl\" | \"members\", methods?, describe? }');\n }\n const unknownKey = Object.keys(options).find(\n (key) => ![\"mode\", \"methods\", \"describe\"].includes(key),\n );\n if (unknownKey !== undefined) {\n fail(`unknown option \"${unknownKey}\"; the options are mode, methods and describe`);\n }\n const { mode } = options;\n if (mode !== \"acl\" && mode !== \"members\") {\n fail('mode must be \"acl\" (a jsonAcl access list) or \"members\" (a members table)');\n }\n return mode;\n}\n\n/** The methods the options add: those `methods` names, or the mode's default ones. */\nfunction namesOf(mode: SharingMode, methods: unknown): readonly SharingMethodName[] {\n const all: readonly SharingMethodName[] = SHARING_METHODS[mode];\n if (methods === undefined) {\n return all.filter((name) => name !== \"shareByName\" && name !== \"inviteByName\");\n }\n const valid =\n Array.isArray(methods) &&\n methods.length > 0 &&\n methods.every((name) => all.some((known) => known === name)) &&\n new Set(methods).size === methods.length;\n if (!valid) {\n fail(`methods must name one or more distinct methods of mode \"${mode}\": ${quoted(all)}`);\n }\n return methods as SharingMethodName[];\n}\n\n/** The descriptions the options give, each for a method they add. */\nfunction describeOf(names: readonly SharingMethodName[], describe: unknown): UnknownRecord {\n if (describe === undefined) {\n return {};\n }\n if (!isRecord(describe)) {\n fail(\"describe must map method names to descriptions\");\n }\n for (const [name, text] of Object.entries(describe)) {\n if (!names.some((added) => added === name)) {\n fail(`describe names \"${name}\", which these options do not add`);\n }\n if (typeof text !== \"string\" || text.length === 0) {\n fail(`describe for \"${name}\" must be a non-empty string`);\n }\n }\n return describe;\n}\n\nfunction contract<const O extends SharingContractOptions>(options: O): SharingMethods<O> {\n const mode = modeOf(options);\n const names = namesOf(mode, options.methods);\n const described = describeOf(names, options.describe);\n const methods: Record<string, MethodDef> = {};\n for (const name of names) {\n const own = described[name];\n const def = BUILD[name](typeof own === \"string\" ? own : DESCRIBE[name]);\n SPECS.set(def, Object.freeze({ method: name, mode }));\n methods[name] = def;\n }\n return Object.freeze(methods) as unknown as SharingMethods<O>;\n}\n\n/**\n * The sharing and membership kit's contract half: `sharing.contract({ mode,\n * methods?, describe? })` makes the methods that share a row through its\n * JSON access list (`mode: \"acl\"`) or its membership table\n * (`mode: \"members\"`), which the server half (`sharing.handlers` on\n * `./server`) implements.\n */\nexport const sharing = Object.freeze({ contract });\n"],"mappings":"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","names":["isRecord","SPECS","DESCRIBE","isRecord","fail","describeOf","contract"]}
|
|
1
|
+
{"version":3,"sources":["../src/contract/kits/crud.ts","../src/contract/kits/sharingSchemas.ts","../src/contract/kits/sharing.ts"],"sourcesContent":["// The read/write kit's contract half (RFC 0003 section 12.1). `crud.contract`\n// returns ordinary `query` and `mutation` entries to spread into a contract's\n// `methods`, one per method it names and no others:\n//\n// export const task = defineContract(\"taskService\", {\n// entity: taskSchema,\n// projections: { card: cardSchema },\n// methods: {\n// ...crud.contract({\n// entity: taskSchema,\n// get: true,\n// list: { item: cardSchema, filter: [\"projectId\", \"status\"], sort: [\"ordinal\"] },\n// update: { input: taskPatch },\n// }),\n// rename: mutation({ ... }),\n// },\n// });\n//\n// The server half (`crud.handlers` on `./server`) finds the kit's methods in\n// the contract and implements exactly those: each entry carries what it\n// needs (`crudSpecOf`), and its type carries which kit method it is\n// (`CrudTag`), so the two halves cannot disagree. A client sees ordinary\n// members. About 270 of the 579 methods in the seven smaller 4.1 apps are\n// these shapes, each with its own paging and guards.\n\nimport type { RowSchema } from \"../defineContract\";\nimport {\n listOf,\n mutation,\n query,\n type MethodDef,\n type MutationDef,\n type ProjectionList,\n type QueryDef,\n} from \"../methods\";\nimport { isStandardSchema, type InferOutput, type StandardSchemaV1 } from \"../standardSchema\";\nimport { listInput, pageOutput, type ListPage, type ListQuery, type ListSort } from \"./crudList\";\nimport {\n bulkPatch,\n countOutput,\n CRUD_MAX_IDS,\n idInput,\n idsInput,\n nullOutput,\n reorderInput,\n withId,\n type BulkPatch,\n type BulkResult,\n type IdInput,\n type IdsInput,\n type ReorderInput,\n type WithId,\n} from \"./crudSchemas\";\nimport type { KitSchema } from \"./schemas\";\n\n/** The methods the read/write kit can add, each opt-in by name. */\nexport type CrudMethodName =\n | \"get\"\n | \"getMany\"\n | \"list\"\n | \"create\"\n | \"update\"\n | \"delete\"\n | \"reorder\"\n | \"bulkUpdate\"\n | \"bulkDelete\";\n\n/** Every method of the read/write kit, in the order of RFC 0003 section 12.1. */\nexport const CRUD_METHODS: readonly CrudMethodName[] = Object.freeze([\n \"get\",\n \"getMany\",\n \"list\",\n \"create\",\n \"update\",\n \"delete\",\n \"reorder\",\n \"bulkUpdate\",\n \"bulkDelete\",\n]);\n\n/** Type-only: marks a method the read/write kit made, and which one. Never set. */\nexport interface CrudTag<Kind extends CrudMethodName> {\n readonly \"~crud\"?: Kind;\n}\n\ntype RowOf<Entity> = Entity extends StandardSchemaV1 ? InferOutput<Entity> : never;\n\n/** The entity's fields holding strings, numbers or booleans: what `list` filters and sorts on. */\nexport type ScalarFieldOf<Row> = {\n [Field in keyof Row]-?: NonNullable<Row[Field]> extends string | number | boolean ? Field : never;\n}[keyof Row] &\n string;\n\n/** The entity's fields holding numbers: a `reorder` column. */\nexport type NumberFieldOf<Row> = {\n [Field in keyof Row]-?: NonNullable<Row[Field]> extends number ? Field : never;\n}[keyof Row] &\n string;\n\ninterface Described {\n /** The method's description, for people and agents; the kit's own when absent. */\n readonly describe?: string;\n}\n\n/** Turns a method without options on: `true`, or `{ describe }`. */\nexport type CrudToggle = boolean | Described;\n\n/** `list`'s options. */\nexport interface CrudListOptions<Field extends string = string> extends Described {\n /**\n * The schema of each item: the contract's entity schema (the default) or\n * one of its projections' schemas, the same object.\n */\n readonly item?: RowSchema;\n /** The fields a caller may filter on, by equality. */\n readonly filter?: readonly Field[];\n /** The fields a caller may sort by; the first is the default order. */\n readonly sort?: readonly Field[];\n}\n\n/** `create`'s, `update`'s and `bulkUpdate`'s options: the app's schema of the fields written. */\nexport interface CrudInputOptions extends Described {\n /**\n * `create`: the new row's fields. `update` and `bulkUpdate`: the fields a\n * call may change, every one optional (`z.object({...}).partial()`); the\n * kit adds `id`, or `ids`.\n */\n readonly input: StandardSchemaV1;\n}\n\n/** `reorder`'s options. */\nexport interface CrudReorderOptions<\n Column extends string = string,\n Scope extends string = string,\n> extends Described {\n /** The numeric column rows are ordered by, ascending. */\n readonly column: Column;\n /** The columns whose values make one ordered list (`\"projectId\"`); the whole table when absent. */\n readonly within?: Scope | readonly Scope[];\n}\n\n/** `crud.contract`'s options: the entity, and each method to add. */\nexport interface CrudContractOptions {\n /** The contract's entity schema: the fields `list` and `reorder` name are its fields. */\n readonly entity: RowSchema;\n readonly get?: CrudToggle;\n readonly getMany?: CrudToggle;\n readonly list?: CrudListOptions;\n readonly create?: CrudInputOptions;\n readonly update?: CrudInputOptions;\n readonly delete?: CrudToggle;\n readonly reorder?: CrudReorderOptions;\n readonly bulkUpdate?: CrudInputOptions;\n readonly bulkDelete?: CrudToggle;\n}\n\ntype Quoted<Names> = `\"${Names & string}\"`;\n\ntype NamesIn<Value> = Value extends string\n ? Value\n : Value extends readonly (infer Name)[]\n ? Name\n : never;\n\ntype OptionNames<O, Option extends string, Key extends string> = O extends {\n readonly [K in Option]: { readonly [P in Key]: infer Value };\n}\n ? NamesIn<Value>\n : never;\n\n/** A broken option becomes a message: replacing the whole option keeps TypeScript from printing `never`. */\ntype Problem<Option extends string, Wrong, Text extends string> = [Wrong] extends [never]\n ? unknown\n : { readonly [K in Option]: `crud.contract: ${Option}: ${Quoted<Wrong>} ${Text}` };\n\n/** The checks against the entity's fields that the option types alone cannot state. */\ntype CrudChecks<O> = O extends { readonly entity: infer Entity }\n ? Problem<\n \"list\",\n Exclude<\n OptionNames<O, \"list\", \"filter\"> | OptionNames<O, \"list\", \"sort\">,\n ScalarFieldOf<RowOf<Entity>>\n >,\n \"is not a field of the entity holding strings, numbers or booleans\"\n > &\n Problem<\n \"reorder\",\n Exclude<OptionNames<O, \"reorder\", \"column\">, NumberFieldOf<RowOf<Entity>>>,\n \"is not a field of the entity holding numbers\"\n > &\n Problem<\n \"reorder\",\n Exclude<OptionNames<O, \"reorder\", \"within\">, ScalarFieldOf<RowOf<Entity>>>,\n \"is not a field of the entity holding strings, numbers or booleans\"\n >\n : unknown;\n\n/** What a `list` call passes: a filter and a sort over the declared fields, and paging. */\nexport type ListInput<Row, Filter extends string, Sort extends string> = {\n readonly filter?: [Filter] extends [never]\n ? undefined\n : { readonly [Field in Filter]?: Field extends keyof Row ? Row[Field] : never };\n readonly sort?: [Sort] extends [never] ? undefined : ListSort<Sort>;\n /** The `nextCursor` of the page before; the first page when absent. */\n readonly cursor?: string;\n /** The page size: default 50, at most 200 (a larger one is clamped). */\n readonly limit?: number;\n /** Also count every matching row (a second statement). */\n readonly totalCount?: boolean;\n};\n\nexport type CrudGet = QueryDef<KitSchema<IdInput>, \"entity\"> & CrudTag<\"get\">;\nexport type CrudGetMany = QueryDef<\n KitSchema<IdsInput, { readonly ids: string[] }>,\n ProjectionList<\"entity\">\n> &\n CrudTag<\"getMany\">;\nexport type CrudList<Row, Item, Filter extends string, Sort extends string> = QueryDef<\n KitSchema<ListInput<Row, Filter, Sort> | undefined, ListQuery<Filter, Sort>>,\n KitSchema<ListPage<Item>>\n> &\n CrudTag<\"list\">;\nexport type CrudCreate<Input extends StandardSchemaV1> = MutationDef<Input, \"entity\"> &\n CrudTag<\"create\">;\nexport type CrudUpdate<Patch extends StandardSchemaV1> = MutationDef<WithId<Patch>, \"entity\"> &\n CrudTag<\"update\">;\nexport type CrudDelete = MutationDef<KitSchema<IdInput>, KitSchema<null>> & CrudTag<\"delete\">;\nexport type CrudReorder = MutationDef<KitSchema<ReorderInput>, \"entity\"> & CrudTag<\"reorder\">;\nexport type CrudBulkUpdate<Patch extends StandardSchemaV1> = MutationDef<\n BulkPatch<Patch>,\n KitSchema<BulkResult>\n> &\n CrudTag<\"bulkUpdate\">;\nexport type CrudBulkDelete = MutationDef<\n KitSchema<IdsInput, { readonly ids: string[] }>,\n KitSchema<BulkResult>\n> &\n CrudTag<\"bulkDelete\">;\n\ntype EntityIn<O> = O extends { readonly entity: infer Entity } ? Entity : never;\n\ntype ListFieldsIn<O, Key extends \"filter\" | \"sort\"> = O extends {\n readonly list: { readonly [K in Key]: readonly (infer Field extends string)[] };\n}\n ? Field\n : never;\n\ntype ListItemIn<O> = O extends { readonly list: { readonly item: infer Item } }\n ? RowOf<Item>\n : RowOf<EntityIn<O>>;\n\ntype InputIn<O, Name extends \"create\" | \"update\" | \"bulkUpdate\"> = O extends {\n readonly [K in Name]: { readonly input: infer Input extends StandardSchemaV1 };\n}\n ? Input\n : never;\n\ntype CrudMethodOf<O, Name extends CrudMethodName> = {\n get: CrudGet;\n getMany: CrudGetMany;\n list: CrudList<\n RowOf<EntityIn<O>>,\n ListItemIn<O>,\n ListFieldsIn<O, \"filter\">,\n ListFieldsIn<O, \"sort\">\n >;\n create: CrudCreate<InputIn<O, \"create\">>;\n update: CrudUpdate<InputIn<O, \"update\">>;\n delete: CrudDelete;\n reorder: CrudReorder;\n bulkUpdate: CrudBulkUpdate<InputIn<O, \"bulkUpdate\">>;\n bulkDelete: CrudBulkDelete;\n}[Name];\n\ntype Enabled<O, Name extends CrudMethodName> = Name extends keyof O\n ? [O[Name]] extends [false | undefined]\n ? never\n : Name\n : never;\n\n/** The entries `crud.contract(options)` returns: one per method the options turn on. */\nexport type CrudMethods<O> = {\n readonly [Name in CrudMethodName as Enabled<O, Name>]: CrudMethodOf<O, Name>;\n};\n\n/** What the server half needs to know about one of the kit's methods. */\nexport type CrudSpec =\n | { readonly method: Exclude<CrudMethodName, \"list\" | \"reorder\"> }\n | {\n readonly method: \"list\";\n readonly filter: readonly string[];\n readonly sort: readonly string[];\n /** The item's schema; `undefined` for the entity. */\n readonly item: RowSchema | undefined;\n }\n | { readonly method: \"reorder\"; readonly column: string; readonly within: readonly string[] };\n\nconst SPECS = new WeakMap<object, CrudSpec>();\n\n/** What the read/write kit made `method` for, or `undefined` for any other method. */\nexport function crudSpecOf(method: unknown): CrudSpec | undefined {\n return typeof method === \"object\" && method !== null ? SPECS.get(method) : undefined;\n}\n\nconst DESCRIBE: Readonly<Record<CrudMethodName, string>> = Object.freeze({\n get: \"Reads one row by id.\",\n getMany: `Reads up to ${CRUD_MAX_IDS} rows by id, in the order asked; ids the caller cannot read, or with no row, are left out.`,\n list: \"Lists the rows the caller can read, a page at a time, filtered and sorted by the declared fields. Pass a page's nextCursor back as cursor for the next page.\",\n create: \"Creates a row.\",\n update: \"Changes the given fields of one row.\",\n delete: \"Deletes one row.\",\n reorder:\n \"Moves a row between two others: beforeId is the row that will come right before it, afterId the row right after; one is enough.\",\n bulkUpdate: `Changes the same fields on up to ${CRUD_MAX_IDS} rows, skipping rows the caller cannot change; returns how many changed.`,\n bulkDelete: `Deletes up to ${CRUD_MAX_IDS} rows, skipping rows the caller cannot delete; returns how many were deleted.`,\n});\n\ntype UnknownRecord = Readonly<Record<string, unknown>>;\n\nfunction isRecord(value: unknown): value is UnknownRecord {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction fail(message: string): never {\n throw new TypeError(`crud.contract: ${message}`);\n}\n\nfunction checkKeys(owner: string, value: UnknownRecord, allowed: readonly string[]): void {\n const unknownKey = Object.keys(value).find((key) => !allowed.includes(key));\n if (unknownKey !== undefined) {\n fail(`${owner} has an unknown option \"${unknownKey}\"; the options are ${allowed.join(\", \")}`);\n }\n}\n\n/** The method's description: its own `describe`, or the kit's. */\nfunction describeOf(name: CrudMethodName, option: unknown): string {\n const own = isRecord(option) ? option.describe : undefined;\n if (own !== undefined && (typeof own !== \"string\" || own.length === 0)) {\n fail(`${name}: describe must be a non-empty string`);\n }\n return own ?? DESCRIBE[name];\n}\n\nfunction checkToggle(name: CrudMethodName, option: unknown): void {\n if (typeof option !== \"boolean\" && !isRecord(option)) {\n fail(`${name} must be true, false or { describe }`);\n }\n if (isRecord(option)) {\n checkKeys(name, option, [\"describe\"]);\n }\n}\n\nfunction checkFields(owner: string, fields: unknown): readonly string[] {\n if (fields === undefined) {\n return [];\n }\n const valid =\n Array.isArray(fields) &&\n fields.every((field) => typeof field === \"string\" && field.length > 0) &&\n new Set(fields).size === fields.length;\n if (!valid) {\n fail(`${owner} must be a list of distinct field names`);\n }\n return Object.freeze([...(fields as string[])]);\n}\n\nfunction listSpec(option: unknown): CrudSpec {\n if (!isRecord(option)) {\n fail(\"list must be { item?, filter?, sort?, describe? }\");\n }\n checkKeys(\"list\", option, [\"item\", \"filter\", \"sort\", \"describe\"]);\n if (option.item !== undefined && !isStandardSchema(option.item)) {\n fail(\"list.item must be the entity schema or one of the contract's projection schemas\");\n }\n return {\n method: \"list\",\n filter: checkFields(\"list.filter\", option.filter),\n sort: checkFields(\"list.sort\", option.sort),\n item: option.item as RowSchema | undefined,\n };\n}\n\nfunction inputOf(name: CrudMethodName, option: unknown): StandardSchemaV1 {\n if (!isRecord(option) || !isStandardSchema(option.input)) {\n fail(`${name} must be { input: <Standard Schema>, describe? }`);\n }\n checkKeys(name, option, [\"input\", \"describe\"]);\n return option.input;\n}\n\nfunction reorderSpec(option: unknown): CrudSpec {\n if (!isRecord(option) || typeof option.column !== \"string\" || option.column.length === 0) {\n fail(\"reorder must be { column, within?, describe? } with the name of a numeric column\");\n }\n checkKeys(\"reorder\", option, [\"column\", \"within\", \"describe\"]);\n const { column } = option;\n const within = checkFields(\n \"reorder.within\",\n typeof option.within === \"string\" ? [option.within] : option.within,\n );\n if (column === \"id\" || within.includes(column)) {\n fail(\"reorder.column must be a numeric column other than id and the within columns\");\n }\n return { method: \"reorder\", column, within };\n}\n\n/** One method of the kit: its entry, and what the server half needs to know about it. */\nfunction build(\n name: CrudMethodName,\n options: UnknownRecord,\n entity: StandardSchemaV1,\n): { def: MethodDef; spec: CrudSpec } {\n const option = options[name];\n const describe = describeOf(name, option);\n switch (name) {\n case \"list\": {\n const spec = listSpec(option) as Extract<CrudSpec, { method: \"list\" }>;\n const input = listInput(spec, entity);\n return { def: query({ input, output: pageOutput(spec.item ?? entity), describe }), spec };\n }\n case \"create\":\n return {\n def: mutation({ input: inputOf(name, option), output: \"entity\", describe }),\n spec: { method: name },\n };\n case \"update\":\n return {\n def: mutation({ input: withId(inputOf(name, option)), output: \"entity\", describe }),\n spec: { method: name },\n };\n case \"bulkUpdate\":\n return {\n def: mutation({ input: bulkPatch(inputOf(name, option)), output: countOutput(), describe }),\n spec: { method: name },\n };\n case \"reorder\":\n return {\n def: mutation({ input: reorderInput(), output: \"entity\", describe }),\n spec: reorderSpec(option),\n };\n default:\n checkToggle(name, option);\n return { def: toggled(name, describe), spec: { method: name } };\n }\n}\n\n/** The entry of a method without options. */\nfunction toggled(name: CrudMethodName, describe: string): MethodDef {\n switch (name) {\n case \"get\":\n return query({ input: idInput(), output: \"entity\", describe });\n case \"getMany\":\n return query({ input: idsInput(), output: listOf(\"entity\"), describe });\n case \"delete\":\n return mutation({ input: idInput(), output: nullOutput(), describe });\n default:\n return mutation({ input: idsInput(), output: countOutput(), describe });\n }\n}\n\nfunction checkOptions(options: unknown): UnknownRecord {\n if (!isRecord(options)) {\n fail(\"options must be { entity, ...methods }\");\n }\n checkKeys(\"the options\", options, [\"entity\", ...CRUD_METHODS]);\n if (!isStandardSchema(options.entity)) {\n fail(\"entity must be the contract's entity schema\");\n }\n return options;\n}\n\nfunction contract<const O extends CrudContractOptions>(\n options: O & NoInfer<CrudChecks<O>>,\n): CrudMethods<O> {\n const checked = checkOptions(options);\n const entity = checked.entity as StandardSchemaV1;\n const methods: Record<string, MethodDef> = {};\n for (const name of CRUD_METHODS) {\n const option = checked[name];\n if (option === undefined || option === false) {\n continue;\n }\n const { def, spec } = build(name, checked, entity);\n SPECS.set(def, Object.freeze(spec));\n methods[name] = def;\n }\n return Object.freeze(methods) as unknown as CrudMethods<O>;\n}\n\n/**\n * The read/write kit's contract half: `crud.contract(options)`. Each method is\n * opt-in by name (`get: true`, `list: { filter, sort }`, `update: { input }`),\n * and the server half (`crud.handlers` on `./server`) implements exactly the\n * methods named.\n */\nexport const crud = Object.freeze({ contract });\n","// The inputs and outputs the sharing and membership kit generates (RFC 0003\n// section 12.3). An access list method names its row as `id` and its user as\n// `userId`, and grants one of the levels a list entry can usefully hold\n// (`Read`, `Moderate` or `Admin`); a membership method names its row as\n// `entryId` and a stored role as `role`, which the server checks against the\n// service's `members` policy (the contract cannot know its roles). The\n// by-name methods take `name` or `email` instead of `userId`, for the\n// server's `resolveUser`. `listMembers` pages like `list` (`crudList.ts`):\n// 50 members by default, at most 200, by keyset cursor.\n//\n// Outputs: an access list as `ACL` (`[{ userId, level }]`, one entry per\n// user), a member as `{ userId, role, level }`, a page of members, or `null`.\n\nimport { ACCESS_LEVELS, isAccessLevel, type ACL, type AccessLevel } from \"../access\";\nimport type { StandardSchemaV1 } from \"../standardSchema\";\nimport { LIST_DEFAULT_LIMIT, LIST_MAX_LIMIT, MAX_CURSOR_LENGTH, pagingIssues } from \"./crudList\";\nimport {\n idJson,\n isId,\n isRecord,\n kitSchema,\n objectJson,\n unknownKeys,\n type JsonSchema,\n type KitSchema,\n} from \"./schemas\";\n\n/** A level an access list entry is given: `Public` grants nothing, so it is not one. */\nexport type ShareLevel = Exclude<AccessLevel, \"Public\">;\n\n/** The levels `share` and `setLevel` grant, lowest first. */\nexport const SHARE_LEVELS: readonly ShareLevel[] = Object.freeze([\"Read\", \"Moderate\", \"Admin\"]);\n\n/** The longest role, name or email the kit accepts. */\nconst MAX_TEXT_LENGTH = 256;\n\n/** `share` and `setLevel`: give `userId` `level` on the row `id`. */\nexport interface ShareInput {\n readonly id: string;\n readonly userId: string;\n readonly level: ShareLevel;\n}\n\n/** `unshare`: take `userId`'s level on the row `id` away. */\nexport interface UnshareInput {\n readonly id: string;\n readonly userId: string;\n}\n\n/** Who a by-name method means: their name or their email, at least one. */\nexport interface UserLookupInput {\n readonly name?: string;\n readonly email?: string;\n}\n\n/** `shareByName`: `share`, with the user found by name or email. */\nexport interface ShareByNameInput extends UserLookupInput {\n readonly id: string;\n readonly level: ShareLevel;\n}\n\n/** `shareByName`'s input as its handler receives it. */\nexport interface ShareByNameQuery {\n readonly id: string;\n readonly name: string | undefined;\n readonly email: string | undefined;\n readonly level: ShareLevel;\n}\n\n/** `invite`: make `userId` a member of the row `entryId`, with `role` (the lowest by default). */\nexport interface InviteInput {\n readonly entryId: string;\n readonly userId: string;\n readonly role?: string;\n}\n\n/** `invite`'s input as its handler receives it. */\nexport interface InviteQuery {\n readonly entryId: string;\n readonly userId: string;\n readonly role: string | undefined;\n}\n\n/** `inviteByName`: `invite`, with the user found by name or email. */\nexport interface InviteByNameInput extends UserLookupInput {\n readonly entryId: string;\n readonly role?: string;\n}\n\n/** `inviteByName`'s input as its handler receives it. */\nexport interface InviteByNameQuery {\n readonly entryId: string;\n readonly name: string | undefined;\n readonly email: string | undefined;\n readonly role: string | undefined;\n}\n\n/** `remove`: end `userId`'s membership of the row `entryId`. */\nexport interface MemberInput {\n readonly entryId: string;\n readonly userId: string;\n}\n\n/** `leave`: end the caller's own membership of the row `entryId`. */\nexport interface LeaveInput {\n readonly entryId: string;\n}\n\n/** `setRole`: change `userId`'s role on the row `entryId`. */\nexport interface SetRoleInput {\n readonly entryId: string;\n readonly userId: string;\n readonly role: string;\n}\n\n/** `listMembers`: one page of the members of the row `entryId`. */\nexport interface ListMembersInput {\n readonly entryId: string;\n /** The `nextCursor` of the page before; the first page when absent. */\n readonly cursor?: string;\n /** The page size: default 50, at most 200 (a larger one is clamped). */\n readonly limit?: number;\n}\n\n/** `listMembers`' input as its handler receives it: the limit defaulted and clamped. */\nexport interface ListMembersQuery {\n readonly entryId: string;\n readonly cursor: string | undefined;\n readonly limit: number;\n}\n\n/** One member of a row. */\nexport interface Member {\n readonly userId: string;\n /** The role the membership table stores. */\n readonly role: string;\n /** The access level the role gives, or `null` for a role that gives none. */\n readonly level: AccessLevel | null;\n}\n\n/** One page of `listMembers`. */\nexport interface MembersPage {\n readonly items: Member[];\n /** Passed back as `cursor` for the next page; `null` on the last page. */\n readonly nextCursor: string | null;\n}\n\ntype Issues = StandardSchemaV1.Issue[];\n\n/** What one input key holds: a row or user id, a level, a role, or a name or email. */\ntype FieldKind = \"id\" | \"level\" | \"role\" | \"text\";\n\ninterface Field {\n readonly kind: FieldKind;\n readonly optional?: boolean;\n}\n\nconst MESSAGES: Readonly<Record<FieldKind, string>> = Object.freeze({\n id: \"Expected a non-empty string\",\n level: 'Expected \"Read\", \"Moderate\" or \"Admin\"',\n role: `Expected a role: a non-empty string of at most ${MAX_TEXT_LENGTH} characters`,\n text: `Expected a non-empty string of at most ${MAX_TEXT_LENGTH} characters`,\n});\n\nfunction isText(value: unknown): boolean {\n return typeof value === \"string\" && value.length > 0 && value.length <= MAX_TEXT_LENGTH;\n}\n\nfunction fits(kind: FieldKind, value: unknown): boolean {\n if (kind === \"id\") {\n return isId(value);\n }\n return kind === \"level\" ? SHARE_LEVELS.some((level) => level === value) : isText(value);\n}\n\nfunction fieldJson(kind: FieldKind): JsonSchema {\n if (kind === \"id\") {\n return idJson();\n }\n return kind === \"level\"\n ? { type: \"string\", enum: [...SHARE_LEVELS] }\n : { type: \"string\", minLength: 1, maxLength: MAX_TEXT_LENGTH };\n}\n\n/** How one input is checked and parsed. */\ninterface InputShape {\n readonly fields: Readonly<Record<string, Field>>;\n /** Keys checked by `issues` rather than by a field: `listMembers`' paging. */\n readonly more?: Readonly<Record<string, JsonSchema>>;\n /** Issues no single field shows: a by-name method needs a name or an email. */\n readonly issues?: (value: Readonly<Record<string, unknown>>) => Issues;\n /** The parsed input, when it is not the known keys as given. */\n readonly parse?: (value: Readonly<Record<string, unknown>>) => unknown;\n}\n\nfunction fieldIssues(shape: InputShape, value: Readonly<Record<string, unknown>>): Issues {\n return Object.entries(shape.fields).flatMap(([key, field]): Issues => {\n const given = value[key];\n const missing = given === undefined && field.optional !== true;\n const wrong = given !== undefined && !fits(field.kind, given);\n return missing || wrong ? [{ message: MESSAGES[field.kind], path: [key] }] : [];\n });\n}\n\nfunction inputJson(shape: InputShape): JsonSchema {\n const properties = Object.fromEntries(\n Object.entries(shape.fields).map(([key, field]) => [key, fieldJson(field.kind)]),\n );\n const required = Object.entries(shape.fields)\n .filter(([, field]) => field.optional !== true)\n .map(([key]) => key);\n return objectJson({ ...properties, ...shape.more }, required);\n}\n\n/** An input made of `shape`'s keys and nothing else. */\nfunction objectInput<Input, Parsed = Input>(shape: InputShape): KitSchema<Input, Parsed> {\n const keys = [...Object.keys(shape.fields), ...Object.keys(shape.more ?? {})];\n return kitSchema<Input, Parsed>(\n (input) => {\n if (!isRecord(input)) {\n return { issues: [{ message: \"Expected an object\", path: [] }] };\n }\n const issues = [\n ...unknownKeys(input, keys),\n ...fieldIssues(shape, input),\n ...(shape.issues?.(input) ?? []),\n ];\n if (issues.length > 0) {\n return { issues };\n }\n const parsed =\n shape.parse?.(input) ?? Object.fromEntries(keys.map((key) => [key, input[key]]));\n return { value: parsed as Parsed };\n },\n { input: () => inputJson(shape) },\n );\n}\n\nconst ID: Field = { kind: \"id\" };\nconst LEVEL: Field = { kind: \"level\" };\nconst ROLE: Field = { kind: \"role\" };\nconst OPTIONAL_ROLE: Field = { kind: \"role\", optional: true };\nconst OPTIONAL_TEXT: Field = { kind: \"text\", optional: true };\n\nfunction lookupIssues(value: Readonly<Record<string, unknown>>): Issues {\n return value.name === undefined && value.email === undefined\n ? [{ message: \"Give name or email\", path: [] }]\n : [];\n}\n\n/** `{ id, userId, level }`: `share`'s and `setLevel`'s input. */\nexport function shareInput(): KitSchema<ShareInput> {\n return objectInput<ShareInput>({ fields: { id: ID, userId: ID, level: LEVEL } });\n}\n\n/** `{ id, name?, email?, level }`: `shareByName`'s input. */\nexport function shareByNameInput(): KitSchema<ShareByNameInput, ShareByNameQuery> {\n return objectInput<ShareByNameInput, ShareByNameQuery>({\n fields: { id: ID, name: OPTIONAL_TEXT, email: OPTIONAL_TEXT, level: LEVEL },\n issues: lookupIssues,\n });\n}\n\n/** `{ id, userId }`: `unshare`'s input. */\nexport function unshareInput(): KitSchema<UnshareInput> {\n return objectInput<UnshareInput>({ fields: { id: ID, userId: ID } });\n}\n\n/** `{ entryId, userId, role? }`: `invite`'s input. */\nexport function inviteInput(): KitSchema<InviteInput, InviteQuery> {\n return objectInput<InviteInput, InviteQuery>({\n fields: { entryId: ID, userId: ID, role: OPTIONAL_ROLE },\n });\n}\n\n/** `{ entryId, name?, email?, role? }`: `inviteByName`'s input. */\nexport function inviteByNameInput(): KitSchema<InviteByNameInput, InviteByNameQuery> {\n return objectInput<InviteByNameInput, InviteByNameQuery>({\n fields: { entryId: ID, name: OPTIONAL_TEXT, email: OPTIONAL_TEXT, role: OPTIONAL_ROLE },\n issues: lookupIssues,\n });\n}\n\n/** `{ entryId, userId }`: `remove`'s input. */\nexport function memberInput(): KitSchema<MemberInput> {\n return objectInput<MemberInput>({ fields: { entryId: ID, userId: ID } });\n}\n\n/** `{ entryId }`: `leave`'s input. */\nexport function leaveInput(): KitSchema<LeaveInput> {\n return objectInput<LeaveInput>({ fields: { entryId: ID } });\n}\n\n/** `{ entryId, userId, role }`: `setRole`'s input. */\nexport function setRoleInput(): KitSchema<SetRoleInput> {\n return objectInput<SetRoleInput>({ fields: { entryId: ID, userId: ID, role: ROLE } });\n}\n\n/** `{ entryId, cursor?, limit? }`: `listMembers`' input. */\nexport function listMembersInput(): KitSchema<ListMembersInput, ListMembersQuery> {\n return objectInput<ListMembersInput, ListMembersQuery>({\n fields: { entryId: ID },\n more: {\n cursor: { type: \"string\", minLength: 1, maxLength: MAX_CURSOR_LENGTH },\n limit: { type: \"integer\", minimum: 1, maximum: LIST_MAX_LIMIT, default: LIST_DEFAULT_LIMIT },\n },\n issues: (value) => pagingIssues({ cursor: value.cursor, limit: value.limit }),\n parse: (value): ListMembersQuery => ({\n entryId: value.entryId as string,\n cursor: value.cursor as string | undefined,\n limit: Math.min((value.limit as number | undefined) ?? LIST_DEFAULT_LIMIT, LIST_MAX_LIMIT),\n }),\n });\n}\n\nfunction shareIssues(entry: unknown, path: readonly PropertyKey[]): Issues {\n const valid =\n isRecord(entry) &&\n isId(entry.userId) &&\n isAccessLevel(entry.level) &&\n Object.keys(entry).length === 2;\n return valid ? [] : [{ message: \"Expected { userId, level }\", path: [...path] }];\n}\n\nfunction shareJson(): JsonSchema {\n return objectJson({ userId: idJson(), level: { type: \"string\", enum: [...ACCESS_LEVELS] } }, [\n \"userId\",\n \"level\",\n ]);\n}\n\n/** `[{ userId, level }]`: an access list, as `share`, `unshare`, `setLevel` and `listShares` return it. */\nexport function aclOutput(): KitSchema<ACL> {\n return kitSchema<ACL>(\n (value) => {\n if (!Array.isArray(value)) {\n return { issues: [{ message: \"Expected an access list\", path: [] }] };\n }\n const issues = value.flatMap((entry: unknown, index) => shareIssues(entry, [index]));\n return issues.length > 0 ? { issues } : { value: value as ACL };\n },\n { input: () => ({ type: \"array\", items: shareJson() }) },\n );\n}\n\nfunction memberIssues(member: unknown, path: readonly PropertyKey[]): Issues {\n const valid =\n isRecord(member) &&\n isId(member.userId) &&\n typeof member.role === \"string\" &&\n (member.level === null || isAccessLevel(member.level)) &&\n Object.keys(member).length === 3;\n return valid ? [] : [{ message: \"Expected { userId, role, level }\", path: [...path] }];\n}\n\nfunction memberJson(): JsonSchema {\n return objectJson(\n { userId: idJson(), role: { type: \"string\" }, level: { enum: [...ACCESS_LEVELS, null] } },\n [\"userId\", \"role\", \"level\"],\n );\n}\n\n/** `{ userId, role, level }`: a member, as `invite` and `setRole` return it. */\nexport function memberOutput(): KitSchema<Member> {\n return kitSchema<Member>(\n (value) => {\n const issues = memberIssues(value, []);\n return issues.length > 0 ? { issues } : { value: value as Member };\n },\n { input: memberJson },\n );\n}\n\n/** `{ items, nextCursor }`: one page of `listMembers`. */\nexport function membersPageOutput(): KitSchema<MembersPage> {\n return kitSchema<MembersPage>(\n (value) => {\n if (!isRecord(value) || !Array.isArray(value.items)) {\n return { issues: [{ message: \"Expected a page of members\", path: [] }] };\n }\n const issues = [\n ...unknownKeys(value, [\"items\", \"nextCursor\"]),\n ...value.items.flatMap((member: unknown, index) => memberIssues(member, [\"items\", index])),\n ];\n if (value.nextCursor !== null && typeof value.nextCursor !== \"string\") {\n issues.push({ message: \"Expected a cursor or null\", path: [\"nextCursor\"] });\n }\n return issues.length > 0 ? { issues } : { value: value as unknown as MembersPage };\n },\n {\n input: () =>\n objectJson(\n {\n items: { type: \"array\", items: memberJson() },\n nextCursor: { type: [\"string\", \"null\"] },\n },\n [\"items\", \"nextCursor\"],\n ),\n },\n );\n}\n","// The sharing and membership kit's contract half (RFC 0003 section 12.3).\n// `sharing.contract` returns ordinary `query` and `mutation` entries to\n// spread into a contract's `methods`, for one of two ways a service's rows\n// are shared:\n//\n// export const project = defineContract(\"projectService\", {\n// entity: projectSchema,\n// methods: {\n// ...sharing.contract({ mode: \"acl\" }), // share, unshare, setLevel, listShares\n// ...sharing.contract({ mode: \"members\", methods: [\"invite\", \"remove\", \"leave\", \"listMembers\"] }),\n// },\n// });\n//\n// `\"acl\"` edits the JSON access list a `jsonAcl` policy reads; `\"members\"`\n// edits the membership table a `members` policy reads. `methods` picks the\n// methods to add, by name; without it a mode adds all of its methods but the\n// by-name ones (`shareByName`, `inviteByName`), which find the user by name\n// or email through the server's `resolveUser`. The server half\n// (`sharing.handlers` on `./server`) finds the kit's entries in the contract\n// by what they were made for (`sharingSpecOf`), and its types by their tag\n// (`SharingTag`), so the two halves name the same methods.\n\nimport type { ACL } from \"../access\";\nimport { mutation, query, type MethodDef, type MutationDef, type QueryDef } from \"../methods\";\nimport { idInput, nullOutput, type IdInput } from \"./crudSchemas\";\nimport type { KitSchema } from \"./schemas\";\nimport {\n aclOutput,\n inviteByNameInput,\n inviteInput,\n leaveInput,\n listMembersInput,\n memberInput,\n memberOutput,\n membersPageOutput,\n setRoleInput,\n shareByNameInput,\n shareInput,\n unshareInput,\n type InviteByNameInput,\n type InviteByNameQuery,\n type InviteInput,\n type InviteQuery,\n type LeaveInput,\n type ListMembersInput,\n type ListMembersQuery,\n type Member,\n type MemberInput,\n type MembersPage,\n type SetRoleInput,\n type ShareByNameInput,\n type ShareByNameQuery,\n type ShareInput,\n type UnshareInput,\n} from \"./sharingSchemas\";\n\n/** How a service's rows are shared: a JSON access list (`jsonAcl`) or a membership table (`members`). */\nexport type SharingMode = \"acl\" | \"members\";\n\n/** The methods of `mode: \"acl\"`. */\nexport type AclMethodName = \"share\" | \"shareByName\" | \"unshare\" | \"setLevel\" | \"listShares\";\n\n/** The methods of `mode: \"members\"`. */\nexport type MembersMethodName =\n | \"invite\"\n | \"inviteByName\"\n | \"remove\"\n | \"leave\"\n | \"setRole\"\n | \"listMembers\";\n\n/** Every method the sharing kit can add. */\nexport type SharingMethodName = AclMethodName | MembersMethodName;\n\n/** The methods that find their user by name or email: opt-in, since they need `resolveUser`. */\nexport type SharingByNameMethod = \"shareByName\" | \"inviteByName\";\n\n/** Every method of each mode: those of RFC 0003 section 12.3, then the by-name one. */\nexport const SHARING_METHODS: {\n readonly acl: readonly AclMethodName[];\n readonly members: readonly MembersMethodName[];\n} = Object.freeze({\n acl: Object.freeze<AclMethodName[]>([\n \"share\",\n \"unshare\",\n \"setLevel\",\n \"listShares\",\n \"shareByName\",\n ]),\n members: Object.freeze<MembersMethodName[]>([\n \"invite\",\n \"remove\",\n \"leave\",\n \"setRole\",\n \"listMembers\",\n \"inviteByName\",\n ]),\n});\n\n/** Type-only: marks a method the sharing kit made, and which one. Never set. */\nexport interface SharingTag<Kind extends SharingMethodName> {\n readonly \"~sharing\"?: Kind;\n}\n\n/** `sharing.contract`'s options, for one mode. */\nexport type SharingContractOptions =\n | {\n readonly mode: \"acl\";\n /** The methods to add; all but `shareByName` when absent. */\n readonly methods?: readonly AclMethodName[];\n /** Descriptions for people and agents, per method; the kit's own for the rest. */\n readonly describe?: { readonly [Name in AclMethodName]?: string };\n }\n | {\n readonly mode: \"members\";\n /** The methods to add; all but `inviteByName` when absent. */\n readonly methods?: readonly MembersMethodName[];\n /** Descriptions for people and agents, per method; the kit's own for the rest. */\n readonly describe?: { readonly [Name in MembersMethodName]?: string };\n };\n\nexport type SharingShare = MutationDef<KitSchema<ShareInput>, KitSchema<ACL>> & SharingTag<\"share\">;\nexport type SharingShareByName = MutationDef<\n KitSchema<ShareByNameInput, ShareByNameQuery>,\n KitSchema<ACL>\n> &\n SharingTag<\"shareByName\">;\nexport type SharingUnshare = MutationDef<KitSchema<UnshareInput>, KitSchema<ACL>> &\n SharingTag<\"unshare\">;\nexport type SharingSetLevel = MutationDef<KitSchema<ShareInput>, KitSchema<ACL>> &\n SharingTag<\"setLevel\">;\nexport type SharingListShares = QueryDef<KitSchema<IdInput>, KitSchema<ACL>> &\n SharingTag<\"listShares\">;\nexport type SharingInvite = MutationDef<KitSchema<InviteInput, InviteQuery>, KitSchema<Member>> &\n SharingTag<\"invite\">;\nexport type SharingInviteByName = MutationDef<\n KitSchema<InviteByNameInput, InviteByNameQuery>,\n KitSchema<Member>\n> &\n SharingTag<\"inviteByName\">;\nexport type SharingRemove = MutationDef<KitSchema<MemberInput>, KitSchema<null>> &\n SharingTag<\"remove\">;\nexport type SharingLeave = MutationDef<KitSchema<LeaveInput>, KitSchema<null>> &\n SharingTag<\"leave\">;\nexport type SharingSetRole = MutationDef<KitSchema<SetRoleInput>, KitSchema<Member>> &\n SharingTag<\"setRole\">;\nexport type SharingListMembers = QueryDef<\n KitSchema<ListMembersInput, ListMembersQuery>,\n KitSchema<MembersPage>\n> &\n SharingTag<\"listMembers\">;\n\n/** The entry `sharing.contract` makes for method `Name`. */\nexport type SharingDefOf<Name extends SharingMethodName> = {\n share: SharingShare;\n shareByName: SharingShareByName;\n unshare: SharingUnshare;\n setLevel: SharingSetLevel;\n listShares: SharingListShares;\n invite: SharingInvite;\n inviteByName: SharingInviteByName;\n remove: SharingRemove;\n leave: SharingLeave;\n setRole: SharingSetRole;\n listMembers: SharingListMembers;\n}[Name];\n\ntype ModeIn<O> = O extends { readonly mode: infer Mode } ? Mode : never;\n\ntype DefaultNames<Mode> = Mode extends \"acl\"\n ? Exclude<AclMethodName, SharingByNameMethod>\n : Mode extends \"members\"\n ? Exclude<MembersMethodName, SharingByNameMethod>\n : never;\n\ntype NamesIn<O> = O extends { readonly methods: readonly (infer Name extends SharingMethodName)[] }\n ? Name\n : DefaultNames<ModeIn<O>>;\n\n/** The entries `sharing.contract(options)` returns: one per method it adds. */\nexport type SharingMethods<O> = { readonly [Name in NamesIn<O>]: SharingDefOf<Name> };\n\n/** What the server half needs to know about a method the sharing kit made. */\nexport interface SharingSpec {\n readonly method: SharingMethodName;\n readonly mode: SharingMode;\n}\n\nconst SPECS = new WeakMap<object, SharingSpec>();\n\n/** What the sharing kit made `method` for, or `undefined` for any other method. */\nexport function sharingSpecOf(method: unknown): SharingSpec | undefined {\n return typeof method === \"object\" && method !== null ? SPECS.get(method) : undefined;\n}\n\nconst DESCRIBE: Readonly<Record<SharingMethodName, string>> = Object.freeze({\n share:\n \"Gives a user a level on one row: Read, Moderate or Admin. A user the row is shared with already gets the new level, unless theirs is above yours.\",\n shareByName:\n \"Gives the user this name or email finds a level on one row: Read, Moderate or Admin. Use the email when several users may share the name.\",\n unshare:\n \"Takes a user's level on one row away. The owner's cannot be taken away, nor that of a user whose level is above yours.\",\n setLevel:\n \"Changes the level of a user one row is already shared with, unless their level is above yours.\",\n listShares: \"Lists the users one row is shared with, and their levels.\",\n invite: \"Makes a user a member of one row, with a role (the lowest role when none is given).\",\n inviteByName:\n \"Makes the user this name or email finds a member of one row, with a role (the lowest role when none is given). Use the email when several users may share the name.\",\n remove:\n \"Ends a user's membership of one row. The last Admin member cannot be removed, nor a member whose level is above yours.\",\n leave: \"Ends the caller's own membership of one row. The last Admin member cannot leave.\",\n setRole: \"Changes a member's role on one row, unless their level is above yours.\",\n listMembers:\n \"Lists the members of one row and their roles, a page at a time. Pass a page's nextCursor back as cursor for the next page.\",\n});\n\nconst BUILD: Readonly<Record<SharingMethodName, (describe: string) => MethodDef>> = Object.freeze({\n share: (describe) => mutation({ input: shareInput(), output: aclOutput(), describe }),\n shareByName: (describe) => mutation({ input: shareByNameInput(), output: aclOutput(), describe }),\n unshare: (describe) => mutation({ input: unshareInput(), output: aclOutput(), describe }),\n setLevel: (describe) => mutation({ input: shareInput(), output: aclOutput(), describe }),\n listShares: (describe) => query({ input: idInput(), output: aclOutput(), describe }),\n invite: (describe) => mutation({ input: inviteInput(), output: memberOutput(), describe }),\n inviteByName: (describe) =>\n mutation({ input: inviteByNameInput(), output: memberOutput(), describe }),\n remove: (describe) => mutation({ input: memberInput(), output: nullOutput(), describe }),\n leave: (describe) => mutation({ input: leaveInput(), output: nullOutput(), describe }),\n setRole: (describe) => mutation({ input: setRoleInput(), output: memberOutput(), describe }),\n listMembers: (describe) =>\n query({ input: listMembersInput(), output: membersPageOutput(), describe }),\n});\n\ntype UnknownRecord = Readonly<Record<string, unknown>>;\n\nfunction isRecord(value: unknown): value is UnknownRecord {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction fail(message: string): never {\n throw new TypeError(`sharing.contract: ${message}`);\n}\n\nfunction quoted(names: readonly string[]): string {\n return names.map((name) => `\"${name}\"`).join(\", \");\n}\n\nfunction modeOf(options: unknown): SharingMode {\n if (!isRecord(options)) {\n fail('options must be { mode: \"acl\" | \"members\", methods?, describe? }');\n }\n const unknownKey = Object.keys(options).find(\n (key) => ![\"mode\", \"methods\", \"describe\"].includes(key),\n );\n if (unknownKey !== undefined) {\n fail(`unknown option \"${unknownKey}\"; the options are mode, methods and describe`);\n }\n const { mode } = options;\n if (mode !== \"acl\" && mode !== \"members\") {\n fail('mode must be \"acl\" (a jsonAcl access list) or \"members\" (a members table)');\n }\n return mode;\n}\n\n/** The methods the options add: those `methods` names, or the mode's default ones. */\nfunction namesOf(mode: SharingMode, methods: unknown): readonly SharingMethodName[] {\n const all: readonly SharingMethodName[] = SHARING_METHODS[mode];\n if (methods === undefined) {\n return all.filter((name) => name !== \"shareByName\" && name !== \"inviteByName\");\n }\n const valid =\n Array.isArray(methods) &&\n methods.length > 0 &&\n methods.every((name) => all.some((known) => known === name)) &&\n new Set(methods).size === methods.length;\n if (!valid) {\n fail(`methods must name one or more distinct methods of mode \"${mode}\": ${quoted(all)}`);\n }\n return methods as SharingMethodName[];\n}\n\n/** The descriptions the options give, each for a method they add. */\nfunction describeOf(names: readonly SharingMethodName[], describe: unknown): UnknownRecord {\n if (describe === undefined) {\n return {};\n }\n if (!isRecord(describe)) {\n fail(\"describe must map method names to descriptions\");\n }\n for (const [name, text] of Object.entries(describe)) {\n if (!names.some((added) => added === name)) {\n fail(`describe names \"${name}\", which these options do not add`);\n }\n if (typeof text !== \"string\" || text.length === 0) {\n fail(`describe for \"${name}\" must be a non-empty string`);\n }\n }\n return describe;\n}\n\nfunction contract<const O extends SharingContractOptions>(options: O): SharingMethods<O> {\n const mode = modeOf(options);\n const names = namesOf(mode, options.methods);\n const described = describeOf(names, options.describe);\n const methods: Record<string, MethodDef> = {};\n for (const name of names) {\n const own = described[name];\n const def = BUILD[name](typeof own === \"string\" ? own : DESCRIBE[name]);\n SPECS.set(def, Object.freeze({ method: name, mode }));\n methods[name] = def;\n }\n return Object.freeze(methods) as unknown as SharingMethods<O>;\n}\n\n/**\n * The sharing and membership kit's contract half: `sharing.contract({ mode,\n * methods?, describe? })` makes the methods that share a row through its\n * JSON access list (`mode: \"acl\"`) or its membership table\n * (`mode: \"members\"`), which the server half (`sharing.handlers` on\n * `./server`) implements.\n */\nexport const sharing = Object.freeze({ contract });\n"],"mappings":"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","names":["isRecord","SPECS","DESCRIBE","isRecord","fail","describeOf","contract"]}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isRecord
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
|
|
5
|
-
// src/version.ts
|
|
6
|
-
var QUICKDRAW_VERSION = "5.1.1";
|
|
3
|
+
} from "./chunk-QSU47XEX.js";
|
|
7
4
|
|
|
8
5
|
// src/contract/names.ts
|
|
9
6
|
function entityRoom(service, id, level) {
|
|
@@ -22,6 +19,7 @@ function collectionTopic(collection, scope) {
|
|
|
22
19
|
function streamRoom(service, stream, scope) {
|
|
23
20
|
return scope === void 0 ? `qd:s:${service}:${stream}` : `qd:s:${service}:${stream}:${scope}`;
|
|
24
21
|
}
|
|
22
|
+
var STREAM_ROOM_PREFIX = "qd:s:";
|
|
25
23
|
function userRoom(userId) {
|
|
26
24
|
return `user:${userId}`;
|
|
27
25
|
}
|
|
@@ -29,6 +27,10 @@ var RESERVED_ROOM_PREFIXES = Object.freeze(["qd:", "user:"]);
|
|
|
29
27
|
function reservedRoomPrefix(room) {
|
|
30
28
|
return RESERVED_ROOM_PREFIXES.find((prefix) => room.startsWith(prefix));
|
|
31
29
|
}
|
|
30
|
+
var SOCKET_ID = /^[\w-]{20}$/u;
|
|
31
|
+
function socketIdShaped(room) {
|
|
32
|
+
return SOCKET_ID.test(room);
|
|
33
|
+
}
|
|
32
34
|
var CLIENT_EVENTS = Object.freeze({
|
|
33
35
|
/** A method call: `{ id, s, m, i, v? }`, answered by ack. */
|
|
34
36
|
call: "qd:call",
|
|
@@ -53,7 +55,11 @@ var CLIENT_EVENTS = Object.freeze({
|
|
|
53
55
|
/** Unsubscribes from a stream. */
|
|
54
56
|
streamUnsub: "qd:stream:unsub",
|
|
55
57
|
/** A fire-and-forget channel message, sent volatile: `[s, channel, payload]`. */
|
|
56
|
-
channel: "qd:ch"
|
|
58
|
+
channel: "qd:ch",
|
|
59
|
+
/** A peer link's signal to another socket of an app room: `{ room, to, sig }`. */
|
|
60
|
+
peer: "qd:peer",
|
|
61
|
+
/** A peer link's message for the server to relay: `[room, to, label, text]`, dropped on `fast` when backed up. */
|
|
62
|
+
relay: "qd:relay"
|
|
57
63
|
});
|
|
58
64
|
var SERVER_EVENTS = Object.freeze({
|
|
59
65
|
/** The handshake reply: `{ protocol, server, limits, features, userId, serviceAccess }`. */
|
|
@@ -75,7 +81,13 @@ var SERVER_EVENTS = Object.freeze({
|
|
|
75
81
|
/** Reconnect within a jitter window. */
|
|
76
82
|
rotate: "qd:rotate",
|
|
77
83
|
/** The user's service grants changed. */
|
|
78
|
-
access: "qd:access"
|
|
84
|
+
access: "qd:access",
|
|
85
|
+
/** A peer link's signal from another socket of an app room: `{ room, from, userId, sig }`. */
|
|
86
|
+
peer: "qd:peer",
|
|
87
|
+
/** A peer link's message the server relays: `[room, from, label, text]`. */
|
|
88
|
+
relay: "qd:relay",
|
|
89
|
+
/** An app room's host (host mode): `[room, host]`, the host's socket id or `null`. */
|
|
90
|
+
host: "qd:host"
|
|
79
91
|
});
|
|
80
92
|
|
|
81
93
|
// src/protocol/version.ts
|
|
@@ -90,33 +102,38 @@ function isProtocolMismatch(value) {
|
|
|
90
102
|
function isAuthenticationRefused(value) {
|
|
91
103
|
return isRecord(value) && value.code === "UNAUTHENTICATED";
|
|
92
104
|
}
|
|
105
|
+
function isAuthenticationUnavailable(value) {
|
|
106
|
+
return isRecord(value) && value.code === "INTERNAL";
|
|
107
|
+
}
|
|
93
108
|
var MAX_SUBSCRIBE_IDS = 500;
|
|
109
|
+
var MAX_ENTITY_IDS_PER_SOCKET = 1e4;
|
|
110
|
+
var MAX_SCOPES_PER_SOCKET = 1e3;
|
|
111
|
+
var MAX_TOPICS_PER_SOCKET = 1e3;
|
|
94
112
|
var MAX_SCOPE_LENGTH = 256;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
var
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
113
|
+
var MAX_PEER_LINKS = 64;
|
|
114
|
+
var MAX_PEER_MESSAGE_BYTES = 65536;
|
|
115
|
+
var MAX_PEER_SIGNAL_LENGTH = 16384;
|
|
116
|
+
var MAX_PEER_CANDIDATE_LENGTH = 1024;
|
|
117
|
+
var PEER_SIGNAL_RATE = 20;
|
|
118
|
+
var PEER_SIGNAL_BURST = 40;
|
|
119
|
+
var PEER_RELAY_RATE = 120;
|
|
120
|
+
var PEER_RELAY_BURST = 240;
|
|
121
|
+
var PEER_RELAY_BYTES_PER_SECOND = 1048576;
|
|
122
|
+
var PEER_RELAY_BYTES_BURST = 2097152;
|
|
123
|
+
var PEER_RELAY_FAST_BACKLOG = 64;
|
|
106
124
|
|
|
107
125
|
export {
|
|
108
|
-
QUICKDRAW_VERSION,
|
|
109
|
-
ACCESS_LEVELS,
|
|
110
|
-
isAccessLevel,
|
|
111
126
|
entityRoom,
|
|
112
127
|
collectionRoom,
|
|
113
128
|
topicRoom,
|
|
114
129
|
SERVICE_TOPIC,
|
|
115
130
|
collectionTopic,
|
|
116
131
|
streamRoom,
|
|
132
|
+
STREAM_ROOM_PREFIX,
|
|
117
133
|
userRoom,
|
|
118
134
|
RESERVED_ROOM_PREFIXES,
|
|
119
135
|
reservedRoomPrefix,
|
|
136
|
+
socketIdShaped,
|
|
120
137
|
CLIENT_EVENTS,
|
|
121
138
|
SERVER_EVENTS,
|
|
122
139
|
PROTOCOL_VERSION,
|
|
@@ -124,7 +141,22 @@ export {
|
|
|
124
141
|
PROTOCOL_MISMATCH,
|
|
125
142
|
isProtocolMismatch,
|
|
126
143
|
isAuthenticationRefused,
|
|
144
|
+
isAuthenticationUnavailable,
|
|
127
145
|
MAX_SUBSCRIBE_IDS,
|
|
128
|
-
|
|
146
|
+
MAX_ENTITY_IDS_PER_SOCKET,
|
|
147
|
+
MAX_SCOPES_PER_SOCKET,
|
|
148
|
+
MAX_TOPICS_PER_SOCKET,
|
|
149
|
+
MAX_SCOPE_LENGTH,
|
|
150
|
+
MAX_PEER_LINKS,
|
|
151
|
+
MAX_PEER_MESSAGE_BYTES,
|
|
152
|
+
MAX_PEER_SIGNAL_LENGTH,
|
|
153
|
+
MAX_PEER_CANDIDATE_LENGTH,
|
|
154
|
+
PEER_SIGNAL_RATE,
|
|
155
|
+
PEER_SIGNAL_BURST,
|
|
156
|
+
PEER_RELAY_RATE,
|
|
157
|
+
PEER_RELAY_BURST,
|
|
158
|
+
PEER_RELAY_BYTES_PER_SECOND,
|
|
159
|
+
PEER_RELAY_BYTES_BURST,
|
|
160
|
+
PEER_RELAY_FAST_BACKLOG
|
|
129
161
|
};
|
|
130
|
-
//# sourceMappingURL=chunk-
|
|
162
|
+
//# sourceMappingURL=chunk-X4ZU4QH5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/contract/names.ts","../src/protocol/version.ts"],"sourcesContent":["// Room and event names for protocol v5 (RFC 0003 sections 6, 7 and 8). Ported\n// from the 4.1 room helpers (4.1 `src/shared/types.ts:364-405`), which were\n// plain template strings so shared code could name a room without holding a\n// service instance. The 5.0 rooms live under a `qd:` prefix so they never\n// collide with an app's own rooms; the per-user room keeps its 4.1 name.\n\nimport type { AccessLevel } from \"./access\";\n\n/**\n * The room for one entity at one access tier: `qd:e:{service}:{id}@{level}`.\n * A subscriber joins the room for its own level, so a frame is built once and\n * stripped once per occupied tier.\n */\nexport function entityRoom(service: string, id: string, level: AccessLevel): string {\n return `qd:e:${service}:${id}@${level}`;\n}\n\n/** The room for one scope of a collection: `qd:c:{service}:{collection}:{scope}`. */\nexport function collectionRoom(service: string, collection: string, scope: string): string {\n return `qd:c:${service}:${collection}:${scope}`;\n}\n\n/** The room for a watched topic of a service: `qd:t:{service}:{topic}`. */\nexport function topicRoom(service: string, topic: string): string {\n return `qd:t:${service}:${topic}`;\n}\n\n/**\n * The change topic of a whole service (RFC 0003 section 11.3): every flush\n * that changes one of its rows or collection scopes signals it.\n */\nexport const SERVICE_TOPIC = \"service\";\n\n/** The change topic of one scope of a collection: `{collection}:{scope}` (RFC 0003 section 11.3). */\nexport function collectionTopic(collection: string, scope: string): string {\n return `${collection}:${scope}`;\n}\n\n/**\n * The room of a stream's feed (RFC 0003 section 12.5):\n * `qd:s:{service}:{stream}:{scope}` for one scope of a scoped stream, and\n * `qd:s:{service}:{stream}` for a global stream. Stream names hold no colon.\n */\nexport function streamRoom(service: string, stream: string, scope?: string): string {\n return scope === undefined ? `qd:s:${service}:${stream}` : `qd:s:${service}:${stream}:${scope}`;\n}\n\n/**\n * The prefix every room {@link streamRoom} names starts with. The builder\n * spells it out, since docs/protocol-v5.md is generated from its template.\n */\nexport const STREAM_ROOM_PREFIX = \"qd:s:\";\n\n/** The room every authenticated socket of a user joins: `user:{userId}`, as in 4.1. */\nexport function userRoom(userId: string): string {\n return `user:${userId}`;\n}\n\n/**\n * The prefixes of the rooms the framework manages: entity, collection, topic\n * and stream rooms (`qd:`) and user rooms (`user:`). `ctx.rooms.join` refuses\n * them; a socket enters those rooms only through their authorized paths.\n */\nexport const RESERVED_ROOM_PREFIXES: readonly string[] = Object.freeze([\"qd:\", \"user:\"]);\n\n/**\n * The reserved prefix `room` starts with (`qd:` or `user:`), or `undefined`\n * when an app room may have that name.\n */\nexport function reservedRoomPrefix(room: string): string | undefined {\n return RESERVED_ROOM_PREFIXES.find((prefix) => room.startsWith(prefix));\n}\n\nconst SOCKET_ID = /^[\\w-]{20}$/u;\n\n/**\n * True when `room` has the shape of a Socket.IO socket id: 20 letters,\n * digits, `_` or `-`. Socket.IO puts every socket alone in a room named by\n * its id, 15 random bytes as base64url (the `base64id` package), so an app\n * room of that shape could be one socket's own room: `ctx.rooms.join`,\n * `rooms.size`, `rooms.leave` and presence refuse such names (the 5.1\n * audit's RT-I1), and no app room has that shape.\n */\nexport function socketIdShaped(room: string): boolean {\n return SOCKET_ID.test(room);\n}\n\n/** Events a client sends to the server (RFC 0003 section 8.2). */\nexport const CLIENT_EVENTS = Object.freeze({\n /** A method call: `{ id, s, m, i, v? }`, answered by ack. */\n call: \"qd:call\",\n /** Cancels an in-flight call: `{ id }`. */\n cancel: \"qd:cancel\",\n /** Subscribes to entities by id, with the revisions held. */\n sub: \"qd:sub\",\n /** Unsubscribes from entities. */\n unsub: \"qd:unsub\",\n /** Subscribes to a collection scope, or resumes it from a revision. */\n collectionSub: \"qd:col:sub\",\n /** Unsubscribes from a collection scope. */\n collectionUnsub: \"qd:col:unsub\",\n /** Loads collection items by id. */\n collectionItems: \"qd:col:items\",\n /** Joins a watched topic: `{ s, topic }`. */\n watch: \"qd:watch\",\n /** Leaves a watched topic. */\n unwatch: \"qd:unwatch\",\n /** Subscribes to a stream and receives its seed. */\n streamSub: \"qd:stream:sub\",\n /** Unsubscribes from a stream. */\n streamUnsub: \"qd:stream:unsub\",\n /** A fire-and-forget channel message, sent volatile: `[s, channel, payload]`. */\n channel: \"qd:ch\",\n /** A peer link's signal to another socket of an app room: `{ room, to, sig }`. */\n peer: \"qd:peer\",\n /** A peer link's message for the server to relay: `[room, to, label, text]`, dropped on `fast` when backed up. */\n relay: \"qd:relay\",\n} as const);\n\n/** Events the server sends to a client (RFC 0003 section 8.3). */\nexport const SERVER_EVENTS = Object.freeze({\n /** The handshake reply: `{ protocol, server, limits, features, userId, serviceAccess }`. */\n hello: \"qd:hello\",\n /** Entity frames: full update, patch or removal. */\n entity: \"qd:e\",\n /** Collection deltas, batched per flush. */\n collection: \"qd:c\",\n /** A watched topic changed; the client invalidates its queries. */\n changed: \"qd:changed\",\n /** Access to a subscription was revoked. */\n revoked: \"qd:revoked\",\n /** Stream items. */\n stream: \"qd:stream\",\n /** Typed custom room events declared in a contract's `events`. */\n event: \"qd:event\",\n /** Who is in an app room the socket is in: the list on joining, then each user who joins or leaves. */\n presence: \"qd:presence\",\n /** Reconnect within a jitter window. */\n rotate: \"qd:rotate\",\n /** The user's service grants changed. */\n access: \"qd:access\",\n /** A peer link's signal from another socket of an app room: `{ room, from, userId, sig }`. */\n peer: \"qd:peer\",\n /** A peer link's message the server relays: `[room, from, label, text]`. */\n relay: \"qd:relay\",\n /** An app room's host (host mode): `[room, host]`, the host's socket id or `null`. */\n host: \"qd:host\",\n} as const);\n\n/** The name of an event a client sends. */\nexport type ClientEventName = (typeof CLIENT_EVENTS)[keyof typeof CLIENT_EVENTS];\n\n/** The name of an event the server sends. */\nexport type ServerEventName = (typeof SERVER_EVENTS)[keyof typeof SERVER_EVENTS];\n","// The protocol version and the connection handshake (RFC 0003 section 8.1).\n// A v5 client connects with `auth: { token, qd: { protocol: 5, client } }`.\n// The server answers with `qd:hello`, or refuses a client that names another\n// protocol with `PROTOCOL_MISMATCH` in the `connect_error` data, and one whose\n// authentication failed with `UNAUTHENTICATED` there. A client that sends no\n// `auth.qd` is a 4.x client, which only the legacy shim serves (section 8.5).\n// There is no capability negotiation: the version decides.\n\nimport type { AccessLevel } from \"../contract/access\";\nimport { isRecord } from \"./guards\";\n\n/** The wire protocol this package speaks. */\nexport const PROTOCOL_VERSION = 5;\n\n/** `auth.qd`: what a v5 client sends when it connects. */\nexport interface QdHandshake {\n /** The protocol the client speaks. The server refuses any but its own. */\n readonly protocol: number;\n /** The client package's version, for logs. */\n readonly client: string;\n}\n\n/** The Socket.IO `auth` a v5 client connects with. */\nexport interface HandshakeAuth {\n /** The session token the server's `authenticate` reads. */\n readonly token?: string;\n /** The protocol the client speaks. A client without it is a 4.x client (RFC 0003 section 8.5). */\n readonly qd: QdHandshake;\n}\n\n/**\n * True when `value` has the shape of `auth.qd`, whatever protocol it names.\n * Compare `protocol` with {@link PROTOCOL_VERSION} to decide whether to serve it.\n */\nexport function isQdHandshake(value: unknown): value is QdHandshake {\n return isRecord(value) && Number.isInteger(value.protocol) && typeof value.client === \"string\";\n}\n\n/** The code in the `connect_error` data of a connection refused for its protocol. */\nexport const PROTOCOL_MISMATCH = \"PROTOCOL_MISMATCH\";\n\n/**\n * The `data` of the error a server refuses a connection with when the client\n * speaks another protocol, or none. It is not an `ErrorCode`: no call failed.\n */\nexport interface ProtocolMismatch {\n readonly code: typeof PROTOCOL_MISMATCH;\n /** The protocol the server speaks. */\n readonly expected: number;\n}\n\n/** True when a `connect_error`'s `data` says the server speaks another protocol. */\nexport function isProtocolMismatch(value: unknown): value is ProtocolMismatch {\n return isRecord(value) && value.code === PROTOCOL_MISMATCH && Number.isInteger(value.expected);\n}\n\n/**\n * The `data` of the error a server refuses a connection with when it refused\n * the credentials: the app's `authenticate` threw a `QuickdrawError` with\n * code `UNAUTHENTICATED` (or `FORBIDDEN`), or the session it recorded\n * expired during the handshake. A client tells it apart from\n * {@link ProtocolMismatch} by `code`; reconnecting needs other credentials,\n * so the user is signed out. A server that could not check the credentials\n * refuses with {@link AuthenticationUnavailable} instead.\n */\nexport interface AuthenticationRefused {\n readonly code: \"UNAUTHENTICATED\";\n}\n\n/** True when a `connect_error`'s `data` says the server refused the connection's credentials. */\nexport function isAuthenticationRefused(value: unknown): value is AuthenticationRefused {\n return isRecord(value) && value.code === \"UNAUTHENTICATED\";\n}\n\n/**\n * The `data` of the error a server refuses a connection with when it could\n * not authenticate it: the app's `authenticate` (or `loadServiceAccess`)\n * failed with anything but a refusal, its session store or database down,\n * say. The credentials may well be good, so a client keeps the user it had\n * and connects again later, backing off as after a lost connection.\n */\nexport interface AuthenticationUnavailable {\n readonly code: \"INTERNAL\";\n}\n\n/** True when a `connect_error`'s `data` says the server could not authenticate the connection. */\nexport function isAuthenticationUnavailable(value: unknown): value is AuthenticationUnavailable {\n return isRecord(value) && value.code === \"INTERNAL\";\n}\n\n/** The most ids one `qd:sub` may name (RFC 0003 section 6), as `qd:hello` announces it. */\nexport const MAX_SUBSCRIBE_IDS = 500;\n\n/** The most rows one socket may hold entity subscriptions to at once, as `qd:hello` announces it. */\nexport const MAX_ENTITY_IDS_PER_SOCKET = 10_000;\n\n/** The most collection scopes one socket may hold at once, as `qd:hello` announces it. */\nexport const MAX_SCOPES_PER_SOCKET = 1_000;\n\n/** The most change topics one socket may watch at once, as `qd:hello` announces it. */\nexport const MAX_TOPICS_PER_SOCKET = 1_000;\n\n/** The longest collection scope value or change topic a frame may name, in characters. */\nexport const MAX_SCOPE_LENGTH = 256;\n\n/** The most peers one socket may signal at once (`qd:peer`), as `qd:hello` announces it. */\nexport const MAX_PEER_LINKS = 64;\n\n/** The longest peer link message, in UTF-8 bytes, on the data channel and through the relay alike. */\nexport const MAX_PEER_MESSAGE_BYTES = 65_536;\n\n/** The longest session description a `qd:peer` signal carries, in characters. */\nexport const MAX_PEER_SIGNAL_LENGTH = 16_384;\n\n/** The longest string of a `qd:peer` candidate (`candidate`, `sdpMid`, `usernameFragment`), in characters. */\nexport const MAX_PEER_CANDIDATE_LENGTH = 1024;\n\n/** Signals one socket may send per second (`qd:peer`); more are refused `RATE_LIMITED`. */\nexport const PEER_SIGNAL_RATE = 20;\n\n/** Signals one socket may send at once, after a pause. */\nexport const PEER_SIGNAL_BURST = 40;\n\n/** Relay messages one socket may send to one peer per second (`qd:relay`); more are dropped. */\nexport const PEER_RELAY_RATE = 120;\n\n/** Relay messages one socket may send to one peer at once, after a pause. */\nexport const PEER_RELAY_BURST = 240;\n\n/** Relay bytes one socket may send per second, to all its peers together; more are dropped. */\nexport const PEER_RELAY_BYTES_PER_SECOND = 1_048_576;\n\n/** Relay bytes one socket may send at once, after a pause. */\nexport const PEER_RELAY_BYTES_BURST = 2_097_152;\n\n/** Relay messages waiting for a socket's connection past which the relay drops a `fast` one to it. */\nexport const PEER_RELAY_FAST_BACKLOG = 64;\n\n/**\n * Each socket's lane of subscription events (`qd:sub`, `qd:col:sub`,\n * `qd:col:items`, `qd:watch`; RFC 0003 section 8.2), and how much one\n * socket may hold at once, as `qd:hello` announces them.\n */\nexport interface HelloSubscriptionLimits {\n /** Subscription events one socket may have running at once. */\n readonly maxInFlight: number;\n /** Subscription events one socket may have waiting; past that one fails with `RATE_LIMITED`. */\n readonly maxQueued: number;\n /** Rows one socket may hold entity subscriptions to at once; one more is `CONFLICT`. Absent from servers up to 5.1.0, which have no such cap. */\n readonly maxEntityIds?: number;\n /** Collection scopes one socket may hold at once; one more is `CONFLICT`. Absent from servers up to 5.1.0, which have no such cap. */\n readonly maxScopes?: number;\n /** Change topics one socket may watch at once; one more is `CONFLICT`. Absent from servers up to 5.1.0, which have no such cap. */\n readonly maxTopics?: number;\n}\n\n/**\n * What one socket may signal and relay over peer links (RFC 0003 section\n * 12.5), as `qd:hello` announces it. A client keeps its relay messages\n * within these, so a `rel` message is never dropped for its rate.\n */\nexport interface HelloPeerLimits {\n /** Peers one socket may signal at once; a signal to one more is `CONFLICT`. */\n readonly maxLinks: number;\n /** The longest link message, in UTF-8 bytes, on the data channel and through the relay alike. */\n readonly maxMessageBytes: number;\n /** Relay messages one socket may send to one peer per second; more are dropped. */\n readonly relayRate: number;\n /** Relay messages one socket may send to one peer at once, after a pause. */\n readonly relayBurst: number;\n /** Relay bytes one socket may send per second, to all its peers together; more are dropped. */\n readonly relayBytesPerSecond: number;\n /** Relay bytes one socket may send at once, after a pause. */\n readonly relayBytesBurst: number;\n}\n\n/** The limits a server announces in `qd:hello`, so a client can stay inside them. */\nexport interface HelloLimits {\n /** Queries one socket may have running at once; more wait in a queue (RFC 0003 section 9). */\n readonly maxInFlightQueries: number;\n /** Queries one socket may have waiting; past that a query fails with `RATE_LIMITED`. */\n readonly maxQueuedQueries: number;\n /** Ids one `qd:sub` may name (RFC 0003 section 6). */\n readonly maxSubscribeIds: number;\n /**\n * A call's default time limit in milliseconds, after which it fails with\n * `TIMEOUT`. A v5 client waits this long plus 2 s for an answer, so a slow\n * call ends with the server's `TIMEOUT` rather than its own.\n */\n readonly callTimeoutMs: number;\n /** The socket's lane of subscription events. */\n readonly subscriptions: HelloSubscriptionLimits;\n /** What peer links may signal and relay. Absent from a server that serves no peer links, whose `features` lacks `\"peer\"`. */\n readonly peers?: HelloPeerLimits;\n}\n\n/**\n * `qd:hello`: the server's answer to a v5 handshake, sent once per\n * connection, after the socket's listeners are in place. It says who the\n * socket acts for, so a client knows its user without another call.\n */\nexport interface HelloFrame {\n /** The protocol the server speaks. */\n readonly protocol: typeof PROTOCOL_VERSION;\n /** The server package's version. */\n readonly server: string;\n /**\n * This server's id: random, new each time a server starts. A client that\n * reconnects to the same id came back after a blip, to another id to a\n * restarted server (a game's world and clock start over) or, behind a\n * cluster, to another node, each of which has its own.\n */\n readonly serverId: string;\n /** What a client must stay within. */\n readonly limits: HelloLimits;\n /**\n * Names of optional server features that are on: `\"binary\"` (the stock\n * parser, `createServer({ binary: true })`) and `\"peer\"` (peer links,\n * `createServer({ peers: true })`). Informational: a client that finds no\n * `\"peer\"` opens no peer link.\n */\n readonly features: readonly string[];\n /** The user the socket acts for, or `null` when it is anonymous. */\n readonly userId: string | null;\n /**\n * The principal's service-wide grants when the socket connected, by\n * service name: `{ taskService: \"Admin\" }`. Empty for an anonymous socket\n * or a principal without grants. Later changes arrive as `qd:access`.\n */\n readonly serviceAccess: Readonly<Record<string, AccessLevel>>;\n}\n"],"mappings":";;;;;AAaO,SAAS,WAAW,SAAiB,IAAY,OAA4B;AAClF,SAAO,QAAQ,OAAO,IAAI,EAAE,IAAI,KAAK;AACvC;AAGO,SAAS,eAAe,SAAiB,YAAoB,OAAuB;AACzF,SAAO,QAAQ,OAAO,IAAI,UAAU,IAAI,KAAK;AAC/C;AAGO,SAAS,UAAU,SAAiB,OAAuB;AAChE,SAAO,QAAQ,OAAO,IAAI,KAAK;AACjC;AAMO,IAAM,gBAAgB;AAGtB,SAAS,gBAAgB,YAAoB,OAAuB;AACzE,SAAO,GAAG,UAAU,IAAI,KAAK;AAC/B;AAOO,SAAS,WAAW,SAAiB,QAAgB,OAAwB;AAClF,SAAO,UAAU,SAAY,QAAQ,OAAO,IAAI,MAAM,KAAK,QAAQ,OAAO,IAAI,MAAM,IAAI,KAAK;AAC/F;AAMO,IAAM,qBAAqB;AAG3B,SAAS,SAAS,QAAwB;AAC/C,SAAO,QAAQ,MAAM;AACvB;AAOO,IAAM,yBAA4C,OAAO,OAAO,CAAC,OAAO,OAAO,CAAC;AAMhF,SAAS,mBAAmB,MAAkC;AACnE,SAAO,uBAAuB,KAAK,CAAC,WAAW,KAAK,WAAW,MAAM,CAAC;AACxE;AAEA,IAAM,YAAY;AAUX,SAAS,eAAe,MAAuB;AACpD,SAAO,UAAU,KAAK,IAAI;AAC5B;AAGO,IAAM,gBAAgB,OAAO,OAAO;AAAA;AAAA,EAEzC,MAAM;AAAA;AAAA,EAEN,QAAQ;AAAA;AAAA,EAER,KAAK;AAAA;AAAA,EAEL,OAAO;AAAA;AAAA,EAEP,eAAe;AAAA;AAAA,EAEf,iBAAiB;AAAA;AAAA,EAEjB,iBAAiB;AAAA;AAAA,EAEjB,OAAO;AAAA;AAAA,EAEP,SAAS;AAAA;AAAA,EAET,WAAW;AAAA;AAAA,EAEX,aAAa;AAAA;AAAA,EAEb,SAAS;AAAA;AAAA,EAET,MAAM;AAAA;AAAA,EAEN,OAAO;AACT,CAAU;AAGH,IAAM,gBAAgB,OAAO,OAAO;AAAA;AAAA,EAEzC,OAAO;AAAA;AAAA,EAEP,QAAQ;AAAA;AAAA,EAER,YAAY;AAAA;AAAA,EAEZ,SAAS;AAAA;AAAA,EAET,SAAS;AAAA;AAAA,EAET,QAAQ;AAAA;AAAA,EAER,OAAO;AAAA;AAAA,EAEP,UAAU;AAAA;AAAA,EAEV,QAAQ;AAAA;AAAA,EAER,QAAQ;AAAA;AAAA,EAER,MAAM;AAAA;AAAA,EAEN,OAAO;AAAA;AAAA,EAEP,MAAM;AACR,CAAU;;;ACvIH,IAAM,mBAAmB;AAsBzB,SAAS,cAAc,OAAsC;AAClE,SAAO,SAAS,KAAK,KAAK,OAAO,UAAU,MAAM,QAAQ,KAAK,OAAO,MAAM,WAAW;AACxF;AAGO,IAAM,oBAAoB;AAa1B,SAAS,mBAAmB,OAA2C;AAC5E,SAAO,SAAS,KAAK,KAAK,MAAM,SAAS,qBAAqB,OAAO,UAAU,MAAM,QAAQ;AAC/F;AAgBO,SAAS,wBAAwB,OAAgD;AACtF,SAAO,SAAS,KAAK,KAAK,MAAM,SAAS;AAC3C;AAcO,SAAS,4BAA4B,OAAoD;AAC9F,SAAO,SAAS,KAAK,KAAK,MAAM,SAAS;AAC3C;AAGO,IAAM,oBAAoB;AAG1B,IAAM,4BAA4B;AAGlC,IAAM,wBAAwB;AAG9B,IAAM,wBAAwB;AAG9B,IAAM,mBAAmB;AAGzB,IAAM,iBAAiB;AAGvB,IAAM,yBAAyB;AAG/B,IAAM,yBAAyB;AAG/B,IAAM,4BAA4B;AAGlC,IAAM,mBAAmB;AAGzB,IAAM,oBAAoB;AAG1B,IAAM,kBAAkB;AAGxB,IAAM,mBAAmB;AAGzB,IAAM,8BAA8B;AAGpC,IAAM,yBAAyB;AAG/B,IAAM,0BAA0B;","names":[]}
|