@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.
Files changed (152) hide show
  1. package/README.md +1308 -219
  2. package/dist/{admin-DKwkIh5i.d.ts → admin-CS7JB_UH.d.ts} +2 -2
  3. package/dist/{adminSchemas-2DUA_hZ8.d.ts → adminSchemas-29cGgrg5.d.ts} +6 -4
  4. package/dist/{auth-BhWqdyb-.d.ts → auth-DtdYjrZ9.d.ts} +51 -21
  5. package/dist/{caller-CXTFswBO.d.ts → caller-BTwGl7vm.d.ts} +296 -33
  6. package/dist/chunk-2FDSKLBO.js +82 -0
  7. package/dist/chunk-2FDSKLBO.js.map +1 -0
  8. package/dist/{chunk-RT6OILOI.js → chunk-4D2DOGAW.js} +2 -14
  9. package/dist/chunk-4D2DOGAW.js.map +1 -0
  10. package/dist/{chunk-OECZPRPW.js → chunk-64I6MYBC.js} +162 -775
  11. package/dist/chunk-64I6MYBC.js.map +1 -0
  12. package/dist/chunk-7473HK24.js +17 -0
  13. package/dist/chunk-7473HK24.js.map +1 -0
  14. package/dist/chunk-7Y5ROPXL.js +33 -0
  15. package/dist/chunk-7Y5ROPXL.js.map +1 -0
  16. package/dist/chunk-A2RBQD66.js +7 -0
  17. package/dist/chunk-A2RBQD66.js.map +1 -0
  18. package/dist/chunk-AK23XSWD.js +16 -0
  19. package/dist/chunk-AK23XSWD.js.map +1 -0
  20. package/dist/chunk-ASAMUGHG.js +651 -0
  21. package/dist/chunk-ASAMUGHG.js.map +1 -0
  22. package/dist/{chunk-B2P7AZ25.js → chunk-CYE4UBOB.js} +6 -4
  23. package/dist/chunk-CYE4UBOB.js.map +1 -0
  24. package/dist/chunk-DKE6QO26.js +136 -0
  25. package/dist/chunk-DKE6QO26.js.map +1 -0
  26. package/dist/chunk-DNP65TD5.js +832 -0
  27. package/dist/chunk-DNP65TD5.js.map +1 -0
  28. package/dist/{chunk-H23KEFBA.js → chunk-DUBU3BB3.js} +30 -1
  29. package/dist/chunk-DUBU3BB3.js.map +1 -0
  30. package/dist/chunk-GYLWEJHW.js +19 -0
  31. package/dist/chunk-GYLWEJHW.js.map +1 -0
  32. package/dist/{chunk-SHEGLZEP.js → chunk-I3JCFS7H.js} +34 -13
  33. package/dist/chunk-I3JCFS7H.js.map +1 -0
  34. package/dist/{chunk-X5CWPX7H.js → chunk-IIQ3WMJK.js} +13 -2
  35. package/dist/chunk-IIQ3WMJK.js.map +1 -0
  36. package/dist/{chunk-RZTP4IEW.js → chunk-JVTONQFI.js} +6339 -4226
  37. package/dist/chunk-JVTONQFI.js.map +1 -0
  38. package/dist/{chunk-QEKVYG5F.js → chunk-KKQANRLG.js} +2 -2
  39. package/dist/chunk-KKQANRLG.js.map +1 -0
  40. package/dist/{chunk-CDKG4TFA.js → chunk-MPQAT55F.js} +38 -205
  41. package/dist/chunk-MPQAT55F.js.map +1 -0
  42. package/dist/chunk-NO4M6PO5.js +101 -0
  43. package/dist/chunk-NO4M6PO5.js.map +1 -0
  44. package/dist/{chunk-TBMKJ26R.js → chunk-NW6U45HX.js} +3 -1
  45. package/dist/chunk-NW6U45HX.js.map +1 -0
  46. package/dist/chunk-NX2V6SIL.js +202 -0
  47. package/dist/chunk-NX2V6SIL.js.map +1 -0
  48. package/dist/{chunk-ZIVPHYRG.js → chunk-NZHA4TZD.js} +72 -175
  49. package/dist/chunk-NZHA4TZD.js.map +1 -0
  50. package/dist/{chunk-2PWMUVC7.js → chunk-OCZJ4EYV.js} +518 -27
  51. package/dist/chunk-OCZJ4EYV.js.map +1 -0
  52. package/dist/{chunk-REX5YPHX.js → chunk-PHCURHC7.js} +76 -21
  53. package/dist/chunk-PHCURHC7.js.map +1 -0
  54. package/dist/{chunk-4YO5H5ON.js → chunk-Q4OGQM5I.js} +4 -10
  55. package/dist/chunk-Q4OGQM5I.js.map +1 -0
  56. package/dist/chunk-QSU47XEX.js +13 -0
  57. package/dist/chunk-QSU47XEX.js.map +1 -0
  58. package/dist/{chunk-DD4SKCNO.js → chunk-RZXJTDH5.js} +13 -2
  59. package/dist/chunk-RZXJTDH5.js.map +1 -0
  60. package/dist/{chunk-Z2LZ7IZ7.js → chunk-SN3OZVEQ.js} +5 -31
  61. package/dist/chunk-SN3OZVEQ.js.map +1 -0
  62. package/dist/{chunk-4DIQHASK.js → chunk-SQ5VUG77.js} +28 -8
  63. package/dist/chunk-SQ5VUG77.js.map +1 -0
  64. package/dist/chunk-STH5DLQP.js +1192 -0
  65. package/dist/chunk-STH5DLQP.js.map +1 -0
  66. package/dist/{chunk-QCHKWIRS.js → chunk-VZFJTKIW.js} +13 -13
  67. package/dist/{chunk-QCHKWIRS.js.map → chunk-VZFJTKIW.js.map} +1 -1
  68. package/dist/{chunk-VSQBHPWH.js → chunk-X4ZU4QH5.js} +54 -22
  69. package/dist/chunk-X4ZU4QH5.js.map +1 -0
  70. package/dist/chunk-XD7SRDUY.js +188 -0
  71. package/dist/chunk-XD7SRDUY.js.map +1 -0
  72. package/dist/chunk-XH2IZVLT.js +62 -0
  73. package/dist/chunk-XH2IZVLT.js.map +1 -0
  74. package/dist/chunk-Z7I6SONO.js +825 -0
  75. package/dist/chunk-Z7I6SONO.js.map +1 -0
  76. package/dist/cli/quickdraw-docs.js +132 -112
  77. package/dist/cli/quickdraw-docs.js.map +1 -1
  78. package/dist/client/core.d.ts +387 -0
  79. package/dist/client/core.js +109 -0
  80. package/dist/client/index.d.ts +52 -203
  81. package/dist/client/index.js +59 -218
  82. package/dist/client/index.js.map +1 -1
  83. package/dist/{clientTypes-Bc-G8n-U.d.ts → clientTypes-CUUNJwWs.d.ts} +3 -3
  84. package/dist/{connection-Cr1BU07W.d.ts → connection-2EeQ_Et0.d.ts} +22 -11
  85. package/dist/{createServer-CP9eDHZZ.d.ts → createServer-Bx5HOcYK.d.ts} +67 -17
  86. package/dist/{createTestApp-UKVGEmOf.d.ts → createTestApp-DAMANT79.d.ts} +46 -13
  87. package/dist/{envelope-Cp3YcVxC.d.ts → envelope-BMBaxppV.d.ts} +216 -10
  88. package/dist/{http-Djj2Inuu.d.ts → http-YzLAar3w.d.ts} +76 -60
  89. package/dist/index.d.ts +8 -7
  90. package/dist/index.js +82 -25
  91. package/dist/index.js.map +1 -1
  92. package/dist/{infer-Teo1xpsd.d.ts → infer-Br62cnyW.d.ts} +82 -11
  93. package/dist/logger-Yz93KzdV.d.ts +30 -0
  94. package/dist/{metrics-BdBu5o8G.d.ts → metrics-CTVpw0gu.d.ts} +2 -2
  95. package/dist/origins-kvpxnVcW.d.ts +8 -0
  96. package/dist/parser.js +2 -1
  97. package/dist/prisma/index.d.ts +14 -7
  98. package/dist/prisma/index.js +279 -70
  99. package/dist/prisma/index.js.map +1 -1
  100. package/dist/{rateLimit-ZMIZJUZF.js → rateLimit-5RKBFLUR.js} +2 -2
  101. package/dist/rateLimit-5RKBFLUR.js.map +1 -0
  102. package/dist/remote-N7OUAGOW.js +15 -0
  103. package/dist/remote-N7OUAGOW.js.map +1 -0
  104. package/dist/server/auth/index.d.ts +82 -33
  105. package/dist/server/auth/index.js +277 -270
  106. package/dist/server/auth/index.js.map +1 -1
  107. package/dist/server/express/index.d.ts +9 -2
  108. package/dist/server/express/index.js +1 -1
  109. package/dist/server/index.d.ts +268 -84
  110. package/dist/server/index.js +664 -418
  111. package/dist/server/index.js.map +1 -1
  112. package/dist/server/mcp/index.d.ts +89 -27
  113. package/dist/server/mcp/index.js +281 -62
  114. package/dist/server/mcp/index.js.map +1 -1
  115. package/dist/server/otel.d.ts +3 -3
  116. package/dist/{sharing-DVa8W-yR.d.ts → sharing-BrsmlDv_.d.ts} +3 -3
  117. package/dist/testing/client.d.ts +14 -14
  118. package/dist/testing/client.js +19 -10
  119. package/dist/testing/client.js.map +1 -1
  120. package/dist/testing/index.d.ts +50 -13
  121. package/dist/testing/index.js +113 -21
  122. package/dist/testing/index.js.map +1 -1
  123. package/dist/testing/mock.d.ts +5 -5
  124. package/dist/testing/mock.js +17 -9
  125. package/dist/tracked-BR5b2O-U.d.ts +43 -0
  126. package/dist/{types-Cu0vmx7c.d.ts → types-CLRCQGnO.d.ts} +4 -3
  127. package/dist/types-tMagHbjy.d.ts +151 -0
  128. package/dist/utils/index.d.ts +16 -4
  129. package/dist/utils/index.js +6 -3
  130. package/package.json +5 -1
  131. package/dist/chunk-2PWMUVC7.js.map +0 -1
  132. package/dist/chunk-4DIQHASK.js.map +0 -1
  133. package/dist/chunk-4YO5H5ON.js.map +0 -1
  134. package/dist/chunk-B2P7AZ25.js.map +0 -1
  135. package/dist/chunk-CDKG4TFA.js.map +0 -1
  136. package/dist/chunk-DD4SKCNO.js.map +0 -1
  137. package/dist/chunk-H23KEFBA.js.map +0 -1
  138. package/dist/chunk-OECZPRPW.js.map +0 -1
  139. package/dist/chunk-QEKVYG5F.js.map +0 -1
  140. package/dist/chunk-REX5YPHX.js.map +0 -1
  141. package/dist/chunk-RT6OILOI.js.map +0 -1
  142. package/dist/chunk-RZTP4IEW.js.map +0 -1
  143. package/dist/chunk-SHEGLZEP.js.map +0 -1
  144. package/dist/chunk-TBMKJ26R.js.map +0 -1
  145. package/dist/chunk-VSQBHPWH.js.map +0 -1
  146. package/dist/chunk-X5CWPX7H.js.map +0 -1
  147. package/dist/chunk-YSWH776Y.js +0 -13
  148. package/dist/chunk-YSWH776Y.js.map +0 -1
  149. package/dist/chunk-Z2LZ7IZ7.js.map +0 -1
  150. package/dist/chunk-ZIVPHYRG.js.map +0 -1
  151. package/dist/logger-BY28acW-.d.ts +0 -16
  152. /package/dist/{rateLimit-ZMIZJUZF.js.map → client/core.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/testing/mockClient.ts","../src/testing/mockLive.ts","../src/testing/mockAdmin.ts","../src/testing/mockScope.ts","../src/testing/mockCollections.ts","../src/testing/mockRealtime.ts","../src/testing/mockSearch.ts","../src/testing/mockSession.tsx"],"sourcesContent":["// `createMockClient(contracts)` (RFC 0003 section 13): a client of the same\n// type as `createQuickdrawClient`'s whose members are stubs, for component\n// tests and stories that do not care about the transport. Nothing connects:\n// there is no socket and no server, and the members' hooks need no provider\n// above them. 4.1's `createMockSocket` mocked the socket under the hooks instead\n// (4.1 `src/client/testing.tsx:24-41`), which tested a fake transport.\n//\n// - A query member's `useQuery` is TanStack's own `useQuery`, on the mock's\n// `QueryClient` (retries off), fetching from the method's stub; `call`\n// and `prefetch` answer from the stub too. A mutation's `useMutation` is\n// TanStack's `useMutation` over the stub. Optimistic layers are not shown:\n// the test sets the rows a component reads.\n// - Every method's member carries its stub (`mockResolvedValue`,\n// `mockRejectedValue`, `mockImplementation`, `mockReset`, `calls`). A\n// search kit method's `useSearch` asks its stub at once, with no debounce\n// and no collection cache (`mockSearch.ts`).\n// - `useEntity`, `useEntities` and `useCollection` show what the test sets\n// with `mockRow`, `mockRemoved`, `mockError` and `mockScope`\n// (`mockLive.ts`).\n// - `qd.<service>.admin` holds the same mocked members of the admin kit's\n// methods, and `useAdminServices` asks their `adminMeta` stubs\n// (`mockAdmin.ts`).\n// - `qd.invalidate` invalidates the mock's cache through an invalidation\n// coordinator, as the real client does.\n// - `$Provider` makes the mock the provider of what it renders: the real\n// `useQuickdraw()` and `usePresence(room)` read the mock's session\n// (`$session`) and rooms (`$presence`), with no server (`mockSession.tsx`).\n// - Everything set is forgotten after each test, when the test runner has a\n// global `afterEach` (as Testing Library unmounts after each test), and by\n// `$reset()`.\n\nimport {\n QueryClient,\n useMutation,\n useQuery,\n type QueryKey,\n type Updater,\n} from \"@tanstack/react-query\";\nimport { createBinding, invalidateWith, registerQuery, type Binding } from \"../client/binding\";\nimport type { MethodMutationOptions, MethodQueryOptions } from \"../client/hooks\";\nimport { methodKey, methodKeyPrefix, type MethodQueryKey } from \"../client/keys\";\nimport { buildCaller, type MethodTarget } from \"../client/members\";\nimport type { ContractMap } from \"../contract/infer\";\nimport { createMockStore, mockLiveMembers } from \"./mockLive\";\nimport { mockSearchMember } from \"./mockSearch\";\nimport { mockSessionControls, startingSession } from \"./mockSession\";\nimport type { MethodStub, MockClient, MockClientOptions } from \"./mockTypes\";\n\n/** How a stub answers its calls. */\ntype Answer =\n | { readonly kind: \"pending\" }\n | { readonly kind: \"value\"; readonly value: unknown }\n | { readonly kind: \"error\"; readonly error: unknown }\n | { readonly kind: \"implementation\"; readonly run: (input: unknown) => unknown };\n\n/** A method's stub: its controls, and the call that answers as they say. */\ninterface Stub extends MethodStub<unknown, unknown> {\n invoke(input: unknown): Promise<unknown>;\n /** Forgets its answer and its calls without refetching anything: the test is over. */\n forget(): void;\n}\n\nconst PENDING: Answer = Object.freeze({ kind: \"pending\" });\n\n/** The answer of a call no answer was set for: it never settles. */\nconst NEVER = new Promise<never>(() => {\n // Never settles, as a call the server never answers.\n});\n\n/** A stub that calls `changed` whenever its answer changes. */\nfunction createStub(changed: () => void): Stub {\n let answer = PENDING;\n const calls: unknown[] = [];\n const answerWith = (next: Answer): void => {\n answer = next;\n changed();\n };\n return {\n invoke(input) {\n calls.push(input);\n const current = answer;\n switch (current.kind) {\n case \"value\":\n return Promise.resolve(current.value);\n case \"error\":\n return Promise.reject(current.error);\n case \"implementation\":\n return Promise.resolve().then(() => current.run(input));\n default:\n return NEVER;\n }\n },\n mockResolvedValue: (value) => {\n answerWith({ kind: \"value\", value });\n },\n mockRejectedValue: (error) => {\n answerWith({ kind: \"error\", error });\n },\n mockImplementation: (run) => {\n answerWith({ kind: \"implementation\", run });\n },\n mockReset: () => {\n calls.length = 0;\n answerWith(PENDING);\n },\n forget: () => {\n calls.length = 0;\n answer = PENDING;\n },\n get calls() {\n return [...calls];\n },\n };\n}\n\n/** `member` with `stub`'s controls on it, frozen. */\nfunction withStub<T extends object>(member: T, stub: Stub): T {\n return Object.freeze(\n Object.defineProperties(member, {\n mockResolvedValue: { value: stub.mockResolvedValue, enumerable: true },\n mockRejectedValue: { value: stub.mockRejectedValue, enumerable: true },\n mockImplementation: { value: stub.mockImplementation, enumerable: true },\n mockReset: { value: stub.mockReset, enumerable: true },\n calls: { get: () => stub.calls, enumerable: true },\n }),\n );\n}\n\n/** What the members of one mock client share. */\ninterface MockContext {\n readonly queryClient: QueryClient;\n readonly binding: Binding;\n /** A new stub for the method of `target`. */\n stub(target: MethodTarget): Stub;\n}\n\nfunction useMockQuery(\n queryClient: QueryClient,\n stub: Stub,\n queryKey: MethodQueryKey,\n input: unknown,\n options: MethodQueryOptions<unknown> = {},\n) {\n return useQuery(\n { retry: false, ...options, queryKey, queryFn: () => stub.invoke(input) },\n queryClient,\n );\n}\n\nfunction mockQueryMember(\n context: MockContext,\n target: MethodTarget,\n search: (invoke: Stub[\"invoke\"]) => Readonly<Record<string, unknown>>,\n): object {\n const stub = context.stub(target);\n const key = (input?: unknown): MethodQueryKey => methodKey(target.service, target.method, input);\n const member = withStub(\n {\n ...search(stub.invoke),\n useQuery: (input?: unknown, options?: MethodQueryOptions<unknown>) =>\n useMockQuery(context.queryClient, stub, key(input), input, options),\n call: (input?: unknown): Promise<unknown> => stub.invoke(input),\n key,\n setData: (input: unknown, updater: Updater<unknown, unknown>): unknown =>\n context.queryClient.setQueryData(key(input), updater),\n prefetch: (queryClient: QueryClient, input?: unknown): Promise<void> =>\n queryClient.prefetchQuery({ queryKey: key(input), queryFn: () => stub.invoke(input) }),\n },\n stub,\n );\n registerQuery(context.binding, member, target);\n return member;\n}\n\nfunction useMockMutation(\n queryClient: QueryClient,\n target: MethodTarget,\n stub: Stub,\n options: MethodMutationOptions<unknown, unknown> = {},\n) {\n return useMutation(\n {\n mutationKey: methodKeyPrefix(target.service, target.method),\n ...options,\n mutationFn: (input: unknown) => stub.invoke(input),\n },\n queryClient,\n );\n}\n\nfunction mockMutationMember(context: MockContext, target: MethodTarget): object {\n const stub = context.stub(target);\n return withStub(\n {\n useMutation: (options?: MethodMutationOptions<unknown, unknown>) =>\n useMockMutation(context.queryClient, target, stub, options),\n call: (input?: unknown): Promise<unknown> => stub.invoke(input),\n },\n stub,\n );\n}\n\n/** The cache of a mock client: retries off, results kept fresh. */\nfunction createMockQueryClient(): QueryClient {\n return new QueryClient({\n defaultOptions: {\n queries: { retry: false, staleTime: Number.POSITIVE_INFINITY, refetchOnWindowFocus: false },\n mutations: { retry: false },\n },\n });\n}\n\n/**\n * Fetches the cached results of a query again with its new answer. Reads\n * still waiting on the old one are cancelled first: TanStack keeps a read\n * in flight for a query that has no data yet, and an unanswered read never\n * ends.\n */\nasync function refetchWithNewAnswer(queryClient: QueryClient, queryKey: QueryKey): Promise<void> {\n await queryClient.cancelQueries({ queryKey });\n await queryClient.invalidateQueries({ queryKey });\n}\n\n/** A new stub per method, fetching a query's cached results again when its answer changes. */\nfunction stubMaker(queryClient: QueryClient, stubs: Stub[]): (target: MethodTarget) => Stub {\n return (target) => {\n const queryKey: QueryKey = methodKeyPrefix(target.service, target.method);\n const stub = createStub(() => {\n if (target.kind === \"query\") {\n void refetchWithNewAnswer(queryClient, queryKey);\n }\n });\n stubs.push(stub);\n return stub;\n };\n}\n\n/**\n * Forgets everything set on a mock: answers, calls, rows, scopes and cached\n * results. `quiet` tells no mounted hook, for the reset after a test, which\n * may run before Testing Library unmounts what the test rendered.\n */\nfunction resetMock(\n queryClient: QueryClient,\n store: ReturnType<typeof createMockStore>,\n stubs: readonly Stub[],\n quiet: boolean,\n): void {\n queryClient.clear();\n store.clear(quiet);\n for (const stub of stubs) {\n if (quiet) {\n stub.forget();\n } else {\n stub.mockReset();\n }\n }\n}\n\n/**\n * Runs `reset` after each test when the test runner has a global\n * `afterEach` (vitest with `globals: true`, jest), as Testing Library\n * registers its cleanup. A runner that takes no hook where the mock is made\n * (inside a test) leaves it to `$reset()`.\n */\nfunction resetAfterEachTest(reset: () => void): void {\n const runnerAfterEach: unknown = (globalThis as { readonly afterEach?: unknown }).afterEach;\n if (typeof runnerAfterEach !== \"function\") {\n return;\n }\n try {\n (runnerAfterEach as (hook: () => void) => void)(reset);\n } catch {\n // No hook can be registered here; `$reset()` still forgets everything.\n }\n}\n\n/**\n * Creates a mock client of `contracts`: the type of\n * `createQuickdrawClient(contracts)`, with every member a stub and no\n * transport behind it, for component tests and stories. A call stays\n * pending until the test sets its answer; a row or a scope stays loading\n * until the test sets it. Its members need no provider; components that read\n * `useQuickdraw()` or `usePresence(room)` render inside `$Provider`, which\n * shows the mock's session (`$session`). Everything set is forgotten after\n * each test (see `resetAfterEach`).\n *\n * @example\n * const qd = createMockClient({ task }, { session: { userId: \"ada\" } });\n * vi.mock(\"../lib/qd\", () => ({ qd }));\n * qd.task.get.mockResolvedValue({ id: \"t1\", title: \"Write the spec\" });\n * qd.task.useEntity.mockRow({ id: \"t1\", title: \"Write the spec\" });\n * qd.task.byProject.mockScope(\"p1\", [{ id: \"t1\", title: \"Write the spec\" }]);\n * qd.$session({ serviceAccess: { taskService: \"Admin\" } });\n * render(<TaskCard id=\"t1\" />, { wrapper: qd.$Provider });\n * expect(qd.task.get.calls).toEqual([{ id: \"t1\" }]);\n */\nexport function createMockClient<const Contracts extends ContractMap>(\n contracts: Contracts & { readonly invalidate?: never },\n options: MockClientOptions = {},\n): MockClient<Contracts> {\n const queryClient = options.queryClient ?? createMockQueryClient();\n const base = startingSession(options);\n const store = createMockStore(base);\n const session = mockSessionControls(store, queryClient, base);\n const binding = createBinding();\n binding.coordinator = session.coordinator;\n const stubs: Stub[] = [];\n const context: MockContext = { queryClient, binding, stub: stubMaker(queryClient, stubs) };\n const client = buildCaller(\n \"createMockClient\",\n contracts,\n (target, definition, contract) =>\n target.kind === \"query\"\n ? mockQueryMember(context, target, (invoke) =>\n mockSearchMember(queryClient, invoke, target, definition, contract),\n )\n : mockMutationMember(context, target),\n [\"invalidate\"],\n mockLiveMembers(store, queryClient),\n );\n Object.defineProperties(client, {\n invalidate: { value: invalidateWith(binding) },\n $queryClient: { value: queryClient },\n $Provider: { value: session.Provider },\n $session: { value: session.setSession },\n $presence: { value: session.setPresence },\n $reset: {\n value: (): void => {\n resetMock(queryClient, store, stubs, false);\n },\n },\n });\n if (options.resetAfterEach !== false) {\n resetAfterEachTest(() => {\n resetMock(queryClient, store, stubs, true);\n });\n }\n return Object.freeze(client) as MockClient<Contracts>;\n}\n","// The live members of a mock client (`mockClient.ts`): `useEntity`,\n// `useEntities` and each collection's `useCollection`, showing what the test\n// set through their controls (`mockRow`, `mockScope`, ...) instead of\n// subscribing over a socket, and the stream, channel and event members\n// (`mockRealtime.ts`). They return what the real hooks return\n// (`client/live/useEntity.ts`, `client/live/useCollection.ts`): a row or a\n// scope nobody set is loading, a view runs the contract's predicate over the\n// index rows made from the items, and a component re-renders when the test\n// changes what it shows.\n\nimport { useSyncExternalStore } from \"react\";\nimport type {\n UseEntitiesResult,\n UseEntityOptions,\n UseEntityResult,\n} from \"../client/live/memberTypes\";\nimport type { AnyContract } from \"../contract/defineContract\";\nimport type { QuickdrawError } from \"../protocol/errors\";\nimport { mockAdminNamespace } from \"./mockAdmin\";\nimport { mockCollectionMember } from \"./mockCollections\";\nimport { mockRealtimeMembers } from \"./mockRealtime\";\nimport type { EntityMock, MockScope, SessionState } from \"./mockTypes\";\n\n/** What one mocked row shows: the row, its removal, or an error. */\ntype RowState =\n | { readonly row: { readonly id: string } }\n | { readonly removed: true }\n | { readonly error: QuickdrawError };\n\n/** What one mocked scope shows: its items, or an error. */\nexport type ScopeState =\n | (MockScope & { readonly items: readonly { readonly id: string }[] })\n | { readonly error: QuickdrawError };\n\n/** The rows, scopes and session of one mock client, and the hooks' views of them. */\nexport interface MockStore {\n readonly subscribe: (listener: () => void) => () => void;\n /** The value `build` makes for `key`, the same object until the store changes. */\n read<T>(key: string, build: () => T): T;\n row(service: string, id: string): RowState | undefined;\n setRow(service: string, id: string, state: RowState): void;\n scope(service: string, collection: string, scope: string): ScopeState | undefined;\n setScope(service: string, collection: string, scope: string, state: ScopeState): void;\n /**\n * A mocked value by key: a feed's items, a channel's sent messages\n * (`mockRealtime.ts`), a room's users (`mockSession.tsx`).\n */\n value(key: string): unknown;\n setValue(key: string, value: unknown): void;\n /** Who the mock acts for (`mockSession.tsx`): the same object until it is set again. */\n session(): SessionState;\n setSession(session: SessionState): void;\n /**\n * Forgets every row, scope and value, and puts back the session the mock\n * started with; `quiet` tells no hook that shows them (they are about to\n * unmount).\n */\n clear(quiet?: boolean): void;\n}\n\nfunction keyOf(...parts: readonly string[]): string {\n return parts.join(\"\\u0000\");\n}\n\n/** Creates the store of one mock client, whose session starts as `base`. */\nexport function createMockStore(base: SessionState): MockStore {\n let session = base;\n const rows = new Map<string, RowState>();\n const scopes = new Map<string, ScopeState>();\n const values = new Map<string, unknown>();\n const views = new Map<string, unknown>();\n const listeners = new Set<() => void>();\n const changed = (): void => {\n views.clear();\n for (const listener of [...listeners]) {\n listener();\n }\n };\n return {\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n read<T>(key: string, build: () => T): T {\n if (!views.has(key)) {\n views.set(key, build());\n }\n return views.get(key) as T;\n },\n row: (service, id) => rows.get(keyOf(service, id)),\n setRow(service, id, state) {\n rows.set(keyOf(service, id), state);\n changed();\n },\n scope: (service, collection, scope) => scopes.get(keyOf(service, collection, scope)),\n setScope(service, collection, scope, state) {\n scopes.set(keyOf(service, collection, scope), state);\n changed();\n },\n value: (key) => values.get(key),\n setValue(key, value) {\n values.set(key, value);\n changed();\n },\n session: () => session,\n setSession(next) {\n session = next;\n changed();\n },\n clear(quiet = false) {\n rows.clear();\n scopes.clear();\n values.clear();\n session = base;\n if (quiet) {\n views.clear();\n } else {\n changed();\n }\n },\n };\n}\n\n/** What `useEntity` shows of a mocked row. */\nfunction rowResult(state: RowState | undefined, active: boolean): UseEntityResult<unknown> {\n if (state === undefined) {\n return { data: undefined, isLoading: active, isRemoved: false, error: null };\n }\n if (\"row\" in state) {\n return { data: state.row, isLoading: false, isRemoved: false, error: null };\n }\n if (\"removed\" in state) {\n return { data: undefined, isLoading: false, isRemoved: true, error: null };\n }\n return { data: undefined, isLoading: false, isRemoved: false, error: state.error };\n}\n\n/** What `useEntities` shows of the mocked rows `ids`. */\nfunction rowsResult(\n store: MockStore,\n service: string,\n ids: readonly string[],\n active: boolean,\n): UseEntitiesResult<unknown> {\n const data: unknown[] = [];\n const byId = new Map<string, unknown>();\n const errors = new Map<string, QuickdrawError>();\n let isLoading = false;\n for (const id of ids) {\n const shows = active && id !== \"\";\n const shown = rowResult(shows ? store.row(service, id) : undefined, shows);\n data.push(shown.data);\n if (shown.data !== undefined) {\n byId.set(id, shown.data);\n }\n if (shown.error !== null) {\n errors.set(id, shown.error);\n }\n isLoading ||= shown.isLoading;\n }\n return { data, byId, isLoading, error: errors.values().next().value ?? null, errors };\n}\n\nfunction useMockRow(\n store: MockStore,\n service: string,\n id: string | null | undefined,\n options: UseEntityOptions = {},\n): UseEntityResult<unknown> {\n const rowId = typeof id === \"string\" ? id : \"\";\n const active = options.enabled !== false && rowId !== \"\";\n const read = (): UseEntityResult<unknown> =>\n store.read(keyOf(\"row\", service, rowId, String(active)), () =>\n rowResult(active ? store.row(service, rowId) : undefined, active),\n );\n return useSyncExternalStore(store.subscribe, read, read);\n}\n\nfunction useMockRows(\n store: MockStore,\n service: string,\n ids: readonly string[],\n options: UseEntityOptions = {},\n): UseEntitiesResult<unknown> {\n const rowIds = ids.map((id) => (typeof id === \"string\" ? id : \"\"));\n const active = options.enabled !== false;\n const read = (): UseEntitiesResult<unknown> =>\n store.read(keyOf(\"rows\", service, String(active), ...rowIds), () =>\n rowsResult(store, service, rowIds, active),\n );\n return useSyncExternalStore(store.subscribe, read, read);\n}\n\n/** The id of a row the test passed to `mockRow`. */\nfunction idOf(row: unknown): string {\n const id: unknown = typeof row === \"object\" && row !== null ? (row as { id?: unknown }).id : \"\";\n if (typeof id !== \"string\" || id === \"\") {\n throw new TypeError(\"mockRow: the row needs a string id\");\n }\n return id;\n}\n\n/** The controls of a mocked service's rows. */\nfunction rowControls(store: MockStore, service: string): EntityMock<unknown> {\n return Object.freeze({\n mockRow(row: unknown) {\n store.setRow(service, idOf(row), { row: row as { readonly id: string } });\n },\n mockRemoved(id: string) {\n store.setRow(service, id, { removed: true });\n },\n mockError(id: string, error: QuickdrawError) {\n store.setRow(service, id, { error });\n },\n });\n}\n\n/**\n * The live members of each contract's service on a mock client, as\n * `buildCaller` takes them: the entity hooks for a contract with an entity,\n * each carrying the controls of the rows it shows, one member per\n * collection, and `admin` for a contract with the admin kit (its `adminMeta`\n * queries run on `queryClient`, the mock's cache).\n */\nexport function mockLiveMembers(\n store: MockStore,\n queryClient: Parameters<typeof mockAdminNamespace>[2],\n): (\n contract: AnyContract,\n methods: Readonly<Record<string, object>>,\n) => Readonly<Record<string, object>> {\n return (contract, methods) => {\n const service = contract.name;\n const members: [string, object][] = [];\n if (contract.entity !== undefined) {\n const controls = rowControls(store, service);\n const useEntity = (id: string | null | undefined, options?: UseEntityOptions) =>\n useMockRow(store, service, id, options);\n const useEntities = (ids: readonly string[], options?: UseEntityOptions) =>\n useMockRows(store, service, ids, options);\n members.push([\"useEntity\", Object.freeze(Object.assign(useEntity, controls))]);\n members.push([\"useEntities\", Object.freeze(Object.assign(useEntities, controls))]);\n }\n for (const [collection, def] of Object.entries(contract.collections)) {\n members.push([collection, mockCollectionMember(store, { service, collection, def })]);\n }\n members.push(...mockRealtimeMembers(store, contract));\n members.push(...Object.entries(mockAdminNamespace(contract, methods, queryClient, store)));\n return Object.freeze(Object.fromEntries(members));\n };\n}\n","// The mock client's `qd.<service>.admin` (`mockClient.ts`): the same\n// namespace as the typed client's (`../client/admin.ts`), holding the mocked\n// members of the admin methods, so `qd.task.admin.adminList.mockResolvedValue`\n// sets the same stub as `qd.task.adminList`. `useAdminServices` on a mock\n// client asks each `adminMeta` stub the session's grants allow (`$session`,\n// `serviceAccess`) at once, on the mock's cache, so a component test lists\n// services with `adminMeta.mockResolvedValue(meta)`.\n\nimport { useQueries, type QueryClient, type UseQueryResult } from \"@tanstack/react-query\";\nimport { useSyncExternalStore } from \"react\";\nimport {\n adminNamespace,\n type AdminMetaQuery,\n type AdminMetaState,\n type UseAdminGrants,\n type UseAdminMeta,\n} from \"../client/admin\";\nimport { methodKey } from \"../client/keys\";\nimport type { AnyContract } from \"../contract/defineContract\";\nimport type { MockStore } from \"./mockLive\";\nimport { sessionIn, useSessionScope } from \"./mockScope\";\n\nfunction statesOf(results: UseQueryResult<unknown>[]): AdminMetaState[] {\n return results.map((result) => ({\n data: result.data as AdminMetaState[\"data\"],\n error: result.error,\n }));\n}\n\n/** The mock client's `admin` member of a contract's service, on the mock's cache, with its session's grants. */\nexport function mockAdminNamespace(\n contract: AnyContract,\n methods: Readonly<Record<string, object>>,\n queryClient: QueryClient,\n store: MockStore,\n): Readonly<Record<string, object>> {\n const useMockAdminMeta: UseAdminMeta = (queries: readonly AdminMetaQuery[], enabled: boolean) =>\n useQueries(\n {\n queries: queries.map((query) => ({\n queryKey: methodKey(query.serviceName, query.method, undefined),\n queryFn: () => query.member.call(),\n enabled,\n retry: false,\n })),\n combine: statesOf,\n },\n queryClient,\n );\n const useMockGrants: UseAdminGrants = () => {\n // Under a `$Provider` with a `session` prop, that session's grants (`mockScope.ts`).\n const scope = useSessionScope(store);\n const grants = () => {\n const session = sessionIn(store, scope);\n return session.isKnown ? session.serviceAccess : null;\n };\n return useSyncExternalStore(store.subscribe, grants, grants);\n };\n return adminNamespace(contract, methods, { useMeta: useMockAdminMeta, useGrants: useMockGrants });\n}\n","// The session `<mock.$Provider session={...}>` gives its subtree (finding\n// F6.2 of the quickdraw-chat migration): a Storybook docs page renders every\n// story at once, each inside the mock's provider, and `$session(...)` is one\n// session for the whole mock, so every story showed the last one set. The\n// prop is laid over the mock's own session (`$session`) field by field for\n// what renders inside that provider: the real `useQuickdraw()` and\n// `usePresence(room)` read it through the provider's connection\n// (`mockSession.tsx`), and the mock's own hooks that depend on who the user\n// is (collection views, the admin grants) read it from this context.\n\nimport { createContext, useContext } from \"react\";\nimport type { MockStore } from \"./mockLive\";\nimport type { SessionState } from \"./mockTypes\";\n\n/** Lays a `$Provider`'s `session` prop over the mock's own session: the same object while that one does not change. */\nexport type SessionScope = (session: SessionState) => SessionState;\n\n/** The scope a `$Provider` with a `session` prop set, for one mock's store. */\nexport interface ScopedSession {\n readonly store: MockStore;\n readonly scope: SessionScope;\n}\n\n/** Where a `$Provider` with a `session` prop puts its scope; `undefined` elsewhere. */\nexport const MockSessionScope = createContext<ScopedSession | undefined>(undefined);\n\n/** The scope that applies to `store` where this renders: one of its own providers' `session` prop. */\nexport function useSessionScope(store: MockStore): SessionScope | undefined {\n const scoped = useContext(MockSessionScope);\n return scoped?.store === store ? scoped.scope : undefined;\n}\n\n/** The session `store` shows under `scope`: its own, or the prop over it. */\nexport function sessionIn(store: MockStore, scope: SessionScope | undefined): SessionState {\n const session = store.session();\n return scope === undefined ? session : scope(session);\n}\n","// The collections of a mock client (`mockLive.ts`): each collection's\n// `useCollection`, showing the scope the test set (`mockScope`) instead of\n// subscribing over a socket, as the real hook returns it\n// (`client/live/useCollection.ts`): a scope nobody set is loading, and a\n// view runs the contract's predicate over the index rows made from the\n// items, for the session's user, or under a `$Provider` with a `session`\n// prop for that session's (`mockScope.ts`).\n\nimport { useSyncExternalStore } from \"react\";\nimport { indexRowFromItem, type IndexRow } from \"../client/live/collectionIndex\";\nimport type { UseCollectionOptions, UseCollectionResult } from \"../client/live/memberTypes\";\nimport { viewPredicate } from \"../client/live/views\";\nimport type { CollectionDef, Viewer } from \"../contract/collections\";\nimport type { QuickdrawError } from \"../protocol/errors\";\nimport type { MockStore, ScopeState } from \"./mockLive\";\nimport { sessionIn, useSessionScope } from \"./mockScope\";\nimport type { MockScope, SessionState } from \"./mockTypes\";\n\nfunction keyOf(...parts: readonly string[]): string {\n return parts.join(\"\\u0000\");\n}\n\nconst idle = (): Promise<void> => Promise.resolve();\n\n/** A mock shows no optimistic additions, so none is ever pending. */\nconst NONE_PENDING: ReadonlySet<string> = new Set();\n\n/** A mocked scope with nothing in it yet. */\nfunction emptyScope(active: boolean): UseCollectionResult<unknown, IndexRow> {\n return {\n items: [],\n pending: NONE_PENDING,\n checking: NONE_PENDING,\n // A mock's mutations add nothing, so nothing is refused either.\n refused: [],\n index: undefined,\n byId: new Map(),\n totalCount: null,\n hasMore: false,\n clamped: false,\n indexTruncated: false,\n isLoading: active,\n isLoadingMore: false,\n error: null,\n loadMore: idle,\n loadItems: idle,\n refresh: idle,\n };\n}\n\n/** What `useCollection` shows of a mocked scope, through `view` for `who`. */\nfunction scopeResult(\n def: CollectionDef,\n state: ScopeState | undefined,\n options: { readonly view: string | undefined; readonly who: Viewer; readonly active: boolean },\n): UseCollectionResult<unknown, IndexRow> {\n const empty = emptyScope(options.active);\n if (state === undefined) {\n return empty;\n }\n if (\"error\" in state) {\n return { ...empty, isLoading: false, error: state.error };\n }\n const selects = viewPredicate(def, options.view);\n const keep = (row: Readonly<Record<string, unknown>>): boolean =>\n selects === undefined || selects(row, options.who);\n const index =\n def.index === undefined\n ? undefined\n : state.items.map((item) => indexRowFromItem(def, item)).filter(keep);\n const members = new Set(index?.map((row) => row.id));\n const items = state.items.filter((item) =>\n index === undefined ? keep(item as Readonly<Record<string, unknown>>) : members.has(item.id),\n );\n return {\n ...empty,\n items,\n index,\n byId: new Map(items.map((item) => [item.id, item])),\n totalCount: state.totalCount ?? state.items.length,\n hasMore: state.hasMore ?? false,\n isLoading: false,\n };\n}\n\n/** One collection of a contract, as its mock member shows it. */\ninterface MockCollectionTarget {\n readonly service: string;\n readonly collection: string;\n readonly def: CollectionDef;\n}\n\n/**\n * Who the views select members for: the session's user once known, as the\n * real views read the hello; under a `$Provider` with a `session` prop,\n * that session's (`mockScope.ts`).\n */\nfunction viewerOf(session: SessionState): Viewer {\n return { userId: session.isKnown ? (session.userId ?? \"\") : \"\" };\n}\n\nfunction useMockScope(\n store: MockStore,\n target: MockCollectionTarget,\n scope: string | null | undefined,\n options: UseCollectionOptions = {},\n): UseCollectionResult<unknown, IndexRow> {\n const value = typeof scope === \"string\" ? scope : \"\";\n const active = options.enabled !== false && value !== \"\";\n const { service, collection, def } = target;\n const sessionScope = useSessionScope(store);\n const read = (): UseCollectionResult<unknown, IndexRow> => {\n const who = viewerOf(sessionIn(store, sessionScope));\n // The viewer is part of the key: two providers' sessions may see one scope's views apart.\n const view = options.view ?? \"\";\n const key = keyOf(\"scope\", service, collection, value, view, String(active), who.userId);\n return store.read(key, () =>\n scopeResult(def, active ? store.scope(service, collection, value) : undefined, {\n view: options.view,\n who,\n active,\n }),\n );\n };\n return useSyncExternalStore(store.subscribe, read, read);\n}\n\n/** `qd.<service>.<collection>` of a mock client. */\nexport function mockCollectionMember(store: MockStore, target: MockCollectionTarget): object {\n return Object.freeze({\n useCollection: (scope: string | null | undefined, options?: UseCollectionOptions) =>\n useMockScope(store, target, scope, options),\n mockScope(scope: string, items: readonly { readonly id: string }[], extra: MockScope = {}) {\n store.setScope(target.service, target.collection, scope, { ...extra, items: [...items] });\n },\n mockError(scope: string, error: QuickdrawError) {\n store.setScope(target.service, target.collection, scope, { error });\n },\n });\n}\n","// The stream, channel and event members of a mock client (`mockClient.ts`,\n// RFC 0003 section 12.5), with no socket behind them:\n//\n// - a stream's `useStream` shows what the test set with `mockItems` (the\n// latest `max` of them) or `mockError`; a feed nobody set is loading;\n// - a channel's `useChannel` is always ready, and its `send` records each\n// payload in the member's `sent`;\n// - an event's `useEvent` registers its handler, and `mockEmit(payload)`\n// calls every mounted one (wrap it in Testing Library's `act`).\n//\n// What the test set is forgotten with the rest of the mock (`$reset`, or\n// after each test).\n\nimport { useEffect, useRef, useSyncExternalStore } from \"react\";\nimport { streamMaxOf, type StreamState } from \"../client/live/streams\";\nimport type { UseEventOptions } from \"../client/live/useEvent\";\nimport type { UseStreamOptions, UseStreamResult } from \"../client/live/useStream\";\nimport type { AnyContract } from \"../contract/defineContract\";\nimport { isScopedStream } from \"../contract/realtime\";\nimport type { QuickdrawError } from \"../protocol/errors\";\nimport type { MockStore } from \"./mockLive\";\n\n/** One feed as the test set it: its items, or why it failed. */\ntype FeedState = { readonly items: readonly unknown[] } | { readonly error: QuickdrawError };\n\nfunction keyOf(...parts: readonly string[]): string {\n return parts.join(\"\\u0000\");\n}\n\n/** What `useStream` shows of a mocked feed. */\nfunction feedResult(state: unknown, active: boolean, max: number): StreamState {\n if (!active || state === undefined) {\n return { items: [], isLoading: active, error: null };\n }\n const feed = state as FeedState;\n if (\"error\" in feed) {\n return { items: [], isLoading: false, error: feed.error };\n }\n const items = feed.items.length > max ? feed.items.slice(feed.items.length - max) : feed.items;\n return { items, isLoading: false, error: null };\n}\n\n/** `qd.<service>.<stream>` of a mock client. */\nfunction mockStreamMember(\n store: MockStore,\n service: string,\n stream: string,\n scoped: boolean,\n): object {\n const feedKey = (scope: unknown): string =>\n keyOf(\"stream\", service, stream, scoped && typeof scope === \"string\" ? scope : \"\");\n const useMockStream = (first?: unknown, second?: UseStreamOptions): UseStreamResult<unknown> => {\n const scope = scoped ? first : undefined;\n const options = (scoped ? second : (first as UseStreamOptions | undefined)) ?? {};\n const active =\n options.enabled !== false && (!scoped || (typeof scope === \"string\" && scope !== \"\"));\n const max = streamMaxOf(options.max);\n const read = (): StreamState =>\n store.read(keyOf(\"view\", feedKey(scope), String(active), String(max)), () =>\n feedResult(store.value(feedKey(scope)), active, max),\n );\n return useSyncExternalStore(store.subscribe, read, read);\n };\n // `(scope, value)` for a scoped stream, `(value)` for a global one.\n const set = (args: readonly unknown[], value: (last: unknown) => FeedState): void => {\n store.setValue(feedKey(scoped ? args[0] : undefined), value(args.at(-1)));\n };\n return Object.freeze({\n useStream: useMockStream,\n mockItems: (...args: unknown[]) => {\n set(args, (items) => ({ items: [...(items as readonly unknown[])] }));\n },\n mockError: (...args: unknown[]) => {\n set(args, (error) => ({ error: error as QuickdrawError }));\n },\n });\n}\n\n/** `qd.<service>.<channel>` of a mock client. */\nfunction mockChannelMember(store: MockStore, service: string, channel: string): object {\n const sentKey = keyOf(\"sent\", service, channel);\n const sent = (): readonly unknown[] =>\n (store.value(sentKey) as readonly unknown[] | undefined) ?? [];\n const result = Object.freeze({\n send: (payload: unknown) => {\n store.setValue(sentKey, [...sent(), payload]);\n },\n isReady: true,\n });\n return Object.freeze(\n Object.defineProperties(\n { useChannel: () => result },\n { sent: { get: () => [...sent()], enumerable: true } },\n ),\n );\n}\n\n/** `qd.<service>.<event>` of a mock client. */\nfunction mockEventMember(): object {\n const handlers = new Set<(payload: unknown) => void>();\n return Object.freeze({\n useEvent: (handler: (payload: unknown) => void, options: UseEventOptions = {}) => {\n const latest = useRef(handler);\n useEffect(() => {\n latest.current = handler;\n });\n const enabled = options.enabled !== false;\n useEffect(() => {\n if (!enabled) {\n return undefined;\n }\n const entry = (payload: unknown): void => {\n latest.current(payload);\n };\n handlers.add(entry);\n return () => {\n handlers.delete(entry);\n };\n }, [enabled]);\n },\n mockEmit: (payload: unknown) => {\n for (const handler of [...handlers]) {\n handler(payload);\n }\n },\n });\n}\n\n/** The stream, channel and event members of one contract's service on a mock client. */\nexport function mockRealtimeMembers(store: MockStore, contract: AnyContract): [string, object][] {\n const service = contract.name;\n return [\n ...Object.entries(contract.streams).map(([stream, def]): [string, object] => [\n stream,\n mockStreamMember(store, service, stream, isScopedStream(def)),\n ]),\n ...Object.keys(contract.channels).map((channel): [string, object] => [\n channel,\n mockChannelMember(store, service, channel),\n ]),\n ...Object.keys(contract.events).map((event): [string, object] => [event, mockEventMember()]),\n ];\n}\n","// The mock client's `useSearch` (`mockClient.ts`), on the member of a method\n// the search kit made: the method's stub answers each query at once, with no\n// debounce, server or collection cache, so a component test sets the results\n// it shows with `mockResolvedValue({ items, nextCursor })` and reads what was\n// searched for in `calls`.\n\nimport { useQuery, type QueryClient } from \"@tanstack/react-query\";\nimport { methodKey } from \"../client/keys\";\nimport { searchCall, searchTargetOf, type SearchTarget } from \"../client/live/searchResults\";\nimport type { UseSearchOptions, UseSearchResult } from \"../client/live/searchTypes\";\nimport type { MethodTarget } from \"../client/members\";\nimport type { AnyContract } from \"../contract/defineContract\";\nimport type { SearchPage } from \"../contract/kits/searchSchemas\";\nimport type { MethodDef } from \"../contract/methods\";\nimport type { QuickdrawError } from \"../protocol/errors\";\n\n/** Answers one call of the stubbed method. */\ntype Invoke = (input: unknown) => Promise<unknown>;\n\nconst NO_RESULTS: readonly never[] = Object.freeze([]);\n\nfunction useMockSearch(\n queryClient: QueryClient,\n invoke: Invoke,\n target: SearchTarget,\n q: string,\n options: UseSearchOptions,\n): UseSearchResult<unknown> {\n const typed = typeof q === \"string\" ? q.trim() : \"\";\n const scope = typeof options.scope === \"string\" ? options.scope : \"\";\n const input = searchCall(typed, scope, options.limit);\n const active = options.enabled !== false && typed.length >= target.minLength;\n const result = useQuery(\n {\n queryKey: methodKey(target.service, target.method, input),\n queryFn: () => invoke(input) as Promise<SearchPage<unknown>>,\n enabled: active,\n retry: false,\n },\n queryClient,\n );\n const page = active ? result.data : undefined;\n return {\n items: page?.items ?? NO_RESULTS,\n hasMore: (page?.nextCursor ?? null) !== null,\n isSearching: active && result.isFetching,\n isLoading: active && result.isFetching && page === undefined,\n error: active ? (result.error as QuickdrawError | null) : null,\n };\n}\n\n/** The mock `useSearch` a query member gets when the search kit made its method; nothing otherwise. */\nexport function mockSearchMember(\n queryClient: QueryClient,\n invoke: Invoke,\n target: MethodTarget,\n definition: MethodDef,\n contract: AnyContract,\n): Readonly<Record<string, unknown>> {\n const search = searchTargetOf(target, definition, contract);\n if (search === undefined) {\n return {};\n }\n return {\n useSearch: (q: string, options: UseSearchOptions = {}) =>\n useMockSearch(queryClient, invoke, search, q, options),\n };\n}\n","// The session of a mock client (`mockClient.ts`, RFC 0003 section 13): who\n// the mock acts for, as the real `useQuickdraw()` and `usePresence(room)`\n// read it under the mock's provider (`mock.$Provider`), with no server and\n// no socket. 4.1 had nothing of the kind, and an app on the first 5.0\n// candidates re-exported `useQuickdraw` from the module it mocked, so that a\n// story or a test could fake the connection state (finding F3.1).\n//\n// - The provider fills the same context `QuickdrawProvider` fills, with a\n// connection that never opens: its state is made from the session (a\n// hello naming `userId` with `serviceAccess`, connected or connecting,\n// known or not), and every hook that reads the context reads it, on a\n// server too (`renderOnServerAs`).\n// - `usePresence(room)` shows the users `$presence(room, users)` set, while\n// the session is known (a socket is in no room before its hello).\n// - The views of the mock's collections select members for the session's\n// user, as the real ones do for the user the hello names.\n// - `<mock.$Provider session={...}>` lays its `session` over the mock's for\n// what renders inside it (finding F6.2): its own connection shows that\n// session, and the mock's hooks read it from `mockScope.ts`, so stories\n// rendered side by side (a Storybook docs page) each show their own.\n//\n// Imports nothing of Testing Library, so a browser bundle (Storybook) can\n// take it: `./testing/mock` is its own entry.\n\nimport { QueryClientProvider, type QueryClient } from \"@tanstack/react-query\";\nimport { useMemo, type ReactElement, type ReactNode } from \"react\";\nimport {\n createQuickdrawConnection,\n type ConnectionState,\n type QuickdrawConnection,\n} from \"../client/connection\";\nimport { QuickdrawContext, renderOnServerAs, type QuickdrawContextValue } from \"../client/context\";\nimport { createInvalidationCoordinator, type InvalidationCoordinator } from \"../client/coordinator\";\nimport { inertLiveData } from \"../client/live/liveData\";\nimport type { PresenceStore } from \"../client/live/presence\";\nimport { MAX_SUBSCRIBE_IDS, PROTOCOL_VERSION, type HelloFrame } from \"../protocol/version\";\nimport { QUICKDRAW_VERSION } from \"../version\";\nimport type { MockStore } from \"./mockLive\";\nimport { MockSessionScope, type ScopedSession, type SessionScope } from \"./mockScope\";\nimport type { MockClientOptions, MockSession, SessionState } from \"./mockTypes\";\n\n/** The URL of a mock's connection, which never opens. */\nconst MOCK_URL = \"http://quickdraw-mock.invalid\";\n\nconst NOBODY: readonly string[] = Object.freeze([]);\n\n/** The limits a mock's hello announces: the server's defaults. */\nconst MOCK_LIMITS: HelloFrame[\"limits\"] = Object.freeze({\n maxInFlightQueries: 16,\n maxQueuedQueries: 64,\n maxSubscribeIds: MAX_SUBSCRIBE_IDS,\n callTimeoutMs: 30_000,\n subscriptions: Object.freeze({ maxInFlight: 8, maxQueued: 64 }),\n});\n\nfunction checkUser(owner: string, userId: unknown): string | null | undefined {\n if (userId === undefined || userId === null || (typeof userId === \"string\" && userId !== \"\")) {\n return userId;\n }\n throw new TypeError(`${owner}: userId must be a non-empty string, or null for an anonymous user`);\n}\n\nfunction checkFlag(owner: string, name: string, value: unknown): boolean | undefined {\n if (value === undefined || typeof value === \"boolean\") {\n return value;\n }\n throw new TypeError(`${owner}: ${name} must be true or false`);\n}\n\n/**\n * `session` over `base`: what it sets replaces what `base` has, field by\n * field; a field it leaves out (or sets to `undefined`) keeps `base`'s.\n * Throws a `TypeError` naming `owner` for a field of the wrong kind.\n */\nexport function sessionOver(\n owner: string,\n base: SessionState,\n session: MockSession | undefined,\n): SessionState {\n if (session === undefined) {\n return base;\n }\n if (typeof session !== \"object\" || session === null) {\n throw new TypeError(\n `${owner}: the session is { userId?, serviceAccess?, isConnected?, isKnown? }`,\n );\n }\n const { serviceAccess } = session;\n if (\n serviceAccess !== undefined &&\n (typeof serviceAccess !== \"object\" || serviceAccess === null)\n ) {\n throw new TypeError(`${owner}: serviceAccess maps service names to access levels`);\n }\n const userId = checkUser(owner, session.userId);\n const isConnected = checkFlag(owner, \"isConnected\", session.isConnected);\n const isKnown = checkFlag(owner, \"isKnown\", session.isKnown);\n return Object.freeze({\n userId: userId === undefined ? base.userId : userId,\n serviceAccess: Object.freeze({ ...(serviceAccess ?? base.serviceAccess) }),\n isConnected: isConnected ?? base.isConnected,\n isKnown: isKnown ?? base.isKnown,\n });\n}\n\n/** The id a mock's hello names its server by: one mock server, never restarted. */\nconst MOCK_SERVER_ID = \"mock\";\n\n/** The connection state a session shows: connected or connecting, with a hello once it is known. */\nfunction stateOf(session: SessionState): ConnectionState {\n const hello: HelloFrame | null = session.isKnown\n ? Object.freeze({\n protocol: PROTOCOL_VERSION,\n server: QUICKDRAW_VERSION,\n serverId: MOCK_SERVER_ID,\n limits: MOCK_LIMITS,\n features: Object.freeze([]),\n userId: session.userId,\n serviceAccess: session.serviceAccess,\n })\n : null;\n return Object.freeze({\n status: session.isConnected ? \"connected\" : \"connecting\",\n reconnecting: !session.isConnected && session.isKnown,\n hello,\n serviceAccess: hello === null ? null : session.serviceAccess,\n refusal: null,\n backoff: Object.freeze({}),\n });\n}\n\n/**\n * The connection of a mock client: a real connection that is never opened\n * (its socket, lane and topics stay idle, so a real client's hook rendered\n * under the mock's provider by mistake waits rather than throws), whose\n * state comes from the mock's session. Opening, closing and new credentials\n * do nothing.\n */\nfunction mockConnection(store: MockStore, sessionOf: () => SessionState): QuickdrawConnection {\n const inert = createQuickdrawConnection({ url: MOCK_URL, transports: [\"websocket\"] });\n let shown: { readonly session: SessionState; readonly state: ConnectionState } | undefined;\n const getState = (): ConnectionState => {\n const session = sessionOf();\n if (shown?.session !== session) {\n shown = { session, state: stateOf(session) };\n }\n return shown.state;\n };\n const nothing = (): void => undefined;\n return Object.freeze({\n url: inert.url,\n timeoutMs: inert.timeoutMs,\n socket: inert.socket,\n subscriptionLane: inert.subscriptionLane,\n getState,\n subscribe: store.subscribe,\n open: nothing,\n close: nothing,\n retain: () => nothing,\n setAuth: () => false,\n nextCallId: inert.nextCallId,\n reportRateLimited: nothing,\n backoffRemaining: () => 0,\n onReconnect: () => nothing,\n onHello: (listener: (hello: HelloFrame) => void) => mockHellos(getState, store, listener),\n watch: () => nothing,\n waitForJoin: () => undefined,\n });\n}\n\n/**\n * `onHello` on a mock's connection: each session that is connected and\n * known is a hello (`$session(...)` that sets one is a reconnect), the\n * current one in a microtask, as a real connection gives it.\n */\nfunction mockHellos(\n getState: () => ConnectionState,\n store: MockStore,\n listener: (hello: HelloFrame) => void,\n): () => void {\n let last: HelloFrame | null = null;\n let stopped = false;\n const deliver = (): void => {\n const { status, hello } = getState();\n if (!stopped && status === \"connected\" && hello !== null && hello !== last) {\n last = hello;\n listener(hello);\n }\n };\n queueMicrotask(deliver);\n const stop = store.subscribe(deliver);\n return () => {\n stopped = true;\n stop();\n };\n}\n\n/** The key of a room's users in the mock's store. */\nfunction presenceKey(room: string): string {\n return `presence\\u0000${room}`;\n}\n\n/**\n * The presence `usePresence` reads on a mock: the users the test set per\n * room, nobody while the session is not known. No frame reaches it.\n */\nfunction mockPresence(store: MockStore, sessionOf: () => SessionState): PresenceStore {\n return Object.freeze({\n users(room: string): readonly string[] {\n if (!sessionOf().isKnown) {\n return NOBODY;\n }\n return (store.value(presenceKey(room)) as readonly string[] | undefined) ?? NOBODY;\n },\n // Every change of the store: `users` returns the same array until the room's own changes.\n listen: (_room: string, listener: () => void) => store.subscribe(listener),\n receive: () => undefined,\n clear: () => undefined,\n });\n}\n\n/** What a mock client gives its tests besides its members: its provider, session and presence. */\n/** The props of a mock's provider: what it renders, and the session it shows there. */\nexport interface MockProviderProps {\n readonly children?: ReactNode;\n /**\n * The session the subtree shows, laid over the mock's own (`$session`)\n * field by field: what it leaves out follows the mock's. Each provider\n * has its own, so stories rendered side by side show their own sessions.\n */\n readonly session?: MockSession;\n}\n\nexport interface MockSessionControls {\n readonly Provider: (props: MockProviderProps) => ReactElement;\n /** The invalidation coordinator of the mock's cache, which the provider gives the hooks too. */\n readonly coordinator: InvalidationCoordinator;\n setSession(session: MockSession): void;\n setPresence(room: string, users: readonly string[]): void;\n}\n\n/** The session a mock starts with: its `session` option over `userId`, connected and known. */\nexport function startingSession(options: MockClientOptions): SessionState {\n const fallback: SessionState = Object.freeze({\n userId: null,\n serviceAccess: Object.freeze({}),\n isConnected: true,\n isKnown: true,\n });\n const withUser = sessionOver(\n \"createMockClient\",\n fallback,\n options.userId === undefined ? undefined : { userId: options.userId },\n );\n return sessionOver(\"createMockClient\", withUser, options.session);\n}\n\n/**\n * `session` laid over the session of `store` (`$Provider`'s prop): checked\n * at once, and the same object while the mock's own session does not change.\n */\nfunction scopeOf(store: MockStore, session: MockSession): SessionScope {\n sessionOver(\"$Provider\", store.session(), session);\n let last: { readonly base: SessionState; readonly shown: SessionState } | undefined;\n return (base) => {\n if (last?.base !== base) {\n last = { base, shown: sessionOver(\"$Provider\", base, session) };\n }\n return last.shown;\n };\n}\n\n/**\n * The provider, session and presence of one mock client: `base` is the\n * session it starts with and goes back to on a reset; `$session` sets\n * another over it.\n */\nexport function mockSessionControls(\n store: MockStore,\n queryClient: QueryClient,\n base: SessionState,\n): MockSessionControls {\n const coordinator = createInvalidationCoordinator(queryClient);\n /** The context value of a connection showing `sessionOf`'s session. */\n const contextOver = (sessionOf: () => SessionState): QuickdrawContextValue => {\n const connection = mockConnection(store, sessionOf);\n // A server renders the session too, so a story or a test that renders on one hydrates it.\n renderOnServerAs(connection, connection.getState);\n inertLiveData(connection, queryClient, mockPresence(store, sessionOf));\n return Object.freeze({ connection, queryClient, coordinator });\n };\n const value = contextOver(store.session);\n /** A provider's `session` prop: its own connection, and the scope the mock's hooks read. */\n const scopedBy = (key: string): { value: QuickdrawContextValue; scoped: ScopedSession } => {\n const scope = scopeOf(store, JSON.parse(key) as MockSession);\n return {\n value: contextOver(() => scope(store.session())),\n scoped: Object.freeze({ store, scope }),\n };\n };\n function MockQuickdrawProvider({ children, session }: MockProviderProps): ReactElement {\n // By value: a story passes a new object on every render.\n const key = session === undefined ? undefined : JSON.stringify(session);\n const scoped = useMemo(() => (key === undefined ? undefined : scopedBy(key)), [key]);\n return (\n <QueryClientProvider client={queryClient}>\n <QuickdrawContext.Provider value={scoped?.value ?? value}>\n <MockSessionScope.Provider value={scoped?.scoped}>{children}</MockSessionScope.Provider>\n </QuickdrawContext.Provider>\n </QueryClientProvider>\n );\n }\n return Object.freeze({\n Provider: MockQuickdrawProvider,\n coordinator,\n setSession(session: MockSession): void {\n store.setSession(sessionOver(\"$session\", base, session));\n },\n setPresence(room: string, users: readonly string[]): void {\n if (typeof room !== \"string\" || room === \"\") {\n throw new TypeError(\"$presence: room must be a room's name\");\n }\n if (!Array.isArray(users) || !users.every((user) => typeof user === \"string\")) {\n throw new TypeError(\"$presence: users must be a list of user ids\");\n }\n store.setValue(presenceKey(room), Object.freeze([...new Set(users)]));\n },\n });\n}\n"],"mappings":"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","names":["useQuery","useSyncExternalStore","useSyncExternalStore","useSyncExternalStore","useSyncExternalStore","keyOf","useSyncExternalStore","keyOf","useSyncExternalStore","useQuery"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/server/express/rateLimit.ts"],"sourcesContent":["/**\n * Express rate-limit presets built on express-rate-limit.\n *\n * Lives in the dedicated `/server/express` subpath so the core `/server`\n * entry point carries no express-rate-limit dependency. All presets are\n * factories (not module-level singletons) so env/config is read at call time.\n *\n * Note: distinct from the Socket.IO `createRateLimiter` exported from\n * `/server` — these protect REST routes (OAuth, webhooks, public APIs).\n */\n\nimport type { NextFunction, Request, Response } from \"express\";\nimport {\n ipKeyGenerator,\n rateLimit,\n type Options,\n type RateLimitRequestHandler,\n} from \"express-rate-limit\";\n\ntype PartialOptions = Partial<Options>;\n\nfunction handler(_req: Request, res: Response, _next: NextFunction, options: Options): void {\n const retryAfterSec = Math.max(1, Math.ceil((options.windowMs ?? 60_000) / 1000));\n if (!res.getHeader(\"Retry-After\")) {\n res.setHeader(\"Retry-After\", String(retryAfterSec));\n }\n res.status(options.statusCode ?? 429).json({ error: \"Rate limit exceeded\" });\n}\n\nconst jsonLimiter: PartialOptions = {\n standardHeaders: true,\n legacyHeaders: false,\n handler,\n};\n\n/** Normalize IPv4-mapped IPv6 addresses (::ffff:1.2.3.4 → 1.2.3.4). */\nexport function stripIp(raw: string | undefined): string {\n const ip = raw ?? \"unknown\";\n return ip.startsWith(\"::ffff:\") ? ip.slice(7) : ip;\n}\n\n/**\n * Key by Bearer token when present, falling back to client IP. Lets\n * token-authenticated clients get their own bucket instead of sharing\n * a NAT'd IP.\n */\nexport function bearerOrIp(req: Request): string {\n const auth = req.headers.authorization ?? \"\";\n if (auth.startsWith(\"Bearer \")) return auth.slice(7);\n return ipKeyGenerator(stripIp(req.ip));\n}\n\n/**\n * Base factory: JSON 429 body + Retry-After header + standard headers.\n */\nexport function createJsonRateLimiter(options: PartialOptions): RateLimitRequestHandler {\n return rateLimit({ ...jsonLimiter, ...options });\n}\n\n/** OAuth flows: 20 requests / 15 min per IP (status sub-routes excluded). */\nexport function createAuthLimiter(overrides: PartialOptions = {}): RateLimitRequestHandler {\n return createJsonRateLimiter({\n windowMs: 15 * 60 * 1000,\n max: 20,\n skip: (req) => req.path.endsWith(\"/status\"),\n ...overrides,\n });\n}\n\n/** Auth status polling: 120 requests / 15 min per IP. */\nexport function createAuthStatusLimiter(overrides: PartialOptions = {}): RateLimitRequestHandler {\n return createJsonRateLimiter({\n windowMs: 15 * 60 * 1000,\n max: 120,\n ...overrides,\n });\n}\n\n/** Inbound webhooks: 300 requests / min per IP. */\nexport function createWebhookLimiter(overrides: PartialOptions = {}): RateLimitRequestHandler {\n return createJsonRateLimiter({\n windowMs: 60 * 1000,\n max: 300,\n ...overrides,\n });\n}\n\n/** Generic public API: 60 requests / min per IP. */\nexport function createPublicApiLimiter(overrides: PartialOptions = {}): RateLimitRequestHandler {\n return createJsonRateLimiter({\n windowMs: 60 * 1000,\n max: 60,\n ...overrides,\n });\n}\n\n/**\n * Answers a refused call in the HTTP transport's own reply shape, so a\n * server caller (`createServerCaller`) reports `RATE_LIMITED` with\n * `retryAfterMs` instead of an unreadable reply.\n */\nfunction callHandler(req: Request, res: Response, _next: NextFunction, options: Options): void {\n const info = (req as Request & Partial<Record<string, { resetTime?: Date }>>)[\n options.requestPropertyName\n ];\n const untilReset =\n info?.resetTime === undefined ? options.windowMs : info.resetTime.getTime() - Date.now();\n const retryAfterMs = Math.max(1000, untilReset);\n if (!res.getHeader(\"Retry-After\")) {\n res.setHeader(\"Retry-After\", String(Math.ceil(retryAfterMs / 1000)));\n }\n res.status(options.statusCode).json({\n ok: false,\n e: { code: \"RATE_LIMITED\", message: \"Rate limit exceeded\", data: { retryAfterMs } },\n });\n}\n\n/**\n * Calls through the HTTP transport (RFC 0003 section 10), mounted with\n * `createServer({ http: { rateLimit: createCallLimiter() } })`: 300 calls per\n * minute per IP, refused with `429 { ok: false, e: { code: \"RATE_LIMITED\",\n * message, data: { retryAfterMs } } }`. Server-side prefetch reaches the API\n * from the web server's address, so key it some other way (`keyGenerator`)\n * or raise `max` when the web server prefetches for many users.\n */\nexport function createCallLimiter(overrides: PartialOptions = {}): RateLimitRequestHandler {\n return createJsonRateLimiter({\n windowMs: 60 * 1000,\n max: 300,\n handler: callHandler,\n ...overrides,\n });\n}\n"],"mappings":";AAYA;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAIP,SAAS,QAAQ,MAAe,KAAe,OAAqB,SAAwB;AAC1F,QAAM,gBAAgB,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,YAAY,OAAU,GAAI,CAAC;AAChF,MAAI,CAAC,IAAI,UAAU,aAAa,GAAG;AACjC,QAAI,UAAU,eAAe,OAAO,aAAa,CAAC;AAAA,EACpD;AACA,MAAI,OAAO,QAAQ,cAAc,GAAG,EAAE,KAAK,EAAE,OAAO,sBAAsB,CAAC;AAC7E;AAEA,IAAM,cAA8B;AAAA,EAClC,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf;AACF;AAGO,SAAS,QAAQ,KAAiC;AACvD,QAAM,KAAK,OAAO;AAClB,SAAO,GAAG,WAAW,SAAS,IAAI,GAAG,MAAM,CAAC,IAAI;AAClD;AAOO,SAAS,WAAW,KAAsB;AAC/C,QAAM,OAAO,IAAI,QAAQ,iBAAiB;AAC1C,MAAI,KAAK,WAAW,SAAS,EAAG,QAAO,KAAK,MAAM,CAAC;AACnD,SAAO,eAAe,QAAQ,IAAI,EAAE,CAAC;AACvC;AAKO,SAAS,sBAAsB,SAAkD;AACtF,SAAO,UAAU,EAAE,GAAG,aAAa,GAAG,QAAQ,CAAC;AACjD;AAGO,SAAS,kBAAkB,YAA4B,CAAC,GAA4B;AACzF,SAAO,sBAAsB;AAAA,IAC3B,UAAU,KAAK,KAAK;AAAA,IACpB,KAAK;AAAA,IACL,MAAM,CAAC,QAAQ,IAAI,KAAK,SAAS,SAAS;AAAA,IAC1C,GAAG;AAAA,EACL,CAAC;AACH;AAGO,SAAS,wBAAwB,YAA4B,CAAC,GAA4B;AAC/F,SAAO,sBAAsB;AAAA,IAC3B,UAAU,KAAK,KAAK;AAAA,IACpB,KAAK;AAAA,IACL,GAAG;AAAA,EACL,CAAC;AACH;AAGO,SAAS,qBAAqB,YAA4B,CAAC,GAA4B;AAC5F,SAAO,sBAAsB;AAAA,IAC3B,UAAU,KAAK;AAAA,IACf,KAAK;AAAA,IACL,GAAG;AAAA,EACL,CAAC;AACH;AAGO,SAAS,uBAAuB,YAA4B,CAAC,GAA4B;AAC9F,SAAO,sBAAsB;AAAA,IAC3B,UAAU,KAAK;AAAA,IACf,KAAK;AAAA,IACL,GAAG;AAAA,EACL,CAAC;AACH;AAOA,SAAS,YAAY,KAAc,KAAe,OAAqB,SAAwB;AAC7F,QAAM,OAAQ,IACZ,QAAQ,mBACV;AACA,QAAM,aACJ,MAAM,cAAc,SAAY,QAAQ,WAAW,KAAK,UAAU,QAAQ,IAAI,KAAK,IAAI;AACzF,QAAM,eAAe,KAAK,IAAI,KAAM,UAAU;AAC9C,MAAI,CAAC,IAAI,UAAU,aAAa,GAAG;AACjC,QAAI,UAAU,eAAe,OAAO,KAAK,KAAK,eAAe,GAAI,CAAC,CAAC;AAAA,EACrE;AACA,MAAI,OAAO,QAAQ,UAAU,EAAE,KAAK;AAAA,IAClC,IAAI;AAAA,IACJ,GAAG,EAAE,MAAM,gBAAgB,SAAS,uBAAuB,MAAM,EAAE,aAAa,EAAE;AAAA,EACpF,CAAC;AACH;AAUO,SAAS,kBAAkB,YAA4B,CAAC,GAA4B;AACzF,SAAO,sBAAsB;AAAA,IAC3B,UAAU,KAAK;AAAA,IACf,KAAK;AAAA,IACL,SAAS;AAAA,IACT,GAAG;AAAA,EACL,CAAC;AACH;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/version.ts","../src/contract/names.ts","../src/protocol/version.ts","../src/contract/access.ts"],"sourcesContent":["/** The published version of `@fitzzero/quickdraw-core`. Kept in step with package.json. */\nexport const QUICKDRAW_VERSION = \"5.1.1\";\n","// 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/** 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\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} 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} 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\n * authenticating it failed: the app's `authenticate` threw, so the\n * credentials are bad or could not be checked. A client tells it apart from\n * {@link ProtocolMismatch} by `code`; reconnecting needs other credentials.\n */\nexport interface AuthenticationRefused {\n readonly code: \"UNAUTHENTICATED\";\n}\n\n/** True when a `connect_error`'s `data` says authenticating the connection failed. */\nexport function isAuthenticationRefused(value: unknown): value is AuthenticationRefused {\n return isRecord(value) && value.code === \"UNAUTHENTICATED\";\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 longest collection scope value or change topic a frame may name, in characters. */\nexport const MAX_SCOPE_LENGTH = 256;\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), as `qd:hello` announces it.\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}\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}\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 /** Names of optional server features that are on. Informational only. */\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","// Access levels and access-control lists, unchanged from 4.1\n// (4.1 `src/shared/types.ts:7-14`). The level names are stored in app data\n// (`User.serviceAccess`, JSON access lists), so they never change.\n\nexport type AccessLevel = \"Public\" | \"Read\" | \"Moderate\" | \"Admin\";\n\n/** Every access level, lowest first. */\nexport const ACCESS_LEVELS: readonly AccessLevel[] = Object.freeze([\n \"Public\",\n \"Read\",\n \"Moderate\",\n \"Admin\",\n]);\n\n/** True when `value` is one of the four access level names. */\nexport function isAccessLevel(value: unknown): value is AccessLevel {\n return typeof value === \"string\" && (ACCESS_LEVELS as readonly string[]).includes(value);\n}\n\nexport type ACE = {\n userId: string;\n level: AccessLevel;\n};\n\nexport type ACL = ACE[];\n"],"mappings":";;;;;AACO,IAAM,oBAAoB;;;ACY1B,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;AAGO,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;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;AACX,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;AACV,CAAU;;;ACzGH,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;AAaO,SAAS,wBAAwB,OAAgD;AACtF,SAAO,SAAS,KAAK,KAAK,MAAM,SAAS;AAC3C;AAGO,IAAM,oBAAoB;AAG1B,IAAM,mBAAmB;;;ACpEzB,IAAM,gBAAwC,OAAO,OAAO;AAAA,EACjE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGM,SAAS,cAAc,OAAsC;AAClE,SAAO,OAAO,UAAU,YAAa,cAAoC,SAAS,KAAK;AACzF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/client/keys.ts","../src/client/members.ts"],"sourcesContent":["// TanStack Query keys (RFC 0003 section 11.5): a method call's result is\n// cached under `[\"qd\", service, \"m\", method, input]`, a live entity under\n// `[\"qd\", service, \"e\", id]` and a live collection scope under\n// `[\"qd\", service, \"c\", collection, scope]`, with the service's wire name\n// (the contract's `name`) and the input as the caller passed it. TanStack\n// hashes a key with sorted object keys, so two inputs with the same fields\n// share one cache entry whatever their key order. 4.1 put\n// `JSON.stringify(payload)` in the key\n// (4.1 `src/client/useServiceQuery.ts:88-91`), which made the cache depend\n// on key order and hid the input from `queryKey` filters.\n//\n// React-free: the hooks, the server-side caller and non-React code build the\n// same keys from here, so a query prefetched on the server is the one the\n// hook reads.\n\n/** The first element of every key the client caches under. */\nexport const KEY_ROOT = \"qd\";\n\n/** The key of one method call's result: `[\"qd\", service, \"m\", method, input]`. */\nexport type MethodQueryKey<Input = unknown> = readonly [\n root: typeof KEY_ROOT,\n service: string,\n kind: \"m\",\n method: string,\n input: Input,\n];\n\n/** The prefix of every key of one method: matches all its inputs in a `queryKey` filter. */\nexport type MethodKeyPrefix = readonly [\n root: typeof KEY_ROOT,\n service: string,\n kind: \"m\",\n method: string,\n];\n\n/** The prefix of every key of one service: its method calls, and later its entities and collections. */\nexport type ServiceKeyPrefix = readonly [root: typeof KEY_ROOT, service: string];\n\n/** The key a call of `service.method` with `input` is cached under. */\nexport function methodKey<Input>(\n service: string,\n method: string,\n input: Input,\n): MethodQueryKey<Input> {\n return [KEY_ROOT, service, \"m\", method, input];\n}\n\n/** The prefix of the keys of every call of `service.method`, for `invalidateQueries` and the like. */\nexport function methodKeyPrefix(service: string, method: string): MethodKeyPrefix {\n return [KEY_ROOT, service, \"m\", method];\n}\n\n/** The prefix of every key of `service`. */\nexport function serviceKeyPrefix(service: string): ServiceKeyPrefix {\n return [KEY_ROOT, service];\n}\n\n/** The key a live entity is cached under: `[\"qd\", service, \"e\", id]`. */\nexport type EntityQueryKey = readonly [\n root: typeof KEY_ROOT,\n service: string,\n kind: \"e\",\n id: string,\n];\n\n/** The key a live collection scope is cached under: `[\"qd\", service, \"c\", collection, scope]`. */\nexport type CollectionQueryKey = readonly [\n root: typeof KEY_ROOT,\n service: string,\n kind: \"c\",\n collection: string,\n scope: string,\n];\n\n/** The key row `id` of `service` is cached under while `useEntity` or `useEntities` holds it. */\nexport function entityKey(service: string, id: string): EntityQueryKey {\n return [KEY_ROOT, service, \"e\", id];\n}\n\n/** The key one scope of a collection is cached under while `useCollection` holds it. */\nexport function collectionKey(\n service: string,\n collection: string,\n scope: string,\n): CollectionQueryKey {\n return [KEY_ROOT, service, \"c\", collection, scope];\n}\n","// Builds a caller object from a map of contracts at runtime, with no code\n// generation: `client[key][method]` for every contract in the map and every\n// method of its contract. The typed client (`createClient.ts`) and the HTTP\n// server caller (`serverCaller.ts`) are both built here.\n//\n// Every member object is made once, when the caller is created, and frozen,\n// so a member's functions (hooks among them) are the same on every render;\n// the caller freezes the object of services once it has added its own keys.\n// No member is named `then`, so `await` never takes a caller or a service\n// for a promise: contracts reserve `then` and `$`-names for their methods\n// (`contract/validateContract.ts`), and the map's keys are checked here.\n// Building from own enumerable entries into fresh objects means a key such\n// as `__proto__` becomes an ordinary member, never a prototype.\n//\n// React-free.\n\nimport type { CollectionDef } from \"../contract/collections\";\nimport type { AnyContract } from \"../contract/defineContract\";\nimport type { MethodDef, MethodKind, MethodOutput, ServiceWatch, Watch } from \"../contract/methods\";\nimport type { ContractMap } from \"../contract/infer\";\n\n/** One method of one service, as a member is built for it. */\nexport interface MethodTarget {\n /** The service's name on the wire: the contract's `name`. */\n readonly service: string;\n readonly method: string;\n readonly kind: MethodKind;\n /** The contract's `output`: a schema, or a projection reference such as `\"entity\"` or `listOf(\"card\")`. */\n readonly output?: MethodOutput;\n /** The query's `watch` declaration, when it has one. */\n readonly watch?: Watch<string> | ServiceWatch | undefined;\n /** The collections of the method's contract, for a mutation's `addEntity`. */\n readonly collections?: Readonly<Record<string, CollectionDef>>;\n}\n\nfunction isContract(value: unknown): value is AnyContract {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const { name, methods } = value as { readonly name?: unknown; readonly methods?: unknown };\n return typeof name === \"string\" && typeof methods === \"object\" && methods !== null;\n}\n\nfunction checkEntry(\n owner: string,\n key: string,\n contract: unknown,\n reserved: readonly string[],\n): AnyContract {\n if (key === \"then\" || key.startsWith(\"$\")) {\n throw new TypeError(\n `${owner}: \"${key}\" cannot name a service; \"then\" and $-names are reserved`,\n );\n }\n if (reserved.includes(key)) {\n throw new TypeError(`${owner}: \"${key}\" cannot name a service; the client uses it`);\n }\n if (!isContract(contract)) {\n throw new TypeError(`${owner}: \"${key}\" is not a contract from defineContract`);\n }\n return contract;\n}\n\n/**\n * Builds `{ [key]: { [method]: member(target, definition, contract) } }`\n * from `contracts`, frozen. `member` gets the method's declaration and its\n * contract too, for what a kit made (the search kit's `useSearch`). `owner`\n * names the function that builds it, in error messages; `reserved` are keys\n * the caller object uses itself, which no service may take. `live`, when\n * given, adds members of its own to each service (the typed client's\n * `useEntity`, `useEntities` and one member per collection); a contract\n * keeps them apart from its methods, since methods, collections and the\n * reserved names share one namespace. It gets the service's method members\n * too, for members that group them (the admin kit's `admin`).\n */\nexport function buildCaller(\n owner: string,\n contracts: ContractMap,\n member: (target: MethodTarget, definition: MethodDef, contract: AnyContract) => object,\n reserved: readonly string[] = [],\n live?: (\n contract: AnyContract,\n methods: Readonly<Record<string, object>>,\n ) => Readonly<Record<string, object>>,\n): Record<string, object> {\n if (typeof contracts !== \"object\" || contracts === null) {\n throw new TypeError(`${owner}: pass the contracts as an object, { task, project }`);\n }\n const services = Object.entries(contracts).map(([key, value]) => {\n const contract = checkEntry(owner, key, value, reserved);\n const methods = Object.entries(contract.methods).map(([method, definition]) => [\n method,\n member(\n {\n service: contract.name,\n method,\n kind: definition.kind,\n output: definition.output,\n watch: definition.watch,\n collections: contract.collections,\n },\n definition,\n contract,\n ),\n ]);\n const extra = Object.entries(live?.(contract, Object.fromEntries(methods)) ?? {});\n return [key, Object.freeze(Object.fromEntries([...methods, ...extra]))];\n });\n return Object.fromEntries(services) as Record<string, object>;\n}\n"],"mappings":";AAgBO,IAAM,WAAW;AAuBjB,SAAS,UACd,SACA,QACA,OACuB;AACvB,SAAO,CAAC,UAAU,SAAS,KAAK,QAAQ,KAAK;AAC/C;AAGO,SAAS,gBAAgB,SAAiB,QAAiC;AAChF,SAAO,CAAC,UAAU,SAAS,KAAK,MAAM;AACxC;AAGO,SAAS,iBAAiB,SAAmC;AAClE,SAAO,CAAC,UAAU,OAAO;AAC3B;AAoBO,SAAS,UAAU,SAAiB,IAA4B;AACrE,SAAO,CAAC,UAAU,SAAS,KAAK,EAAE;AACpC;AAGO,SAAS,cACd,SACA,YACA,OACoB;AACpB,SAAO,CAAC,UAAU,SAAS,KAAK,YAAY,KAAK;AACnD;;;ACnDA,SAAS,WAAW,OAAsC;AACxD,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO;AAAA,EACT;AACA,QAAM,EAAE,MAAM,QAAQ,IAAI;AAC1B,SAAO,OAAO,SAAS,YAAY,OAAO,YAAY,YAAY,YAAY;AAChF;AAEA,SAAS,WACP,OACA,KACA,UACA,UACa;AACb,MAAI,QAAQ,UAAU,IAAI,WAAW,GAAG,GAAG;AACzC,UAAM,IAAI;AAAA,MACR,GAAG,KAAK,MAAM,GAAG;AAAA,IACnB;AAAA,EACF;AACA,MAAI,SAAS,SAAS,GAAG,GAAG;AAC1B,UAAM,IAAI,UAAU,GAAG,KAAK,MAAM,GAAG,6CAA6C;AAAA,EACpF;AACA,MAAI,CAAC,WAAW,QAAQ,GAAG;AACzB,UAAM,IAAI,UAAU,GAAG,KAAK,MAAM,GAAG,yCAAyC;AAAA,EAChF;AACA,SAAO;AACT;AAcO,SAAS,YACd,OACA,WACA,QACA,WAA8B,CAAC,GAC/B,MAIwB;AACxB,MAAI,OAAO,cAAc,YAAY,cAAc,MAAM;AACvD,UAAM,IAAI,UAAU,GAAG,KAAK,sDAAsD;AAAA,EACpF;AACA,QAAM,WAAW,OAAO,QAAQ,SAAS,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;AAC/D,UAAM,WAAW,WAAW,OAAO,KAAK,OAAO,QAAQ;AACvD,UAAM,UAAU,OAAO,QAAQ,SAAS,OAAO,EAAE,IAAI,CAAC,CAAC,QAAQ,UAAU,MAAM;AAAA,MAC7E;AAAA,MACA;AAAA,QACE;AAAA,UACE,SAAS,SAAS;AAAA,UAClB;AAAA,UACA,MAAM,WAAW;AAAA,UACjB,QAAQ,WAAW;AAAA,UACnB,OAAO,WAAW;AAAA,UAClB,aAAa,SAAS;AAAA,QACxB;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF,CAAC;AACD,UAAM,QAAQ,OAAO,QAAQ,OAAO,UAAU,OAAO,YAAY,OAAO,CAAC,KAAK,CAAC,CAAC;AAChF,WAAO,CAAC,KAAK,OAAO,OAAO,OAAO,YAAY,CAAC,GAAG,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC;AAAA,EACxE,CAAC;AACD,SAAO,OAAO,YAAY,QAAQ;AACpC;","names":[]}
@@ -1,13 +0,0 @@
1
- // src/contract/logger.ts
2
- var consoleLogger = {
3
- info: (message, meta) => console.log(`[INFO] ${message}`, meta ?? ""),
4
- warn: (message, meta) => console.warn(`[WARN] ${message}`, meta ?? ""),
5
- error: (message, meta) => console.error(`[ERROR] ${message}`, meta ?? ""),
6
- debug: (message, meta) => console.debug(`[DEBUG] ${message}`, meta ?? ""),
7
- child: () => consoleLogger
8
- };
9
-
10
- export {
11
- consoleLogger
12
- };
13
- //# sourceMappingURL=chunk-YSWH776Y.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/contract/logger.ts"],"sourcesContent":["// The logger contract and its console default, unchanged from 4.1\n// (4.1 `src/shared/types.ts:525-545`).\n\n/**\n * Logger interface compatible with Winston and other loggers\n */\nexport interface Logger {\n info(message: string, meta?: Record<string, unknown>): void;\n warn(message: string, meta?: Record<string, unknown>): void;\n error(message: string, meta?: Record<string, unknown>): void;\n debug(message: string, meta?: Record<string, unknown>): void;\n child(options: Record<string, unknown>): Logger;\n}\n\n/**\n * Default console logger implementation\n */\nexport const consoleLogger: Logger = {\n info: (message, meta) => console.log(`[INFO] ${message}`, meta ?? \"\"),\n warn: (message, meta) => console.warn(`[WARN] ${message}`, meta ?? \"\"),\n error: (message, meta) => console.error(`[ERROR] ${message}`, meta ?? \"\"),\n debug: (message, meta) => console.debug(`[DEBUG] ${message}`, meta ?? \"\"),\n child: () => consoleLogger,\n};\n"],"mappings":";AAiBO,IAAM,gBAAwB;AAAA,EACnC,MAAM,CAAC,SAAS,SAAS,QAAQ,IAAI,UAAU,OAAO,IAAI,QAAQ,EAAE;AAAA,EACpE,MAAM,CAAC,SAAS,SAAS,QAAQ,KAAK,UAAU,OAAO,IAAI,QAAQ,EAAE;AAAA,EACrE,OAAO,CAAC,SAAS,SAAS,QAAQ,MAAM,WAAW,OAAO,IAAI,QAAQ,EAAE;AAAA,EACxE,OAAO,CAAC,SAAS,SAAS,QAAQ,MAAM,WAAW,OAAO,IAAI,QAAQ,EAAE;AAAA,EACxE,OAAO,MAAM;AACf;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/protocol/parser.ts","../src/protocol/preEncoded.ts","../src/protocol/utf8.ts"],"sourcesContent":["// A JSON-only Socket.IO parser (RFC 0003 section 8.4). The stock encoder\n// walks every event and acknowledgement looking for binary values, then\n// serializes it; this one skips the walk. For a packet without binary its\n// output is the stock output byte for byte, because it is the stock encoder's\n// own string encoding: `encode` is overridden to call `encodeAsString`\n// directly (socket.io-parser 4.2.5 `Encoder.encode`). The 4.x legacy shim\n// therefore needs no second parser. The decoder is the stock decoder, so a\n// server using this parser still reads packets from a client that uses the\n// stock one, binary included.\n//\n// Apps that send binary use the stock parser instead (`binary: true` on the\n// server). This module imports `socket.io-parser`, so it is its own export,\n// `@fitzzero/quickdraw-core/parser`, and the package root stays free of\n// dependencies.\n//\n// The server hands it pre-encoded arguments (`preEncoded.ts`) for text that\n// several packets share, such as a shared run's reply to each of its callers:\n// their text is spliced in as written, so it is encoded once, not per packet.\n\nimport { Decoder, Encoder, PacketType, type Packet } from \"socket.io-parser\";\nimport { isPreEncoded, type PreEncoded } from \"./preEncoded\";\nimport { utf8ByteLength } from \"./utf8\";\n\n/** Options of {@link createJsonParser}. */\nexport interface JsonParserOptions {\n /**\n * Called with every packet the encoder writes and its size in UTF-8 bytes:\n * events, acknowledgements (a call's response travels as one) and connection\n * packets. A broadcast is encoded once, so it is reported once, not once per\n * recipient. The hook runs inside `emit`: keep it cheap. It must not throw;\n * if it does, the error is ignored and the packet is still sent.\n */\n readonly onEncoded?: (packet: Packet, byteLength: number) => void;\n}\n\n/** A Socket.IO parser: the `parser` option of both `new Server()` and `io()`. */\nexport interface JsonParser {\n readonly Encoder: typeof Encoder;\n readonly Decoder: typeof Decoder;\n}\n\ntype EncodedHook = NonNullable<JsonParserOptions[\"onEncoded\"]>;\n\n/** The stock encoder's string encoding; `private` in its type declarations only. */\ninterface StringEncoding {\n encodeAsString(this: Encoder, packet: Packet): string;\n}\n\nconst hasBlob = typeof Blob === \"function\";\n\n/** Binary as the stock parser defines it: an `ArrayBuffer`, a view of one (`Buffer` too) or a `Blob`. */\nfunction isBinary(value: unknown): value is object {\n return (\n typeof value === \"object\" &&\n value !== null &&\n (ArrayBuffer.isView(value) ||\n value instanceof ArrayBuffer ||\n (hasBlob && value instanceof Blob))\n );\n}\n\nfunction binaryMessage(packet: Packet, index: number, value: object): string {\n const kind = Object.prototype.toString.call(value).slice(8, -1);\n const where =\n packet.type === PacketType.ACK\n ? `Argument ${index + 1} of acknowledgement ${String(packet.id)}`\n : `Argument ${index} of event \"${String(packet.data[0])}\"`;\n return (\n `${where} is binary (${kind}), which the JSON-only Socket.IO parser cannot send. ` +\n \"Use the stock Socket.IO parser to send binary; a quickdraw server takes `binary: true`.\"\n );\n}\n\n/**\n * Throws when an argument of an event or an acknowledgement is binary. It\n * looks at the arguments themselves, never inside them: that search is the\n * cost this parser exists to remove. Binary nested in an argument is written\n * the way `JSON.stringify` writes it.\n */\nfunction rejectBinaryArguments(packet: Packet): void {\n if (packet.type !== PacketType.EVENT && packet.type !== PacketType.ACK) {\n return;\n }\n const args: unknown = packet.data;\n if (!Array.isArray(args)) {\n return;\n }\n for (let index = 0; index < args.length; index += 1) {\n const arg: unknown = args[index];\n if (isBinary(arg)) {\n throw new TypeError(binaryMessage(packet, index, arg));\n }\n }\n}\n\n/** The stock `encodeAsString`, or a clear error when a socket.io-parser release drops it. */\nfunction stockEncodeAsString(): StringEncoding[\"encodeAsString\"] {\n const { encodeAsString } = Encoder.prototype as unknown as Partial<StringEncoding>;\n if (typeof encodeAsString !== \"function\") {\n throw new TypeError(\n \"createJsonParser: socket.io-parser's Encoder has no encodeAsString method, \" +\n \"which the JSON parser is built on. Install socket.io-parser 4.2.4 or a later 4.x.\",\n );\n }\n return encodeAsString;\n}\n\n/** The arguments of an event or acknowledgement when every one is pre-encoded. */\nfunction preEncodedArguments(packet: Packet): readonly PreEncoded[] | undefined {\n const data: unknown = packet.data;\n if (\n (packet.type !== PacketType.EVENT && packet.type !== PacketType.ACK) ||\n !Array.isArray(data)\n ) {\n return undefined;\n }\n const args: readonly unknown[] = data;\n return args.length > 0 && args.every(isPreEncoded) ? args : undefined;\n}\n\n/**\n * The text of an event or acknowledgement whose arguments are all\n * pre-encoded: the stock encoding of the packet without its arguments, then\n * the arguments' own text as a JSON array, which is what the stock encoding\n * writes for their values. `undefined` for any other packet, and when the\n * encoder was given a `replacer` (the stock encoding then writes the values).\n */\nfunction splicedText(\n encoder: Encoder,\n packet: Packet,\n encodeAsString: StringEncoding[\"encodeAsString\"],\n): string | undefined {\n const args = preEncodedArguments(packet);\n const { replacer } = encoder as unknown as { readonly replacer?: unknown };\n if (args === undefined || replacer !== undefined) {\n return undefined;\n }\n const head = encodeAsString.call(encoder, { ...packet, data: undefined });\n return `${head}[${args.map((arg) => arg.json()).join(\",\")}]`;\n}\n\nfunction report(onEncoded: EncodedHook, packet: Packet, text: string): void {\n try {\n onEncoded(packet, utf8ByteLength(text));\n } catch {\n // Measuring never stops a packet: a throw here would surface from `emit`.\n }\n}\n\n/**\n * Creates a Socket.IO parser that writes packets as JSON text only: the stock\n * encoder without its search for binary values, and the stock decoder. Pass it\n * as `parser` to the server and to the client; either end may also keep the\n * stock parser, since the two write the same text.\n *\n * An event or acknowledgement whose argument is binary (a `Buffer`, typed\n * array, `ArrayBuffer`, `DataView` or `Blob`) throws a `TypeError` from\n * `emit`. Binary inside an argument is not looked for.\n */\nexport function createJsonParser(options: JsonParserOptions = {}): JsonParser {\n const { onEncoded } = options;\n const encodeAsString = stockEncodeAsString();\n\n class JsonEncoder extends Encoder {\n override encode(packet: Packet): string[] {\n rejectBinaryArguments(packet);\n const text = splicedText(this, packet, encodeAsString) ?? encodeAsString.call(this, packet);\n if (onEncoded !== undefined) {\n report(onEncoded, packet, text);\n }\n return [text];\n }\n }\n\n return Object.freeze({ Encoder: JsonEncoder, Decoder });\n}\n","// A Socket.IO argument whose JSON text is written outside the encoder, so\n// text many packets share is written once (RFC 0003 sections 8.4 and 9): the\n// reply of a shared run goes to every caller in the same text. The JSON\n// parser (`parser.ts`) writes that text as the argument. Anything else that\n// serializes the argument, `JSON.stringify` or the stock parser's encoder,\n// writes its value through `toJSON`. Internal: the server builds these and\n// only its own JSON parser reads them.\n\n/** An argument whose JSON text `json()` writes; see the module comment. */\nexport class PreEncoded {\n /**\n * @param value - What the argument is.\n * @param json - Writes `JSON.stringify(value)`; called once per packet, and\n * may throw what `JSON.stringify` throws.\n */\n constructor(\n readonly value: unknown,\n readonly json: () => string,\n ) {}\n\n toJSON(): unknown {\n return this.value;\n }\n}\n\nexport function isPreEncoded(value: unknown): value is PreEncoded {\n return value instanceof PreEncoded;\n}\n","// The UTF-8 size of a string without encoding it. Node and Bun count natively\n// with `Buffer.byteLength`; browsers and React Native have no `Buffer`, so\n// there the string is counted in a loop. Internal to the JSON parser.\n\ninterface ByteCounter {\n byteLength(text: string, encoding: \"utf8\"): number;\n}\n\nconst nodeBuffer: ByteCounter | undefined = (globalThis as { Buffer?: ByteCounter }).Buffer;\n\nfunction isHighSurrogate(unit: number): boolean {\n return unit >= 0xd800 && unit <= 0xdbff;\n}\n\nfunction isLowSurrogate(unit: number): boolean {\n return unit >= 0xdc00 && unit <= 0xdfff;\n}\n\n/**\n * Counts the bytes UTF-8 encodes `text` into. A surrogate pair is one 4-byte\n * code point; a lone surrogate counts as the 3-byte U+FFFD an encoder writes\n * in its place.\n */\nexport function countUtf8Bytes(text: string): number {\n let bytes = 0;\n for (let index = 0; index < text.length; index += 1) {\n const unit = text.charCodeAt(index);\n if (unit < 0x80) {\n bytes += 1;\n } else if (unit < 0x800) {\n bytes += 2;\n } else if (isHighSurrogate(unit) && isLowSurrogate(text.charCodeAt(index + 1))) {\n bytes += 4;\n index += 1;\n } else {\n bytes += 3;\n }\n }\n return bytes;\n}\n\n/** The size of `text` in UTF-8 bytes, as it goes over the wire. */\nexport function utf8ByteLength(text: string): number {\n return nodeBuffer === undefined ? countUtf8Bytes(text) : nodeBuffer.byteLength(text, \"utf8\");\n}\n"],"mappings":"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","names":[]}