@vercel/board 0.29.0 → 0.31.0

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 (156) hide show
  1. package/README.md +8 -17
  2. package/dist/{a2-HUbQJ0RD.mjs → a2-DngWY2Eb.mjs} +741 -415
  3. package/dist/access-CY5xVMIK.mjs +242 -0
  4. package/dist/{access-CV96SYgr.d.mts → access-DPcrKIH_.d.mts} +6 -1
  5. package/dist/access.d.mts +4 -4
  6. package/dist/access.mjs +5 -4
  7. package/dist/{actors-DcJhFbwJ.mjs → actors-Bm-ojWWT.mjs} +2 -2
  8. package/dist/{auth-JO22iIwr.mjs → auth-CyO2aw5n.mjs} +126 -236
  9. package/dist/{auth-CU56Q2Xh.d.mts → auth-DLYSt_-A.d.mts} +32 -4
  10. package/dist/{backend-CPC_vyOo.d.mts → backend-Bob-5CM7.d.mts} +93 -42
  11. package/dist/backend-a2/blob-vercel.d.mts +1 -1
  12. package/dist/backend-a2/contract.d.mts +38 -98
  13. package/dist/backend-a2/contract.mjs +1 -1
  14. package/dist/backend-a2/scheduler-local.d.mts +1 -1
  15. package/dist/backend-a2/scheduler-local.mjs +1 -1
  16. package/dist/backend-a2/scheduler-qstash.d.mts +3 -3
  17. package/dist/backend-a2/scheduler-qstash.mjs +8 -8
  18. package/dist/backend-a2/scheduler-vercel.d.mts +3 -3
  19. package/dist/backend-a2/scheduler-vercel.mjs +6 -6
  20. package/dist/backend-a2/store-memory.d.mts +2 -2
  21. package/dist/backend-a2/store-memory.mjs +3 -3
  22. package/dist/backend-a2/store-postgres.d.mts +1 -1
  23. package/dist/backend-a2/store-postgres.mjs +34 -15
  24. package/dist/backend-a2/store-redis-http.d.mts +1 -1
  25. package/dist/backend-a2/store-redis-http.mjs +6 -6
  26. package/dist/backend-a2/store-redis.d.mts +2 -2
  27. package/dist/backend-a2/store-redis.mjs +5 -5
  28. package/dist/backend-a2/store-sqlite.d.mts +1 -1
  29. package/dist/backend-a2/store-sqlite.mjs +3 -3
  30. package/dist/backend-a2.d.mts +143 -242
  31. package/dist/backend-a2.mjs +2 -2
  32. package/dist/{backend-http-CqfKhrnn.d.mts → backend-http-BRrRbjRx.d.mts} +59 -45
  33. package/dist/{backend-http-CzIJMm58.mjs → backend-http-CvUKOn-m.mjs} +11 -10
  34. package/dist/{backend-http-Bmp0IgD3.mjs → backend-http-D8mUzuKQ.mjs} +2 -2
  35. package/dist/backend-http.d.mts +1 -1
  36. package/dist/backend-http.mjs +1 -1
  37. package/dist/backend-http.node.d.mts +1 -1
  38. package/dist/backend-http.node.mjs +1 -1
  39. package/dist/{backend-vercel-DQc_TaHp.mjs → backend-vercel-Ba8JDtqW.mjs} +3 -3
  40. package/dist/{backend-vercel-4tvcPKqX.d.mts → backend-vercel-BaSEUdwg.d.mts} +62 -47
  41. package/dist/{backend-vercel-BNauAs7w.mjs → backend-vercel-CmY9yZ9m.mjs} +279 -42
  42. package/dist/backend-vercel.d.mts +1 -1
  43. package/dist/backend-vercel.mjs +1 -1
  44. package/dist/backend-vercel.node.d.mts +1 -1
  45. package/dist/backend-vercel.node.mjs +1 -1
  46. package/dist/{blob-DmiQjrYy.d.mts → blob-Bx8QPWEd.d.mts} +2 -2
  47. package/dist/{blob-DdbpJqN1.mjs → blob-WbB2DJKn.mjs} +30 -11
  48. package/dist/{chat-provider-BWHbChcQ.d.mts → chat-provider-BChYrrA7.d.mts} +32 -28
  49. package/dist/{chat-provider-COtsU5jB.mjs → chat-provider-DkFp2hrh.mjs} +105 -60
  50. package/dist/cli-commands.d.mts +1 -1
  51. package/dist/cli-commands.mjs +1 -1
  52. package/dist/cli-embedded.d.mts +2 -2
  53. package/dist/cli-embedded.mjs +1 -1
  54. package/dist/cli.mjs +4 -4
  55. package/dist/{client-BqAjk0Q4.d.mts → client-COfYrgfV.d.mts} +240 -227
  56. package/dist/{commands-D6gvrOh3.d.mts → commands-DK4W3HCJ.d.mts} +47 -10
  57. package/dist/{commands-C3R1AWgm.mjs → commands-L1JSGz2Z.mjs} +212 -54
  58. package/dist/conformance-DK43HiHf.mjs +1000 -0
  59. package/dist/conformance.d.mts +8 -2
  60. package/dist/conformance.mjs +1 -1
  61. package/dist/{engine-DQxXk7dX.mjs → engine-DurYIRt8.mjs} +29 -210
  62. package/dist/{errors-DDktVeUa.d.mts → errors-2vGs49Db.d.mts} +1 -1
  63. package/dist/{client-D0Px7KzF.mjs → errors-CJXhhzvT.mjs} +132 -27
  64. package/dist/{provider-BaVmi4Hi.mjs → events-lujPA3gQ.mjs} +80 -17
  65. package/dist/{exports-BG-0Wyn-.mjs → exports-CWIdN0Ub.mjs} +195 -103
  66. package/dist/exports-FBOQWT7O.d.mts +5 -0
  67. package/dist/{folds-BDMSxBUf.mjs → folds-Cu9Q9zyQ.mjs} +3 -22
  68. package/dist/{folds-ZkHM4M-v.d.mts → folds-DLUzfibJ.d.mts} +15 -40
  69. package/dist/{index-D3VvNfh_.d.mts → index-Ho-ayyQL.d.mts} +2 -2
  70. package/dist/index.d.mts +7 -7
  71. package/dist/{index.default-rB8sJ9RX.mjs → index.default-BUcwDaAe.mjs} +1 -1
  72. package/dist/index.mjs +5 -6
  73. package/dist/index.node.d.mts +7 -7
  74. package/dist/index.node.mjs +3 -4
  75. package/dist/inspect/app.js +62 -61
  76. package/dist/inspect/engine.js +3 -3
  77. package/dist/{internal-CvsrprnU-CRY89I-b.mjs → internal-CvsrprnU-zZMpDiRY.mjs} +1 -1
  78. package/dist/{local-DhkrAqPP.mjs → local-C76Fy5ad.mjs} +1 -1
  79. package/dist/{main-CVBqe3VO.mjs → main-Dz-8komG.mjs} +251 -127
  80. package/dist/{middleware-CXjEeef4.mjs → middleware-BavYSDUa.mjs} +1 -1
  81. package/dist/{node-Zd_C-gxP.mjs → node-BgeGcwX0.mjs} +7 -3
  82. package/dist/{platform-B4TnJtWu-CwcTyKg2.mjs → platform-B4TnJtWu-Do3agKr5.mjs} +1 -1
  83. package/dist/{project-CBXlLftU.mjs → project-CQ4VUibA.mjs} +1 -1
  84. package/dist/{provider-DmSTp9lI.d.mts → provider-gynDUoyW.d.mts} +13 -43
  85. package/dist/{redis-tables-BJQVUspu.mjs → redis-tables-DHf85ixI.mjs} +40 -42
  86. package/dist/{reducer-CjmvZSAb-D_OSISmj.mjs → reducer-Bswr11ar-P324o9vn.mjs} +2 -2
  87. package/dist/{reducer-CYPHsSmd-BQIfozGi.d.mts → reducer-CYPHsSmd-BXjGiptm.d.mts} +1 -1
  88. package/dist/{reducers-DJzVpXwM.mjs → reducers-DcENwtwZ.mjs} +74 -77
  89. package/dist/{retryable-lazy-DZWmHpii-Bye-W8q7.mjs → retryable-lazy-DZWmHpii-DpTc5PIP.mjs} +1 -1
  90. package/dist/{scheduler-task-X1SToDjP-BVoWuInI.mjs → scheduler-task-X1SToDjP-D2Il2rln.mjs} +1 -1
  91. package/dist/{serve-G9RuaDSr.mjs → serve-CTmow-YH.mjs} +7 -7
  92. package/dist/{server-qgez0Xny-D5mLfqFQ.d.mts → server-B9gu0qi1-DI1WvcVK.d.mts} +10 -4
  93. package/dist/{server-BixV_byP.mjs → server-DG8D6VV0.mjs} +4 -4
  94. package/dist/{server-DtFrWxi3-Boa6gdWO.mjs → server-DX7mM6nm-D7N_7bug.mjs} +32 -23
  95. package/dist/{serving-B75Y4BF-.mjs → serving-DBFRo6U_.mjs} +1 -1
  96. package/dist/{session-clear-wHd5GCpZ-BnKSxReK.mjs → session-clear-XJYpmUpg-2q8l_FJR.mjs} +14 -2
  97. package/dist/{sql-D4Z_efJX.mjs → sql-CT5xVuGr.mjs} +2 -8
  98. package/dist/{sqlite-db-Bz2JkfCU.mjs → sqlite-db-0WeSOEZN.mjs} +1 -1
  99. package/dist/{store-D0MhBvg6-4Z2L0Kqd.d.mts → store-DEzcHZOA-B-5vhxef.d.mts} +12 -2
  100. package/dist/store-N8PXxDAS-DWWgv-Vz.mjs +5 -0
  101. package/dist/{store-codec-DwrWlgQx-CYxGW0aD.mjs → store-codec-pAG2oPgQ-SqDXlPBR.mjs} +4 -4
  102. package/dist/store-memory-4IBl0Ztu.mjs +2 -0
  103. package/dist/{store-memory-Bas9Uq5C.mjs → store-memory-B0PkVSuQ.mjs} +14 -7
  104. package/dist/{store-polling-BZt2Rf3D-BiEM6_HB.mjs → store-polling-BZt2Rf3D-Div-bJ_D.mjs} +2 -2
  105. package/dist/{store-presence-polling-JuAy2Ck2-CYpuK75X.mjs → store-presence-polling-JuAy2Ck2-Dh-g_KYM.mjs} +3 -3
  106. package/dist/{store-sqlite-CxxPCcCT.mjs → store-sqlite-D85t3tE7.mjs} +14 -7
  107. package/dist/store-sqlite-iBTYYKBj.mjs +2 -0
  108. package/dist/{stream-buffer-DrTyiIuo-BKy-vI14.mjs → stream-buffer-DrTyiIuo-Be10yOBR.mjs} +2 -2
  109. package/dist/{sync-kapso.d.mts → sync-chat-sdk.d.mts} +33 -37
  110. package/dist/sync-chat-sdk.mjs +44 -0
  111. package/dist/sync-github.d.mts +5 -18
  112. package/dist/sync-github.mjs +4 -70
  113. package/dist/sync-linear.d.mts +3 -3
  114. package/dist/sync-linear.mjs +6 -5
  115. package/dist/sync-slack.d.mts +7 -7
  116. package/dist/sync-slack.mjs +33 -54
  117. package/dist/sync.d.mts +3 -3
  118. package/dist/sync.mjs +2 -4
  119. package/dist/{tokens-CC3izSNr.mjs → tokens-Dnyd_y1n.mjs} +15 -6
  120. package/dist/{types-No0WdhE5.d.mts → types-CmrFjpml.d.mts} +2 -4
  121. package/dist/{watchers-hash-BDhLVkV5.mjs → watchers-hash-BjwlnGD9.mjs} +124 -115
  122. package/docs/01-board/01-mounting.md +2 -2
  123. package/docs/01-board/03-identity.md +5 -6
  124. package/docs/01-board/04-messages.md +96 -2
  125. package/docs/01-board/05-reading.md +2 -1
  126. package/docs/01-board/06-watchers.md +8 -6
  127. package/docs/01-board/08-middleware.md +6 -3
  128. package/docs/01-board/09-access.md +145 -14
  129. package/docs/02-sync/01-overview.md +19 -26
  130. package/docs/02-sync/02-functions.md +8 -9
  131. package/docs/02-sync/03-slack.md +5 -5
  132. package/docs/02-sync/04-github.md +2 -2
  133. package/docs/02-sync/06-providers.md +91 -62
  134. package/docs/03-guides/01-coding-agents.md +1 -1
  135. package/docs/03-guides/03-ci.md +7 -4
  136. package/docs/04-reference/01-cli.md +109 -51
  137. package/docs/04-reference/02-board.md +76 -28
  138. package/docs/04-reference/03-http.md +76 -38
  139. package/docs/04-reference/04-log.md +8 -11
  140. package/docs/04-reference/05-conformance.md +47 -7
  141. package/docs/index.md +2 -2
  142. package/package.json +7 -10
  143. package/sql/postgres/board.sql +1 -1
  144. package/sql/postgres/schema.sql +1 -1
  145. package/dist/conformance-DlnfuzEB.mjs +0 -556
  146. package/dist/errors-CusN4x2l.mjs +0 -88
  147. package/dist/events-qf5XG5VQ.mjs +0 -75
  148. package/dist/exports-mhAYiU4i.d.mts +0 -5
  149. package/dist/store-N8PXxDAS-BWBgzBHp.mjs +0 -5
  150. package/dist/store-memory-ZLiZbH3R.mjs +0 -2
  151. package/dist/store-sqlite-BmwL3pUR.mjs +0 -2
  152. package/dist/sync-kapso.mjs +0 -89
  153. package/dist/sync.chat.d.mts +0 -2
  154. package/dist/sync.chat.mjs +0 -2
  155. package/dist/users-yryHKFMo.mjs +0 -42
  156. package/docs/02-sync/05-whatsapp.md +0 -152
@@ -0,0 +1,242 @@
1
+ import { c as ChannelPath, v as SCOPE_MAX } from "./schemas-DBmUO6pr.mjs";
2
+ import { a as forbidden } from "./errors-CJXhhzvT.mjs";
3
+ //#region src/server/access.ts
4
+ /**
5
+ * What a principal may do in a channel. `read` covers messages, threads,
6
+ * `/events`, and schedules; `post` starts threads and edits your own messages;
7
+ * `manage` is the board's own bookkeeping (inbox, sources).
8
+ */
9
+ const PERMISSIONS = [
10
+ "read",
11
+ "post",
12
+ "react",
13
+ "label",
14
+ "sync",
15
+ "manage"
16
+ ];
17
+ /**
18
+ * One hop of implication, kept as data like Vercel's git broker
19
+ * (`ACTION_IMPLICATIONS`). Each verb that addresses a message by ID implies
20
+ * `read`, so none of them is a blind existence oracle. `post` implies nothing:
21
+ * write-only is `['post']`.
22
+ */
23
+ const IMPLIES = {
24
+ read: [],
25
+ post: [],
26
+ react: ["read"],
27
+ label: ["read"],
28
+ sync: ["read"],
29
+ manage: [
30
+ "read",
31
+ "post",
32
+ "react",
33
+ "label",
34
+ "sync"
35
+ ]
36
+ };
37
+ const ORDER = new Map(PERMISSIONS.map((permission, index) => [permission, index]));
38
+ const SHAPE = "write *, a channel, or a channel and its subtree (feedback/)";
39
+ /** A pattern's parts; any other `*` throws, and says what to type instead. */
40
+ function parsePattern(pattern) {
41
+ if (pattern === "*") return { kind: "all" };
42
+ if (pattern.includes("*")) {
43
+ const before = pattern.slice(0, pattern.indexOf("*")).replace(/\/+$/, "");
44
+ const hint = before === "" ? "write * for every channel" : ChannelPath.safeParse(before).success ? `write ${before}/ for ${before} and its subtree` : SHAPE;
45
+ throw new Error(`invalid channel pattern ${pattern}: ${hint}`);
46
+ }
47
+ const subtree = pattern.endsWith("/");
48
+ const path = subtree ? pattern.slice(0, -1) : pattern;
49
+ if (!ChannelPath.safeParse(path).success) throw new Error(`invalid channel pattern ${pattern || "(empty)"}: ${SHAPE}`);
50
+ return subtree ? {
51
+ kind: "subtree",
52
+ path
53
+ } : {
54
+ kind: "exact",
55
+ path
56
+ };
57
+ }
58
+ const key = (pattern) => pattern.kind === "all" ? "*" : pattern.kind === "subtree" ? `${pattern.path}/` : pattern.path;
59
+ /** A grant with its implications, closed: `['label']` is `read` and `label`. */
60
+ function expand(grant) {
61
+ const out = /* @__PURE__ */ new Set();
62
+ const add = (permission) => {
63
+ if (!ORDER.has(permission)) throw new Error(`unknown permission ${String(permission)}: use ${PERMISSIONS.join(", ")}`);
64
+ if (out.has(permission)) return;
65
+ out.add(permission);
66
+ for (const implied of IMPLIES[permission]) add(implied);
67
+ };
68
+ for (const permission of grant === "all" ? PERMISSIONS : grant) add(permission);
69
+ return out;
70
+ }
71
+ const NOTHING = /* @__PURE__ */ new Set();
72
+ function compile(entries) {
73
+ let all = null;
74
+ const exact = /* @__PURE__ */ new Map();
75
+ const subtrees = [];
76
+ for (const [pattern, permissions] of entries) if (pattern.kind === "all") all = permissions;
77
+ else if (pattern.kind === "exact") exact.set(pattern.path, permissions);
78
+ else subtrees.push([pattern.path, permissions]);
79
+ subtrees.sort((a, b) => b[0].length - a[0].length);
80
+ return {
81
+ all,
82
+ exact,
83
+ subtrees
84
+ };
85
+ }
86
+ const under = (channel, path) => channel === path || channel.startsWith(`${path}/`);
87
+ /** What the most specific pattern covering `channel` gives; with `subtreeOnly`, ignoring an exact rule for it. */
88
+ function resolve(rules, channel, subtreeOnly = false) {
89
+ if (!subtreeOnly) {
90
+ const exact = rules.exact.get(channel);
91
+ if (exact) return exact;
92
+ }
93
+ for (const [path, permissions] of rules.subtrees) if (under(channel, path)) return permissions;
94
+ return rules.all ?? NOTHING;
95
+ }
96
+ /**
97
+ * What `rules` gives every channel `pattern` governs where nothing more
98
+ * specific does. The patterns matching one channel form a chain, so that's one
99
+ * answer: an exact channel resolves as itself, a subtree as whatever covers the
100
+ * subtree's root, ignoring an exact rule for the root.
101
+ */
102
+ const at = (rules, pattern) => pattern.kind === "all" ? rules.all ?? NOTHING : resolve(rules, pattern.path, pattern.kind === "subtree");
103
+ const equal = (a, b) => a.size === b.size && [...a].every((permission) => b.has(permission));
104
+ /** The answer of the pattern next less specific than `pattern` in `rules`. */
105
+ function parent(rules, pattern) {
106
+ if (pattern.kind === "exact") return resolve(rules, pattern.path, true);
107
+ if (pattern.kind === "subtree") {
108
+ for (const [path, permissions] of rules.subtrees) if (path !== pattern.path && under(pattern.path, path)) return permissions;
109
+ }
110
+ return rules.all ?? NOTHING;
111
+ }
112
+ /** Drops rules that say what their parent says, and an empty `*`, so equal access has one summary. */
113
+ function canonical(entries) {
114
+ const rules = compile(entries);
115
+ return compile(entries.filter(([pattern, permissions]) => pattern.kind === "all" ? permissions.size > 0 : !equal(permissions, parent(rules, pattern))));
116
+ }
117
+ function entriesOf(can) {
118
+ if (can === "all" || Array.isArray(can)) return [[{ kind: "all" }, expand(can)]];
119
+ return Object.entries(can).map(([pattern, grant]) => [parsePattern(pattern), expand(grant)]);
120
+ }
121
+ const entriesIn = (rules) => [
122
+ ...rules.all ? [[{ kind: "all" }, rules.all]] : [],
123
+ ...rules.subtrees.map(([path, permissions]) => [{
124
+ kind: "subtree",
125
+ path
126
+ }, permissions]),
127
+ ...[...rules.exact].map(([path, permissions]) => [{
128
+ kind: "exact",
129
+ path
130
+ }, permissions])
131
+ ];
132
+ const RULES = Symbol("board.access");
133
+ const isAccess = (value) => typeof value === "object" && RULES in value;
134
+ function fromRules(rules) {
135
+ const entries = entriesIn(rules);
136
+ const everywhere = (permission) => (rules.all?.has(permission) ?? false) && entries.every(([, permissions]) => permissions.has(permission));
137
+ const readable = /* @__PURE__ */ new Map();
138
+ const compiled = {
139
+ [RULES]: rules,
140
+ can: (permission, channel) => resolve(rules, channel).has(permission),
141
+ readable(channel) {
142
+ let found = readable.get(channel);
143
+ if (found === void 0) readable.set(channel, found = resolve(rules, channel).has("read"));
144
+ return found;
145
+ },
146
+ readsAll: everywhere("read"),
147
+ everywhere,
148
+ anywhere: (permission) => entries.some(([, permissions]) => permissions.has(permission)),
149
+ narrow(scope) {
150
+ const other = isAccess(scope) ? scope[RULES] : canonical(entriesOf(scope));
151
+ const patterns = /* @__PURE__ */ new Map([["*", { kind: "all" }]]);
152
+ for (const [pattern] of [...entries, ...entriesIn(other)]) patterns.set(key(pattern), pattern);
153
+ return fromRules(canonical([...patterns.values()].map((pattern) => {
154
+ const theirs = at(other, pattern);
155
+ return [pattern, new Set([...at(rules, pattern)].filter((permission) => theirs.has(permission)))];
156
+ })));
157
+ },
158
+ summary() {
159
+ const named = entries.map(([pattern, permissions]) => [key(pattern), permissions]);
160
+ named.sort(([a], [b]) => a === "*" ? -1 : b === "*" ? 1 : a.localeCompare(b));
161
+ const out = {};
162
+ for (const [name, permissions] of named) out[name] = [...permissions].sort((a, b) => ORDER.get(a) - ORDER.get(b));
163
+ return out;
164
+ }
165
+ };
166
+ return Object.freeze(compiled);
167
+ }
168
+ /** Access from config: `'all'`, a list for every channel, or a map from pattern to grant. Throws on a bad pattern or permission. */
169
+ function compileAccess(can) {
170
+ return fromRules(canonical(entriesOf(can)));
171
+ }
172
+ const FULL_ACCESS = compileAccess("all");
173
+ const NO_ACCESS = compileAccess([]);
174
+ /** What any of `accesses` allows, channel by channel: grants that match one principal add up. */
175
+ function union(accesses) {
176
+ const all = accesses.filter(isAccess);
177
+ if (all.length === 0) return NO_ACCESS;
178
+ const rules = all.map((access) => access[RULES]);
179
+ const patterns = /* @__PURE__ */ new Map([["*", { kind: "all" }]]);
180
+ for (const side of rules) for (const [pattern] of entriesIn(side)) patterns.set(key(pattern), pattern);
181
+ return fromRules(canonical([...patterns.values()].map((pattern) => [pattern, new Set(rules.flatMap((side) => [...at(side, pattern)]))])));
182
+ }
183
+ /** An access as `x-board-scope` writes it, `dev/=read,post;ci/=read`, or `*=` for nothing: what a stored principal keeps. */
184
+ function scopeOf(access) {
185
+ const entries = Object.entries(access.summary()).map(([pattern, permissions]) => `${pattern}=${permissions.join(",")}`);
186
+ return entries.length > 0 ? entries.join(";") : "*=";
187
+ }
188
+ /**
189
+ * `x-board-scope`: `dev/=read,post,react,label;ci/=read`. Each entry is a
190
+ * pattern and its permissions (or `all`). It's meant for a proxy that holds the
191
+ * credential, like Pi's sandbox: the board intersects it with the principal's
192
+ * access, so it can only narrow, and a caller holding its own token gains
193
+ * nothing by sending it.
194
+ */
195
+ function parseScope(header) {
196
+ if (header.length > 4e3) throw new Error(`x-board-scope is longer than ${SCOPE_MAX} characters`);
197
+ const out = {};
198
+ const entries = header.split(";").map((entry) => entry.trim());
199
+ if (entries.every((entry) => entry === "")) throw new Error("x-board-scope is empty: write pattern=permissions, like dev/=read,post");
200
+ for (const entry of entries) {
201
+ if (entry === "") continue;
202
+ const split = entry.indexOf("=");
203
+ if (split < 0) throw new Error(`x-board-scope entry ${entry} needs =: write pattern=permissions, like dev/=read,post`);
204
+ const pattern = entry.slice(0, split).trim();
205
+ const list = entry.slice(split + 1).trim();
206
+ if (Object.hasOwn(out, pattern)) throw new Error(`x-board-scope lists ${pattern} twice`);
207
+ parsePattern(pattern);
208
+ const permissions = list === "" ? [] : list.split(",").map((permission) => permission.trim());
209
+ if (permissions.length === 1 && permissions[0] === "all") {
210
+ out[pattern] = "all";
211
+ continue;
212
+ }
213
+ for (const permission of permissions) if (!ORDER.has(permission)) throw new Error(`x-board-scope: unknown permission ${permission || "(empty)"}: use ${PERMISSIONS.join(", ")}, or all`);
214
+ out[pattern] = permissions;
215
+ }
216
+ return out;
217
+ }
218
+ const PHRASE = {
219
+ read: (channel) => `read ${channel}`,
220
+ post: (channel) => `post in ${channel}`,
221
+ react: (channel) => `react in ${channel}`,
222
+ label: (channel) => `label in ${channel}`,
223
+ sync: (channel) => `sync in ${channel}`,
224
+ manage: (channel) => `manage ${channel}`
225
+ };
226
+ /**
227
+ * A proof that `access` allows `permission` in `channel`, or a 403 naming what's
228
+ * missing: `this token can't label in dev`. Pass the channel the board looked
229
+ * up, never one the request named. What the caller can't read should answer as
230
+ * if it didn't exist: pass that error as `hidden`, and it's thrown instead
231
+ * whenever `channel` isn't readable.
232
+ */
233
+ function authorize(access, permission, channel, hidden) {
234
+ if (hidden && !access.readable(channel)) throw hidden();
235
+ if (!access.can(permission, channel)) throw forbidden(`this token can't ${PHRASE[permission](channel)}`);
236
+ return Object.freeze({
237
+ permission,
238
+ channel
239
+ });
240
+ }
241
+ //#endregion
242
+ export { scopeOf as a, parseScope as i, authorize as n, union as o, compileAccess as r, FULL_ACCESS as t };
@@ -28,5 +28,10 @@ type Access = {
28
28
  /** Each pattern and its permissions, `*` first and the rest sorted; an empty `*` is left out. */
29
29
  summary(): AccessSummary;
30
30
  };
31
+ /** Access from config: `'all'`, a list for every channel, or a map from pattern to grant. Throws on a bad pattern or permission. */
32
+ declare function compileAccess(can: Can): Access;
33
+ declare const FULL_ACCESS: Access;
34
+ /** What any of `accesses` allows, channel by channel: grants that match one principal add up. */
35
+ declare function union(accesses: readonly Access[]): Access;
31
36
  //#endregion
32
- export { Grant as a, ChannelPattern as i, AccessSummary as n, Permission as o, Can as r, Access as t };
37
+ export { FULL_ACCESS as a, compileAccess as c, ChannelPattern as i, union as l, AccessSummary as n, Grant as o, Can as r, Permission as s, Access as t };
package/dist/access.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { a as Grant, i as ChannelPattern, n as AccessSummary, o as Permission, r as Can, t as Access } from "./access-CV96SYgr.mjs";
2
- import { _ as oidc, a as CallerRequest, c as HostPrincipal, d as OidcTokensOptions, f as Principal, g as githubActions, h as compileRules, i as AuthenticateContext, l as IssuerConfig, m as RuleFunction, n as AccessRule, o as Claims, p as PrincipalKind, r as Authenticate, t as AccessFunction, u as Known, v as vercelProject } from "./auth-CU56Q2Xh.mjs";
3
- import { C as Author, t as Actor } from "./folds-ZkHM4M-v.mjs";
1
+ import { a as FULL_ACCESS, c as compileAccess, i as ChannelPattern, l as union, n as AccessSummary, o as Grant, r as Can, s as Permission, t as Access } from "./access-DPcrKIH_.mjs";
2
+ import { a as CallerRequest, b as oidc, d as Known, f as OidcTokensOptions, g as RuleFunction, h as PrincipalKind, i as AuthenticateContext, l as HostPrincipal, m as Principal, n as AccessRule, o as Claims, r as Authenticate, s as DEFAULT_CAN, t as AccessFunction, u as IssuerConfig, v as compileRules, x as vercelProject, y as githubActions } from "./auth-DLYSt_-A.mjs";
3
+ import { C as Author, t as Actor } from "./folds-DLUzfibJ.mjs";
4
4
  //#region src/server/tokens.d.ts
5
5
  /**
6
6
  * The OIDC tokens made out to this board: a request's bearer token, from Vercel (people and
@@ -26,4 +26,4 @@ type ResolvedActor = {
26
26
  */
27
27
  export declare function resolveActor(principal: Principal, claims: Claims, audience: string): ResolvedActor;
28
28
  //#endregion
29
- export { type Access, type AccessFunction, type AccessRule, type AccessSummary, type Authenticate, type AuthenticateContext, type CallerRequest, type Can, type ChannelPattern, type Claims, type Grant, type HostPrincipal, type IssuerConfig, type Known, type OidcTokensOptions, type Permission, type Principal, type PrincipalKind, type RuleFunction, compileRules, githubActions, oidc, vercelProject };
29
+ export { type Access, type AccessFunction, type AccessRule, type AccessSummary, type Authenticate, type AuthenticateContext, type CallerRequest, type Can, type ChannelPattern, type Claims, DEFAULT_CAN, FULL_ACCESS, type Grant, type HostPrincipal, type IssuerConfig, type Known, type OidcTokensOptions, type Permission, type Principal, type PrincipalKind, type RuleFunction, compileAccess, compileRules, githubActions, oidc, union, vercelProject };
package/dist/access.mjs CHANGED
@@ -1,4 +1,5 @@
1
- import { a as compileRules, c as githubActions, h as vercelProject, p as oidc } from "./auth-JO22iIwr.mjs";
2
- import { i as resolveActor } from "./actors-DcJhFbwJ.mjs";
3
- import { t as oidcTokens } from "./tokens-CC3izSNr.mjs";
4
- export { compileRules, githubActions, oidc, oidcTokens, resolveActor, vercelProject };
1
+ import { o as union, r as compileAccess, t as FULL_ACCESS } from "./access-CY5xVMIK.mjs";
2
+ import { i as resolveActor } from "./actors-Bm-ojWWT.mjs";
3
+ import { _ as oidc, c as compileRules, d as githubActions, n as DEFAULT_CAN, x as vercelProject } from "./auth-CyO2aw5n.mjs";
4
+ import { t as oidcTokens } from "./tokens-Dnyd_y1n.mjs";
5
+ export { DEFAULT_CAN, FULL_ACCESS, compileAccess, compileRules, githubActions, oidc, oidcTokens, resolveActor, union, vercelProject };
@@ -1,8 +1,8 @@
1
1
  import { C as toStorable } from "./schemas-DBmUO6pr.mjs";
2
- import { r as badRequest } from "./errors-CusN4x2l.mjs";
2
+ import { r as badRequest } from "./errors-CJXhhzvT.mjs";
3
+ import { i as parseScope } from "./access-CY5xVMIK.mjs";
3
4
  import { t as own } from "./own-OwgPWdmh.mjs";
4
5
  import { t as extractMentions } from "./mentions-CB6ShY4R.mjs";
5
- import { y as parseScope } from "./auth-JO22iIwr.mjs";
6
6
  //#region src/server/actors.ts
7
7
  /** Agents whose avatars the board serves itself. */
8
8
  const KNOWN_AGENTS = [