@ikeboy003/cloudrest-client 0.0.1
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/dist/client.d.ts +20 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +40 -0
- package/dist/client.js.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +13 -0
- package/dist/errors.js.map +1 -0
- package/dist/filter.d.ts +17 -0
- package/dist/filter.d.ts.map +1 -0
- package/dist/filter.js +42 -0
- package/dist/filter.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/mutation.d.ts +47 -0
- package/dist/mutation.d.ts.map +1 -0
- package/dist/mutation.js +121 -0
- package/dist/mutation.js.map +1 -0
- package/dist/query.d.ts +56 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +120 -0
- package/dist/query.js.map +1 -0
- package/dist/rpc.d.ts +10 -0
- package/dist/rpc.d.ts.map +1 -0
- package/dist/rpc.js +50 -0
- package/dist/rpc.js.map +1 -0
- package/dist/serialize.d.ts +2 -0
- package/dist/serialize.d.ts.map +1 -0
- package/dist/serialize.js +46 -0
- package/dist/serialize.js.map +1 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +25 -0
- package/src/client.ts +57 -0
- package/src/errors.ts +10 -0
- package/src/filter.ts +50 -0
- package/src/index.ts +14 -0
- package/src/mutation.ts +158 -0
- package/src/query.ts +152 -0
- package/src/rpc.ts +54 -0
- package/src/serialize.ts +38 -0
- package/src/types.ts +31 -0
package/src/rpc.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// RPC caller. Posts JSON args to `/rpc/<name>` and returns the parsed body.
|
|
2
|
+
// Awaitable directly (PromiseLike), matching the QueryBuilder ergonomics:
|
|
3
|
+
//
|
|
4
|
+
// const result = await db.rpc("update_mastery", { p_concept_id, p_correct: true });
|
|
5
|
+
//
|
|
6
|
+
// PostgREST RPC convention:
|
|
7
|
+
// - POST /rpc/<fn> body = { argName: value, ... }
|
|
8
|
+
// - Response is the function return — scalar, row, or set-of rows.
|
|
9
|
+
|
|
10
|
+
import type { ExecContext } from "./query.js";
|
|
11
|
+
import { CloudRestError } from "./errors.js";
|
|
12
|
+
|
|
13
|
+
export class RpcCaller<Result = unknown> implements PromiseLike<Result> {
|
|
14
|
+
constructor(
|
|
15
|
+
private readonly ctx: ExecContext,
|
|
16
|
+
private readonly fn: string,
|
|
17
|
+
private readonly args: Record<string, unknown> = {},
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
private async exec(): Promise<Result> {
|
|
21
|
+
const url = `${this.ctx.baseUrl}/rpc/${this.fn}`;
|
|
22
|
+
const headers: Record<string, string> = {
|
|
23
|
+
Accept: "application/json",
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
...this.ctx.headers,
|
|
26
|
+
};
|
|
27
|
+
const token = await this.ctx.getToken?.();
|
|
28
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
29
|
+
|
|
30
|
+
const res = await this.ctx.fetch(url, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers,
|
|
33
|
+
body: JSON.stringify(this.args),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
const body = await res.text().catch(() => "");
|
|
38
|
+
throw new CloudRestError(res.status, res.statusText, body);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Functions returning void produce 204; everything else returns JSON.
|
|
42
|
+
if (res.status === 204) return undefined as Result;
|
|
43
|
+
const text = await res.text();
|
|
44
|
+
if (!text) return undefined as Result;
|
|
45
|
+
return JSON.parse(text) as Result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
then<T1 = Result, T2 = never>(
|
|
49
|
+
onfulfilled?: ((value: Result) => T1 | PromiseLike<T1>) | null,
|
|
50
|
+
onrejected?: ((reason: unknown) => T2 | PromiseLike<T2>) | null,
|
|
51
|
+
): PromiseLike<T1 | T2> {
|
|
52
|
+
return this.exec().then(onfulfilled, onrejected as never);
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/serialize.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// PostgREST-grammar value serialization. Mirrors the server's parser
|
|
2
|
+
// expectations: see cloudrest-workerd/src/parser/operators.ts.
|
|
3
|
+
//
|
|
4
|
+
// Rules:
|
|
5
|
+
// - null → "null" (use with `is.null`, never `eq.null`)
|
|
6
|
+
// - Date → ISO string
|
|
7
|
+
// - array → "(a,b,c)" with each element quoted if it contains special chars
|
|
8
|
+
// - string with special chars → wrapped in double quotes, internal " → \"
|
|
9
|
+
|
|
10
|
+
// Scalar values: only `,` and `"` need quoting. Parens / dots / spaces
|
|
11
|
+
// are fine bare — wrapping them in quotes makes the server search for
|
|
12
|
+
// the literal quoted string and return 0 rows.
|
|
13
|
+
const SCALAR_SPECIAL = /[,"]/;
|
|
14
|
+
// List items are inside `(a,b,c)`, so `(`, `)`, and `,` all need quoting.
|
|
15
|
+
const LIST_SPECIAL = /[(),."]/;
|
|
16
|
+
|
|
17
|
+
export function serializeValue(v: unknown): string {
|
|
18
|
+
if (v === null || v === undefined) return "null";
|
|
19
|
+
if (v instanceof Date) return v.toISOString();
|
|
20
|
+
if (Array.isArray(v)) return `(${v.map(serializeListItem).join(",")})`;
|
|
21
|
+
if (typeof v === "boolean") return v ? "true" : "false";
|
|
22
|
+
if (typeof v === "number" || typeof v === "bigint") return String(v);
|
|
23
|
+
return escapeIfNeeded(String(v), SCALAR_SPECIAL);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function serializeListItem(v: unknown): string {
|
|
27
|
+
if (v === null || v === undefined) return "null";
|
|
28
|
+
if (v instanceof Date) return v.toISOString();
|
|
29
|
+
if (typeof v === "boolean") return v ? "true" : "false";
|
|
30
|
+
if (typeof v === "number" || typeof v === "bigint") return String(v);
|
|
31
|
+
return escapeIfNeeded(String(v), LIST_SPECIAL);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function escapeIfNeeded(s: string, special: RegExp): string {
|
|
35
|
+
if (s === "") return '""';
|
|
36
|
+
if (!special.test(s)) return s;
|
|
37
|
+
return `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
38
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Generic helpers that let callers parameterize the client with their
|
|
2
|
+
// openapi-typescript-generated `paths` interface.
|
|
3
|
+
//
|
|
4
|
+
// Usage:
|
|
5
|
+
// import type { paths } from "./schema";
|
|
6
|
+
// const db = createClient<paths>({ baseUrl, getToken });
|
|
7
|
+
|
|
8
|
+
export type AnyPaths = Record<string, any>;
|
|
9
|
+
|
|
10
|
+
// Extract the GET response row type for a table path, e.g. "/canvas_assignments".
|
|
11
|
+
// openapi-typescript emits: paths["/x"]["get"]["responses"][200]["content"]["application/json"]
|
|
12
|
+
// which is `Array<Row>`. We unwrap to `Row`.
|
|
13
|
+
export type RowOf<P extends AnyPaths, T extends keyof P> =
|
|
14
|
+
P[T] extends { get: { responses: { 200: { content: { "application/json": infer Body } } } } }
|
|
15
|
+
? Body extends ReadonlyArray<infer Item>
|
|
16
|
+
? Item
|
|
17
|
+
: Body
|
|
18
|
+
: never;
|
|
19
|
+
|
|
20
|
+
// Tables = path keys that look like top-level table routes ("/foo", not "/rpc/...").
|
|
21
|
+
export type TableName<P extends AnyPaths> = Extract<keyof P, string>;
|
|
22
|
+
|
|
23
|
+
// Token getter: sync or async, may return null when unauthenticated.
|
|
24
|
+
export type TokenGetter = () => string | null | Promise<string | null>;
|
|
25
|
+
|
|
26
|
+
export interface ClientOptions {
|
|
27
|
+
baseUrl: string;
|
|
28
|
+
getToken?: TokenGetter;
|
|
29
|
+
headers?: Record<string, string>;
|
|
30
|
+
fetch?: typeof fetch;
|
|
31
|
+
}
|