@fitzzero/quickdraw-core 5.1.0 → 5.1.2

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 (107) hide show
  1. package/README.md +533 -180
  2. package/dist/{admin-DKwkIh5i.d.ts → admin-DWWrT1h6.d.ts} +2 -2
  3. package/dist/{adminSchemas-2DUA_hZ8.d.ts → adminSchemas-D4bFKsrb.d.ts} +6 -4
  4. package/dist/{auth-BhWqdyb-.d.ts → auth-DXiSJZaf.d.ts} +42 -16
  5. package/dist/{caller-CXTFswBO.d.ts → caller-BT-Mv4bD.d.ts} +117 -22
  6. package/dist/{chunk-RT6OILOI.js → chunk-4D2DOGAW.js} +2 -14
  7. package/dist/chunk-4D2DOGAW.js.map +1 -0
  8. package/dist/chunk-5CGO426C.js +15 -0
  9. package/dist/chunk-5CGO426C.js.map +1 -0
  10. package/dist/{chunk-A6DJBGOG.js → chunk-6K5BY47X.js} +4594 -3481
  11. package/dist/chunk-6K5BY47X.js.map +1 -0
  12. package/dist/{chunk-VJTFXHOK.js → chunk-753VJIUP.js} +13 -15
  13. package/dist/chunk-753VJIUP.js.map +1 -0
  14. package/dist/{chunk-JZBCLNDU.js → chunk-7QCUX4TK.js} +4 -4
  15. package/dist/chunk-7QCUX4TK.js.map +1 -0
  16. package/dist/chunk-AK23XSWD.js +16 -0
  17. package/dist/chunk-AK23XSWD.js.map +1 -0
  18. package/dist/{chunk-3674EIRT.js → chunk-AONAFOYU.js} +14 -5
  19. package/dist/{chunk-3674EIRT.js.map → chunk-AONAFOYU.js.map} +1 -1
  20. package/dist/chunk-DKE6QO26.js +136 -0
  21. package/dist/chunk-DKE6QO26.js.map +1 -0
  22. package/dist/{chunk-H23KEFBA.js → chunk-DUBU3BB3.js} +30 -1
  23. package/dist/chunk-DUBU3BB3.js.map +1 -0
  24. package/dist/{chunk-CDKG4TFA.js → chunk-GKH3LJ7N.js} +1 -1
  25. package/dist/chunk-GKH3LJ7N.js.map +1 -0
  26. package/dist/{chunk-KNDDCVQJ.js → chunk-GSIPUNJL.js} +49 -9
  27. package/dist/chunk-GSIPUNJL.js.map +1 -0
  28. package/dist/{chunk-DD4SKCNO.js → chunk-LCC7HNFP.js} +12 -1
  29. package/dist/chunk-LCC7HNFP.js.map +1 -0
  30. package/dist/{chunk-2PWMUVC7.js → chunk-MBH6IDSM.js} +480 -12
  31. package/dist/chunk-MBH6IDSM.js.map +1 -0
  32. package/dist/{chunk-2UELNHDD.js → chunk-NMXYENH4.js} +10 -10
  33. package/dist/{chunk-2UELNHDD.js.map → chunk-NMXYENH4.js.map} +1 -1
  34. package/dist/chunk-NO4M6PO5.js +101 -0
  35. package/dist/chunk-NO4M6PO5.js.map +1 -0
  36. package/dist/{chunk-TBMKJ26R.js → chunk-NW6U45HX.js} +3 -1
  37. package/dist/chunk-NW6U45HX.js.map +1 -0
  38. package/dist/{chunk-REX5YPHX.js → chunk-RJ5I2VO3.js} +74 -19
  39. package/dist/chunk-RJ5I2VO3.js.map +1 -0
  40. package/dist/{chunk-QEKVYG5F.js → chunk-RL4VKLBX.js} +1 -1
  41. package/dist/{chunk-QEKVYG5F.js.map → chunk-RL4VKLBX.js.map} +1 -1
  42. package/dist/chunk-XH2IZVLT.js +62 -0
  43. package/dist/chunk-XH2IZVLT.js.map +1 -0
  44. package/dist/{chunk-4DIQHASK.js → chunk-Z5ILULF2.js} +26 -7
  45. package/dist/chunk-Z5ILULF2.js.map +1 -0
  46. package/dist/cli/quickdraw-docs.js +5 -3
  47. package/dist/cli/quickdraw-docs.js.map +1 -1
  48. package/dist/client/index.d.ts +30 -18
  49. package/dist/client/index.js +8 -6
  50. package/dist/client/index.js.map +1 -1
  51. package/dist/{clientTypes-Bc-G8n-U.d.ts → clientTypes-hWcPUVS9.d.ts} +3 -3
  52. package/dist/{connection-Cr1BU07W.d.ts → connection-CgijdGpT.d.ts} +8 -4
  53. package/dist/{createServer-CP9eDHZZ.d.ts → createServer-Cp-Wk500.d.ts} +36 -14
  54. package/dist/{createTestApp-UKVGEmOf.d.ts → createTestApp-BB_PkqQ7.d.ts} +16 -10
  55. package/dist/{envelope-Cp3YcVxC.d.ts → envelope-DzWyIXjj.d.ts} +18 -4
  56. package/dist/{http-Djj2Inuu.d.ts → http-DotQ5bj8.d.ts} +68 -17
  57. package/dist/index.d.ts +7 -7
  58. package/dist/index.js +37 -13
  59. package/dist/index.js.map +1 -1
  60. package/dist/{infer-Teo1xpsd.d.ts → infer-BmoZsL9a.d.ts} +43 -10
  61. package/dist/logger-Yz93KzdV.d.ts +30 -0
  62. package/dist/{metrics-BdBu5o8G.d.ts → metrics-DW80Exve.d.ts} +2 -2
  63. package/dist/origins-kvpxnVcW.d.ts +8 -0
  64. package/dist/prisma/index.d.ts +5 -5
  65. package/dist/prisma/index.js +130 -67
  66. package/dist/prisma/index.js.map +1 -1
  67. package/dist/{rateLimit-ZMIZJUZF.js → rateLimit-5RKBFLUR.js} +2 -2
  68. package/dist/server/auth/index.d.ts +82 -33
  69. package/dist/server/auth/index.js +273 -268
  70. package/dist/server/auth/index.js.map +1 -1
  71. package/dist/server/express/index.d.ts +9 -2
  72. package/dist/server/express/index.js +1 -1
  73. package/dist/server/index.d.ts +147 -67
  74. package/dist/server/index.js +520 -381
  75. package/dist/server/index.js.map +1 -1
  76. package/dist/server/mcp/index.d.ts +63 -17
  77. package/dist/server/mcp/index.js +256 -54
  78. package/dist/server/mcp/index.js.map +1 -1
  79. package/dist/server/otel.d.ts +3 -3
  80. package/dist/{sharing-DVa8W-yR.d.ts → sharing-UDLn1049.d.ts} +3 -3
  81. package/dist/testing/client.d.ts +14 -14
  82. package/dist/testing/client.js +9 -7
  83. package/dist/testing/client.js.map +1 -1
  84. package/dist/testing/index.d.ts +12 -12
  85. package/dist/testing/index.js +21 -12
  86. package/dist/testing/index.js.map +1 -1
  87. package/dist/testing/mock.d.ts +5 -5
  88. package/dist/testing/mock.js +8 -6
  89. package/dist/{types-Cu0vmx7c.d.ts → types-CYliTP65.d.ts} +1 -1
  90. package/dist/utils/index.d.ts +1 -1
  91. package/package.json +1 -1
  92. package/dist/chunk-2PWMUVC7.js.map +0 -1
  93. package/dist/chunk-4DIQHASK.js.map +0 -1
  94. package/dist/chunk-A6DJBGOG.js.map +0 -1
  95. package/dist/chunk-CDKG4TFA.js.map +0 -1
  96. package/dist/chunk-DD4SKCNO.js.map +0 -1
  97. package/dist/chunk-H23KEFBA.js.map +0 -1
  98. package/dist/chunk-JZBCLNDU.js.map +0 -1
  99. package/dist/chunk-KNDDCVQJ.js.map +0 -1
  100. package/dist/chunk-REX5YPHX.js.map +0 -1
  101. package/dist/chunk-RT6OILOI.js.map +0 -1
  102. package/dist/chunk-TBMKJ26R.js.map +0 -1
  103. package/dist/chunk-VJTFXHOK.js.map +0 -1
  104. package/dist/chunk-YSWH776Y.js +0 -13
  105. package/dist/chunk-YSWH776Y.js.map +0 -1
  106. package/dist/logger-BY28acW-.d.ts +0 -16
  107. /package/dist/{rateLimit-ZMIZJUZF.js.map → rateLimit-5RKBFLUR.js.map} +0 -0
@@ -1,3 +1,7 @@
1
+ import {
2
+ isPrismaError,
3
+ prismaSummary
4
+ } from "./chunk-DKE6QO26.js";
1
5
  import {
2
6
  QuickdrawError
3
7
  } from "./chunk-4YO5H5ON.js";
@@ -7,6 +11,13 @@ function describeError(error, depth = 0) {
7
11
  if (!(error instanceof Error)) {
8
12
  return { value: String(error) };
9
13
  }
14
+ if (isPrismaError(error)) {
15
+ const summary = prismaSummary(error);
16
+ if (error.cause !== void 0 && depth < 3) {
17
+ summary.cause = describeError(error.cause, depth + 1);
18
+ }
19
+ return summary;
20
+ }
10
21
  const described = {
11
22
  name: error.name,
12
23
  message: error.message,
@@ -83,4 +94,4 @@ export {
83
94
  failureLevel,
84
95
  createRecorder
85
96
  };
86
- //# sourceMappingURL=chunk-DD4SKCNO.js.map
97
+ //# sourceMappingURL=chunk-LCC7HNFP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/pipeline/metrics.ts"],"sourcesContent":["// The completion record (RFC 0003 section 9, step 9) and call logging. Every\n// call produces exactly one record, handed to `onCall`, and one log entry:\n// `debug` normally, `warn` when slower than `slowMs` or larger than\n// `maxResponseBytes`, `error` when the call failed with `INTERNAL` or\n// `TIMEOUT` (the failures that are the server's fault). An `INTERNAL`\n// entry carries the original error with its stack. This replaces 4.1's two\n// `info` entries per call (4.1 `src/server/ServiceRegistry.ts:331-373`).\n\nimport { isPrismaError, prismaSummary, type Logger } from \"../../contract/logger\";\nimport type { MethodKind } from \"../../contract/methods\";\nimport { QuickdrawError, type ErrorCode } from \"../../protocol/errors\";\nimport type { DevWarnings } from \"../devWarnings\";\nimport type { Transport } from \"../types\";\n\n/** How a call ended: `\"ok\"`, `\"not-modified\"`, or the error code it failed with. */\nexport type CallOutcome = \"ok\" | \"not-modified\" | ErrorCode;\n\n/** One call's completion record, handed to the dispatcher's `onCall`. */\nexport interface CallRecord {\n readonly service: string;\n readonly method: string;\n /** `undefined` when the method does not exist. */\n readonly kind: MethodKind | undefined;\n readonly transport: Transport;\n readonly requestId: string;\n readonly outcome: CallOutcome;\n /** From the call's arrival until its result was ready, in milliseconds. */\n readonly durationMs: number;\n /** Time spent waiting for a query slot, in milliseconds. */\n readonly queueMs: number;\n /** The reply's size as the transport measured it; 0 when it measured nothing. */\n readonly bytes: number;\n /** True when the result came from another call's run of the same query, or its `ttlMs` cache. */\n readonly shared: boolean;\n /**\n * Database statements the handler run issued, when the unit of work counts\n * them: the handler's own reads and writes, the reads a kit handler makes\n * to filter by access included. The access check before the handler (its\n * reads run before the unit opens), the flush after it and the per-caller\n * strip of field tiers are not counted. A shared run is counted once, on\n * the call that started it; a call that joined it reports `undefined`.\n */\n readonly sqlStatements: number | undefined;\n}\n\n/** Options of {@link createRecorder}. */\nexport interface RecorderOptions {\n readonly logger: Logger;\n readonly onCall: ((record: CallRecord) => void) | undefined;\n readonly slowMs: number;\n readonly maxResponseBytes: number;\n /** Raises `oversized-response` for a reply over `maxResponseBytes`. */\n readonly warnings?: DevWarnings;\n}\n\n/** What a record is logged with besides the record itself. */\nexport interface RecordDetails {\n /** The error the call failed with. */\n readonly error?: QuickdrawError;\n /** The caller's user id, for the log only. */\n readonly userId?: string;\n}\n\n/**\n * The parts of an error worth logging, including its stack and cause. A\n * Prisma error is summarized: its message renders the call, values\n * included.\n */\nexport function describeError(error: unknown, depth = 0): Record<string, unknown> {\n if (!(error instanceof Error)) {\n return { value: String(error) };\n }\n if (isPrismaError(error)) {\n const summary = prismaSummary(error);\n if (error.cause !== undefined && depth < 3) {\n summary.cause = describeError(error.cause, depth + 1);\n }\n return summary;\n }\n const described: Record<string, unknown> = {\n name: error.name,\n message: error.message,\n stack: error.stack,\n };\n if (\"code\" in error) {\n described.code = error.code;\n }\n if (\"data\" in error && error.data !== undefined) {\n described.data = error.data;\n }\n if (error.cause !== undefined && depth < 3) {\n described.cause = describeError(error.cause, depth + 1);\n }\n return described;\n}\n\ntype Level = \"debug\" | \"warn\" | \"error\";\n\n/** True for the codes of server faults, which log at error; every other code the client caused. */\nfunction isFault(code: unknown): boolean {\n return code === \"INTERNAL\" || code === \"TIMEOUT\";\n}\n\n/**\n * The level a failure outside a call logs at, as a call's failure does:\n * `debug` for a `QuickdrawError` the client caused (any code but `INTERNAL`\n * and `TIMEOUT`), else `error`. A channel handler refusing a payload must\n * not fill the error log.\n */\nexport function failureLevel(error: unknown): \"debug\" | \"error\" {\n return error instanceof QuickdrawError && !isFault(error.code) ? \"debug\" : \"error\";\n}\n\nfunction levelOf(record: CallRecord, options: RecorderOptions): Level {\n if (isFault(record.outcome)) {\n return \"error\";\n }\n const slow = record.durationMs > options.slowMs;\n return slow || record.bytes > options.maxResponseBytes ? \"warn\" : \"debug\";\n}\n\n/** Hands `record` to `onCall`; what it throws is logged, never thrown. */\nfunction emit(options: RecorderOptions, record: CallRecord): void {\n if (options.onCall === undefined) {\n return;\n }\n try {\n options.onCall(record);\n } catch (error) {\n options.logger.error(\"onCall threw; the call was not affected\", {\n category: \"quickdraw.call\",\n error: describeError(error),\n });\n }\n}\n\n/**\n * The `oversized-response` development warning, for a method whose reply\n * was larger than `maxResponseBytes`. It is raised after the record went to\n * `onCall`, because a strict test app throws it.\n */\nfunction warnOversized(options: RecorderOptions, record: CallRecord): void {\n if (record.kind === undefined || record.bytes <= options.maxResponseBytes) {\n return;\n }\n options.warnings?.warn({\n kind: \"oversized-response\",\n service: record.service,\n method: record.method,\n message:\n `replied with ${record.bytes} bytes, more than maxResponseBytes (${options.maxResponseBytes}); ` +\n \"page the result (take and a cursor), return a leaner projection, or serve it as a collection\",\n meta: { bytes: record.bytes, maxResponseBytes: options.maxResponseBytes },\n });\n}\n\n/**\n * Returns the function that logs a call's record and hands it to `onCall`.\n * In a strict test app it throws an oversized reply's `DevWarningError`\n * once it has done both.\n */\nexport function createRecorder(\n options: RecorderOptions,\n): (record: CallRecord, details: RecordDetails) => void {\n return (record, details) => {\n const meta: Record<string, unknown> = { category: \"quickdraw.call\", ...record };\n if (details.userId !== undefined) {\n meta.userId = details.userId;\n }\n if (details.error !== undefined) {\n meta.error = describeError(details.error);\n }\n const duration = Math.round(record.durationMs);\n const message = `${record.service}.${record.method} ${record.outcome} in ${duration} ms`;\n options.logger[levelOf(record, options)](message, meta);\n emit(options, record);\n warnOversized(options, record);\n };\n}\n"],"mappings":";;;;;;;;;AAoEO,SAAS,cAAc,OAAgB,QAAQ,GAA4B;AAChF,MAAI,EAAE,iBAAiB,QAAQ;AAC7B,WAAO,EAAE,OAAO,OAAO,KAAK,EAAE;AAAA,EAChC;AACA,MAAI,cAAc,KAAK,GAAG;AACxB,UAAM,UAAU,cAAc,KAAK;AACnC,QAAI,MAAM,UAAU,UAAa,QAAQ,GAAG;AAC1C,cAAQ,QAAQ,cAAc,MAAM,OAAO,QAAQ,CAAC;AAAA,IACtD;AACA,WAAO;AAAA,EACT;AACA,QAAM,YAAqC;AAAA,IACzC,MAAM,MAAM;AAAA,IACZ,SAAS,MAAM;AAAA,IACf,OAAO,MAAM;AAAA,EACf;AACA,MAAI,UAAU,OAAO;AACnB,cAAU,OAAO,MAAM;AAAA,EACzB;AACA,MAAI,UAAU,SAAS,MAAM,SAAS,QAAW;AAC/C,cAAU,OAAO,MAAM;AAAA,EACzB;AACA,MAAI,MAAM,UAAU,UAAa,QAAQ,GAAG;AAC1C,cAAU,QAAQ,cAAc,MAAM,OAAO,QAAQ,CAAC;AAAA,EACxD;AACA,SAAO;AACT;AAKA,SAAS,QAAQ,MAAwB;AACvC,SAAO,SAAS,cAAc,SAAS;AACzC;AAQO,SAAS,aAAa,OAAmC;AAC9D,SAAO,iBAAiB,kBAAkB,CAAC,QAAQ,MAAM,IAAI,IAAI,UAAU;AAC7E;AAEA,SAAS,QAAQ,QAAoB,SAAiC;AACpE,MAAI,QAAQ,OAAO,OAAO,GAAG;AAC3B,WAAO;AAAA,EACT;AACA,QAAM,OAAO,OAAO,aAAa,QAAQ;AACzC,SAAO,QAAQ,OAAO,QAAQ,QAAQ,mBAAmB,SAAS;AACpE;AAGA,SAAS,KAAK,SAA0B,QAA0B;AAChE,MAAI,QAAQ,WAAW,QAAW;AAChC;AAAA,EACF;AACA,MAAI;AACF,YAAQ,OAAO,MAAM;AAAA,EACvB,SAAS,OAAO;AACd,YAAQ,OAAO,MAAM,2CAA2C;AAAA,MAC9D,UAAU;AAAA,MACV,OAAO,cAAc,KAAK;AAAA,IAC5B,CAAC;AAAA,EACH;AACF;AAOA,SAAS,cAAc,SAA0B,QAA0B;AACzE,MAAI,OAAO,SAAS,UAAa,OAAO,SAAS,QAAQ,kBAAkB;AACzE;AAAA,EACF;AACA,UAAQ,UAAU,KAAK;AAAA,IACrB,MAAM;AAAA,IACN,SAAS,OAAO;AAAA,IAChB,QAAQ,OAAO;AAAA,IACf,SACE,gBAAgB,OAAO,KAAK,uCAAuC,QAAQ,gBAAgB;AAAA,IAE7F,MAAM,EAAE,OAAO,OAAO,OAAO,kBAAkB,QAAQ,iBAAiB;AAAA,EAC1E,CAAC;AACH;AAOO,SAAS,eACd,SACsD;AACtD,SAAO,CAAC,QAAQ,YAAY;AAC1B,UAAM,OAAgC,EAAE,UAAU,kBAAkB,GAAG,OAAO;AAC9E,QAAI,QAAQ,WAAW,QAAW;AAChC,WAAK,SAAS,QAAQ;AAAA,IACxB;AACA,QAAI,QAAQ,UAAU,QAAW;AAC/B,WAAK,QAAQ,cAAc,QAAQ,KAAK;AAAA,IAC1C;AACA,UAAM,WAAW,KAAK,MAAM,OAAO,UAAU;AAC7C,UAAM,UAAU,GAAG,OAAO,OAAO,IAAI,OAAO,MAAM,IAAI,OAAO,OAAO,OAAO,QAAQ;AACnF,YAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC,EAAE,SAAS,IAAI;AACtD,SAAK,SAAS,MAAM;AACpB,kBAAc,SAAS,MAAM;AAAA,EAC/B;AACF;","names":[]}
@@ -1,3 +1,7 @@
1
+ import {
2
+ ACCESS_LEVELS,
3
+ isAccessLevel
4
+ } from "./chunk-AK23XSWD.js";
1
5
  import {
2
6
  hasJsonSchema,
3
7
  isStandardSchema,
@@ -9,6 +13,388 @@ import {
9
13
  isErrorCode
10
14
  } from "./chunk-4YO5H5ON.js";
11
15
 
16
+ // src/server/access/levels.ts
17
+ var RANK = Object.freeze({
18
+ Public: 0,
19
+ Read: 1,
20
+ Moderate: 2,
21
+ Admin: 3
22
+ });
23
+ function meetsLevel(level, required) {
24
+ if (level === null || level === void 0) {
25
+ return false;
26
+ }
27
+ return (RANK[level] ?? -1) >= RANK[required];
28
+ }
29
+ function maxLevel(a, b) {
30
+ const left = isAccessLevel(a) ? a : null;
31
+ const right = isAccessLevel(b) ? b : null;
32
+ if (left === null || right === null) {
33
+ return left ?? right;
34
+ }
35
+ return RANK[right] > RANK[left] ? right : left;
36
+ }
37
+ function levelsAtLeast(required) {
38
+ return ACCESS_LEVELS.filter((level) => RANK[level] >= RANK[required]);
39
+ }
40
+ function serviceGrant(principal, service) {
41
+ const grants = principal.serviceAccess;
42
+ if (grants === null || grants === void 0 || !Object.hasOwn(grants, service)) {
43
+ return void 0;
44
+ }
45
+ return grants[service];
46
+ }
47
+
48
+ // src/server/emit/tiers.ts
49
+ var SUBSCRIBE_LEVEL = "Read";
50
+ var SUBSCRIBER_LEVELS = Object.freeze([
51
+ "Read",
52
+ "Moderate",
53
+ "Admin"
54
+ ]);
55
+ var NOTHING = Object.freeze(/* @__PURE__ */ new Set());
56
+ function hiddenAt(fields, keys, level) {
57
+ const hidden = keys.filter((key) => {
58
+ const required = Object.hasOwn(fields, key) ? fields[key] : void 0;
59
+ return required !== void 0 && !meetsLevel(level, required);
60
+ });
61
+ return hidden.length === 0 ? NOTHING : Object.freeze(new Set(hidden));
62
+ }
63
+ function tiersOf(fields, keys) {
64
+ const byLevel = /* @__PURE__ */ new Map();
65
+ const labels = /* @__PURE__ */ new Map();
66
+ const hiding = (level) => {
67
+ let found = byLevel.get(level);
68
+ if (found === void 0) {
69
+ const set = hiddenAt(fields, keys, level);
70
+ const signature = [...set].join("\0");
71
+ const label = labels.get(signature) ?? String.fromCodePoint(97 + labels.size);
72
+ labels.set(signature, label);
73
+ found = { hidden: set, label };
74
+ byLevel.set(level, found);
75
+ }
76
+ return found;
77
+ };
78
+ const hidden = (level) => hiding(level).hidden;
79
+ const groups = /* @__PURE__ */ new Map();
80
+ for (const level of SUBSCRIBER_LEVELS) {
81
+ const set = hidden(level);
82
+ const signature = [...set].join("\0");
83
+ const group = groups.get(signature);
84
+ if (group === void 0) {
85
+ groups.set(signature, { levels: [level], hidden: set });
86
+ } else {
87
+ group.levels.push(level);
88
+ }
89
+ }
90
+ return Object.freeze({
91
+ tiered: keys.some((key) => Object.hasOwn(fields, key)),
92
+ hidden,
93
+ label: (level) => hiding(level).label,
94
+ groups: Object.freeze([...groups.values()].map((group) => Object.freeze(group)))
95
+ });
96
+ }
97
+ function strip(row, hidden) {
98
+ if (hidden.size === 0) {
99
+ return row;
100
+ }
101
+ const kept = {};
102
+ for (const [key, value] of Object.entries(row)) {
103
+ if (!hidden.has(key)) {
104
+ kept[key] = value;
105
+ }
106
+ }
107
+ return kept;
108
+ }
109
+
110
+ // src/server/emit/projection.ts
111
+ function isRecord(value) {
112
+ return typeof value === "object" && value !== null && !Array.isArray(value);
113
+ }
114
+ function isPlain(value) {
115
+ if (typeof value !== "object" || value === null) {
116
+ return false;
117
+ }
118
+ const prototype = Object.getPrototypeOf(value);
119
+ return Array.isArray(value) || prototype === Object.prototype || prototype === null;
120
+ }
121
+ function referenced(node, root) {
122
+ const ref = node.$ref;
123
+ if (typeof ref !== "string") {
124
+ return node;
125
+ }
126
+ if (ref === "#") {
127
+ return root;
128
+ }
129
+ const match = /^#\/(definitions|\$defs)\/(.+)$/.exec(ref);
130
+ const defs = match === null ? void 0 : root[match[1] ?? ""];
131
+ const name = (match?.[2] ?? "").replaceAll("~1", "/").replaceAll("~0", "~");
132
+ const target = isRecord(defs) ? defs[name] : void 0;
133
+ return isRecord(target) ? target : void 0;
134
+ }
135
+ function pointerToken(name) {
136
+ return name.replaceAll("~", "~0").replaceAll("/", "~1");
137
+ }
138
+ function schemaKeys(schema) {
139
+ if (!hasJsonSchema(schema)) {
140
+ return void 0;
141
+ }
142
+ let json;
143
+ try {
144
+ json = schema["~standard"].jsonSchema.output({ target: "draft-07" });
145
+ } catch {
146
+ return void 0;
147
+ }
148
+ const described = isRecord(json) ? referenced(json, json) : void 0;
149
+ const properties = described?.properties;
150
+ return isRecord(properties) ? Object.keys(properties) : void 0;
151
+ }
152
+ var BRANCHES = ["anyOf", "oneOf", "allOf"];
153
+ var UNREPRESENTABLE_AS_ANY = Object.freeze({ unrepresentable: "any" });
154
+ function addBranchKeys(node, root, keys, seen) {
155
+ const target = isRecord(node) ? referenced(node, root) : void 0;
156
+ if (target === void 0 || seen.has(target)) {
157
+ return;
158
+ }
159
+ seen.add(target);
160
+ if (isRecord(target.properties)) {
161
+ for (const key of Object.keys(target.properties)) {
162
+ keys.add(key);
163
+ }
164
+ }
165
+ for (const keyword of BRANCHES) {
166
+ const members = target[keyword];
167
+ if (Array.isArray(members)) {
168
+ for (const member of members) {
169
+ addBranchKeys(member, root, keys, seen);
170
+ }
171
+ }
172
+ }
173
+ }
174
+ function inputKeys(schema) {
175
+ if (!hasJsonSchema(schema)) {
176
+ return void 0;
177
+ }
178
+ let json;
179
+ try {
180
+ json = schema["~standard"].jsonSchema.input({
181
+ target: "draft-07",
182
+ libraryOptions: UNREPRESENTABLE_AS_ANY
183
+ });
184
+ } catch {
185
+ return void 0;
186
+ }
187
+ if (!isRecord(json)) {
188
+ return void 0;
189
+ }
190
+ const keys = /* @__PURE__ */ new Set();
191
+ addBranchKeys(json, json, keys, /* @__PURE__ */ new Set());
192
+ return [...keys];
193
+ }
194
+ var OPTION_KEYS = /* @__PURE__ */ new Set(["keys", "select", "map"]);
195
+ function checkKeyList(owner, keys, fail) {
196
+ const valid = Array.isArray(keys) && keys.every((key) => typeof key === "string" && key.length > 0) && new Set(keys).size === keys.length;
197
+ if (!valid) {
198
+ fail(`${owner}: keys must be a list of distinct field names`);
199
+ }
200
+ if (!keys.includes("id")) {
201
+ fail(`${owner}: keys must include "id"; every row is keyed by it`);
202
+ }
203
+ return keys;
204
+ }
205
+ function checkOption(owner, option, fail) {
206
+ if (option === void 0) {
207
+ return {};
208
+ }
209
+ if (!isRecord(option)) {
210
+ fail(`${owner} must be { keys?, select?, map? }`);
211
+ }
212
+ const unknownKey = Object.keys(option).find((key) => !OPTION_KEYS.has(key));
213
+ if (unknownKey !== void 0) {
214
+ fail(`${owner} has an unknown option "${unknownKey}"; the options are keys, select and map`);
215
+ }
216
+ if (option.select !== void 0 && !isRecord(option.select)) {
217
+ fail(`${owner}: select must be a select object, as in { id: true, labels: { select: ... } }`);
218
+ }
219
+ if (option.map !== void 0 && typeof option.map !== "function") {
220
+ fail(`${owner}: map must be a function of the row its select reads`);
221
+ }
222
+ return option;
223
+ }
224
+ function keysFor(name, schema, option, fail) {
225
+ const owner = `projection "${name}"`;
226
+ if (option.keys !== void 0) {
227
+ return checkKeyList(owner, option.keys, fail);
228
+ }
229
+ const keys = schemaKeys(schema);
230
+ if (keys === void 0) {
231
+ fail(
232
+ `${owner}: its schema cannot list its keys (it has no Standard JSON Schema the framework can read); use Zod 4.2 or later for it, or declare them with project: { ${name}: { keys: [...] } }`
233
+ );
234
+ }
235
+ return keys;
236
+ }
237
+ function compileOne(name, schema, option, fields, fail) {
238
+ const checked = checkOption(`projection "${name}"`, option, fail);
239
+ const keys = keysFor(name, schema, checked, fail);
240
+ const ordered = ["id", ...keys.filter((key) => key !== "id")];
241
+ const select = isRecord(checked.select) ? { id: true, ...checked.select } : Object.fromEntries(ordered.map((key) => [key, true]));
242
+ return Object.freeze({
243
+ name,
244
+ keys: Object.freeze(ordered),
245
+ select: Object.freeze(select),
246
+ map: checked.map,
247
+ tiers: tiersOf(fields, ordered)
248
+ });
249
+ }
250
+ function compileProjections(contract, option, fail) {
251
+ if (option !== void 0 && !isRecord(option)) {
252
+ fail("project must map projection names to { keys?, select?, map? }");
253
+ }
254
+ const options = option ?? {};
255
+ const schemas = new Map(Object.entries(contract.projections));
256
+ if (contract.entity !== void 0) {
257
+ schemas.set("entity", contract.entity);
258
+ }
259
+ const unknownName = Object.keys(options).find((name) => !schemas.has(name));
260
+ if (unknownName !== void 0) {
261
+ fail(
262
+ `project names "${unknownName}", which is not a projection of the contract; the projections are ${[...schemas.keys()].map((name) => `"${name}"`).join(", ") || "none"}`
263
+ );
264
+ }
265
+ const compiled = /* @__PURE__ */ new Map();
266
+ for (const [name, schema] of schemas) {
267
+ compiled.set(name, compileOne(name, schema, options[name], contract.fields, fail));
268
+ }
269
+ return compiled;
270
+ }
271
+ function projectedOutput(output, projections) {
272
+ if (typeof output === "string") {
273
+ const projection2 = projections.get(output);
274
+ return projection2 === void 0 ? void 0 : { projection: projection2, kind: "one" };
275
+ }
276
+ if (!("kind" in output)) {
277
+ return void 0;
278
+ }
279
+ const projection = projections.get(output.projection);
280
+ const kind = output.kind === "nullable" ? "nullable" : "list";
281
+ return projection === void 0 ? void 0 : { projection, kind };
282
+ }
283
+ function isoDates(value) {
284
+ if (value instanceof Date) {
285
+ return value.toISOString();
286
+ }
287
+ if (!isPlain(value)) {
288
+ return value;
289
+ }
290
+ if (Array.isArray(value)) {
291
+ return value.map(isoDates);
292
+ }
293
+ const copy = {};
294
+ for (const [key, item] of Object.entries(value)) {
295
+ copy[key] = isoDates(item);
296
+ }
297
+ return copy;
298
+ }
299
+ function pickKeys(row, keys) {
300
+ const picked = {};
301
+ for (const key of keys) {
302
+ if (Object.hasOwn(row, key)) {
303
+ picked[key] = isoDates(row[key]);
304
+ }
305
+ }
306
+ return picked;
307
+ }
308
+ function projectRow(projection, row) {
309
+ if (!isRecord(row)) {
310
+ return row;
311
+ }
312
+ const source = projection.map === void 0 ? row : projection.map(row);
313
+ return isRecord(source) ? pickKeys(source, projection.keys) : source;
314
+ }
315
+ function projectOutput(output, value) {
316
+ if (output.kind === "list") {
317
+ return Array.isArray(value) ? value.map((row) => projectRow(output.projection, row)) : value;
318
+ }
319
+ if (value === null && output.kind === "nullable") {
320
+ return null;
321
+ }
322
+ return projectRow(output.projection, value);
323
+ }
324
+ function rowIds(value) {
325
+ const rows = Array.isArray(value) ? value : [value];
326
+ const ids = [];
327
+ for (const row of rows) {
328
+ if (isRecord(row) && typeof row.id === "string" && row.id.length > 0) {
329
+ ids.push(row.id);
330
+ }
331
+ }
332
+ return ids;
333
+ }
334
+ function wholeView(value) {
335
+ return { key: "", strip: () => value };
336
+ }
337
+ async function readerView(output, value, levelsOf) {
338
+ const { tiers } = output.projection;
339
+ if (!tiers.tiered || value === null || value === void 0) {
340
+ return wholeView(value);
341
+ }
342
+ const ids = [...new Set(rowIds(value))];
343
+ const levels = ids.length === 0 ? /* @__PURE__ */ new Map() : await levelsOf(ids);
344
+ const levelOf = (row) => typeof row.id === "string" ? levels.get(row.id) ?? null : null;
345
+ const rows = Array.isArray(value) ? value : [value];
346
+ const key = rows.map((row) => isRecord(row) ? tiers.label(levelOf(row)) : "-").join("");
347
+ const one = (row) => isRecord(row) ? strip(row, tiers.hidden(levelOf(row))) : row;
348
+ return { key, strip: () => Array.isArray(value) ? value.map(one) : one(value) };
349
+ }
350
+
351
+ // src/server/service.ts
352
+ var runtimes = /* @__PURE__ */ new WeakMap();
353
+ function registerRuntime(service, runtime) {
354
+ runtimes.set(service, runtime);
355
+ }
356
+ function runtimeOf(service) {
357
+ return typeof service === "object" && service !== null ? runtimes.get(service) : void 0;
358
+ }
359
+ var handlerChecks = /* @__PURE__ */ new WeakMap();
360
+ function checkWhenDefined(handler, check) {
361
+ handlerChecks.set(handler, check);
362
+ }
363
+ var grantsEditors = /* @__PURE__ */ new WeakSet();
364
+ function markGrantsEditor(handler) {
365
+ grantsEditors.add(handler);
366
+ }
367
+ function editsGrants(handler) {
368
+ return grantsEditors.has(handler);
369
+ }
370
+ function handlerProblem(handler, service) {
371
+ return handlerChecks.get(handler)?.(service);
372
+ }
373
+ var servedChecks = /* @__PURE__ */ new WeakMap();
374
+ function checkWhenServed(handler, check) {
375
+ servedChecks.set(handler, check);
376
+ }
377
+ function servedProblem(handler, served) {
378
+ return servedChecks.get(handler)?.(served);
379
+ }
380
+ function isKitHandler(handler) {
381
+ return handlerChecks.has(handler);
382
+ }
383
+ var rowChecked = /* @__PURE__ */ new WeakSet();
384
+ function checksRowsItself(handler) {
385
+ rowChecked.add(handler);
386
+ }
387
+ function isRowChecked(handler) {
388
+ return rowChecked.has(handler);
389
+ }
390
+ var callerDependent = /* @__PURE__ */ new WeakSet();
391
+ function dependsOnCaller(handler) {
392
+ callerDependent.add(handler);
393
+ }
394
+ function isCallerDependent(handler) {
395
+ return callerDependent.has(handler);
396
+ }
397
+
12
398
  // src/server/caller.ts
13
399
  var CALLER_GRANTS = /* @__PURE__ */ new WeakMap();
14
400
  function setCallerGrants(dispatcher, grants) {
@@ -107,6 +493,44 @@ function createCaller(resolve, principal, settings = {}) {
107
493
  );
108
494
  }
109
495
 
496
+ // src/server/access/kinds.ts
497
+ function isKindList(value) {
498
+ return Array.isArray(value) && value.length > 0 && value.every((kind) => typeof kind === "string" && kind.length > 0);
499
+ }
500
+ function narrowKinds(value, above, fail) {
501
+ if (value === void 0) {
502
+ return above?.kinds;
503
+ }
504
+ if (!isKindList(value)) {
505
+ fail(
506
+ 'kinds must be a non-empty list of principal kinds (strings), such as ["user"]; leave it out to admit every kind'
507
+ );
508
+ }
509
+ if (above?.kinds !== void 0) {
510
+ const { kinds, by } = above;
511
+ const wider = value.find((kind) => !kinds.includes(kind));
512
+ if (wider !== void 0) {
513
+ fail(
514
+ `kinds may only narrow the kinds ${by} admits (${kinds.join(", ")}), and "${wider}" is not one of them`
515
+ );
516
+ }
517
+ }
518
+ return Object.freeze([...new Set(value)]);
519
+ }
520
+ function checkKind(kinds, principal, what) {
521
+ if (kinds === void 0 || principal === null) {
522
+ return;
523
+ }
524
+ const { kind } = principal;
525
+ if (typeof kind === "string" && kinds.includes(kind)) {
526
+ return;
527
+ }
528
+ throw new QuickdrawError(
529
+ "FORBIDDEN",
530
+ kind === void 0 ? `${what} is not open to a principal without a kind` : `${what} is not open to principals of kind "${String(kind)}"`
531
+ );
532
+ }
533
+
110
534
  // src/server/pipeline/errors.ts
111
535
  function cancelledError() {
112
536
  return new QuickdrawError("CANCELLED", "The call was cancelled");
@@ -147,7 +571,7 @@ function toQuickdrawError(error) {
147
571
 
148
572
  // src/server/pipeline/schemaOutput.ts
149
573
  var ANY = Object.freeze({ any: true, object: void 0, array: void 0 });
150
- function isRecord(value) {
574
+ function isRecord2(value) {
151
575
  return typeof value === "object" && value !== null && !Array.isArray(value);
152
576
  }
153
577
  function isPlainObject(value) {
@@ -169,11 +593,11 @@ function follow(ref, root) {
169
593
  } catch {
170
594
  return void 0;
171
595
  }
172
- node = isRecord(node) && Object.hasOwn(node, part) ? node[part] : void 0;
596
+ node = isRecord2(node) && Object.hasOwn(node, part) ? node[part] : void 0;
173
597
  }
174
598
  return node;
175
599
  }
176
- var BRANCHES = ["anyOf", "oneOf", "allOf"];
600
+ var BRANCHES2 = ["anyOf", "oneOf", "allOf"];
177
601
  function hasAny(node, keywords) {
178
602
  return keywords.some((keyword) => node[keyword] !== void 0);
179
603
  }
@@ -222,7 +646,7 @@ var Compiler = class {
222
646
  if (value === void 0 || value === true) {
223
647
  return this.make(true);
224
648
  }
225
- if (!isRecord(value)) {
649
+ if (!isRecord2(value)) {
226
650
  return this.make(false);
227
651
  }
228
652
  const known = this.nodes.get(value);
@@ -242,7 +666,7 @@ var Compiler = class {
242
666
  const target = follow($ref, this.root);
243
667
  node.members.push(target === void 0 ? this.make(true) : this.read(target));
244
668
  }
245
- for (const keyword of BRANCHES) {
669
+ for (const keyword of BRANCHES2) {
246
670
  const members = json[keyword];
247
671
  if (!Array.isArray(members)) {
248
672
  continue;
@@ -280,14 +704,14 @@ var Compiler = class {
280
704
  }
281
705
  readObject(json) {
282
706
  const properties = /* @__PURE__ */ new Map();
283
- if (isRecord(json.properties)) {
707
+ if (isRecord2(json.properties)) {
284
708
  for (const [key, schema] of Object.entries(json.properties)) {
285
709
  properties.set(key, this.read(schema));
286
710
  }
287
711
  }
288
712
  const patterns = [];
289
713
  let open = false;
290
- if (isRecord(json.patternProperties)) {
714
+ if (isRecord2(json.patternProperties)) {
291
715
  for (const [source, schema] of Object.entries(json.patternProperties)) {
292
716
  const regex = patternOf(source);
293
717
  if (regex === void 0) {
@@ -491,7 +915,7 @@ function keyPaths(root) {
491
915
  }
492
916
  return paths;
493
917
  }
494
- var UNREPRESENTABLE_AS_ANY = Object.freeze({ unrepresentable: "any" });
918
+ var UNREPRESENTABLE_AS_ANY2 = Object.freeze({ unrepresentable: "any" });
495
919
  function compileSchemaOutput(schema) {
496
920
  if (!hasJsonSchema(schema)) {
497
921
  return void 0;
@@ -500,12 +924,12 @@ function compileSchemaOutput(schema) {
500
924
  try {
501
925
  json = schema["~standard"].jsonSchema.output({
502
926
  target: "draft-07",
503
- libraryOptions: UNREPRESENTABLE_AS_ANY
927
+ libraryOptions: UNREPRESENTABLE_AS_ANY2
504
928
  });
505
929
  } catch {
506
930
  return void 0;
507
931
  }
508
- if (!isRecord(json)) {
932
+ if (!isRecord2(json)) {
509
933
  return void 0;
510
934
  }
511
935
  const compiler = new Compiler(json);
@@ -678,8 +1102,51 @@ function withSignal(ctx, signal) {
678
1102
  const next = services === void 0 ? { ...ctx, signal } : { ...ctx, signal, services: services(signal) };
679
1103
  return withRuntime(Object.freeze(next), KIT_RUNTIMES.get(ctx), services);
680
1104
  }
1105
+ function watchCallerReads(ctx, onRead) {
1106
+ const watched = new Proxy(ctx, {
1107
+ get(target, key, receiver) {
1108
+ if (key === "principal" || key === "services" || key === "mcp") {
1109
+ onRead(key);
1110
+ }
1111
+ return Reflect.get(target, key, receiver);
1112
+ }
1113
+ });
1114
+ return withRuntime(watched, KIT_RUNTIMES.get(ctx), SERVICE_CALLERS.get(ctx));
1115
+ }
681
1116
 
682
1117
  export {
1118
+ meetsLevel,
1119
+ maxLevel,
1120
+ levelsAtLeast,
1121
+ serviceGrant,
1122
+ SUBSCRIBE_LEVEL,
1123
+ SUBSCRIBER_LEVELS,
1124
+ strip,
1125
+ referenced,
1126
+ pointerToken,
1127
+ inputKeys,
1128
+ compileProjections,
1129
+ projectedOutput,
1130
+ pickKeys,
1131
+ projectRow,
1132
+ projectOutput,
1133
+ wholeView,
1134
+ readerView,
1135
+ registerRuntime,
1136
+ runtimeOf,
1137
+ checkWhenDefined,
1138
+ markGrantsEditor,
1139
+ editsGrants,
1140
+ handlerProblem,
1141
+ checkWhenServed,
1142
+ servedProblem,
1143
+ isKitHandler,
1144
+ checksRowsItself,
1145
+ isRowChecked,
1146
+ dependsOnCaller,
1147
+ isCallerDependent,
1148
+ narrowKinds,
1149
+ checkKind,
683
1150
  cancelledError,
684
1151
  timeoutError,
685
1152
  throwIfCancelled,
@@ -697,6 +1164,7 @@ export {
697
1164
  kitRuntimeOf,
698
1165
  NEVER_ABORTED,
699
1166
  createContext,
700
- withSignal
1167
+ withSignal,
1168
+ watchCallerReads
701
1169
  };
702
- //# sourceMappingURL=chunk-2PWMUVC7.js.map
1170
+ //# sourceMappingURL=chunk-MBH6IDSM.js.map