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.
@@ -0,0 +1,65 @@
1
+ import type { AuthClient } from "../auth/client.js";
2
+ import { type HttpConfig } from "../http.js";
3
+ import type { NaroResponse } from "../types.js";
4
+ import type { DownloadOption, FileObject, FolderEntry, ListOptions, UpdateOptions, UploadBody, UploadOptions, UploadResult } from "./types.js";
5
+ /** One bucket's objects: supabase-js's StorageFileApi. */
6
+ export declare class StorageFileApi {
7
+ #private;
8
+ constructor(http: HttpConfig, auth: AuthClient, bucket: string);
9
+ /**
10
+ * A new object; with `upsert: true`, one of the same name is replaced.
11
+ * `null` options, as a JS caller may pass, are no options.
12
+ */
13
+ upload(path: string, body: UploadBody, options?: UploadOptions): Promise<NaroResponse<UploadResult>>;
14
+ /** Replaces an object that is there (`OBJECT_NOT_FOUND` otherwise). */
15
+ update(path: string, body: UploadBody, options?: UpdateOptions): Promise<NaroResponse<UploadResult>>;
16
+ /**
17
+ * The object's bytes, as a Blob of the type the worker serves it under:
18
+ * the stored type for the images, audio, video, PDF, plain text, CSV,
19
+ * JSON and SVG it serves as themselves, `application/octet-stream` for
20
+ * anything else (HTML, scripts, archives, unknown types — `info` and
21
+ * `list` hold the stored type).
22
+ */
23
+ download(path: string): Promise<NaroResponse<Blob>>;
24
+ info(path: string): Promise<NaroResponse<FileObject>>;
25
+ /**
26
+ * Whether the caller can see the object: `OBJECT_NOT_FOUND` is false,
27
+ * every other error comes back as it is (plan C1 — asked with `info`,
28
+ * not HEAD, whose answer has no body to tell a missing bucket from a
29
+ * missing object).
30
+ */
31
+ exists(path: string): Promise<NaroResponse<boolean>>;
32
+ /**
33
+ * What is directly below `prefix` (the whole bucket by default): folders
34
+ * first, then files. The options go to the worker as they are given.
35
+ */
36
+ list(prefix?: string, options?: ListOptions): Promise<NaroResponse<(FileObject | FolderEntry)[]>>;
37
+ /** Removes the objects named — 1 to 1,000 — and answers the ones it removed. */
38
+ remove(paths: string[]): Promise<NaroResponse<FileObject[]>>;
39
+ /**
40
+ * A link to the object for `expiresIn` seconds (1–604,800). It stops
41
+ * working once the object is replaced or removed.
42
+ */
43
+ createSignedUrl(path: string, expiresIn: number, options?: DownloadOption): Promise<NaroResponse<{
44
+ signedUrl: string;
45
+ }>>;
46
+ /**
47
+ * A public bucket's URL for the object. No request is made, so whether
48
+ * the bucket is public is not checked (as in supabase-js); a path no URL
49
+ * can carry is INVALID_PATH (plan C3), and a download name none can carry
50
+ * is the worker's INVALID_REQUEST for it.
51
+ */
52
+ getPublicUrl(path: string, options?: DownloadOption): NaroResponse<{
53
+ publicUrl: string;
54
+ }>;
55
+ }
56
+ /**
57
+ * `naro.storage`. Buckets are made with `naro storage bucket create` or the
58
+ * MCP server, never from a client (original spec §6).
59
+ */
60
+ export declare class StorageClient {
61
+ #private;
62
+ constructor(http: HttpConfig, auth: AuthClient);
63
+ /** The objects of one bucket. */
64
+ from(bucket: string): StorageFileApi;
65
+ }
@@ -0,0 +1,76 @@
1
+ import type { NaroError } from "../errors.js";
2
+ /**
3
+ * The most one upload request carries (original spec §9.1): 50 MiB.
4
+ * data-core's MAX_SINGLE_UPLOAD_BYTES — storage/wire.test.ts holds them
5
+ * equal. Over it, the edge cuts the body off and the browser often cannot
6
+ * read the answer: the SDK refuses first.
7
+ */
8
+ export declare const MAX_SINGLE_UPLOAD_BYTES = 52428800;
9
+ /** The worker's words (data-core's catalog) for what the SDK refuses itself. */
10
+ export declare const STORAGE_MESSAGES: {
11
+ readonly bucket: "The bucket name is not valid.";
12
+ readonly tooLarge: "Uploads over 50 MiB are not supported yet.";
13
+ readonly request: "The storage request is not valid.";
14
+ readonly body: "upload takes a Blob, File, ArrayBuffer, typed array or string.";
15
+ };
16
+ /**
17
+ * The words the worker's routes take as the first segment after `/object/`
18
+ * (data-core's STORAGE_RESERVED_WORDS): as a bucket, the URL would name
19
+ * another route. No bucket can have one of these names.
20
+ */
21
+ export declare const RESERVED_BUCKETS: ReadonlySet<string>;
22
+ /** Why the worker refuses a `download` name (serve.ts's DOWNLOAD_NAME_REASON). */
23
+ export declare const DOWNLOAD_NAME_REASON = "must be a file name of up to 255 bytes";
24
+ export type Encoded = {
25
+ ok: true;
26
+ encoded: string;
27
+ } | {
28
+ ok: false;
29
+ error: NaroError;
30
+ };
31
+ export declare function invalidPath(reason: string): NaroError;
32
+ export declare function objectTooLarge(): NaroError;
33
+ /**
34
+ * An object path as a URL carries it, each segment encoded once and `/`
35
+ * kept as the separator — or the worker's INVALID_PATH for the paths no
36
+ * URL can carry as they are (original spec §13, plan C2): an empty name, a
37
+ * `/` at either end, `//`, a `.` or `..` segment (WHATWG URL parsing folds
38
+ * `..` and `%2E%2E` alike, so the worker would get another path), and a
39
+ * lone surrogate (`encodeURIComponent` throws on one). Every other rule is
40
+ * the worker's alone — one set of rules, in one place.
41
+ */
42
+ export declare function encodePath(path: string): Encoded;
43
+ /**
44
+ * `?download` for the object's own name, `?download=<name>` for another, or
45
+ * the worker's own INVALID_REQUEST for a name `encodeURIComponent` throws on
46
+ * (a lone surrogate: the sign route refuses the same name in its body). Every
47
+ * other rule on the name is the worker's alone.
48
+ */
49
+ export declare function downloadQuery(download: boolean | string | undefined): Encoded;
50
+ /**
51
+ * A bucket name as the URL's segment — encoded (a valid name is unchanged
52
+ * by it), or INVALID_PATH for the names that would not reach a bucket:
53
+ * empty, `.`, `..`, a lone surrogate and the routes' own words.
54
+ */
55
+ export declare function encodeBucket(bucket: string): Encoded;
56
+ /** Why the worker refuses a cache-control it cannot read (its upload.ts). */
57
+ export declare const CACHE_CONTROL_REASON = "must be max-age=<seconds> from 0 to 31536000";
58
+ /**
59
+ * The upload's `contentType` and `cacheControl` as headers, or the worker's
60
+ * answer for a value it cannot read when no header can carry it (PR #90
61
+ * review 1): INVALID_CONTENT_TYPE with data-core's words (the type cut at
62
+ * 256 UTF-16 units, as `storageName` cuts it), and INVALID_REQUEST
63
+ * `cache-control`. Every other value is the worker's to judge.
64
+ */
65
+ export declare function headerProblem(contentType: string | undefined, cacheControl: string): NaroError | null;
66
+ export type Payload = {
67
+ ok: true;
68
+ body: Blob | ArrayBuffer | Uint8Array<ArrayBuffer>;
69
+ size: number;
70
+ type: string;
71
+ } | {
72
+ ok: false;
73
+ error: NaroError;
74
+ };
75
+ /** The upload's body, its bytes and its type — or INVALID_BODY. */
76
+ export declare function payloadOf(body: unknown, contentType: string | undefined): Payload;
@@ -0,0 +1,58 @@
1
+ /** An object as the worker answers it. */
2
+ export type FileObject = {
3
+ id: string;
4
+ /** The full path from `info` and `remove`; the name below the prefix in `list`. */
5
+ name: string;
6
+ bucket_id: string;
7
+ /** Who uploaded it; null for the anon or the service key. */
8
+ owner: string | null;
9
+ owner_id: string | null;
10
+ /** The bytes' etag. */
11
+ version: string;
12
+ created_at: string;
13
+ updated_at: string;
14
+ /** `eTag`, `size`, `mimetype`, `cacheControl`, `lastModified`, `contentLength`, `httpStatusCode`. */
15
+ metadata: Record<string, unknown>;
16
+ };
17
+ /** A folder in a listing: a name and nothing else. Folders come first. */
18
+ export type FolderEntry = {
19
+ name: string;
20
+ id: null;
21
+ created_at: null;
22
+ updated_at: null;
23
+ metadata: null;
24
+ };
25
+ /**
26
+ * What an upload sends: bytes whose length `fetch` knows, so it sets
27
+ * `Content-Length`. A string goes as UTF-8. Not a stream, not `FormData`.
28
+ */
29
+ export type UploadBody = Blob | ArrayBuffer | ArrayBufferView | string;
30
+ export type UploadOptions = {
31
+ /** Default: the Blob's type, `text/plain;charset=utf-8` for a string, else `application/octet-stream`. */
32
+ contentType?: string;
33
+ /** Seconds a browser may keep it, as text. Default `"3600"`. */
34
+ cacheControl?: string;
35
+ /** Replace an object of the same name. Default false. */
36
+ upsert?: boolean;
37
+ };
38
+ export type UpdateOptions = Omit<UploadOptions, "upsert">;
39
+ export type UploadResult = {
40
+ id: string;
41
+ path: string;
42
+ fullPath: string;
43
+ };
44
+ export type ListOptions = {
45
+ /** 1–1,000; 100 by default. */
46
+ limit?: number;
47
+ /** 0–100,000. */
48
+ offset?: number;
49
+ /** Folders come first whatever the sort. Default name ascending. */
50
+ sortBy?: {
51
+ column?: "name" | "created_at" | "updated_at";
52
+ order?: "asc" | "desc";
53
+ };
54
+ };
55
+ /** `true` downloads under the object's own name, a string under that name. */
56
+ export type DownloadOption = {
57
+ download?: boolean | string;
58
+ };
package/dist/types.d.ts CHANGED
@@ -81,10 +81,19 @@ export type OrderOptions = {
81
81
  /** Where NULLs go. Omitted, SQLite's default: first ascending, last descending. */
82
82
  nullsFirst?: boolean;
83
83
  };
84
- /** Better Auth's user (spec §13), as JSON: dates are ISO strings. */
84
+ /**
85
+ * The signed-in user, as naro Auth answers it (spec §13): dates are ISO
86
+ * strings. `email` is null for a user created with only a username
87
+ * (`auth.admin.createUser({ username, password })`).
88
+ */
85
89
  export type User = {
86
90
  id: string;
87
- email: string;
91
+ email: string | null;
92
+ /**
93
+ * Lowercased; null for a user without one. Absent from a backend whose
94
+ * Naro worker predates usernames (`naro backend enable` updates it).
95
+ */
96
+ username?: string | null;
88
97
  name: string;
89
98
  emailVerified: boolean;
90
99
  image?: string | null;
@@ -92,7 +101,7 @@ export type User = {
92
101
  updatedAt: string;
93
102
  [field: string]: unknown;
94
103
  };
95
- /** Better Auth's session, as JSON. */
104
+ /** The session, as naro Auth answers it. */
96
105
  export type Session = {
97
106
  id: string;
98
107
  userId: string;
@@ -124,7 +133,16 @@ export type SignOutResponse = {
124
133
  export type SignUpCredentials = {
125
134
  email: string;
126
135
  password: string;
127
- /** Better Auth requires a name; omitted, the email's local part is sent. */
136
+ /**
137
+ * A name to sign in by as well as the email (spec 2026-09-26 §2.3): 3–30
138
+ * letters, digits, `_` and `.`, stored lowercased. One that is taken
139
+ * answers 409 USERNAME_TAKEN.
140
+ */
141
+ username?: string;
142
+ /**
143
+ * Every user has a display name; omitted, the username is sent, or else
144
+ * the email's local part.
145
+ */
128
146
  name?: string;
129
147
  /**
130
148
  * Where the confirmation link sends the browser back to, when the project
@@ -133,8 +151,17 @@ export type SignUpCredentials = {
133
151
  */
134
152
  redirectTo?: string;
135
153
  };
154
+ /**
155
+ * An email and its password, or a username and its password (spec
156
+ * 2026-09-26 §2.3). A username is matched in any case.
157
+ */
136
158
  export type SignInCredentials = {
137
159
  email: string;
160
+ username?: undefined;
161
+ password: string;
162
+ } | {
163
+ username: string;
164
+ email?: undefined;
138
165
  password: string;
139
166
  };
140
167
  export type OAuthProvider = "google" | "github";
@@ -183,4 +210,59 @@ export type AuthActionResponse = {
183
210
  data: null;
184
211
  error: NaroError | null;
185
212
  };
213
+ /**
214
+ * A user as `naro.auth.admin` answers it — never a password or a hash.
215
+ * `email` is null for a user created with only a username.
216
+ */
217
+ export type AdminUser = {
218
+ id: string;
219
+ email: string | null;
220
+ /** Lowercased; null for a user without one. */
221
+ username: string | null;
222
+ emailVerified: boolean;
223
+ /** ISO 8601. */
224
+ createdAt: string;
225
+ };
226
+ /**
227
+ * `auth.admin.createUser()`: an email, a username or both, and a password
228
+ * of at least the project's minimum length (6 unless
229
+ * `naro backend auth password --min-length` changed it). The user is
230
+ * confirmed — signs in at once, even where sign-in waits for a confirmed
231
+ * email — unless `emailConfirm: false`, which needs an email. (Supabase's
232
+ * `email_confirm` is false by default; here confirmed is the default.)
233
+ */
234
+ export type AdminUserAttributes = {
235
+ email?: string;
236
+ username?: string;
237
+ password: string;
238
+ emailConfirm?: boolean;
239
+ };
240
+ /** `auth.admin.listUsers()`: page 1 and 50 a page unless given; at most 1000. */
241
+ export type PageParams = {
242
+ page?: number;
243
+ perPage?: number;
244
+ };
245
+ export type AdminUserResponse = AuthResponse<{
246
+ user: AdminUser | null;
247
+ }>;
248
+ /** A page of users, newest first, and how many there are in all. */
249
+ export type AdminUserListResponse = AuthResponse<{
250
+ users: AdminUser[];
251
+ total: number;
252
+ }>;
253
+ /**
254
+ * The codes sign-up, sign-in and `auth.admin` answer that an app branches
255
+ * on. `NaroError.code` can be any other string too.
256
+ */
257
+ export type AuthErrorCode =
258
+ /** Sign-ups are closed: only `auth.admin.createUser` makes users. */
259
+ "SIGNUPS_DISABLED"
260
+ /** Sign-up or `createUser` with a username someone has, in any case. */
261
+ | "USERNAME_TAKEN" | "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL"
262
+ /** "Use at least {n} characters." — the project's minimum. */
263
+ | "PASSWORD_TOO_SHORT" | "PASSWORD_TOO_LONG" | "USERNAME_TOO_SHORT" | "USERNAME_TOO_LONG" | "INVALID_USERNAME" | "INVALID_EMAIL" | "INVALID_EMAIL_OR_PASSWORD" | "INVALID_USERNAME_OR_PASSWORD" | "EMAIL_NOT_VERIFIED" | "INVALID_SESSION"
264
+ /** `auth.admin` without the service key. */
265
+ | "PERMISSION_DENIED"
266
+ /** `auth.admin.deleteUser` with an id that is no user. */
267
+ | "USER_NOT_FOUND" | "RATE_LIMITED" | "WRITE_QUOTA_EXCEEDED";
186
268
  export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "naro-js",
3
- "version": "0.1.0",
4
- "description": "Auth and data for your Naro project, from the browser or the server.",
3
+ "version": "0.3.0",
4
+ "description": "Auth, data and files for your Naro project, from the browser or the server.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "files": [
@@ -25,8 +25,8 @@
25
25
  "vitest": "3.2.7",
26
26
  "@next-deploy/backend-worker": "0.1.0",
27
27
  "@next-deploy/config": "0.1.0",
28
- "@next-deploy/sdk": "0.1.0",
29
- "@next-deploy/data-core": "0.1.0"
28
+ "@next-deploy/data-core": "0.1.0",
29
+ "@next-deploy/sdk": "0.1.0"
30
30
  },
31
31
  "scripts": {
32
32
  "build": "tsup && pnpm build:types",