@supersuit/suggest-a-change 0.1.12 → 0.2.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/CHANGELOG.md +22 -1
- package/README.md +163 -8
- package/dist/client/adapters.d.ts +27 -0
- package/dist/client/context.d.ts +61 -0
- package/dist/client/context.js +279 -0
- package/dist/client/detail.d.ts +2 -2
- package/dist/client/detail.js +12 -8
- package/dist/client/entry.js +10 -5
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +1 -0
- package/dist/client/mine.js +4 -2
- package/dist/client/runtime.js +3 -0
- package/dist/client/seen.d.ts +12 -0
- package/dist/client/seen.js +30 -7
- package/dist/client/sheet.js +64 -4
- package/dist/client/words.d.ts +10 -1
- package/dist/client/words.js +11 -2
- package/dist/contract.d.mts +32 -22
- package/dist/contract.mjs +127 -37
- package/dist/github/api.d.ts +100 -0
- package/dist/github/api.js +164 -0
- package/dist/github/compose.d.ts +152 -0
- package/dist/github/compose.js +309 -0
- package/dist/github/filer.d.ts +72 -0
- package/dist/github/filer.js +89 -0
- package/dist/github/hosts.d.ts +41 -0
- package/dist/github/hosts.js +154 -0
- package/dist/github/index.d.ts +12 -0
- package/dist/github/index.js +9 -0
- package/dist/github/privacy.d.ts +34 -0
- package/dist/github/privacy.js +142 -0
- package/dist/github/relay.d.ts +159 -0
- package/dist/github/relay.js +734 -0
- package/dist/server/geo.d.ts +17 -0
- package/dist/server/geo.js +35 -0
- package/dist/server/handlers.d.ts +54 -3
- package/dist/server/handlers.js +143 -41
- package/dist/server/index.d.ts +3 -1
- package/dist/server/index.js +1 -0
- package/package.json +5 -1
- package/src/client/adapters.ts +18 -1
- package/src/client/context.ts +243 -0
- package/src/client/detail.tsx +13 -8
- package/src/client/entry.tsx +9 -5
- package/src/client/index.ts +2 -0
- package/src/client/mine.tsx +7 -3
- package/src/client/runtime.ts +3 -0
- package/src/client/seen.ts +27 -6
- package/src/client/sheet.tsx +78 -4
- package/src/client/styles.css +11 -2
- package/src/client/words.ts +15 -2
- package/src/contract.mjs +110 -37
- package/src/github/api.ts +169 -0
- package/src/github/compose.ts +354 -0
- package/src/github/filer.ts +96 -0
- package/src/github/hosts.ts +117 -0
- package/src/github/index.ts +15 -0
- package/src/github/privacy.ts +131 -0
- package/src/github/relay.ts +637 -0
- package/src/server/geo.ts +32 -0
- package/src/server/handlers.ts +158 -45
- package/src/server/index.ts +3 -1
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { type GeoHook } from '../server/geo.ts';
|
|
2
|
+
import { type Fetch } from './api.ts';
|
|
3
|
+
import { type Person } from './privacy.ts';
|
|
4
|
+
/** A voice recording's ceiling; the same number as the server half's VOICE_FILE_BYTES (a test holds them together). */
|
|
5
|
+
export declare const RELAY_VOICE_BYTES: number;
|
|
6
|
+
export interface RelayLimits {
|
|
7
|
+
/** Different reports one identity may file or attach to in a day. */
|
|
8
|
+
perPersonPerDay: number;
|
|
9
|
+
/** Bytes of files one identity may send in a day, across every action. */
|
|
10
|
+
bytesPerPersonPerDay: number;
|
|
11
|
+
images: number;
|
|
12
|
+
imageBytes: number;
|
|
13
|
+
videos: number;
|
|
14
|
+
videoBytes: number;
|
|
15
|
+
/** Recordings per report: the report's voice notes and those added to it later. */
|
|
16
|
+
voice: number;
|
|
17
|
+
voiceBytes: number;
|
|
18
|
+
/** How long after an issue closed a file may still be attached to it. */
|
|
19
|
+
closedGraceMs: number;
|
|
20
|
+
/** The largest file taken inside a request body. A larger one is staged (upload-url), or refused without staging.
|
|
21
|
+
* Serverless hosts cap a request body (Vercel at about 4.5 MB), so the default leaves room for base64 and the rest. */
|
|
22
|
+
inlineBytes: number;
|
|
23
|
+
}
|
|
24
|
+
export declare const RELAY_LIMITS: RelayLimits;
|
|
25
|
+
export interface FiledRecord {
|
|
26
|
+
account: string;
|
|
27
|
+
reportId: string;
|
|
28
|
+
number: number;
|
|
29
|
+
url: string;
|
|
30
|
+
}
|
|
31
|
+
/** A day's allowance held for one request, until it is committed (it happened) or released (it did not). */
|
|
32
|
+
export type Reservation = {
|
|
33
|
+
ok: true;
|
|
34
|
+
token: string;
|
|
35
|
+
} | {
|
|
36
|
+
ok: false;
|
|
37
|
+
over: 'reports' | 'bytes';
|
|
38
|
+
};
|
|
39
|
+
/** What the relay remembers. Every method is ATOMIC in a real store (one transaction or a conditional write): a check
|
|
40
|
+
* and the change it guards are one step, so two requests at once can never both pass a cap of one. */
|
|
41
|
+
export interface RelayUsage {
|
|
42
|
+
/** Holds today's allowance for this request: one more report (unless `reportId` was already counted today for
|
|
43
|
+
* `account`) and `bytes` more bytes, refusing when either would pass its limit. Nothing is held on a refusal. */
|
|
44
|
+
reserve(account: string, day: string, reportId: string, bytes: number, limits: {
|
|
45
|
+
reports: number;
|
|
46
|
+
bytes: number;
|
|
47
|
+
}): Promise<Reservation>;
|
|
48
|
+
/** The request that reserved this happened: keep it. */
|
|
49
|
+
commit(token: string): Promise<void>;
|
|
50
|
+
/** The request that reserved this did not happen: give it back. */
|
|
51
|
+
release(token: string): Promise<void>;
|
|
52
|
+
/** A report belongs to the first identity that files or attaches for it: 'ok' for that identity, 'taken' for anyone else. */
|
|
53
|
+
bind(repo: string, reportId: string, account: string): Promise<'ok' | 'taken'>;
|
|
54
|
+
/** Starts filing a report, first caller wins: 'go', or 'busy' while another filing of it runs, or the record of the issue it already became. */
|
|
55
|
+
/** Starts filing a report under a lease of `leaseMs`, first caller wins: 'go', or 'busy' while another filing of it holds a
|
|
56
|
+
* live lease, or the record of the issue it already became. A lease past its time is taken over (its process died). */
|
|
57
|
+
begin(repo: string, reportId: string, account: string, leaseMs: number): Promise<{
|
|
58
|
+
state: 'go';
|
|
59
|
+
} | {
|
|
60
|
+
state: 'busy';
|
|
61
|
+
} | ({
|
|
62
|
+
state: 'filed';
|
|
63
|
+
} & FiledRecord)>;
|
|
64
|
+
/** upload-url issued a link for this staging key, for at most `bytes`: a later read is bounded by it. */
|
|
65
|
+
stage(key: string, bytes: number): Promise<void>;
|
|
66
|
+
/** The bytes upload-url held for this key, or null when it never issued one. */
|
|
67
|
+
staged(key: string): Promise<number | null>;
|
|
68
|
+
/** The staged file was written and discarded: forget its key. */
|
|
69
|
+
unstage(key: string): Promise<void>;
|
|
70
|
+
/** The filing that began succeeded: keep the record, by report and by issue number. */
|
|
71
|
+
finish(repo: string, record: FiledRecord): Promise<void>;
|
|
72
|
+
/** The filing that began failed: forget it, so a retry can begin again. */
|
|
73
|
+
abandon(repo: string, reportId: string): Promise<void>;
|
|
74
|
+
/** Who filed this issue through the relay, or null. */
|
|
75
|
+
filer(repo: string, number: number): Promise<FiledRecord | null>;
|
|
76
|
+
}
|
|
77
|
+
/** In memory, so ONE SERVER INSTANCE ONLY: a platform that runs several instances (most serverless hosts) needs a store
|
|
78
|
+
* backed by its database, or the caps, the idempotency and who-filed-what are per instance and lost on restart. Each
|
|
79
|
+
* method below changes what it checks before it yields, which is what makes it atomic in one process. */
|
|
80
|
+
export declare function memoryUsage({ now }?: {
|
|
81
|
+
now?: () => number;
|
|
82
|
+
}): RelayUsage;
|
|
83
|
+
/** Who is calling, as the host knows them. The issue's name, role, app and contact come from here and never from the
|
|
84
|
+
* request: a request can say whether to share the contact (it sends `report.who.contact`), never what it is. */
|
|
85
|
+
export interface RelayIdentity {
|
|
86
|
+
account: string;
|
|
87
|
+
/** The name the issue says it was filed by; "Someone" without one. */
|
|
88
|
+
name?: string;
|
|
89
|
+
role?: 'operator' | 'member' | 'maintainer';
|
|
90
|
+
/** The GitHub login this person files under, when the host knows it: `attach` to an issue the relay did not file
|
|
91
|
+
* is accepted only when that issue was opened by this login. */
|
|
92
|
+
githubLogin?: string;
|
|
93
|
+
/** How to reach them, shared only when the request asks (the person left it ticked). */
|
|
94
|
+
contact?: {
|
|
95
|
+
email?: string;
|
|
96
|
+
phone?: string;
|
|
97
|
+
handle?: string;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export interface SuggestionRelayOptions {
|
|
101
|
+
/** The GitHub App the relay acts as: its numeric id and its private key (PEM, PKCS#1 as GitHub issues it, or PKCS#8). */
|
|
102
|
+
app: {
|
|
103
|
+
id: number | string;
|
|
104
|
+
privateKey: string;
|
|
105
|
+
};
|
|
106
|
+
/** Who is calling, from the request (a session, an API key, whatever the server already uses), or null. */
|
|
107
|
+
identity(req: Request): Promise<RelayIdentity | null>;
|
|
108
|
+
/** The repos this identity may file on; null (or no function) means any repo the App is installed on. */
|
|
109
|
+
repos?(account: string): string[] | null | Promise<string[] | null>;
|
|
110
|
+
/** What the privacy catch looks for besides credentials, emails and phones: third parties, and secrets the host knows. */
|
|
111
|
+
privacy?: {
|
|
112
|
+
people?: Person[];
|
|
113
|
+
secrets?: string[];
|
|
114
|
+
};
|
|
115
|
+
/** Roughly where the filer was, from the request (see the server half's geo hooks); added to the issue's context. */
|
|
116
|
+
geo?: GeoHook;
|
|
117
|
+
/** The apps this relay files for, by the id a request names (`app`): the issue's app name and its `app:<label>`
|
|
118
|
+
* come from here. A request naming an app not in the table is refused; without a table no request names one. */
|
|
119
|
+
apps?: Record<string, {
|
|
120
|
+
name: string;
|
|
121
|
+
label: string;
|
|
122
|
+
}>;
|
|
123
|
+
/** Where files too large for one request body wait: the filer PUTs each to a one-time signed link (`upload-url`),
|
|
124
|
+
* and `file` or `attach` names it as `staged`. Without it, only files up to `inlineBytes` can be sent. */
|
|
125
|
+
staging?: StagingAdapter;
|
|
126
|
+
/** May lower any limit, never raise one. */
|
|
127
|
+
limits?: Partial<RelayLimits>;
|
|
128
|
+
usage?: RelayUsage;
|
|
129
|
+
fetch?: Fetch;
|
|
130
|
+
now?: () => number;
|
|
131
|
+
/** Default https://api.github.com */
|
|
132
|
+
api?: string;
|
|
133
|
+
}
|
|
134
|
+
/** The 0.2.0 name, for a relay that only attaches. */
|
|
135
|
+
export type AttachRelayOptions = SuggestionRelayOptions;
|
|
136
|
+
/** A bucket the relay controls, for files past a request body limit. `uploadUrl` answers a one-time signed PUT that
|
|
137
|
+
* can only create `key` once, with this type and at most these bytes: on Google Cloud Storage a V4 signed URL for PUT
|
|
138
|
+
* with `x-goog-if-generation-match: 0` and `x-goog-content-length-range: 0,<bytes>` as signed headers; on S3 a
|
|
139
|
+
* presigned PUT with `If-None-Match: *` and the content type and length signed. `read` answers what arrived (or null),
|
|
140
|
+
* `discard` deletes it once the relay has written it to GitHub. */
|
|
141
|
+
export interface StagingAdapter {
|
|
142
|
+
uploadUrl(key: string, o: {
|
|
143
|
+
type: string;
|
|
144
|
+
bytes: number;
|
|
145
|
+
}): Promise<{
|
|
146
|
+
url: string;
|
|
147
|
+
headers?: Record<string, string>;
|
|
148
|
+
}>;
|
|
149
|
+
read(key: string): Promise<ReadableStream<Uint8Array> | Uint8Array | ArrayBuffer | null>;
|
|
150
|
+
discard(key: string): Promise<void>;
|
|
151
|
+
}
|
|
152
|
+
/** How long a filing holds its report before another try may take it over (its process is presumed dead). */
|
|
153
|
+
export declare const FILING_LEASE_MS: number;
|
|
154
|
+
declare const ACTIONS: readonly ['file', 'follow-up', 'status', 'attach', 'upload-url'];
|
|
155
|
+
type Action = (typeof ACTIONS)[number];
|
|
156
|
+
export declare function createSuggestionRelay(opts: SuggestionRelayOptions, only?: Action): (req: Request) => Promise<Response>;
|
|
157
|
+
/** A relay that only attaches (the 0.2.0 shape): every request is `attach`, whatever its path. */
|
|
158
|
+
export declare const createAttachRelay: (opts: AttachRelayOptions) => (req: Request) => Promise<Response>;
|
|
159
|
+
export {};
|