naro-js 0.1.0 → 0.3.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 +187 -3
- package/dist/auth/admin.d.ts +23 -0
- package/dist/auth/client.d.ts +24 -5
- package/dist/auth/flows.d.ts +1 -1
- package/dist/client.d.ts +11 -1
- package/dist/errors.d.ts +15 -5
- package/dist/http.d.ts +27 -4
- package/dist/index.d.ts +4 -1
- package/dist/index.js +571 -18
- package/dist/storage/client.d.ts +65 -0
- package/dist/storage/path.d.ts +76 -0
- package/dist/storage/types.d.ts +58 -0
- package/dist/types.d.ts +86 -4
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# naro-js
|
|
2
2
|
|
|
3
|
-
Auth and
|
|
3
|
+
Auth, data and files for your [Naro](https://naro.sh) project, from the browser or
|
|
4
4
|
the server. A project's backend brings its own database; this client signs
|
|
5
5
|
users in and reads and writes it through the project's policies. No SQL, no
|
|
6
6
|
database credentials, no dependencies.
|
|
@@ -71,12 +71,133 @@ await naro.from("posts").delete().eq("id", 10);
|
|
|
71
71
|
`auth: { storage }` with your own `getItem` / `setItem` / `removeItem`.
|
|
72
72
|
- Types: `createClient<Database>()` with the `Database` that
|
|
73
73
|
`naro gen types` writes — see [Types](#types).
|
|
74
|
+
- Sign-in is naro Auth, the project's own (like Supabase Auth). Email and
|
|
75
|
+
password sign-up and sign-in need no setup: they work as soon as the
|
|
76
|
+
backend is on. Resend is needed only to send confirmation and password
|
|
77
|
+
reset emails.
|
|
78
|
+
- Admin accounts, closed sign-ups, sign-in by username and the password
|
|
79
|
+
length — see [Admin accounts and closed sign-ups](#admin-accounts-and-closed-sign-ups).
|
|
74
80
|
- Email and social sign-in — see [Sign-in flows](#sign-in-flows).
|
|
81
|
+
- Files — see [Storage](#storage).
|
|
82
|
+
|
|
83
|
+
## Policies
|
|
84
|
+
|
|
85
|
+
What a request may do is decided by the table's policies — Postgres's
|
|
86
|
+
row-level security, as in Supabase. A table without a policy refuses every
|
|
87
|
+
anon and signed-in request (only the service key gets through), so write a
|
|
88
|
+
table's policies in the migration that creates it: `create policy` after the
|
|
89
|
+
`create table`, in the same file. `naro db push` applies them with the table,
|
|
90
|
+
all or nothing.
|
|
91
|
+
|
|
92
|
+
```sql
|
|
93
|
+
-- migrations/0002_todos.sql: each signed-in user reads and changes their own rows.
|
|
94
|
+
create table todos (
|
|
95
|
+
id integer primary key,
|
|
96
|
+
user_id uuid not null,
|
|
97
|
+
title text not null
|
|
98
|
+
);
|
|
99
|
+
create policy "own rows" on todos for all to authenticated
|
|
100
|
+
using ((select auth.uid()) = user_id)
|
|
101
|
+
with check ((select auth.uid()) = user_id);
|
|
102
|
+
|
|
103
|
+
-- migrations/0003_posts.sql: anyone reads, signed in or not.
|
|
104
|
+
create table posts (id integer primary key, title text not null);
|
|
105
|
+
create policy "read" on posts for select to anon, authenticated using (true);
|
|
106
|
+
|
|
107
|
+
-- migrations/0004_reports.sql: an app only its admins sign in to. First run
|
|
108
|
+
-- naro backend auth signups off and create the admin accounts: authenticated
|
|
109
|
+
-- then means exactly those accounts.
|
|
110
|
+
create table reports (id integer primary key, body text not null);
|
|
111
|
+
create policy "admins" on reports for all to authenticated using (true) with check (true);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
A request no policy lets through, and a written row that fails a check,
|
|
115
|
+
answer `403 PERMISSION_DENIED`; rows a policy's `using` does not pass are left
|
|
116
|
+
out without an error. The `naro-cli` README lists what a
|
|
117
|
+
migration accepts and refuses (`DISABLE ROW LEVEL SECURITY`, `GRANT`,
|
|
118
|
+
`REVOKE` and `ALTER POLICY` are refused), the warnings it answers with, and
|
|
119
|
+
`naro policy set`, a shorthand for the common rules.
|
|
120
|
+
|
|
121
|
+
## Admin accounts and closed sign-ups
|
|
122
|
+
|
|
123
|
+
An app that only its admins sign in to: close sign-ups, then create the
|
|
124
|
+
accounts yourself. With sign-ups closed, `authenticated` in a policy means
|
|
125
|
+
exactly the users you created.
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
naro backend auth signups off
|
|
129
|
+
echo "$ADMIN_PASSWORD" | naro backend users create --username admin --password-stdin
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
// In the app: sign in by username, or by email.
|
|
134
|
+
await naro.auth.signIn({ username: "admin", password });
|
|
135
|
+
await naro.auth.signIn({ email, password });
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The same from your own server, with supabase-js's `auth.admin` names:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
// Server only: the service key skips every policy.
|
|
142
|
+
const admin = createClient({
|
|
143
|
+
url: "https://my-app.naro.sh",
|
|
144
|
+
serviceKey: process.env.NARO_SERVICE_KEY,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const { data, error } = await admin.auth.admin.createUser({
|
|
148
|
+
username: "admin", // and/or email
|
|
149
|
+
password,
|
|
150
|
+
// emailConfirm: false — only with an email; users are confirmed otherwise
|
|
151
|
+
});
|
|
152
|
+
const {
|
|
153
|
+
data: { users, total },
|
|
154
|
+
} = await admin.auth.admin.listUsers({ page: 1, perPage: 50 });
|
|
155
|
+
await admin.auth.admin.deleteUser(data.user.id);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
- `createUser` takes an email, a username or both. The user is confirmed
|
|
159
|
+
and signs in at once — while sign-ups are closed, and while sign-in waits
|
|
160
|
+
for a confirmed email — unless `emailConfirm: false` (which needs an
|
|
161
|
+
email). A user with only a username answers `email: null`, here, in
|
|
162
|
+
`signIn` and in `getUser()`, and signs in with its username. Answers
|
|
163
|
+
carry `id`, `email`, `username`, `emailVerified` and `createdAt`, never a
|
|
164
|
+
password.
|
|
165
|
+
- `listUsers` is newest first, 50 a page unless `perPage` says (at most
|
|
166
|
+
1000), with the `total`. `deleteUser(id)` removes the user and its
|
|
167
|
+
sign-in methods and ends its sessions; an unknown id is
|
|
168
|
+
`404 USER_NOT_FOUND` ("No user has this id.").
|
|
169
|
+
- `auth.admin` works only on a client made with `serviceKey`. On an anon
|
|
170
|
+
client each call answers `403 PERMISSION_DENIED` ("Only the service key
|
|
171
|
+
can manage users.") without a request.
|
|
172
|
+
- A username is 3–30 letters, digits, `_` and `.`, and is matched in any
|
|
173
|
+
case (stored lowercased). `signUp({ email, password, username })` sets one
|
|
174
|
+
at sign-up.
|
|
175
|
+
- Closed sign-ups: every `signUp` answers `403 SIGNUPS_DISABLED` ("Sign-ups
|
|
176
|
+
are closed for this project."), and so does a first Google or GitHub
|
|
177
|
+
sign-in, in `getSessionFromUrl()`. Users that exist keep signing in, with
|
|
178
|
+
a password or a provider. `naro backend auth signups on` opens them again.
|
|
179
|
+
- While sign-ups are open, a sign-up with a username someone has answers
|
|
180
|
+
`409 USERNAME_TAKEN` ("That username is already taken.") — so anyone can
|
|
181
|
+
learn which usernames exist, as fast as sign-up's per-address rate limit
|
|
182
|
+
lets them. With sign-ups closed, every sign-up answers
|
|
183
|
+
`403 SIGNUPS_DISABLED` first. `createUser` answers `USERNAME_TAKEN` too,
|
|
184
|
+
and `422 USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL` for an email in use.
|
|
185
|
+
- Passwords take at least 6 characters unless
|
|
186
|
+
`naro backend auth password --min-length <n>` (6–128) says more. A
|
|
187
|
+
shorter one, on sign-up, `createUser` or a reset, answers
|
|
188
|
+
`400 PASSWORD_TOO_SHORT` naming it: "Use at least 10 characters." Sign-in
|
|
189
|
+
never checks the length, so raising it locks nobody out.
|
|
190
|
+
- Creating and deleting users are writes: past the project's daily write
|
|
191
|
+
limit they answer `429 WRITE_QUOTA_EXCEEDED`, like sign-up.
|
|
192
|
+
- A backend turned on before these existed gets them with its next upload:
|
|
193
|
+
run `naro backend enable` once. Until then `auth.admin` and
|
|
194
|
+
`signIn({ username })` answer `404 NOT_FOUND`.
|
|
75
195
|
|
|
76
196
|
## Sign-in flows
|
|
77
197
|
|
|
78
198
|
Google and GitHub sign-in, email confirmation and password reset, once the
|
|
79
|
-
project has set them up (`naro backend auth`).
|
|
199
|
+
project has set them up (`naro backend auth`). Email and password sign-up
|
|
200
|
+
and sign-in need none of it. The names follow supabase-js.
|
|
80
201
|
|
|
81
202
|
```ts
|
|
82
203
|
// Sends the browser to the project's /_naro/v1/auth/authorize/github, then
|
|
@@ -331,4 +452,67 @@ await naro.from("posts").select().contains("tags", ["a", "b"]);
|
|
|
331
452
|
- Limits: groups nest 4 deep (`not` counts); one request holds at most 100
|
|
332
453
|
conditions, 4 of them `contains` / `containedBy` / `overlaps`; one
|
|
333
454
|
statement binds at most 100 values (a select's limit and offset are two of
|
|
334
|
-
them) and compiles to at most 100,000 bytes of SQL.
|
|
455
|
+
them) and compiles to at most 100,000 bytes of SQL. A `like` or `ilike`
|
|
456
|
+
pattern is at most 50 bytes of UTF-8, which is D1's limit; in `like`, a
|
|
457
|
+
literal `?` or `[` counts as 3. A longer one is `400 INVALID_QUERY` before
|
|
458
|
+
the database is asked.
|
|
459
|
+
|
|
460
|
+
## Storage
|
|
461
|
+
|
|
462
|
+
Files your users upload — avatars, attachments, documents — go in the
|
|
463
|
+
project's Storage: Cloudflare R2 behind the same URL, under policies on
|
|
464
|
+
`storage.objects` written as in Supabase. Turn it on with
|
|
465
|
+
`naro storage enable` (the backend must be on), make buckets with
|
|
466
|
+
`naro storage bucket create avatars --public`, and write the policies in a
|
|
467
|
+
migration that `naro db push` applies:
|
|
468
|
+
|
|
469
|
+
```sql
|
|
470
|
+
create policy "avatars are readable" on storage.objects
|
|
471
|
+
for select to public using (bucket_id = 'avatars');
|
|
472
|
+
create policy "upload into own folder" on storage.objects
|
|
473
|
+
for insert to authenticated
|
|
474
|
+
with check (bucket_id = 'avatars' and (storage.foldername(name))[1] = auth.uid()::text);
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
```ts
|
|
478
|
+
const avatars = naro.storage.from("avatars");
|
|
479
|
+
await avatars.upload(`${user.id}/me.png`, file, { upsert: true });
|
|
480
|
+
const { data: image } = avatars.getPublicUrl(`${user.id}/me.png`); // no request
|
|
481
|
+
const { data: files } = await avatars.list(user.id, { limit: 20 });
|
|
482
|
+
await avatars.remove([`${user.id}/old.png`]);
|
|
483
|
+
const { data: link } = await naro.storage
|
|
484
|
+
.from("docs")
|
|
485
|
+
.createSignedUrl("team-1/plan.pdf", 600);
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
- `upload`, `update`, `download`, `info`, `exists`, `list`, `remove`,
|
|
489
|
+
`createSignedUrl` and `getPublicUrl` take supabase-js's arguments and
|
|
490
|
+
answer its shapes (`FileObject` in snake_case), as `{ data, error }`.
|
|
491
|
+
One exception: supabase-js's `info` answers a camelCase object
|
|
492
|
+
(`bucketId`, `createdAt`, `size`, `contentType`, …); naro-js's `info`
|
|
493
|
+
answers the same snake_case `FileObject` as `list`, with the size and type
|
|
494
|
+
in `metadata.size` and `metadata.mimetype`.
|
|
495
|
+
- An upload sends the file's own bytes, 50 MiB at most, typed as the file
|
|
496
|
+
is (`Blob.type`, or `application/octet-stream`; a string is
|
|
497
|
+
`text/plain;charset=utf-8`). The body is a `Blob`, `File`, `ArrayBuffer`,
|
|
498
|
+
typed array or string — not a stream or `FormData`, and not a buffer
|
|
499
|
+
transferred away (detached), which has no bytes left: `INVALID_BODY`.
|
|
500
|
+
- A `contentType` or `cacheControl` no header can carry (a CR, LF or NUL
|
|
501
|
+
inside, or a character past U+00FF) is answered before a request, as the
|
|
502
|
+
backend answers one it cannot read: `INVALID_CONTENT_TYPE`, and
|
|
503
|
+
`INVALID_REQUEST` with `details.path` `cache-control`.
|
|
504
|
+
- supabase-js's `upload(path, file)` sends `multipart/form-data`, which Naro
|
|
505
|
+
refuses with `400 INVALID_REQUEST`. Without naro-js, send the file's bytes
|
|
506
|
+
as the body of `POST /_naro/v1/storage/object/<bucket>/<path>`.
|
|
507
|
+
- Refused before a request, with the backend's own words (`INVALID_PATH`):
|
|
508
|
+
an empty path, a `/` at either end, `//`, a `.` or `..` segment, a lone
|
|
509
|
+
surrogate (`is not valid UTF-8`: no URL can carry it), and a bucket named
|
|
510
|
+
`""`, `.`, `..`, with a lone surrogate or a word the storage routes use
|
|
511
|
+
(`The bucket name is not valid.`). Everything else is the backend's to
|
|
512
|
+
check.
|
|
513
|
+
- `exists` answers `false` for `OBJECT_NOT_FOUND` and passes every other
|
|
514
|
+
error on. `getPublicUrl` makes no request and answers `{ data, error }`.
|
|
515
|
+
- `OBJECT_RATE_LIMITED` and `STORAGE_OPS_QUOTA_EXCEEDED` carry
|
|
516
|
+
`details.retryAfter`, in seconds.
|
|
517
|
+
- Not yet: `move`, `copy`, `createSignedUrls`, `list`'s `search`, files over
|
|
518
|
+
50 MiB, and bucket methods (buckets are made with `naro storage bucket`).
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type HttpConfig } from "../http.js";
|
|
2
|
+
import type { AdminUserAttributes, AdminUserListResponse, AdminUserResponse, PageParams } from "../types.js";
|
|
3
|
+
export declare class AuthAdminApi {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(http: HttpConfig, serviceKey: boolean);
|
|
6
|
+
/**
|
|
7
|
+
* Creates a user with a password: an email, a username or both. It is
|
|
8
|
+
* made while sign-ups are closed too, and is confirmed unless
|
|
9
|
+
* `emailConfirm: false`. A username-only user answers `email: null` and
|
|
10
|
+
* signs in with `signIn({ username, password })`. Refusals come as the
|
|
11
|
+
* worker says them: USERNAME_TAKEN (409),
|
|
12
|
+
* USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL (422), PASSWORD_TOO_SHORT ("Use at
|
|
13
|
+
* least {n} characters."), VALIDATION_ERROR, WRITE_QUOTA_EXCEEDED (429).
|
|
14
|
+
*/
|
|
15
|
+
createUser(attributes: AdminUserAttributes): Promise<AdminUserResponse>;
|
|
16
|
+
/** The project's users, newest first: page 1 and 50 a page unless given. */
|
|
17
|
+
listUsers(params?: PageParams): Promise<AdminUserListResponse>;
|
|
18
|
+
/**
|
|
19
|
+
* Deletes a user and its sign-in methods, and ends its sessions. Answers
|
|
20
|
+
* the user it was; USER_NOT_FOUND (404) for an id that is no user.
|
|
21
|
+
*/
|
|
22
|
+
deleteUser(id: string): Promise<AdminUserResponse>;
|
|
23
|
+
}
|
package/dist/auth/client.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type NaroError } from "../errors.js";
|
|
2
2
|
import { type HttpConfig } from "../http.js";
|
|
3
3
|
import type { AuthActionResponse, EmailRedirectOptions, OAuthResponse, SessionResponse, SignInCredentials, SignInWithOAuthOptions, SignOutResponse, SignUpCredentials, UrlSessionResponse, UserAttributes, UserResponse } from "../types.js";
|
|
4
|
+
import { AuthAdminApi } from "./admin.js";
|
|
4
5
|
import type { FlowVerifiers } from "./flows.js";
|
|
5
6
|
import type { RecoveryToken } from "./recovery.js";
|
|
6
|
-
/**
|
|
7
|
+
/** Sign-up needs a `name`; the email's local part stands in. */
|
|
7
8
|
export declare function defaultName(email: string): string;
|
|
8
9
|
export type AuthFlowStores = {
|
|
9
10
|
/** The PKCE verifiers of the provider sign-ins this browser began. */
|
|
@@ -24,24 +25,42 @@ export type AuthFlowStores = {
|
|
|
24
25
|
storage: object;
|
|
25
26
|
key: string;
|
|
26
27
|
};
|
|
28
|
+
/** Made with the service key: `admin` sends its requests. */
|
|
29
|
+
serviceKey: boolean;
|
|
27
30
|
};
|
|
28
31
|
/** How long the code exchange a sign-in redirect starts may take. */
|
|
29
32
|
export declare const EXCHANGE_TIMEOUT_MS = 15000;
|
|
30
33
|
/**
|
|
31
|
-
* `naro.auth` —
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
34
|
+
* `naro.auth` — naro Auth, the project's own sign-in (like Supabase Auth):
|
|
35
|
+
* email or username and password, email confirmation, password reset and
|
|
36
|
+
* provider sign-in through the worker's `/_naro/v1/auth/*` routes (spec
|
|
37
|
+
* §8.1, §13; P2-6 §13.3; 2026-09-26 §2.3). Email and password work with no
|
|
38
|
+
* setup; email is needed only to send confirmation and reset links. Every
|
|
39
|
+
* method resolves; none throws.
|
|
35
40
|
*/
|
|
36
41
|
export declare class AuthClient {
|
|
37
42
|
#private;
|
|
43
|
+
/**
|
|
44
|
+
* The service key's user management — createUser, listUsers, deleteUser,
|
|
45
|
+
* as in supabase-js (spec 2026-09-26 §2.2). Only on a client made with
|
|
46
|
+
* `serviceKey`, on a server.
|
|
47
|
+
*/
|
|
48
|
+
readonly admin: AuthAdminApi;
|
|
38
49
|
constructor(http: HttpConfig, flow: AuthFlowStores);
|
|
39
50
|
/**
|
|
40
51
|
* Creates the account and signs in; the token is stored for later calls.
|
|
41
52
|
* When the project requires a confirmed email there is no session yet —
|
|
42
53
|
* `user` comes back, and after the confirmation link the user signs in.
|
|
54
|
+
* While the project's sign-ups are closed every sign-up answers 403
|
|
55
|
+
* SIGNUPS_DISABLED; while they are open, a taken `username` answers 409
|
|
56
|
+
* USERNAME_TAKEN.
|
|
43
57
|
*/
|
|
44
58
|
signUp(credentials: SignUpCredentials): Promise<UserResponse>;
|
|
59
|
+
/**
|
|
60
|
+
* Signs in with an email and password, or a username and password (spec
|
|
61
|
+
* 2026-09-26 §2.3); the token is stored for later calls. A wrong password
|
|
62
|
+
* answers 401 INVALID_EMAIL_OR_PASSWORD, or INVALID_USERNAME_OR_PASSWORD.
|
|
63
|
+
*/
|
|
45
64
|
signIn(credentials: SignInCredentials): Promise<UserResponse>;
|
|
46
65
|
/**
|
|
47
66
|
* Starts a Google or GitHub sign-in: sends the browser, with a top-level
|
package/dist/auth/flows.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TokenStore } from "./storage.js";
|
|
2
|
-
/** A flow's life: the worker's flow row and
|
|
2
|
+
/** A flow's life: the worker's flow row and its sign-in state. */
|
|
3
3
|
export declare const FLOW_TTL_MS: number;
|
|
4
4
|
/** More in flight than anyone clicks; the oldest go first. */
|
|
5
5
|
export declare const MAX_FLOWS = 10;
|
package/dist/client.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { AuthClient } from "./auth/client.js";
|
|
|
2
2
|
import { type AuthStorage } from "./auth/storage.js";
|
|
3
3
|
import { type FetchLike } from "./http.js";
|
|
4
4
|
import { TableBuilder } from "./query/builder.js";
|
|
5
|
+
import { StorageClient } from "./storage/client.js";
|
|
5
6
|
import type { DatabaseShape, InsertOf, RowOf, TableName, TablesOf, UntypedDatabase, UpdateOf } from "./types.js";
|
|
6
7
|
type CommonOptions = {
|
|
7
8
|
/**
|
|
@@ -46,8 +47,17 @@ export declare const CONFIG_MESSAGES: {
|
|
|
46
47
|
};
|
|
47
48
|
export declare class NaroClient<Database extends DatabaseShape<Database>> {
|
|
48
49
|
#private;
|
|
49
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* Sign-up, sign-in, sign-out and the current user (spec §13); with the
|
|
52
|
+
* service key, `auth.admin` manages users.
|
|
53
|
+
*/
|
|
50
54
|
readonly auth: AuthClient;
|
|
55
|
+
/**
|
|
56
|
+
* Files in the project's Naro Storage (first-release design 2026-10-06
|
|
57
|
+
* §8.1): `storage.from(bucket)` uploads, downloads, lists and removes
|
|
58
|
+
* them, as supabase-js does.
|
|
59
|
+
*/
|
|
60
|
+
readonly storage: StorageClient;
|
|
51
61
|
constructor(options: ClientOptions);
|
|
52
62
|
/** Start a query on `table`: `.select()`, `.insert()`, `.update()` or `.delete()`. */
|
|
53
63
|
from<Table extends TableName<Database>>(table: Table): TableBuilder<RowOf<TablesOf<Database>[Table]>, InsertOf<TablesOf<Database>[Table]>, UpdateOf<TablesOf<Database>[Table]>>;
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import type { AuthErrorCode } from "./types.js";
|
|
1
2
|
/**
|
|
2
3
|
* Every failure the SDK hands back (spec §15). The Naro worker's errors
|
|
3
4
|
* (spec §14) arrive as they are; the SDK makes its own only for what never
|
|
4
5
|
* got a readable answer from the worker, and for queries it can tell are
|
|
5
6
|
* malformed before sending them. The messages are a contract — spec §15's
|
|
6
7
|
* "(P1-4 구현 결정)" block quotes them.
|
|
8
|
+
*
|
|
9
|
+
* `code` can be any string. The sign-in and user codes an app branches on
|
|
10
|
+
* (`AuthErrorCode`) are spelled out, so an editor completes them.
|
|
7
11
|
*/
|
|
8
12
|
export type NaroError = {
|
|
9
|
-
code: string;
|
|
13
|
+
code: AuthErrorCode | (string & {});
|
|
10
14
|
message: string;
|
|
11
15
|
details?: unknown;
|
|
12
16
|
};
|
|
@@ -37,15 +41,21 @@ export declare function codeVerifierMissing(): NaroError;
|
|
|
37
41
|
export declare function passwordRecoveryRequired(): NaroError;
|
|
38
42
|
/** `signInWithOAuth` with a provider the worker does not know. */
|
|
39
43
|
export declare function invalidProvider(): NaroError;
|
|
44
|
+
/**
|
|
45
|
+
* `naro.auth.admin` on a client made with the anon key (spec 2026-09-26
|
|
46
|
+
* admin users §2.2): the worker's own refusal, word for word, without the
|
|
47
|
+
* request — the answer would be the same.
|
|
48
|
+
*/
|
|
49
|
+
export declare function serviceKeyRequired(): NaroError;
|
|
40
50
|
/** No `crypto.subtle`: an http page that is not localhost. */
|
|
41
51
|
export declare function cryptoUnavailable(): NaroError;
|
|
42
52
|
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
43
53
|
/**
|
|
44
54
|
* The error a response carries, or null when it carries none. Every error
|
|
45
55
|
* under `/_naro/v1` comes as `{ data: null, error: { code, message,
|
|
46
|
-
* details? } }` — the
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
56
|
+
* details? } }` — the sign-in routes' too (spec §14, P1-2 리뷰 반영). Their
|
|
57
|
+
* success bodies are not wrapped, so a 2xx without an `error` is not an
|
|
58
|
+
* error; a failing status without the envelope is not a Naro answer at all
|
|
59
|
+
* (a proxy's HTML page, the app's own 404).
|
|
50
60
|
*/
|
|
51
61
|
export declare function errorFromBody(status: number, body: unknown): NaroError | null;
|
package/dist/http.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export type HttpConfig = {
|
|
|
15
15
|
tokens: TokenStore;
|
|
16
16
|
};
|
|
17
17
|
export type CallOptions = {
|
|
18
|
-
method: "GET" | "POST";
|
|
18
|
+
method: "GET" | "POST" | "DELETE";
|
|
19
19
|
/** Below `/_naro/v1`, e.g. `/data/query`. */
|
|
20
20
|
path: string;
|
|
21
21
|
/** Sent as JSON. */
|
|
@@ -53,9 +53,32 @@ export type CallResult = {
|
|
|
53
53
|
* §11.1), but only if it is still the token this request carried.
|
|
54
54
|
*/
|
|
55
55
|
export declare function call(config: HttpConfig, options: CallOptions): Promise<CallResult>;
|
|
56
|
+
export type SendOptions = {
|
|
57
|
+
method: "GET" | "POST" | "PUT";
|
|
58
|
+
/** Below `/_naro/v1`, e.g. `/storage/object/avatars/me.png`. */
|
|
59
|
+
path: string;
|
|
60
|
+
headers?: Record<string, string>;
|
|
61
|
+
body?: BodyInit;
|
|
62
|
+
withToken: boolean;
|
|
63
|
+
};
|
|
64
|
+
export type SendResult = {
|
|
65
|
+
error: NaroError;
|
|
66
|
+
} | {
|
|
67
|
+
error: null;
|
|
68
|
+
response: Response;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* A request whose body or answer is not JSON (Storage, first-release design
|
|
72
|
+
* 2026-10-06 §8.1): an upload's bytes go as they are, a download's come
|
|
73
|
+
* back unread. The same `apikey`, bearer token and no cookies as `call`;
|
|
74
|
+
* a failing answer is read the way `call` reads one — the worker's error
|
|
75
|
+
* envelope, or INVALID_RESPONSE — and every case resolves.
|
|
76
|
+
*/
|
|
77
|
+
export declare function send(config: HttpConfig, options: SendOptions): Promise<SendResult>;
|
|
56
78
|
/**
|
|
57
|
-
* The worker's
|
|
58
|
-
* `WRITE_QUOTA_EXCEEDED` (PR C) is the
|
|
59
|
-
* limit — Data API writes
|
|
79
|
+
* The worker's 429s that say how long to wait. `RATE_LIMITED` is the
|
|
80
|
+
* sign-in routes' per-address limiter; `WRITE_QUOTA_EXCEEDED` (PR C) is the
|
|
81
|
+
* project over its daily database write limit — Data API writes, sign-up
|
|
82
|
+
* and creating or deleting users wait for 00:00 UTC, reads still work.
|
|
60
83
|
*/
|
|
61
84
|
export declare const RETRY_AFTER_CODES: ReadonlySet<string>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
export { createClient, NaroClient, type ClientOptions } from "./client.js";
|
|
2
|
+
export type { AuthAdminApi } from "./auth/admin.js";
|
|
2
3
|
export type { AuthClient } from "./auth/client.js";
|
|
3
4
|
export type { AuthStorage } from "./auth/storage.js";
|
|
4
5
|
export type { NaroError } from "./errors.js";
|
|
5
6
|
export type { FetchLike } from "./http.js";
|
|
7
|
+
export type { StorageClient, StorageFileApi } from "./storage/client.js";
|
|
8
|
+
export type { DownloadOption, FileObject, FolderEntry, ListOptions, UpdateOptions, UploadBody, UploadOptions, UploadResult, } from "./storage/types.js";
|
|
6
9
|
export type { ArrayValues, FilterGroup, FilterGroupCallback, InsertBuilder, MatchQuery, MutationBuilder, RawFilterValue, SelectBuilder, TableBuilder, } from "./query/builder.js";
|
|
7
10
|
export { quoteFilterValue, type FilterOperatorName } from "./query/filters.js";
|
|
8
|
-
export type { AuthActionResponse, AuthFlowType, AuthResponse, DatabaseShape, EmailRedirectOptions, OAuthProvider, OAuthResponse, SignInWithOAuthOptions, UrlSessionResponse, UserAttributes, GenericTable, GenericView, NaroResponse, OrderOptions, Session, SessionResponse, SignInCredentials, SignOutResponse, SignUpCredentials, UntypedDatabase, User, UserResponse, } from "./types.js";
|
|
11
|
+
export type { AdminUser, AdminUserAttributes, AdminUserListResponse, AdminUserResponse, AuthActionResponse, AuthErrorCode, AuthFlowType, AuthResponse, DatabaseShape, EmailRedirectOptions, OAuthProvider, OAuthResponse, SignInWithOAuthOptions, UrlSessionResponse, UserAttributes, GenericTable, GenericView, NaroResponse, OrderOptions, PageParams, Session, SessionResponse, SignInCredentials, SignOutResponse, SignUpCredentials, UntypedDatabase, User, UserResponse, } from "./types.js";
|
|
9
12
|
export type { Filter, FilterNode, FilterOperator, Order, Query, Row, Value, } from "./wire.js";
|