@vercel/board 0.16.0 → 0.18.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.
- package/README.md +27 -27
- package/dist/{a2-C41hD8SF.mjs → a2-ChRLRupa.mjs} +969 -25
- package/dist/access.d.mts +1 -1
- package/dist/access.mjs +3 -3
- package/dist/{actors-DjMeZpnU.mjs → actors-BOamkEuz.mjs} +3 -73
- package/dist/attempts-CLG840Oj.mjs +20 -0
- package/dist/{auth-COzc09sn.mjs → auth-Drg9FiC2.mjs} +225 -2
- package/dist/backend-B-SJ7p6p.d.mts +680 -0
- package/dist/backend-a2/contract.d.mts +873 -2
- package/dist/backend-a2/contract.mjs +2 -2
- package/dist/backend-a2/scheduler-local.d.mts +1 -1
- package/dist/backend-a2/scheduler-local.mjs +1 -1
- package/dist/backend-a2/scheduler-qstash.d.mts +78 -1
- package/dist/backend-a2/scheduler-qstash.mjs +521 -1
- package/dist/backend-a2/scheduler-vercel.d.mts +37 -1
- package/dist/backend-a2/scheduler-vercel.mjs +203 -1
- package/dist/backend-a2/store-memory.d.mts +2 -2
- package/dist/backend-a2/store-memory.mjs +3 -3
- package/dist/backend-a2/store-postgres.d.mts +1 -1
- package/dist/backend-a2/store-postgres.mjs +2850 -2
- package/dist/backend-a2/store-redis-http.d.mts +1 -1
- package/dist/backend-a2/store-redis-http.mjs +490 -2
- package/dist/backend-a2/store-redis.d.mts +10 -2
- package/dist/backend-a2/store-redis.mjs +51 -2
- package/dist/backend-a2/store-sqlite.d.mts +1 -1
- package/dist/backend-a2/store-sqlite.mjs +3 -3
- package/dist/backend-a2.d.mts +2706 -3
- package/dist/backend-a2.mjs +1 -1
- package/dist/{backend-http-Bid7RIpC.d.mts → backend-http-7cNb8nJI.d.mts} +10 -3
- package/dist/{backend-http-0roEmjl0.mjs → backend-http-CMlL3yq3.mjs} +2 -2
- package/dist/{backend-http-BVRno-ca.mjs → backend-http-Ddzd5kTS.mjs} +32 -3
- package/dist/backend-http.d.mts +1 -1
- package/dist/backend-http.mjs +1 -1
- package/dist/backend-http.node.d.mts +1 -1
- package/dist/backend-http.node.mjs +1 -1
- package/dist/{chat-provider-XCnQlxq6.d.mts → chat-provider-DNJQyajS.d.mts} +16 -4
- package/dist/{chat-provider-jFSeyNmB.mjs → chat-provider-YHy-WbrB.mjs} +15 -9
- package/dist/cli.mjs +30 -10
- package/dist/{client-DGDPaN7U.mjs → client-BA-J7usH.mjs} +10 -2
- package/dist/{conformance-DRR23YNy.mjs → conformance-CT5EeHeC.mjs} +5 -2
- package/dist/conformance.d.mts +1 -1
- package/dist/conformance.mjs +1 -1
- package/dist/{engine-Bir5s8ts.mjs → engine-D0oBUBEK.mjs} +249 -163
- package/dist/events-CewD4kw7.mjs +75 -0
- package/dist/{exports-Dvc1o52I.mjs → exports-CgjfMrQ-.mjs} +35 -32
- package/dist/exports-ZsgjzEnM.d.mts +4 -0
- package/dist/external-BV_cUncs.mjs +151 -0
- package/dist/folds-CRYY14kc.d.mts +534 -0
- package/dist/{reducers-mUPFuFJV.mjs → folds-Dyjput8M.mjs} +6 -73
- package/dist/ids-C6AYo1bu.mjs +31 -0
- package/dist/index.d.mts +4 -4
- package/dist/{index.default-Bkg77Pxn.mjs → index.default-C5Cpz3sa.mjs} +1 -1
- package/dist/index.mjs +5 -5
- package/dist/index.node.d.mts +4 -4
- package/dist/index.node.mjs +3 -3
- package/dist/inspect/app.js +31 -31
- package/dist/inspect/engine.js +5 -5
- package/dist/internal-CvsrprnU-CRY89I-b.mjs +203 -0
- package/dist/{local-Cr_dSjvs.mjs → local-DhkrAqPP.mjs} +1 -1
- package/dist/mentions-CB6ShY4R.mjs +72 -0
- package/dist/{targets-yM5JBUgr.mjs → middleware-CqaCwucP.mjs} +1 -33
- package/dist/platform-B4TnJtWu-CwcTyKg2.mjs +32 -0
- package/dist/{project-CFiArraX.mjs → project-DMDST9kl.mjs} +1 -1
- package/dist/{provider-BGjz-mDa.d.mts → provider-B0b9Qxw4.d.mts} +170 -40
- package/dist/provider-B3DpYSDq.mjs +220 -0
- package/dist/redis-tables-DN9PfIDi.mjs +3897 -0
- package/dist/reducer-CYPHsSmd-BQIfozGi.d.mts +170 -0
- package/dist/reducer-CjmvZSAb-D_OSISmj.mjs +74 -0
- package/dist/{contract-CuJmlrhw.mjs → reducers-BFmIXilu.mjs} +260 -287
- package/dist/retryable-lazy-DZWmHpii-Bye-W8q7.mjs +19 -0
- package/dist/scheduler-task-X1SToDjP-BVoWuInI.mjs +69 -0
- package/dist/{serve-BsOOc-6_.mjs → serve-BGYw0uNL.mjs} +5 -5
- package/dist/{server-jwPY7_wi.mjs → server-DPxBSogx.mjs} +4 -4
- package/dist/server-DtFrWxi3-Boa6gdWO.mjs +3602 -0
- package/dist/server-qgez0Xny-D5mLfqFQ.d.mts +385 -0
- package/dist/{serving-BNbzc96L.mjs → serving-CNkgUPW1.mjs} +1 -1
- package/dist/session-clear-wHd5GCpZ-BnKSxReK.mjs +51 -0
- package/dist/{sql-DbiEnqM0.mjs → sql-U9WTxrwJ.mjs} +2 -2
- package/dist/{sqlite-db-Cbj03vmp.mjs → sqlite-db-Ds4majtT.mjs} +1 -1
- package/dist/store-D0MhBvg6-4Z2L0Kqd.d.mts +530 -0
- package/dist/store-N8PXxDAS-BWBgzBHp.mjs +5 -0
- package/dist/store-codec-DwrWlgQx-CYxGW0aD.mjs +61 -0
- package/dist/store-memory-Bas9Uq5C.mjs +872 -0
- package/dist/store-memory-ZLiZbH3R.mjs +2 -0
- package/dist/store-polling-BZt2Rf3D-BiEM6_HB.mjs +148 -0
- package/dist/store-presence-polling-JuAy2Ck2-CYpuK75X.mjs +109 -0
- package/dist/store-sqlite-BmwL3pUR.mjs +2 -0
- package/dist/store-sqlite-CxxPCcCT.mjs +1053 -0
- package/dist/stream-buffer-DrTyiIuo-BKy-vI14.mjs +53 -0
- package/dist/sync-github.d.mts +24 -17
- package/dist/sync-github.mjs +89 -22
- package/dist/sync-kapso.d.mts +15 -13
- package/dist/sync-kapso.mjs +17 -14
- package/dist/sync-linear.d.mts +219 -0
- package/dist/sync-linear.mjs +311 -0
- package/dist/sync-slack.d.mts +12 -17
- package/dist/sync-slack.mjs +54 -42
- package/dist/sync.chat.d.mts +1 -1
- package/dist/sync.chat.mjs +1 -1
- package/dist/sync.d.mts +3 -3
- package/dist/sync.mjs +5 -4
- package/dist/targets-C-wdR5wP.mjs +33 -0
- package/dist/{tokens-DKLn5P9B.mjs → tokens-DhuzKzAl.mjs} +1 -1
- package/dist/{types-BHCjTZdB.d.mts → types-8yq2VnKI.d.mts} +2 -2
- package/dist/{users-Dls7_ANV.mjs → users-yryHKFMo.mjs} +2 -2
- package/dist/{watch-spec-W0c7ypHJ.mjs → watchers-hash-RLtnSF-t.mjs} +233 -28
- package/docs/01-board/01-mounting.md +1 -1
- package/docs/02-sync/01-overview.md +72 -48
- package/docs/02-sync/02-functions.md +398 -0
- package/docs/02-sync/03-slack.md +30 -25
- package/docs/02-sync/04-github.md +62 -26
- package/docs/02-sync/05-whatsapp.md +23 -19
- package/docs/02-sync/06-providers.md +60 -27
- package/docs/02-sync/07-linear.md +129 -0
- package/docs/03-guides/04-triage.md +1 -1
- package/docs/04-reference/01-cli.md +26 -6
- package/docs/04-reference/02-board.md +18 -5
- package/docs/04-reference/03-http.md +5 -2
- package/docs/04-reference/05-conformance.md +1 -1
- package/docs/index.md +5 -10
- package/package.json +31 -6
- package/dist/a2-D24cvwpN.d.mts +0 -3359
- package/dist/access-CJU2-vaa.mjs +0 -226
- package/dist/attempts-Bxp9KgPf.mjs +0 -919
- package/dist/exports-DLiE7fLY.d.mts +0 -4
- package/dist/provider-CuewcDNZ.mjs +0 -205
- package/dist/redis-tables-B35vv4CZ.mjs +0 -842
- package/dist/reducers-B_vUGQRp.d.mts +0 -1172
- package/dist/watchers-hash-CLjsa6od.mjs +0 -225
- package/docs/02-sync/02-callbacks.md +0 -265
package/dist/access-CJU2-vaa.mjs
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import { c as ChannelPath, v as SCOPE_MAX } from "./schemas-Be08nyfW.mjs";
|
|
2
|
-
import { a as forbidden } from "./errors-DX3hpwWt.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
|
-
return {
|
|
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
|
-
}
|
|
167
|
-
/** Access from config: `'all'`, a list for every channel, or a map from pattern to grant. Throws on a bad pattern or permission. */
|
|
168
|
-
function compileAccess(can) {
|
|
169
|
-
return fromRules(canonical(entriesOf(can)));
|
|
170
|
-
}
|
|
171
|
-
const FULL_ACCESS = compileAccess("all");
|
|
172
|
-
/**
|
|
173
|
-
* `x-board-scope`: `dev/=read,post,react,label;ci/=read`. Each entry is a
|
|
174
|
-
* pattern and its permissions (or `all`). It's meant for a proxy that holds the
|
|
175
|
-
* credential, like Pi's sandbox: the board intersects it with the principal's
|
|
176
|
-
* access, so it can only narrow, and a caller holding its own token gains
|
|
177
|
-
* nothing by sending it.
|
|
178
|
-
*/
|
|
179
|
-
function parseScope(header) {
|
|
180
|
-
if (header.length > 4e3) throw new Error(`x-board-scope is longer than ${SCOPE_MAX} characters`);
|
|
181
|
-
const out = {};
|
|
182
|
-
const entries = header.split(";").map((entry) => entry.trim());
|
|
183
|
-
if (entries.every((entry) => entry === "")) throw new Error("x-board-scope is empty: write pattern=permissions, like dev/=read,post");
|
|
184
|
-
for (const entry of entries) {
|
|
185
|
-
if (entry === "") continue;
|
|
186
|
-
const split = entry.indexOf("=");
|
|
187
|
-
if (split < 0) throw new Error(`x-board-scope entry ${entry} needs =: write pattern=permissions, like dev/=read,post`);
|
|
188
|
-
const pattern = entry.slice(0, split).trim();
|
|
189
|
-
const list = entry.slice(split + 1).trim();
|
|
190
|
-
if (Object.hasOwn(out, pattern)) throw new Error(`x-board-scope lists ${pattern} twice`);
|
|
191
|
-
parsePattern(pattern);
|
|
192
|
-
const permissions = list === "" ? [] : list.split(",").map((permission) => permission.trim());
|
|
193
|
-
if (permissions.length === 1 && permissions[0] === "all") {
|
|
194
|
-
out[pattern] = "all";
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
for (const permission of permissions) if (!ORDER.has(permission)) throw new Error(`x-board-scope: unknown permission ${permission || "(empty)"}: use ${PERMISSIONS.join(", ")}, or all`);
|
|
198
|
-
out[pattern] = permissions;
|
|
199
|
-
}
|
|
200
|
-
return out;
|
|
201
|
-
}
|
|
202
|
-
const PHRASE = {
|
|
203
|
-
read: (channel) => `read ${channel}`,
|
|
204
|
-
post: (channel) => `post in ${channel}`,
|
|
205
|
-
react: (channel) => `react in ${channel}`,
|
|
206
|
-
label: (channel) => `label in ${channel}`,
|
|
207
|
-
sync: (channel) => `sync in ${channel}`,
|
|
208
|
-
manage: (channel) => `manage ${channel}`
|
|
209
|
-
};
|
|
210
|
-
/**
|
|
211
|
-
* A proof that `access` allows `permission` in `channel`, or a 403 naming what's
|
|
212
|
-
* missing: `this token can't label in dev`. Pass the channel the board looked
|
|
213
|
-
* up, never one the request named. What the caller can't read should answer as
|
|
214
|
-
* if it didn't exist: pass that error as `hidden`, and it's thrown instead
|
|
215
|
-
* whenever `channel` isn't readable.
|
|
216
|
-
*/
|
|
217
|
-
function authorize(access, permission, channel, hidden) {
|
|
218
|
-
if (hidden && !access.readable(channel)) throw hidden();
|
|
219
|
-
if (!access.can(permission, channel)) throw forbidden(`this token can't ${PHRASE[permission](channel)}`);
|
|
220
|
-
return Object.freeze({
|
|
221
|
-
permission,
|
|
222
|
-
channel
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
//#endregion
|
|
226
|
-
export { parseScope as i, authorize as n, compileAccess as r, FULL_ACCESS as t };
|