@pawabase/client 0.0.0-stage → 0.1.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.
Files changed (243) hide show
  1. package/README.md +313 -2
  2. package/dist/cjs/admin.d.ts +187 -0
  3. package/dist/cjs/admin.d.ts.map +1 -0
  4. package/dist/cjs/admin.js +235 -0
  5. package/dist/cjs/admin.js.map +1 -0
  6. package/dist/cjs/auth.d.ts +448 -0
  7. package/dist/cjs/auth.d.ts.map +1 -0
  8. package/dist/cjs/auth.js +1016 -0
  9. package/dist/cjs/auth.js.map +1 -0
  10. package/dist/cjs/client.d.ts +103 -0
  11. package/dist/cjs/client.d.ts.map +1 -0
  12. package/dist/cjs/client.js +144 -0
  13. package/dist/cjs/client.js.map +1 -0
  14. package/dist/cjs/data.d.ts +220 -0
  15. package/dist/cjs/data.d.ts.map +1 -0
  16. package/dist/cjs/data.js +490 -0
  17. package/dist/cjs/data.js.map +1 -0
  18. package/dist/cjs/errors.d.ts +167 -0
  19. package/dist/cjs/errors.d.ts.map +1 -0
  20. package/dist/cjs/errors.js +316 -0
  21. package/dist/cjs/errors.js.map +1 -0
  22. package/dist/cjs/files.d.ts +80 -0
  23. package/dist/cjs/files.d.ts.map +1 -0
  24. package/dist/cjs/files.js +167 -0
  25. package/dist/cjs/files.js.map +1 -0
  26. package/dist/cjs/functions.d.ts +66 -0
  27. package/dist/cjs/functions.d.ts.map +1 -0
  28. package/dist/cjs/functions.js +109 -0
  29. package/dist/cjs/functions.js.map +1 -0
  30. package/dist/cjs/http.d.ts +101 -0
  31. package/dist/cjs/http.d.ts.map +1 -0
  32. package/dist/cjs/http.js +249 -0
  33. package/dist/cjs/http.js.map +1 -0
  34. package/dist/cjs/index.d.ts +22 -0
  35. package/dist/cjs/index.d.ts.map +1 -0
  36. package/dist/cjs/index.js +45 -0
  37. package/dist/cjs/index.js.map +1 -0
  38. package/dist/cjs/manage/access.d.ts +136 -0
  39. package/dist/cjs/manage/access.d.ts.map +1 -0
  40. package/dist/cjs/manage/access.js +162 -0
  41. package/dist/cjs/manage/access.js.map +1 -0
  42. package/dist/cjs/manage/automation.d.ts +106 -0
  43. package/dist/cjs/manage/automation.d.ts.map +1 -0
  44. package/dist/cjs/manage/automation.js +144 -0
  45. package/dist/cjs/manage/automation.js.map +1 -0
  46. package/dist/cjs/manage/base.d.ts +52 -0
  47. package/dist/cjs/manage/base.d.ts.map +1 -0
  48. package/dist/cjs/manage/base.js +69 -0
  49. package/dist/cjs/manage/base.js.map +1 -0
  50. package/dist/cjs/manage/data.d.ts +100 -0
  51. package/dist/cjs/manage/data.d.ts.map +1 -0
  52. package/dist/cjs/manage/data.js +141 -0
  53. package/dist/cjs/manage/data.js.map +1 -0
  54. package/dist/cjs/manage/files.d.ts +45 -0
  55. package/dist/cjs/manage/files.d.ts.map +1 -0
  56. package/dist/cjs/manage/files.js +73 -0
  57. package/dist/cjs/manage/files.js.map +1 -0
  58. package/dist/cjs/manage/functions.d.ts +48 -0
  59. package/dist/cjs/manage/functions.d.ts.map +1 -0
  60. package/dist/cjs/manage/functions.js +61 -0
  61. package/dist/cjs/manage/functions.js.map +1 -0
  62. package/dist/cjs/manage/index.d.ts +76 -0
  63. package/dist/cjs/manage/index.d.ts.map +1 -0
  64. package/dist/cjs/manage/index.js +134 -0
  65. package/dist/cjs/manage/index.js.map +1 -0
  66. package/dist/cjs/manage/lifecycle.d.ts +128 -0
  67. package/dist/cjs/manage/lifecycle.d.ts.map +1 -0
  68. package/dist/cjs/manage/lifecycle.js +167 -0
  69. package/dist/cjs/manage/lifecycle.js.map +1 -0
  70. package/dist/cjs/manage/mail.d.ts +16 -0
  71. package/dist/cjs/manage/mail.d.ts.map +1 -0
  72. package/dist/cjs/manage/mail.js +24 -0
  73. package/dist/cjs/manage/mail.js.map +1 -0
  74. package/dist/cjs/manage/observe.d.ts +149 -0
  75. package/dist/cjs/manage/observe.d.ts.map +1 -0
  76. package/dist/cjs/manage/observe.js +168 -0
  77. package/dist/cjs/manage/observe.js.map +1 -0
  78. package/dist/cjs/manage/realtime.d.ts +52 -0
  79. package/dist/cjs/manage/realtime.d.ts.map +1 -0
  80. package/dist/cjs/manage/realtime.js +71 -0
  81. package/dist/cjs/manage/realtime.js.map +1 -0
  82. package/dist/cjs/package.json +1 -0
  83. package/dist/cjs/realtime.d.ts +207 -0
  84. package/dist/cjs/realtime.d.ts.map +1 -0
  85. package/dist/cjs/realtime.js +704 -0
  86. package/dist/cjs/realtime.js.map +1 -0
  87. package/dist/cjs/session-store.d.ts +16 -0
  88. package/dist/cjs/session-store.d.ts.map +1 -0
  89. package/dist/cjs/session-store.js +37 -0
  90. package/dist/cjs/session-store.js.map +1 -0
  91. package/dist/cjs/typegen.d.ts +26 -0
  92. package/dist/cjs/typegen.d.ts.map +1 -0
  93. package/dist/cjs/typegen.js +167 -0
  94. package/dist/cjs/typegen.js.map +1 -0
  95. package/dist/cjs/types.d.ts +178 -0
  96. package/dist/cjs/types.d.ts.map +1 -0
  97. package/dist/cjs/types.js +4 -0
  98. package/dist/cjs/types.js.map +1 -0
  99. package/dist/cjs/util.d.ts +27 -0
  100. package/dist/cjs/util.d.ts.map +1 -0
  101. package/dist/cjs/util.js +115 -0
  102. package/dist/cjs/util.js.map +1 -0
  103. package/dist/cjs/version.d.ts +2 -0
  104. package/dist/cjs/version.d.ts.map +1 -0
  105. package/dist/cjs/version.js +5 -0
  106. package/dist/cjs/version.js.map +1 -0
  107. package/dist/esm/admin.d.ts +187 -0
  108. package/dist/esm/admin.d.ts.map +1 -0
  109. package/dist/esm/admin.js +222 -0
  110. package/dist/esm/admin.js.map +1 -0
  111. package/dist/esm/auth.d.ts +448 -0
  112. package/dist/esm/auth.d.ts.map +1 -0
  113. package/dist/esm/auth.js +1012 -0
  114. package/dist/esm/auth.js.map +1 -0
  115. package/dist/esm/cli/gen-types.d.ts +2 -0
  116. package/dist/esm/cli/gen-types.d.ts.map +1 -0
  117. package/dist/esm/cli/gen-types.js +100 -0
  118. package/dist/esm/cli/gen-types.js.map +1 -0
  119. package/dist/esm/client.d.ts +103 -0
  120. package/dist/esm/client.d.ts.map +1 -0
  121. package/dist/esm/client.js +139 -0
  122. package/dist/esm/client.js.map +1 -0
  123. package/dist/esm/data.d.ts +220 -0
  124. package/dist/esm/data.d.ts.map +1 -0
  125. package/dist/esm/data.js +485 -0
  126. package/dist/esm/data.js.map +1 -0
  127. package/dist/esm/errors.d.ts +167 -0
  128. package/dist/esm/errors.d.ts.map +1 -0
  129. package/dist/esm/errors.js +293 -0
  130. package/dist/esm/errors.js.map +1 -0
  131. package/dist/esm/files.d.ts +80 -0
  132. package/dist/esm/files.d.ts.map +1 -0
  133. package/dist/esm/files.js +162 -0
  134. package/dist/esm/files.js.map +1 -0
  135. package/dist/esm/functions.d.ts +66 -0
  136. package/dist/esm/functions.d.ts.map +1 -0
  137. package/dist/esm/functions.js +103 -0
  138. package/dist/esm/functions.js.map +1 -0
  139. package/dist/esm/http.d.ts +101 -0
  140. package/dist/esm/http.d.ts.map +1 -0
  141. package/dist/esm/http.js +245 -0
  142. package/dist/esm/http.js.map +1 -0
  143. package/dist/esm/index.d.ts +22 -0
  144. package/dist/esm/index.d.ts.map +1 -0
  145. package/dist/esm/index.js +12 -0
  146. package/dist/esm/index.js.map +1 -0
  147. package/dist/esm/manage/access.d.ts +136 -0
  148. package/dist/esm/manage/access.d.ts.map +1 -0
  149. package/dist/esm/manage/access.js +155 -0
  150. package/dist/esm/manage/access.js.map +1 -0
  151. package/dist/esm/manage/automation.d.ts +106 -0
  152. package/dist/esm/manage/automation.d.ts.map +1 -0
  153. package/dist/esm/manage/automation.js +134 -0
  154. package/dist/esm/manage/automation.js.map +1 -0
  155. package/dist/esm/manage/base.d.ts +52 -0
  156. package/dist/esm/manage/base.d.ts.map +1 -0
  157. package/dist/esm/manage/base.js +63 -0
  158. package/dist/esm/manage/base.js.map +1 -0
  159. package/dist/esm/manage/data.d.ts +100 -0
  160. package/dist/esm/manage/data.d.ts.map +1 -0
  161. package/dist/esm/manage/data.js +130 -0
  162. package/dist/esm/manage/data.js.map +1 -0
  163. package/dist/esm/manage/files.d.ts +45 -0
  164. package/dist/esm/manage/files.d.ts.map +1 -0
  165. package/dist/esm/manage/files.js +68 -0
  166. package/dist/esm/manage/files.js.map +1 -0
  167. package/dist/esm/manage/functions.d.ts +48 -0
  168. package/dist/esm/manage/functions.d.ts.map +1 -0
  169. package/dist/esm/manage/functions.js +57 -0
  170. package/dist/esm/manage/functions.js.map +1 -0
  171. package/dist/esm/manage/index.d.ts +76 -0
  172. package/dist/esm/manage/index.d.ts.map +1 -0
  173. package/dist/esm/manage/index.js +115 -0
  174. package/dist/esm/manage/index.js.map +1 -0
  175. package/dist/esm/manage/lifecycle.d.ts +128 -0
  176. package/dist/esm/manage/lifecycle.d.ts.map +1 -0
  177. package/dist/esm/manage/lifecycle.js +161 -0
  178. package/dist/esm/manage/lifecycle.js.map +1 -0
  179. package/dist/esm/manage/mail.d.ts +16 -0
  180. package/dist/esm/manage/mail.d.ts.map +1 -0
  181. package/dist/esm/manage/mail.js +19 -0
  182. package/dist/esm/manage/mail.js.map +1 -0
  183. package/dist/esm/manage/observe.d.ts +149 -0
  184. package/dist/esm/manage/observe.d.ts.map +1 -0
  185. package/dist/esm/manage/observe.js +161 -0
  186. package/dist/esm/manage/observe.js.map +1 -0
  187. package/dist/esm/manage/realtime.d.ts +52 -0
  188. package/dist/esm/manage/realtime.d.ts.map +1 -0
  189. package/dist/esm/manage/realtime.js +66 -0
  190. package/dist/esm/manage/realtime.js.map +1 -0
  191. package/dist/esm/package.json +1 -0
  192. package/dist/esm/realtime.d.ts +207 -0
  193. package/dist/esm/realtime.d.ts.map +1 -0
  194. package/dist/esm/realtime.js +699 -0
  195. package/dist/esm/realtime.js.map +1 -0
  196. package/dist/esm/session-store.d.ts +16 -0
  197. package/dist/esm/session-store.d.ts.map +1 -0
  198. package/dist/esm/session-store.js +32 -0
  199. package/dist/esm/session-store.js.map +1 -0
  200. package/dist/esm/typegen.d.ts +26 -0
  201. package/dist/esm/typegen.d.ts.map +1 -0
  202. package/dist/esm/typegen.js +163 -0
  203. package/dist/esm/typegen.js.map +1 -0
  204. package/dist/esm/types.d.ts +178 -0
  205. package/dist/esm/types.d.ts.map +1 -0
  206. package/dist/esm/types.js +3 -0
  207. package/dist/esm/types.js.map +1 -0
  208. package/dist/esm/util.d.ts +27 -0
  209. package/dist/esm/util.d.ts.map +1 -0
  210. package/dist/esm/util.js +102 -0
  211. package/dist/esm/util.js.map +1 -0
  212. package/dist/esm/version.d.ts +2 -0
  213. package/dist/esm/version.d.ts.map +1 -0
  214. package/dist/esm/version.js +2 -0
  215. package/dist/esm/version.js.map +1 -0
  216. package/package.json +84 -4
  217. package/src/admin.ts +311 -0
  218. package/src/auth.ts +1180 -0
  219. package/src/cli/gen-types.ts +105 -0
  220. package/src/client.ts +189 -0
  221. package/src/data.ts +576 -0
  222. package/src/errors.ts +330 -0
  223. package/src/files.ts +208 -0
  224. package/src/functions.ts +131 -0
  225. package/src/http.ts +354 -0
  226. package/src/index.ts +56 -0
  227. package/src/manage/access.ts +206 -0
  228. package/src/manage/automation.ts +162 -0
  229. package/src/manage/base.ts +83 -0
  230. package/src/manage/data.ts +159 -0
  231. package/src/manage/files.ts +79 -0
  232. package/src/manage/functions.ts +69 -0
  233. package/src/manage/index.ts +132 -0
  234. package/src/manage/lifecycle.ts +208 -0
  235. package/src/manage/mail.ts +22 -0
  236. package/src/manage/observe.ts +226 -0
  237. package/src/manage/realtime.ts +95 -0
  238. package/src/realtime.ts +765 -0
  239. package/src/session-store.ts +36 -0
  240. package/src/typegen.ts +192 -0
  241. package/src/types.ts +195 -0
  242. package/src/util.ts +114 -0
  243. package/src/version.ts +1 -0
@@ -0,0 +1,36 @@
1
+ /** Where the signed-in session is kept between page loads. */
2
+ export interface SessionStorage {
3
+ getItem(key: string): string | null | Promise<string | null>;
4
+ setItem(key: string, value: string): void | Promise<void>;
5
+ removeItem(key: string): void | Promise<void>;
6
+ }
7
+
8
+ /** Keeps the session in memory only: gone when the process or page ends. The default on servers. */
9
+ export class MemoryStorage implements SessionStorage {
10
+ private readonly items = new Map<string, string>();
11
+ getItem(key: string): string | null {
12
+ return this.items.get(key) ?? null;
13
+ }
14
+ setItem(key: string, value: string): void {
15
+ this.items.set(key, value);
16
+ }
17
+ removeItem(key: string): void {
18
+ this.items.delete(key);
19
+ }
20
+ }
21
+
22
+ /** `localStorage` when the browser allows it (it can be blocked), otherwise `null`. */
23
+ export function browserStorage(): SessionStorage | null {
24
+ // Newer Node versions expose an experimental `localStorage` that warns when touched.
25
+ if (typeof window === "undefined") return null;
26
+ try {
27
+ const store = (globalThis as { localStorage?: Storage }).localStorage;
28
+ if (!store) return null;
29
+ const probe = "__pawabase_probe__";
30
+ store.setItem(probe, "1");
31
+ store.removeItem(probe);
32
+ return store;
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
package/src/typegen.ts ADDED
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Turn a project's OpenAPI document into TypeScript types for `createClient<Database>()`.
3
+ *
4
+ * The compiled API documents each resource as a read model, a create body and an update
5
+ * body. The create body says which fields are required; the read model marks every field
6
+ * nullable (a `select` can leave columns out as `null`), so the generator uses the create
7
+ * body to tell "always present" from "may be null".
8
+ */
9
+
10
+ type Schema = Record<string, any>;
11
+
12
+ export interface TypegenOptions {
13
+ /** The name of the exported root interface. Default `Database`. */
14
+ name?: string;
15
+ /** Text for the header comment, e.g. where the spec came from. */
16
+ source?: string;
17
+ }
18
+
19
+ const RESERVED_SYSTEM = new Set(["created_at", "updated_at"]);
20
+
21
+ function pascal(name: string): string {
22
+ return name
23
+ .split(/[^A-Za-z0-9]+/)
24
+ .filter(Boolean)
25
+ .map((part) => part[0]!.toUpperCase() + part.slice(1))
26
+ .join("");
27
+ }
28
+
29
+ function key(name: string): string {
30
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
31
+ }
32
+
33
+ function resolve(spec: Schema, node: Schema | undefined): Schema | undefined {
34
+ let current = node;
35
+ for (let depth = 0; current && typeof current["$ref"] === "string" && depth < 10; depth++) {
36
+ const path = (current["$ref"] as string).replace(/^#\//, "").split("/");
37
+ let target: any = spec;
38
+ for (const part of path) target = target?.[part];
39
+ current = target;
40
+ }
41
+ return current;
42
+ }
43
+
44
+ /** A schema as a TypeScript type expression. */
45
+ function typeOf(spec: Schema, node: Schema | undefined): string {
46
+ const schema = resolve(spec, node);
47
+ if (!schema || Object.keys(schema).length === 0) return "unknown";
48
+ const variants = schema["anyOf"] ?? schema["oneOf"];
49
+ if (Array.isArray(variants)) {
50
+ const parts = variants.map((variant: Schema) => typeOf(spec, variant));
51
+ const unique = [...new Set(parts)];
52
+ return unique.includes("unknown") ? "unknown" : unique.join(" | ");
53
+ }
54
+ if (Array.isArray(schema["enum"])) return schema["enum"].map((value: unknown) => JSON.stringify(value)).join(" | ");
55
+ switch (schema["type"]) {
56
+ case "integer":
57
+ case "number":
58
+ return "number";
59
+ case "string":
60
+ return "string";
61
+ case "boolean":
62
+ return "boolean";
63
+ case "null":
64
+ return "null";
65
+ case "array": {
66
+ const inner = typeOf(spec, schema["items"]);
67
+ return inner.includes(" | ") ? `Array<${inner}>` : `${inner}[]`;
68
+ }
69
+ case "object":
70
+ return schema["properties"] ? "Record<string, unknown>" : "Record<string, unknown>";
71
+ default:
72
+ return "unknown";
73
+ }
74
+ }
75
+
76
+ /** The schema without its `null` variant, and whether it had one. */
77
+ function stripNull(spec: Schema, node: Schema | undefined): { type: string; nullable: boolean } {
78
+ const type = typeOf(spec, node);
79
+ const parts = type.split(" | ");
80
+ const nullable = parts.includes("null");
81
+ const rest = parts.filter((part) => part !== "null");
82
+ return { type: rest.length ? rest.join(" | ") : "unknown", nullable };
83
+ }
84
+
85
+ function bodySchema(spec: Schema, operation: Schema | undefined): Schema | undefined {
86
+ return resolve(spec, operation?.["requestBody"]?.["content"]?.["application/json"]?.["schema"]);
87
+ }
88
+
89
+ function listRow(spec: Schema, operation: Schema | undefined): Schema | undefined {
90
+ const page = resolve(spec, operation?.["responses"]?.["200"]?.["content"]?.["application/json"]?.["schema"]);
91
+ return resolve(spec, page?.["properties"]?.["data"]?.["items"]);
92
+ }
93
+
94
+ interface Resource {
95
+ name: string;
96
+ row: Schema;
97
+ insert: Schema | undefined;
98
+ update: Schema | undefined;
99
+ }
100
+
101
+ export function findResources(spec: Schema): Resource[] {
102
+ const found: Resource[] = [];
103
+ for (const [path, item] of Object.entries<Schema>(spec["paths"] ?? {})) {
104
+ const match = /^\/rest\/v\d+\/([^/{}]+)$/.exec(path);
105
+ if (!match) continue;
106
+ const row = listRow(spec, item["get"]) ?? resolve(spec, item["post"]?.["responses"]?.["201"]?.["content"]?.["application/json"]?.["schema"]);
107
+ if (!row?.["properties"]) continue; // a custom route, not a resource
108
+ const withId = spec["paths"][`${path}/{id}`] as Schema | undefined;
109
+ found.push({
110
+ name: match[1]!,
111
+ row,
112
+ insert: bodySchema(spec, item["post"]),
113
+ update: bodySchema(spec, withId?.["patch"]),
114
+ });
115
+ }
116
+ return found.sort((a, b) => a.name.localeCompare(b.name));
117
+ }
118
+
119
+ function describe(spec: Schema, resource: Resource): string {
120
+ const type = pascal(resource.name);
121
+ const required = new Set<string>(resource.insert?.["required"] ?? []);
122
+ const insertProps: Schema = resource.insert?.["properties"] ?? {};
123
+ const rowProps: Schema = resource.row["properties"] ?? {};
124
+ const lines: string[] = [];
125
+
126
+ // Row: what a read returns.
127
+ const row: string[] = [];
128
+ const primary = rowProps["id"];
129
+ if (primary) {
130
+ const id = stripNull(spec, primary).type;
131
+ row.push(` id: ${id};`);
132
+ }
133
+ for (const [name, node] of Object.entries<Schema>(rowProps)) {
134
+ if (name === "id") continue;
135
+ const { type } = stripNull(spec, node);
136
+ const guaranteed = required.has(name) || (insertProps[name] && "default" in insertProps[name] && insertProps[name]["default"] !== null);
137
+ row.push(` ${key(name)}: ${type === "unknown" ? "unknown" : guaranteed ? type : `${type} | null`};`);
138
+ }
139
+ for (const system of RESERVED_SYSTEM) {
140
+ if (!(system in rowProps)) row.push(` ${system}?: string;`);
141
+ }
142
+ lines.push(`export interface ${type}Row {`, ...row, "}", "");
143
+
144
+ // Insert: what a create accepts.
145
+ const insert: string[] = [];
146
+ for (const [name, node] of Object.entries<Schema>(insertProps)) {
147
+ const { type, nullable } = stripNull(spec, node);
148
+ insert.push(` ${key(name)}${required.has(name) ? "" : "?"}: ${type}${nullable && type !== "unknown" ? " | null" : ""};`);
149
+ }
150
+ lines.push(`export interface ${type}Insert {`, ...insert, "}", "");
151
+
152
+ // Update: a PATCH takes any field, null included.
153
+ const updateProps: Schema = resource.update?.["properties"] ?? insertProps;
154
+ const update: string[] = [];
155
+ for (const [name, node] of Object.entries<Schema>(updateProps)) {
156
+ const { type } = stripNull(spec, node);
157
+ update.push(` ${key(name)}?: ${type === "unknown" ? "unknown" : `${type} | null`};`);
158
+ }
159
+ lines.push(`export interface ${type}Update {`, ...update, "}", "");
160
+ return lines.join("\n");
161
+ }
162
+
163
+ /** The generated file's text. */
164
+ export function generateTypes(spec: Schema, options: TypegenOptions = {}): string {
165
+ const root = options.name ?? "Database";
166
+ const resources = findResources(spec);
167
+ const title: string = spec["info"]?.["title"] ?? "Pawabase API";
168
+ const version: string = spec["info"]?.["version"] ?? "";
169
+ const out: string[] = [
170
+ "/* eslint-disable */",
171
+ `// Generated by pawabase-types from "${title}" ${version}${options.source ? ` (${options.source})` : ""}.`,
172
+ "// Do not edit by hand: regenerate when your resources change.",
173
+ "",
174
+ ];
175
+ for (const resource of resources) out.push(describe(spec, resource));
176
+ out.push(`export interface ${root} {`, " resources: {");
177
+ for (const resource of resources) {
178
+ const type = pascal(resource.name);
179
+ out.push(` ${key(resource.name)}: { Row: ${type}Row; Insert: ${type}Insert; Update: ${type}Update };`);
180
+ }
181
+ out.push(" };", "}", "");
182
+ out.push(
183
+ `/** A resource's row: \`Row<"posts">\`. */`,
184
+ `export type Row<K extends keyof ${root}["resources"]> = ${root}["resources"][K]["Row"];`,
185
+ `/** What a create accepts: \`Insert<"posts">\`. */`,
186
+ `export type Insert<K extends keyof ${root}["resources"]> = ${root}["resources"][K]["Insert"];`,
187
+ `/** What an update accepts: \`Update<"posts">\`. */`,
188
+ `export type Update<K extends keyof ${root}["resources"]> = ${root}["resources"][K]["Update"];`,
189
+ "",
190
+ );
191
+ return out.join("\n");
192
+ }
package/src/types.ts ADDED
@@ -0,0 +1,195 @@
1
+ /** Shapes the platform sends. They keep the wire's `snake_case` names, so what you see in Studio and the docs is what you read in code. */
2
+
3
+ export interface Identity {
4
+ id: number;
5
+ provider: string;
6
+ email: string | null;
7
+ last_sign_in_at: string | null;
8
+ }
9
+
10
+ export interface User {
11
+ id: string;
12
+ email: string;
13
+ username: string;
14
+ name: string;
15
+ avatar_url: string | null;
16
+ email_verified: boolean;
17
+ email_verified_at: string | null;
18
+ /** Profile data the user may edit. Never trust it for authorization. */
19
+ user_metadata: Record<string, unknown>;
20
+ /** Data only the application (a secret key or Studio) can edit. */
21
+ app_metadata: Record<string, unknown>;
22
+ roles: string[];
23
+ mfa_enabled: boolean;
24
+ identities: Identity[];
25
+ created_at: string | null;
26
+ last_sign_in_at: string | null;
27
+ /** The assurance level of the current session: `aal1` (password) or `aal2` (second factor). Only on `getUser()`. */
28
+ aal?: string | null;
29
+ /** Only on `getUser()`. */
30
+ session_id?: string | null;
31
+ }
32
+
33
+ export interface Session {
34
+ access_token: string;
35
+ refresh_token: string;
36
+ token_type: "bearer";
37
+ /** Seconds the access token lives, as issued. */
38
+ expires_in: number;
39
+ /** Unix time (seconds) at which the access token expires. */
40
+ expires_at: number;
41
+ session_id?: string;
42
+ /** The organization (slug) the token is issued for, if any. */
43
+ org: string | null;
44
+ org_role: string | null;
45
+ user: User;
46
+ }
47
+
48
+ export interface AuthSettings {
49
+ signup_enabled: boolean;
50
+ email_verification_required: boolean;
51
+ magic_link_enabled: boolean;
52
+ email_code_enabled: boolean;
53
+ anonymous_enabled: boolean;
54
+ passkeys_enabled: boolean;
55
+ mfa_enabled: boolean;
56
+ providers: string[];
57
+ password_policy: { kind: string; min_length: number };
58
+ }
59
+
60
+ export interface MfaStatus {
61
+ enabled: boolean;
62
+ aal: string | null;
63
+ recovery_codes_remaining: number;
64
+ }
65
+
66
+ export interface MfaEnrollment {
67
+ factor_id: string;
68
+ /** The shared secret, for manual entry. */
69
+ secret: string;
70
+ /** An `otpauth://` URI to render as a QR code. */
71
+ otpauth_uri: string;
72
+ }
73
+
74
+ export interface SessionInfo {
75
+ id: string;
76
+ method: string;
77
+ aal: string;
78
+ ip: string | null;
79
+ user_agent: string | null;
80
+ created_at: string | null;
81
+ last_refreshed_at: string | null;
82
+ current: boolean;
83
+ }
84
+
85
+ export interface SignInEvent {
86
+ kind: string;
87
+ method: string;
88
+ success: boolean;
89
+ reason: string | null;
90
+ ip: string | null;
91
+ user_agent: string | null;
92
+ at: string;
93
+ }
94
+
95
+ export interface Organization {
96
+ id: number;
97
+ slug: string;
98
+ name: string;
99
+ metadata: Record<string, unknown>;
100
+ /** The caller's role in it. */
101
+ role: string | null;
102
+ created_at: string | null;
103
+ }
104
+
105
+ export type OrgRole = "viewer" | "member" | "admin" | "owner";
106
+
107
+ export interface OrgMember {
108
+ user_id: string;
109
+ email: string;
110
+ name: string;
111
+ role: string;
112
+ }
113
+
114
+ export interface Invitation {
115
+ id: number;
116
+ email: string;
117
+ role: string;
118
+ expires_at: string;
119
+ }
120
+
121
+ export interface Team {
122
+ slug: string;
123
+ name: string;
124
+ [key: string]: unknown;
125
+ }
126
+
127
+ /** One page of a list. */
128
+ export interface Page<T> {
129
+ data: T[];
130
+ /** The 1-based page number. */
131
+ page: number;
132
+ per_page: number;
133
+ /**
134
+ * How many records match for this caller. `null` when a per-row policy makes it unknowable
135
+ * (and then pages can be short, or empty, with more behind them).
136
+ */
137
+ total: number | null;
138
+ }
139
+
140
+ export interface StorageBucket {
141
+ name: string;
142
+ public: boolean;
143
+ accepts: string[];
144
+ max_bytes: number;
145
+ }
146
+
147
+ export interface StoredObject {
148
+ bucket: string;
149
+ key: string;
150
+ size: number;
151
+ content_type: string;
152
+ etag?: string;
153
+ }
154
+
155
+ export interface FileEntry {
156
+ key: string;
157
+ size: number;
158
+ content_type: string;
159
+ modified: string | number | null;
160
+ }
161
+
162
+ export interface FileListing {
163
+ files: FileEntry[];
164
+ /** "Folders": the distinct prefixes below the listed prefix. */
165
+ prefixes: string[];
166
+ /** Pass back to read the next page. Empty when this is the last. */
167
+ cursor: string;
168
+ }
169
+
170
+ export interface SignedUrl {
171
+ url: string;
172
+ /** Unix time (seconds). */
173
+ expires_at: number;
174
+ method: "GET" | "PUT";
175
+ }
176
+
177
+ export interface ChannelMessage<T = unknown> {
178
+ type: "message";
179
+ id: string;
180
+ channel: string;
181
+ event: string;
182
+ payload: T;
183
+ /** The sender's user id; `"server"` for server-side publishes. */
184
+ from: string | null;
185
+ sent_at: string;
186
+ /** Present on messages read from history. */
187
+ seq?: number;
188
+ }
189
+
190
+ export interface PresenceMember<M = Record<string, unknown>> {
191
+ presence_ref: string;
192
+ user_id: string | null;
193
+ meta: M;
194
+ online_at: string;
195
+ }
package/src/util.ts ADDED
@@ -0,0 +1,114 @@
1
+ import { AbortError } from "./errors.js";
2
+
3
+ export type Json = null | boolean | number | string | Json[] | { [key: string]: Json };
4
+
5
+ export function isRecord(value: unknown): value is Record<string, unknown> {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
8
+
9
+ /** Sleep for `ms`, rejecting early with {@link AbortError} when `signal` fires. */
10
+ export function sleep(ms: number, signal?: AbortSignal | null): Promise<void> {
11
+ return new Promise((resolve, reject) => {
12
+ if (signal?.aborted) return reject(new AbortError(undefined, { cause: signal.reason }));
13
+ const timer = setTimeout(() => {
14
+ signal?.removeEventListener("abort", onAbort);
15
+ resolve();
16
+ }, ms);
17
+ const onAbort = () => {
18
+ clearTimeout(timer);
19
+ reject(new AbortError(undefined, { cause: signal?.reason }));
20
+ };
21
+ signal?.addEventListener("abort", onAbort, { once: true });
22
+ });
23
+ }
24
+
25
+ /** Exponential backoff with full jitter: a random delay in `[0, min(max, base * factor^attempt)]`. */
26
+ export function backoff(
27
+ attempt: number,
28
+ options: { baseDelayMs: number; maxDelayMs: number; factor?: number },
29
+ random: () => number = Math.random,
30
+ ): number {
31
+ const ceiling = Math.min(options.maxDelayMs, options.baseDelayMs * (options.factor ?? 2) ** attempt);
32
+ return Math.floor(random() * ceiling);
33
+ }
34
+
35
+ /** One signal that fires when any of `signals` fires. Uses the platform's `AbortSignal.any` when present. */
36
+ export function anySignal(signals: Array<AbortSignal | null | undefined>): AbortSignal | undefined {
37
+ const real = signals.filter((s): s is AbortSignal => !!s);
38
+ if (real.length === 0) return undefined;
39
+ if (real.length === 1) return real[0];
40
+ const native = (AbortSignal as unknown as { any?: (s: AbortSignal[]) => AbortSignal }).any;
41
+ if (native) return native(real);
42
+ const controller = new AbortController();
43
+ for (const signal of real) {
44
+ if (signal.aborted) {
45
+ controller.abort(signal.reason);
46
+ break;
47
+ }
48
+ signal.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
49
+ }
50
+ return controller.signal;
51
+ }
52
+
53
+ /** `a/b c/d` to `a/b%20c/d`: encode each path segment, keep the slashes. */
54
+ export function encodePath(path: string): string {
55
+ return path
56
+ .split("/")
57
+ .map((segment) => encodeURIComponent(segment))
58
+ .join("/");
59
+ }
60
+
61
+ export type QueryValue = string | number | boolean | null | undefined | Date;
62
+
63
+ /** A query string from a flat record. Drops `undefined`/`null`; arrays repeat the key. */
64
+ export function queryString(params: Record<string, QueryValue | QueryValue[]> | undefined): string {
65
+ if (!params) return "";
66
+ const search = new URLSearchParams();
67
+ for (const [key, value] of Object.entries(params)) {
68
+ const items = Array.isArray(value) ? value : [value];
69
+ for (const item of items) {
70
+ if (item === undefined || item === null) continue;
71
+ search.append(key, item instanceof Date ? item.toISOString() : String(item));
72
+ }
73
+ }
74
+ const text = search.toString();
75
+ return text ? `?${text}` : "";
76
+ }
77
+
78
+ export function trimSlash(url: string): string {
79
+ return url.replace(/\/+$/, "");
80
+ }
81
+
82
+ export function randomId(): string {
83
+ const c = (globalThis as { crypto?: Crypto }).crypto;
84
+ if (c?.randomUUID) return c.randomUUID();
85
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
86
+ }
87
+
88
+ export function nowSeconds(): number {
89
+ return Math.floor(Date.now() / 1000);
90
+ }
91
+
92
+ /** Decode a JWT payload without verifying it (the server verifies). Returns `null` for malformed tokens. */
93
+ export function decodeJwt(token: string): Record<string, unknown> | null {
94
+ const part = token.split(".")[1];
95
+ if (!part) return null;
96
+ try {
97
+ const base64 = part.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(part.length / 4) * 4, "=");
98
+ const text =
99
+ typeof atob === "function"
100
+ ? decodeURIComponent(
101
+ Array.from(atob(base64), (ch) => "%" + ch.charCodeAt(0).toString(16).padStart(2, "0")).join(""),
102
+ )
103
+ : (globalThis as unknown as { Buffer: { from(s: string, e: string): { toString(e: string): string } } }).Buffer.from(base64, "base64").toString("utf8");
104
+ const value = JSON.parse(text) as unknown;
105
+ return isRecord(value) ? value : null;
106
+ } catch {
107
+ return null;
108
+ }
109
+ }
110
+
111
+ /** Whether a key is a secret key (`pb_sk_…`, or `sk_…` in older docs). */
112
+ export function isSecretKey(apiKey: string): boolean {
113
+ return /^(pb_)?sk_/.test(apiKey);
114
+ }
package/src/version.ts ADDED
@@ -0,0 +1 @@
1
+ export const VERSION = "0.1.0";