lazypock 0.1.0 → 0.1.2
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 +145 -0
- package/dist/chunk-3MZJ5HCD.js +190 -0
- package/dist/chunk-3MZJ5HCD.js.map +1 -0
- package/dist/cli.cjs +235 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.global.js +243 -0
- package/dist/cli.global.js.map +1 -0
- package/dist/cli.js +105 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +360 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +298 -77
- package/dist/index.d.ts +298 -77
- package/dist/index.global.js +352 -10
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +178 -10
- package/dist/index.js.map +1 -1
- package/package.json +8 -1
- package/src/cli.ts +145 -0
- package/src/client.ts +48 -0
- package/src/codegen.ts +149 -0
- package/src/collection.ts +145 -21
- package/src/index.ts +29 -408
- package/src/lazypock.ts +490 -0
- package/src/realtime.ts +38 -0
- package/src/schema.ts +39 -0
- package/src/typegen.ts +137 -0
- package/src/types.ts +36 -2
package/README.md
CHANGED
|
@@ -36,6 +36,132 @@ const file = await client.files.upload(fileInput.files[0]);
|
|
|
36
36
|
client.collection('posts').subscribe('*', (e) => console.log(e.action, e.record));
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
## Type Safety
|
|
40
|
+
|
|
41
|
+
The SDK offers three levels of type safety — pick what fits your project.
|
|
42
|
+
|
|
43
|
+
### 1. Fully typed via codegen (recommended)
|
|
44
|
+
|
|
45
|
+
Connect to your API once and generate a typed client — every collection becomes
|
|
46
|
+
an interface with the exact field types from your schema (selects become string
|
|
47
|
+
unions, relations become record IDs, etc.).
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# In your app, after installing lazypock:
|
|
51
|
+
npx lazypock-gen \
|
|
52
|
+
--url http://localhost:4000/api \
|
|
53
|
+
--email admin@example.com \
|
|
54
|
+
--password your-password
|
|
55
|
+
# writes ./lazypock.types.ts
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
> `lazypock-gen` remains as a deprecated alias for backwards compatibility —
|
|
59
|
+
> the canonical command is now simply `lazypock`:
|
|
60
|
+
>
|
|
61
|
+
> ```bash
|
|
62
|
+
> npx lazypock --url http://localhost:4000/api --email admin@example.com --password your-password
|
|
63
|
+
> ```
|
|
64
|
+
>
|
|
65
|
+
> **Use an API key instead of a password** (recommended). Generate one from the
|
|
66
|
+
> Studio **Settings → API Keys** dashboard, then:
|
|
67
|
+
>
|
|
68
|
+
> ```bash
|
|
69
|
+
> npx lazypock --url http://localhost:4000/api --api-key lazypock_xxxxxxxx
|
|
70
|
+
> # or via env: LAZYPOCK_URL=... LAZYPOCK_API_KEY=... npx lazypock
|
|
71
|
+
> ```
|
|
72
|
+
>
|
|
73
|
+
> API keys are stored as a SHA-256 hash (raw value shown once at generation) and
|
|
74
|
+
> are scoped to collection listing — ideal for codegen (they can `GET /collections`
|
|
75
|
+
> without a login round-trip, and cannot read or mutate your records).
|
|
76
|
+
|
|
77
|
+
Then in your app:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { createClient } from './lazypock.types';
|
|
81
|
+
|
|
82
|
+
const client = createClient({ baseUrl: 'http://localhost:4000/api' });
|
|
83
|
+
await client.login('admin@example.com', 'password');
|
|
84
|
+
|
|
85
|
+
// Collection access is fully type-checked:
|
|
86
|
+
const post = await client.collection('posts').getOne('abc123');
|
|
87
|
+
// post.title — string, post.published — boolean, …
|
|
88
|
+
|
|
89
|
+
await client.collection('posts').create({ title: 'x' }); // ✓
|
|
90
|
+
await client.collection('posts').create({ nope: 1 }); // ✗ compile error
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
> **Collection names are strict by design.** The typed client accepts only the
|
|
94
|
+
> literal collection names from your schema (`'posts'`, `'users'`, …) and rejects
|
|
95
|
+
> typos at compile time. If you need a *dynamic* collection name (e.g. a route
|
|
96
|
+
> param), use the base client or a cast:
|
|
97
|
+
>
|
|
98
|
+
> ```typescript
|
|
99
|
+
> const base = new LazypockClient({ baseUrl: 'http://localhost:4000/api' });
|
|
100
|
+
> base.collection(name); // dynamic, untyped
|
|
101
|
+
> client.collection(name as keyof LazypockCollections); // typed escape hatch
|
|
102
|
+
> ```
|
|
103
|
+
|
|
104
|
+
### 2. Hand-written generics (no codegen)
|
|
105
|
+
|
|
106
|
+
Pass a record interface to `collection<T>()` or use `.typed<T>()`:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
interface Post {
|
|
110
|
+
id: string;
|
|
111
|
+
title: string;
|
|
112
|
+
published: boolean;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const postsSvc = client.collection('posts').typed<Post>();
|
|
116
|
+
const post = await postsSvc.getOne('abc123'); // post.title: string
|
|
117
|
+
|
|
118
|
+
await postsSvc.create({ title: 'Hi', published: true }); // ✓
|
|
119
|
+
await postsSvc.create({ title: 'Hi', nope: 1 }); // ✗ compile error
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 3. Runtime schema types (experimental)
|
|
123
|
+
|
|
124
|
+
Fetch schemas at runtime and let the client derive field types:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
const res = await fetch('http://localhost:4000/api/collections', {
|
|
128
|
+
headers: { Authorization: 'Bearer ' + token },
|
|
129
|
+
});
|
|
130
|
+
const { items } = await res.json(); // CollectionSchema[]
|
|
131
|
+
|
|
132
|
+
const client = new LazypockClient({
|
|
133
|
+
baseUrl: 'http://localhost:4000/api',
|
|
134
|
+
types: { schemas: items },
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const code = client.generateTypes(); // string — write to lazypock.types.ts
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### CLI reference
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
lazypock [options]
|
|
144
|
+
|
|
145
|
+
Options:
|
|
146
|
+
--url <url> API base URL (or LAZYPOCK_URL)
|
|
147
|
+
--api-key <key> API key (or LAZYPOCK_API_KEY) — recommended, no login round-trip
|
|
148
|
+
--email <email> Superuser email (or LAZYPOCK_EMAIL)
|
|
149
|
+
--password <pw> Superuser password (or LAZYPOCK_PASSWORD)
|
|
150
|
+
--out <file> Output file (default: lazypock.types.ts)
|
|
151
|
+
--package <name> Package name to import (default: lazypock)
|
|
152
|
+
--skip-system Skip system collections
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
> **Note:** `lazypock-gen` is still available as a deprecated alias.
|
|
156
|
+
|
|
157
|
+
You must provide credentials one of two ways (or via the matching env vars):
|
|
158
|
+
|
|
159
|
+
1. `--api-key` / `LAZYPOCK_API_KEY` — scoped to collection listing, no login.
|
|
160
|
+
2. `--email` + `--password` / `LAZYPOCK_EMAIL` + `LAZYPOCK_PASSWORD` — superuser login.
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
|
|
39
165
|
## API Reference
|
|
40
166
|
|
|
41
167
|
### LazypockClient
|
|
@@ -219,6 +345,25 @@ client.collection('posts').subscribe('abc123', (event) => { ... });
|
|
|
219
345
|
|
|
220
346
|
// Unsubscribe
|
|
221
347
|
client.collection('posts').unsubscribe('*');
|
|
348
|
+
|
|
349
|
+
// Each subscribe returns an unsubscribe function for one-shot listeners:
|
|
350
|
+
const off = client.collection('posts').subscribe('*', cb);
|
|
351
|
+
// later: off();
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Anonymous / rule-based realtime
|
|
355
|
+
|
|
356
|
+
Realtime subscriptions honor your API **and list rules** — matching PocketBase
|
|
357
|
+
behavior. This means **non-logged-in users can subscribe** to collections whose
|
|
358
|
+
list rules are public (empty `""` string) or anon-friendly
|
|
359
|
+
(`@request.auth.*` filters). The SDK auto-connects the WebSocket on first use,
|
|
360
|
+
so no token is required to receive public change events:
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
// Works without logging in, as long as the collection's list rule allows it
|
|
364
|
+
const off = client.collection('public_feed').subscribe('*', (e) => {
|
|
365
|
+
console.log(e.action, e.record);
|
|
366
|
+
});
|
|
222
367
|
```
|
|
223
368
|
|
|
224
369
|
## License
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// src/typegen.ts
|
|
2
|
+
function fieldTypeScriptType(field, fallback = "unknown") {
|
|
3
|
+
const opts = field.options ?? {};
|
|
4
|
+
switch (field.type) {
|
|
5
|
+
case "text":
|
|
6
|
+
case "email":
|
|
7
|
+
case "url":
|
|
8
|
+
case "editor":
|
|
9
|
+
case "date":
|
|
10
|
+
case "datetime":
|
|
11
|
+
return "string";
|
|
12
|
+
case "number":
|
|
13
|
+
return "number";
|
|
14
|
+
case "bool":
|
|
15
|
+
return "boolean";
|
|
16
|
+
case "select": {
|
|
17
|
+
const values = Array.isArray(opts.values) ? opts.values : [];
|
|
18
|
+
if (values.length > 0) {
|
|
19
|
+
return values.map((v) => JSON.stringify(String(v))).join(" | ");
|
|
20
|
+
}
|
|
21
|
+
return "string";
|
|
22
|
+
}
|
|
23
|
+
case "multi_select": {
|
|
24
|
+
const values = Array.isArray(opts.values) ? opts.values : [];
|
|
25
|
+
if (values.length > 0) {
|
|
26
|
+
return `(${values.map((v) => JSON.stringify(String(v))).join(" | ")})[]`;
|
|
27
|
+
}
|
|
28
|
+
return "string[]";
|
|
29
|
+
}
|
|
30
|
+
case "file":
|
|
31
|
+
return "string";
|
|
32
|
+
case "multi_file":
|
|
33
|
+
return "string[]";
|
|
34
|
+
case "json":
|
|
35
|
+
case "geo":
|
|
36
|
+
return "Record<string, unknown>";
|
|
37
|
+
case "relation":
|
|
38
|
+
return (opts.maxSelect ?? 1) > 1 ? "string[]" : "string";
|
|
39
|
+
case "password":
|
|
40
|
+
return "never";
|
|
41
|
+
default:
|
|
42
|
+
return fallback;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function fieldTypeKind(field) {
|
|
46
|
+
const opts = field.options ?? {};
|
|
47
|
+
switch (field.type) {
|
|
48
|
+
case "text":
|
|
49
|
+
case "email":
|
|
50
|
+
case "url":
|
|
51
|
+
case "editor":
|
|
52
|
+
case "date":
|
|
53
|
+
case "datetime":
|
|
54
|
+
case "select":
|
|
55
|
+
case "file":
|
|
56
|
+
return "string";
|
|
57
|
+
case "number":
|
|
58
|
+
return "number";
|
|
59
|
+
case "bool":
|
|
60
|
+
return "boolean";
|
|
61
|
+
case "multi_select":
|
|
62
|
+
case "multi_file":
|
|
63
|
+
return "string-array";
|
|
64
|
+
case "json":
|
|
65
|
+
case "geo":
|
|
66
|
+
return "json";
|
|
67
|
+
case "relation":
|
|
68
|
+
return (opts.maxSelect ?? 1) > 1 ? "relation-many" : "relation";
|
|
69
|
+
case "password":
|
|
70
|
+
return "password";
|
|
71
|
+
default:
|
|
72
|
+
return "unknown";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function schemaFieldType(field) {
|
|
76
|
+
switch (fieldTypeKind(field)) {
|
|
77
|
+
case "string":
|
|
78
|
+
return String;
|
|
79
|
+
case "number":
|
|
80
|
+
return Number;
|
|
81
|
+
case "boolean":
|
|
82
|
+
return Boolean;
|
|
83
|
+
case "string-array":
|
|
84
|
+
return [String];
|
|
85
|
+
case "relation":
|
|
86
|
+
return String;
|
|
87
|
+
case "relation-many":
|
|
88
|
+
return [String];
|
|
89
|
+
case "json":
|
|
90
|
+
return Object;
|
|
91
|
+
case "password":
|
|
92
|
+
return void 0;
|
|
93
|
+
case "unknown":
|
|
94
|
+
return void 0;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/codegen.ts
|
|
99
|
+
function collectionTypeName(name) {
|
|
100
|
+
return name.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("").replace(/^[0-9]/, "_$&");
|
|
101
|
+
}
|
|
102
|
+
function fieldKey(name) {
|
|
103
|
+
return name.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
104
|
+
}
|
|
105
|
+
function generateTypes(collections, options = {}) {
|
|
106
|
+
const {
|
|
107
|
+
packageName = "lazypock",
|
|
108
|
+
includeBaseFields = true,
|
|
109
|
+
skipSystem = false
|
|
110
|
+
} = options;
|
|
111
|
+
const filtered = skipSystem ? collections.filter(
|
|
112
|
+
(c) => !c.system && !c.name.startsWith("_") && c.name !== "users"
|
|
113
|
+
) : collections;
|
|
114
|
+
const sections = [];
|
|
115
|
+
sections.push(`// \u2500\u2500 Auto-generated by lazypock-ts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
116
|
+
// Do not edit by hand. Regenerate with: npx lazypock-gen
|
|
117
|
+
// Schema snapshot: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
118
|
+
if (includeBaseFields) {
|
|
119
|
+
sections.push(`export interface BaseRecord {
|
|
120
|
+
id: string;
|
|
121
|
+
collectionId: string;
|
|
122
|
+
collectionName: string;
|
|
123
|
+
created: string;
|
|
124
|
+
updated: string;
|
|
125
|
+
}`);
|
|
126
|
+
}
|
|
127
|
+
for (const coll of filtered) {
|
|
128
|
+
const typeName = collectionTypeName(coll.name);
|
|
129
|
+
const fields = coll.fields ?? [];
|
|
130
|
+
const lines = fields.map((f) => memberLine(f)).filter((l) => l !== "");
|
|
131
|
+
const body = lines.join("\n");
|
|
132
|
+
sections.push(
|
|
133
|
+
`export interface ${typeName}Record${renderInterface({
|
|
134
|
+
extends: includeBaseFields ? "BaseRecord" : void 0,
|
|
135
|
+
body
|
|
136
|
+
})}`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
sections.push(`export interface AuthRecord extends BaseRecord {
|
|
140
|
+
email: string;
|
|
141
|
+
verified: boolean;
|
|
142
|
+
}`);
|
|
143
|
+
const mapEntries = filtered.map(
|
|
144
|
+
(c) => ` "${c.name}": ${collectionTypeName(c.name)}Record${c.type === "auth" ? " & AuthRecord" : ""};`
|
|
145
|
+
).join("\n");
|
|
146
|
+
sections.push(`export interface LazypockCollections {
|
|
147
|
+
${mapEntries}
|
|
148
|
+
}`);
|
|
149
|
+
sections.push(`import { LazypockClient, type LazypockClientOptions, type CollectionService } from "${packageName}";
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Create a Lazypock client typed against this schema snapshot.
|
|
153
|
+
* Collection access is fully type-checked:
|
|
154
|
+
* client.collection("posts").create({ title: "x" }) // title must exist
|
|
155
|
+
*/
|
|
156
|
+
export function createClient(options: LazypockClientOptions): TypedClient {
|
|
157
|
+
return new TypedClient(options);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export class TypedClient extends LazypockClient {
|
|
161
|
+
override collection<K extends keyof LazypockCollections>(name: K): CollectionService<LazypockCollections[K]>;
|
|
162
|
+
override collection(name: string): CollectionService<unknown> {
|
|
163
|
+
return super.collection(name) as unknown as CollectionService<unknown>;
|
|
164
|
+
}
|
|
165
|
+
}`);
|
|
166
|
+
return sections.join("\n\n") + "\n";
|
|
167
|
+
}
|
|
168
|
+
function renderInterface(opts) {
|
|
169
|
+
const ext = opts.extends ? ` extends ${opts.extends}` : "";
|
|
170
|
+
if (!opts.body) return `${ext} {}`;
|
|
171
|
+
return `${ext} {
|
|
172
|
+
${opts.body}
|
|
173
|
+
}`;
|
|
174
|
+
}
|
|
175
|
+
function memberLine(f) {
|
|
176
|
+
const key = fieldKey(f.name);
|
|
177
|
+
const req = f.required || f.type === "password" ? "" : "?";
|
|
178
|
+
const type = fieldTypeScriptType(f);
|
|
179
|
+
if (type === "never") return "";
|
|
180
|
+
return ` ${JSON.stringify(key)}${req}: ${type};`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export {
|
|
184
|
+
fieldTypeScriptType,
|
|
185
|
+
fieldTypeKind,
|
|
186
|
+
schemaFieldType,
|
|
187
|
+
collectionTypeName,
|
|
188
|
+
generateTypes
|
|
189
|
+
};
|
|
190
|
+
//# sourceMappingURL=chunk-3MZJ5HCD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/typegen.ts","../src/codegen.ts"],"sourcesContent":["// ── Type mapping (runtime + codegen) ────────────────────\n// Maps server field types to TypeScript types.\n// Shared by the runtime `SchemaTypes` helper and the codegen CLI.\n\nimport type { SchemaField } from \"./schema\";\n\n/**\n * Map a single server field to its TypeScript type string.\n * Used by the codegen CLI to emit interface members.\n *\n * @param field The field definition.\n * @param fallback Fallback type for unknown field types (default `unknown`).\n */\nexport function fieldTypeScriptType(\n\tfield: SchemaField,\n\tfallback = \"unknown\",\n): string {\n\tconst opts = field.options ?? {};\n\tswitch (field.type) {\n\t\tcase \"text\":\n\t\tcase \"email\":\n\t\tcase \"url\":\n\t\tcase \"editor\":\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\t\treturn \"string\";\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"bool\":\n\t\t\treturn \"boolean\";\n\t\tcase \"select\": {\n\t\t\tconst values = Array.isArray(opts.values) ? opts.values : [];\n\t\t\tif (values.length > 0) {\n\t\t\t\treturn values.map((v) => JSON.stringify(String(v))).join(\" | \");\n\t\t\t}\n\t\t\treturn \"string\";\n\t\t}\n\t\tcase \"multi_select\": {\n\t\t\tconst values = Array.isArray(opts.values) ? opts.values : [];\n\t\t\tif (values.length > 0) {\n\t\t\t\treturn `(${values.map((v) => JSON.stringify(String(v))).join(\" | \")})[]`;\n\t\t\t}\n\t\t\treturn \"string[]\";\n\t\t}\n\t\tcase \"file\":\n\t\t\treturn \"string\";\n\t\tcase \"multi_file\":\n\t\t\treturn \"string[]\";\n\t\tcase \"json\":\n\t\tcase \"geo\":\n\t\t\treturn \"Record<string, unknown>\";\n\t\tcase \"relation\":\n\t\t\t// Relations store the target record's ID (string) — or an array\n\t\t\t// of IDs when multi-relation (maxSelect > 1).\n\t\t\treturn (opts.maxSelect ?? 1) > 1 ? \"string[]\" : \"string\";\n\t\tcase \"password\":\n\t\t\t// Passwords are write-only; never expose on read models.\n\t\t\treturn \"never\";\n\t\tdefault:\n\t\t\treturn fallback;\n\t}\n}\n\n/**\n * Returns the runtime type kind for a field — used by {@link schemaFieldType}\n * to build structural types at runtime.\n */\nexport type FieldTypeKind =\n\t| \"string\"\n\t| \"number\"\n\t| \"boolean\"\n\t| \"string-array\"\n\t| \"json\"\n\t| \"relation\"\n\t| \"relation-many\"\n\t| \"password\"\n\t| \"unknown\";\n\n/** Map a server field to its runtime type kind. */\nexport function fieldTypeKind(field: SchemaField): FieldTypeKind {\n\tconst opts = field.options ?? {};\n\tswitch (field.type) {\n\t\tcase \"text\":\n\t\tcase \"email\":\n\t\tcase \"url\":\n\t\tcase \"editor\":\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\tcase \"select\":\n\t\tcase \"file\":\n\t\t\treturn \"string\";\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"bool\":\n\t\t\treturn \"boolean\";\n\t\tcase \"multi_select\":\n\t\tcase \"multi_file\":\n\t\t\treturn \"string-array\";\n\t\tcase \"json\":\n\t\tcase \"geo\":\n\t\t\treturn \"json\";\n\t\tcase \"relation\":\n\t\t\treturn (opts.maxSelect ?? 1) > 1 ? \"relation-many\" : \"relation\";\n\t\tcase \"password\":\n\t\t\treturn \"password\";\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Derive a TypeScript field type from a {@link SchemaField} — the runtime\n * counterpart to the codegen mapper. Lets consumers build typed clients\n * from a fetched schema without running the CLI.\n */\nexport function schemaFieldType(field: SchemaField): unknown {\n\tswitch (fieldTypeKind(field)) {\n\t\tcase \"string\":\n\t\t\treturn String;\n\t\tcase \"number\":\n\t\t\treturn Number;\n\t\tcase \"boolean\":\n\t\t\treturn Boolean;\n\t\tcase \"string-array\":\n\t\t\treturn [String] as const;\n\t\tcase \"relation\":\n\t\t\treturn String;\n\t\tcase \"relation-many\":\n\t\t\treturn [String] as const;\n\t\tcase \"json\":\n\t\t\treturn Object;\n\t\tcase \"password\":\n\t\t\treturn undefined;\n\t\tcase \"unknown\":\n\t\t\treturn undefined;\n\t}\n}\n","// ── Codegen ─────────────────────────────────────────────\n// Generates a `lazypock.types.ts` module from the live API schema.\n//\n// The generated file exports:\n// - One interface per collection (e.g. `PostsRecord`)\n// - A `LazypockCollections` map: { posts: PostsRecord; users: UsersRecord; ... }\n// - A `createClient()` factory pre-bound to those types, so\n// `client.collection(\"posts\").create({ title: \"x\" })` is fully type-checked.\n//\n// The CLI in `src/cli.ts` wires this to `GET /collections`.\n\nimport type { CollectionSchema, SchemaField } from \"./schema\";\nimport { fieldTypeScriptType } from \"./typegen\";\n\n/** Format a raw collection name into a valid TS identifier (PascalCase). */\nexport function collectionTypeName(name: string): string {\n\treturn name\n\t\t.split(/[^a-zA-Z0-9]+/)\n\t\t.filter(Boolean)\n\t\t.map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n\t\t.join(\"\")\n\t\t.replace(/^[0-9]/, \"_$&\");\n}\n\n/** Interface member name — sanitize hyphens/spaces but keep readability. */\nexport function fieldKey(name: string): string {\n\treturn name.replace(/[^a-zA-Z0-9_]/g, \"_\");\n}\n\n\n\n/**\n * Generate the full TypeScript source for the typed SDK module.\n *\n * @param collections Collections fetched from the API.\n * @param options Generation options.\n */\nexport function generateTypes(\n\tcollections: CollectionSchema[],\n\toptions: {\n\t\t/** Import specifier for the lazypock package (default `lazypock`). */\n\t\tpackageName?: string;\n\t\t/** Emit base record fields (id, created, updated, …). Default true. */\n\t\tincludeBaseFields?: boolean;\n\t\t/** Skip system collections (names starting with `_` or `users`). Default false. */\n\t\tskipSystem?: boolean;\n\t} = {},\n): string {\n\tconst {\n\t\tpackageName = \"lazypock\",\n\t\tincludeBaseFields = true,\n\t\tskipSystem = false,\n\t} = options;\n\n\tconst filtered = skipSystem\n\t\t? collections.filter(\n\t\t\t\t(c) => !c.system && !c.name.startsWith(\"_\") && c.name !== \"users\",\n\t\t\t)\n\t\t: collections;\n\n\tconst sections: string[] = [];\n\tsections.push(`// ── Auto-generated by lazypock-ts ──────────────────────────\n// Do not edit by hand. Regenerate with: npx lazypock-gen\n// Schema snapshot: ${new Date().toISOString()}`);\n\n\tif (includeBaseFields) {\n\t\tsections.push(`export interface BaseRecord {\n id: string;\n collectionId: string;\n collectionName: string;\n created: string;\n updated: string;\n}`);\n\t}\n\n\t// One interface per collection\n\tfor (const coll of filtered) {\n\t\tconst typeName = collectionTypeName(coll.name);\n\t\tconst fields = coll.fields ?? [];\n\t\tconst lines = fields.map((f) => memberLine(f)).filter((l) => l !== \"\");\n\t\tconst body = lines.join(\"\\n\");\n\t\tsections.push(\n\t\t\t`export interface ${typeName}Record${renderInterface({\n\t\t\t\textends: includeBaseFields ? \"BaseRecord\" : undefined,\n\t\t\t\tbody,\n\t\t\t})}`,\n\t\t);\n\t}\n\n\t// Auth collection type\n\tsections.push(`export interface AuthRecord extends BaseRecord {\n email: string;\n verified: boolean;\n}`);\n\n\t// Collections map\n\tconst mapEntries = filtered\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` \"${c.name}\": ${collectionTypeName(c.name)}Record${\n\t\t\t\t\tc.type === \"auth\" ? \" & AuthRecord\" : \"\"\n\t\t\t\t};`,\n\t\t)\n\t\t.join(\"\\n\");\n\tsections.push(`export interface LazypockCollections {\n${mapEntries}\n}`);\n\n\t// createClient factory\n\tsections.push(`import { LazypockClient, type LazypockClientOptions, type CollectionService } from \"${packageName}\";\n\n/**\n * Create a Lazypock client typed against this schema snapshot.\n * Collection access is fully type-checked:\n * client.collection(\"posts\").create({ title: \"x\" }) // title must exist\n */\nexport function createClient(options: LazypockClientOptions): TypedClient {\n return new TypedClient(options);\n}\n\nexport class TypedClient extends LazypockClient {\n override collection<K extends keyof LazypockCollections>(name: K): CollectionService<LazypockCollections[K]>;\n override collection(name: string): CollectionService<unknown> {\n return super.collection(name) as unknown as CollectionService<unknown>;\n }\n}`);\n\n\treturn sections.join(\"\\n\\n\") + \"\\n\";\n}\n\n/**\n * Render the interface body including the extends clause.\n * Empty body → ` extends BaseRecord {}` (valid TS).\n */\nfunction renderInterface(opts: { extends?: string; body: string }): string {\n\tconst ext = opts.extends ? ` extends ${opts.extends}` : \"\";\n\tif (!opts.body) return `${ext} {}`;\n\treturn `${ext} {\\n${opts.body}\\n}`;\n}\n\n/** Render a single interface member line for a field. */\nfunction memberLine(f: SchemaField): string {\n\tconst key = fieldKey(f.name);\n\tconst req = f.required || f.type === \"password\" ? \"\" : \"?\";\n\tconst type = fieldTypeScriptType(f);\n\t// `never` members (passwords) are omitted from read models.\n\tif (type === \"never\") return \"\";\n\treturn ` ${JSON.stringify(key)}${req}: ${type};`;\n}\n"],"mappings":";AAaO,SAAS,oBACf,OACA,WAAW,WACF;AACT,QAAM,OAAO,MAAM,WAAW,CAAC;AAC/B,UAAQ,MAAM,MAAM;AAAA,IACnB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK,UAAU;AACd,YAAM,SAAS,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK,SAAS,CAAC;AAC3D,UAAI,OAAO,SAAS,GAAG;AACtB,eAAO,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK;AAAA,MAC/D;AACA,aAAO;AAAA,IACR;AAAA,IACA,KAAK,gBAAgB;AACpB,YAAM,SAAS,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK,SAAS,CAAC;AAC3D,UAAI,OAAO,SAAS,GAAG;AACtB,eAAO,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC;AAAA,MACpE;AACA,aAAO;AAAA,IACR;AAAA,IACA,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AAAA,IACL,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AAGJ,cAAQ,KAAK,aAAa,KAAK,IAAI,aAAa;AAAA,IACjD,KAAK;AAEJ,aAAO;AAAA,IACR;AACC,aAAO;AAAA,EACT;AACD;AAkBO,SAAS,cAAc,OAAmC;AAChE,QAAM,OAAO,MAAM,WAAW,CAAC;AAC/B,UAAQ,MAAM,MAAM;AAAA,IACnB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AAAA,IACL,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AAAA,IACL,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,cAAQ,KAAK,aAAa,KAAK,IAAI,kBAAkB;AAAA,IACtD,KAAK;AACJ,aAAO;AAAA,IACR;AACC,aAAO;AAAA,EACT;AACD;AAOO,SAAS,gBAAgB,OAA6B;AAC5D,UAAQ,cAAc,KAAK,GAAG;AAAA,IAC7B,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO,CAAC,MAAM;AAAA,IACf,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO,CAAC,MAAM;AAAA,IACf,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,EACT;AACD;;;ACzHO,SAAS,mBAAmB,MAAsB;AACxD,SAAO,KACL,MAAM,eAAe,EACrB,OAAO,OAAO,EACd,IAAI,CAAC,SAAS,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC,CAAC,EAC1D,KAAK,EAAE,EACP,QAAQ,UAAU,KAAK;AAC1B;AAGO,SAAS,SAAS,MAAsB;AAC9C,SAAO,KAAK,QAAQ,kBAAkB,GAAG;AAC1C;AAUO,SAAS,cACf,aACA,UAOI,CAAC,GACI;AACT,QAAM;AAAA,IACL,cAAc;AAAA,IACd,oBAAoB;AAAA,IACpB,aAAa;AAAA,EACd,IAAI;AAEJ,QAAM,WAAW,aACd,YAAY;AAAA,IACZ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,EAAE,KAAK,WAAW,GAAG,KAAK,EAAE,SAAS;AAAA,EAC3D,IACC;AAEH,QAAM,WAAqB,CAAC;AAC5B,WAAS,KAAK;AAAA;AAAA,uBAEO,oBAAI,KAAK,GAAE,YAAY,CAAC,EAAE;AAE/C,MAAI,mBAAmB;AACtB,aAAS,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMd;AAAA,EACD;AAGA,aAAW,QAAQ,UAAU;AAC5B,UAAM,WAAW,mBAAmB,KAAK,IAAI;AAC7C,UAAM,SAAS,KAAK,UAAU,CAAC;AAC/B,UAAM,QAAQ,OAAO,IAAI,CAAC,MAAM,WAAW,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,MAAM,EAAE;AACrE,UAAM,OAAO,MAAM,KAAK,IAAI;AAC5B,aAAS;AAAA,MACR,oBAAoB,QAAQ,SAAS,gBAAgB;AAAA,QACpD,SAAS,oBAAoB,eAAe;AAAA,QAC5C;AAAA,MACD,CAAC,CAAC;AAAA,IACH;AAAA,EACD;AAGA,WAAS,KAAK;AAAA;AAAA;AAAA,EAGb;AAGD,QAAM,aAAa,SACjB;AAAA,IACA,CAAC,MACA,MAAM,EAAE,IAAI,MAAM,mBAAmB,EAAE,IAAI,CAAC,SAC3C,EAAE,SAAS,SAAS,kBAAkB,EACvC;AAAA,EACF,EACC,KAAK,IAAI;AACX,WAAS,KAAK;AAAA,EACb,UAAU;AAAA,EACV;AAGD,WAAS,KAAK,uFAAuF,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgB/G;AAED,SAAO,SAAS,KAAK,MAAM,IAAI;AAChC;AAMA,SAAS,gBAAgB,MAAkD;AAC1E,QAAM,MAAM,KAAK,UAAU,YAAY,KAAK,OAAO,KAAK;AACxD,MAAI,CAAC,KAAK,KAAM,QAAO,GAAG,GAAG;AAC7B,SAAO,GAAG,GAAG;AAAA,EAAO,KAAK,IAAI;AAAA;AAC9B;AAGA,SAAS,WAAW,GAAwB;AAC3C,QAAM,MAAM,SAAS,EAAE,IAAI;AAC3B,QAAM,MAAM,EAAE,YAAY,EAAE,SAAS,aAAa,KAAK;AACvD,QAAM,OAAO,oBAAoB,CAAC;AAElC,MAAI,SAAS,QAAS,QAAO;AAC7B,SAAO,KAAK,KAAK,UAAU,GAAG,CAAC,GAAG,GAAG,KAAK,IAAI;AAC/C;","names":[]}
|
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// src/cli.ts
|
|
5
|
+
var import_promises = require("fs/promises");
|
|
6
|
+
var import_node_path = require("path");
|
|
7
|
+
|
|
8
|
+
// src/typegen.ts
|
|
9
|
+
function fieldTypeScriptType(field, fallback = "unknown") {
|
|
10
|
+
const opts = field.options ?? {};
|
|
11
|
+
switch (field.type) {
|
|
12
|
+
case "text":
|
|
13
|
+
case "email":
|
|
14
|
+
case "url":
|
|
15
|
+
case "editor":
|
|
16
|
+
case "date":
|
|
17
|
+
case "datetime":
|
|
18
|
+
return "string";
|
|
19
|
+
case "number":
|
|
20
|
+
return "number";
|
|
21
|
+
case "bool":
|
|
22
|
+
return "boolean";
|
|
23
|
+
case "select": {
|
|
24
|
+
const values = Array.isArray(opts.values) ? opts.values : [];
|
|
25
|
+
if (values.length > 0) {
|
|
26
|
+
return values.map((v) => JSON.stringify(String(v))).join(" | ");
|
|
27
|
+
}
|
|
28
|
+
return "string";
|
|
29
|
+
}
|
|
30
|
+
case "multi_select": {
|
|
31
|
+
const values = Array.isArray(opts.values) ? opts.values : [];
|
|
32
|
+
if (values.length > 0) {
|
|
33
|
+
return `(${values.map((v) => JSON.stringify(String(v))).join(" | ")})[]`;
|
|
34
|
+
}
|
|
35
|
+
return "string[]";
|
|
36
|
+
}
|
|
37
|
+
case "file":
|
|
38
|
+
return "string";
|
|
39
|
+
case "multi_file":
|
|
40
|
+
return "string[]";
|
|
41
|
+
case "json":
|
|
42
|
+
case "geo":
|
|
43
|
+
return "Record<string, unknown>";
|
|
44
|
+
case "relation":
|
|
45
|
+
return (opts.maxSelect ?? 1) > 1 ? "string[]" : "string";
|
|
46
|
+
case "password":
|
|
47
|
+
return "never";
|
|
48
|
+
default:
|
|
49
|
+
return fallback;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/codegen.ts
|
|
54
|
+
function collectionTypeName(name) {
|
|
55
|
+
return name.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("").replace(/^[0-9]/, "_$&");
|
|
56
|
+
}
|
|
57
|
+
function fieldKey(name) {
|
|
58
|
+
return name.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
59
|
+
}
|
|
60
|
+
function generateTypes(collections, options = {}) {
|
|
61
|
+
const {
|
|
62
|
+
packageName = "lazypock",
|
|
63
|
+
includeBaseFields = true,
|
|
64
|
+
skipSystem = false
|
|
65
|
+
} = options;
|
|
66
|
+
const filtered = skipSystem ? collections.filter(
|
|
67
|
+
(c) => !c.system && !c.name.startsWith("_") && c.name !== "users"
|
|
68
|
+
) : collections;
|
|
69
|
+
const sections = [];
|
|
70
|
+
sections.push(`// \u2500\u2500 Auto-generated by lazypock-ts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
71
|
+
// Do not edit by hand. Regenerate with: npx lazypock-gen
|
|
72
|
+
// Schema snapshot: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
73
|
+
if (includeBaseFields) {
|
|
74
|
+
sections.push(`export interface BaseRecord {
|
|
75
|
+
id: string;
|
|
76
|
+
collectionId: string;
|
|
77
|
+
collectionName: string;
|
|
78
|
+
created: string;
|
|
79
|
+
updated: string;
|
|
80
|
+
}`);
|
|
81
|
+
}
|
|
82
|
+
for (const coll of filtered) {
|
|
83
|
+
const typeName = collectionTypeName(coll.name);
|
|
84
|
+
const fields = coll.fields ?? [];
|
|
85
|
+
const lines = fields.map((f) => memberLine(f)).filter((l) => l !== "");
|
|
86
|
+
const body = lines.join("\n");
|
|
87
|
+
sections.push(
|
|
88
|
+
`export interface ${typeName}Record${renderInterface({
|
|
89
|
+
extends: includeBaseFields ? "BaseRecord" : void 0,
|
|
90
|
+
body
|
|
91
|
+
})}`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
sections.push(`export interface AuthRecord extends BaseRecord {
|
|
95
|
+
email: string;
|
|
96
|
+
verified: boolean;
|
|
97
|
+
}`);
|
|
98
|
+
const mapEntries = filtered.map(
|
|
99
|
+
(c) => ` "${c.name}": ${collectionTypeName(c.name)}Record${c.type === "auth" ? " & AuthRecord" : ""};`
|
|
100
|
+
).join("\n");
|
|
101
|
+
sections.push(`export interface LazypockCollections {
|
|
102
|
+
${mapEntries}
|
|
103
|
+
}`);
|
|
104
|
+
sections.push(`import { LazypockClient, type LazypockClientOptions, type CollectionService } from "${packageName}";
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Create a Lazypock client typed against this schema snapshot.
|
|
108
|
+
* Collection access is fully type-checked:
|
|
109
|
+
* client.collection("posts").create({ title: "x" }) // title must exist
|
|
110
|
+
*/
|
|
111
|
+
export function createClient(options: LazypockClientOptions): TypedClient {
|
|
112
|
+
return new TypedClient(options);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export class TypedClient extends LazypockClient {
|
|
116
|
+
override collection<K extends keyof LazypockCollections>(name: K): CollectionService<LazypockCollections[K]>;
|
|
117
|
+
override collection(name: string): CollectionService<unknown> {
|
|
118
|
+
return super.collection(name) as unknown as CollectionService<unknown>;
|
|
119
|
+
}
|
|
120
|
+
}`);
|
|
121
|
+
return sections.join("\n\n") + "\n";
|
|
122
|
+
}
|
|
123
|
+
function renderInterface(opts) {
|
|
124
|
+
const ext = opts.extends ? ` extends ${opts.extends}` : "";
|
|
125
|
+
if (!opts.body) return `${ext} {}`;
|
|
126
|
+
return `${ext} {
|
|
127
|
+
${opts.body}
|
|
128
|
+
}`;
|
|
129
|
+
}
|
|
130
|
+
function memberLine(f) {
|
|
131
|
+
const key = fieldKey(f.name);
|
|
132
|
+
const req = f.required || f.type === "password" ? "" : "?";
|
|
133
|
+
const type = fieldTypeScriptType(f);
|
|
134
|
+
if (type === "never") return "";
|
|
135
|
+
return ` ${JSON.stringify(key)}${req}: ${type};`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/cli.ts
|
|
139
|
+
function fail(msg) {
|
|
140
|
+
console.error(`
|
|
141
|
+
\u274C ${msg}
|
|
142
|
+
`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
function parseArgs(argv) {
|
|
146
|
+
const args = [...argv];
|
|
147
|
+
const get = (flag, envKey, def = "") => {
|
|
148
|
+
const i = args.indexOf(flag);
|
|
149
|
+
if (i !== -1 && i + 1 < args.length) return args[i + 1];
|
|
150
|
+
return process.env[envKey] ?? def;
|
|
151
|
+
};
|
|
152
|
+
const has = (flag) => args.includes(flag);
|
|
153
|
+
const url = get("--url", "LAZYPOCK_URL");
|
|
154
|
+
const email = get("--email", "LAZYPOCK_EMAIL");
|
|
155
|
+
const password = get("--password", "LAZYPOCK_PASSWORD");
|
|
156
|
+
const apiKey = get("--api-key", "LAZYPOCK_API_KEY");
|
|
157
|
+
if (!url) fail("Missing API URL. Pass --url or set LAZYPOCK_URL.");
|
|
158
|
+
if (!apiKey) {
|
|
159
|
+
if (!email)
|
|
160
|
+
fail(
|
|
161
|
+
"Missing credentials. Pass --api-key, or --email + --password, or set LAZYPOCK_API_KEY / LAZYPOCK_EMAIL."
|
|
162
|
+
);
|
|
163
|
+
if (!password)
|
|
164
|
+
fail("Missing password. Pass --password, or set LAZYPOCK_PASSWORD.");
|
|
165
|
+
}
|
|
166
|
+
const out = get("--out", "LAZYPOCK_OUT", "lazypock.types.ts");
|
|
167
|
+
const packageName = get("--package", "LAZYPOCK_PACKAGE", "lazypock");
|
|
168
|
+
return {
|
|
169
|
+
url,
|
|
170
|
+
email,
|
|
171
|
+
password,
|
|
172
|
+
apiKey,
|
|
173
|
+
out,
|
|
174
|
+
packageName,
|
|
175
|
+
skipSystem: has("--skip-system")
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
async function fetchCollections(opts) {
|
|
179
|
+
const base = opts.url.replace(/\/+$/, "");
|
|
180
|
+
let authKey;
|
|
181
|
+
if (opts.apiKey) {
|
|
182
|
+
authKey = opts.apiKey;
|
|
183
|
+
} else {
|
|
184
|
+
const loginRes = await fetch(base + "/superusers/login", {
|
|
185
|
+
method: "POST",
|
|
186
|
+
headers: { "Content-Type": "application/json" },
|
|
187
|
+
body: JSON.stringify({ email: opts.email, password: opts.password })
|
|
188
|
+
});
|
|
189
|
+
if (!loginRes.ok) {
|
|
190
|
+
const text = await loginRes.text();
|
|
191
|
+
fail(
|
|
192
|
+
`Superuser login failed (${loginRes.status}). ${text.slice(0, 200)}`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
const loginData = await loginRes.json();
|
|
196
|
+
if (!loginData.token) fail("Login response did not include a token.");
|
|
197
|
+
authKey = loginData.token;
|
|
198
|
+
}
|
|
199
|
+
const collRes = await fetch(base + "/collections", {
|
|
200
|
+
headers: { Authorization: "Bearer " + authKey }
|
|
201
|
+
});
|
|
202
|
+
if (!collRes.ok) {
|
|
203
|
+
const text = await collRes.text();
|
|
204
|
+
fail(
|
|
205
|
+
`Failed to fetch collections (${collRes.status}). ${text.slice(0, 200)}`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
return await collRes.json();
|
|
209
|
+
}
|
|
210
|
+
async function main() {
|
|
211
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
212
|
+
const authLabel = opts.apiKey ? `API key ${opts.apiKey.slice(0, 4)}\u2026${opts.apiKey.slice(-4)}` : opts.email;
|
|
213
|
+
console.log(`
|
|
214
|
+
\u{1F50C} Connecting to ${opts.url} as ${authLabel} \u2026`);
|
|
215
|
+
const { items } = await fetchCollections(opts);
|
|
216
|
+
console.log(`\u{1F4E6} Found ${items.length} collection(s).`);
|
|
217
|
+
const source = generateTypes(items, {
|
|
218
|
+
packageName: opts.packageName,
|
|
219
|
+
skipSystem: opts.skipSystem
|
|
220
|
+
});
|
|
221
|
+
const outPath = (0, import_node_path.resolve)(process.cwd(), opts.out);
|
|
222
|
+
await (0, import_promises.writeFile)(outPath, source, "utf8");
|
|
223
|
+
console.log(`\u2705 Wrote ${outPath} (${source.length} bytes).`);
|
|
224
|
+
console.log(
|
|
225
|
+
`
|
|
226
|
+
Import it in your app:
|
|
227
|
+
import { createClient } from './${opts.out.replace(/\.ts$/, "")}';
|
|
228
|
+
`
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
main().catch((err) => {
|
|
232
|
+
console.error(err);
|
|
233
|
+
process.exit(1);
|
|
234
|
+
});
|
|
235
|
+
//# sourceMappingURL=cli.cjs.map
|
package/dist/cli.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/typegen.ts","../src/codegen.ts"],"sourcesContent":["#!/usr/bin/env node\n// ── Codegen CLI ─────────────────────────────────────────\n// `lazypock` — fetches the live collection schema from a Lazypock\n// API and writes a fully-typed `lazypock.types.ts` module.\n//\n// Auth methods (pick one):\n// 1. Superuser email + password:\n// npx lazypock --url http://localhost:4000/api --email admin@... --password ...\n// 2. API key (recommended, generated from the Settings dashboard):\n// npx lazypock --url http://localhost:4000/api --api-key <key>\n//\n// Or via env vars (no flags):\n// LAZYPOCK_URL=... LAZYPOCK_API_KEY=... npx lazypock\n// LAZYPOCK_URL=... LAZYPOCK_EMAIL=... LAZYPOCK_PASSWORD=... npx lazypock\n//\n// NOTE: `lazypock-gen` remains as a deprecated alias for backwards\n// compatibility. Both invoke the same executable.\n\nimport { writeFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { generateTypes } from \"./codegen\";\nimport type { CollectionsResponse } from \"./schema\";\n\ninterface CliOptions {\n\turl: string;\n\temail: string;\n\tpassword: string;\n\tapiKey: string;\n\tout: string;\n\tpackageName: string;\n\tskipSystem: boolean;\n}\n\nfunction fail(msg: string): never {\n\tconsole.error(`\\n❌ ${msg}\\n`);\n\tprocess.exit(1);\n}\n\nfunction parseArgs(argv: string[]): CliOptions {\n\tconst args = [...argv];\n\tconst get = (flag: string, envKey: string, def = \"\"): string => {\n\t\tconst i = args.indexOf(flag);\n\t\tif (i !== -1 && i + 1 < args.length) return args[i + 1];\n\t\treturn process.env[envKey] ?? def;\n\t};\n\tconst has = (flag: string): boolean => args.includes(flag);\n\n\tconst url = get(\"--url\", \"LAZYPOCK_URL\");\n\tconst email = get(\"--email\", \"LAZYPOCK_EMAIL\");\n\tconst password = get(\"--password\", \"LAZYPOCK_PASSWORD\");\n\tconst apiKey = get(\"--api-key\", \"LAZYPOCK_API_KEY\");\n\n\tif (!url) fail(\"Missing API URL. Pass --url or set LAZYPOCK_URL.\");\n\tif (!apiKey) {\n\t\tif (!email)\n\t\t\tfail(\n\t\t\t\t\"Missing credentials. Pass --api-key, or --email + --password, or set LAZYPOCK_API_KEY / LAZYPOCK_EMAIL.\",\n\t\t\t);\n\t\tif (!password)\n\t\t\tfail(\"Missing password. Pass --password, or set LAZYPOCK_PASSWORD.\");\n\t}\n\n\tconst out = get(\"--out\", \"LAZYPOCK_OUT\", \"lazypock.types.ts\");\n\tconst packageName = get(\"--package\", \"LAZYPOCK_PACKAGE\", \"lazypock\");\n\n\treturn {\n\t\turl,\n\t\temail,\n\t\tpassword,\n\t\tapiKey,\n\t\tout,\n\t\tpackageName,\n\t\tskipSystem: has(\"--skip-system\"),\n\t};\n}\n\nasync function fetchCollections(\n\topts: CliOptions,\n): Promise<CollectionsResponse> {\n\tconst base = opts.url.replace(/\\/+$/, \"\");\n\n\tlet authKey: string;\n\tif (opts.apiKey) {\n\t\t// Step 1: use a stored API key directly (no login round-trip).\n\t\t// The key is sent as `Authorization: Bearer <key>` and recognised\n\t\t// by the backend's Auth.Plug as an API key.\n\t\tauthKey = opts.apiKey;\n\t} else {\n\t\t// Step 1: login as superuser to get a token\n\t\tconst loginRes = await fetch(base + \"/superusers/login\", {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ email: opts.email, password: opts.password }),\n\t\t});\n\t\tif (!loginRes.ok) {\n\t\t\tconst text = await loginRes.text();\n\t\t\tfail(\n\t\t\t\t`Superuser login failed (${loginRes.status}). ${text.slice(0, 200)}`,\n\t\t\t);\n\t\t}\n\t\tconst loginData = (await loginRes.json()) as { token?: string };\n\t\tif (!loginData.token) fail(\"Login response did not include a token.\");\n\t\tauthKey = loginData.token;\n\t}\n\n\t// Step 2: fetch collections\n\tconst collRes = await fetch(base + \"/collections\", {\n\t\theaders: { Authorization: \"Bearer \" + authKey },\n\t});\n\tif (!collRes.ok) {\n\t\tconst text = await collRes.text();\n\t\tfail(\n\t\t\t`Failed to fetch collections (${collRes.status}). ${text.slice(0, 200)}`,\n\t\t);\n\t}\n\treturn (await collRes.json()) as CollectionsResponse;\n}\n\nasync function main(): Promise<void> {\n\tconst opts = parseArgs(process.argv.slice(2));\n\tconst authLabel = opts.apiKey\n\t\t? `API key ${opts.apiKey.slice(0, 4)}…${opts.apiKey.slice(-4)}`\n\t\t: opts.email;\n\tconsole.log(`\\n🔌 Connecting to ${opts.url} as ${authLabel} …`);\n\n\tconst { items } = await fetchCollections(opts);\n\tconsole.log(`📦 Found ${items.length} collection(s).`);\n\n\tconst source = generateTypes(items, {\n\t\tpackageName: opts.packageName,\n\t\tskipSystem: opts.skipSystem,\n\t});\n\n\tconst outPath = resolve(process.cwd(), opts.out);\n\tawait writeFile(outPath, source, \"utf8\");\n\tconsole.log(`✅ Wrote ${outPath} (${source.length} bytes).`);\n\tconsole.log(\n\t\t`\\nImport it in your app:\\n import { createClient } from './${opts.out.replace(/\\.ts$/, \"\")}';\\n`,\n\t);\n}\n\nmain().catch((err) => {\n\tconsole.error(err);\n\tprocess.exit(1);\n});\n","// ── Type mapping (runtime + codegen) ────────────────────\n// Maps server field types to TypeScript types.\n// Shared by the runtime `SchemaTypes` helper and the codegen CLI.\n\nimport type { SchemaField } from \"./schema\";\n\n/**\n * Map a single server field to its TypeScript type string.\n * Used by the codegen CLI to emit interface members.\n *\n * @param field The field definition.\n * @param fallback Fallback type for unknown field types (default `unknown`).\n */\nexport function fieldTypeScriptType(\n\tfield: SchemaField,\n\tfallback = \"unknown\",\n): string {\n\tconst opts = field.options ?? {};\n\tswitch (field.type) {\n\t\tcase \"text\":\n\t\tcase \"email\":\n\t\tcase \"url\":\n\t\tcase \"editor\":\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\t\treturn \"string\";\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"bool\":\n\t\t\treturn \"boolean\";\n\t\tcase \"select\": {\n\t\t\tconst values = Array.isArray(opts.values) ? opts.values : [];\n\t\t\tif (values.length > 0) {\n\t\t\t\treturn values.map((v) => JSON.stringify(String(v))).join(\" | \");\n\t\t\t}\n\t\t\treturn \"string\";\n\t\t}\n\t\tcase \"multi_select\": {\n\t\t\tconst values = Array.isArray(opts.values) ? opts.values : [];\n\t\t\tif (values.length > 0) {\n\t\t\t\treturn `(${values.map((v) => JSON.stringify(String(v))).join(\" | \")})[]`;\n\t\t\t}\n\t\t\treturn \"string[]\";\n\t\t}\n\t\tcase \"file\":\n\t\t\treturn \"string\";\n\t\tcase \"multi_file\":\n\t\t\treturn \"string[]\";\n\t\tcase \"json\":\n\t\tcase \"geo\":\n\t\t\treturn \"Record<string, unknown>\";\n\t\tcase \"relation\":\n\t\t\t// Relations store the target record's ID (string) — or an array\n\t\t\t// of IDs when multi-relation (maxSelect > 1).\n\t\t\treturn (opts.maxSelect ?? 1) > 1 ? \"string[]\" : \"string\";\n\t\tcase \"password\":\n\t\t\t// Passwords are write-only; never expose on read models.\n\t\t\treturn \"never\";\n\t\tdefault:\n\t\t\treturn fallback;\n\t}\n}\n\n/**\n * Returns the runtime type kind for a field — used by {@link schemaFieldType}\n * to build structural types at runtime.\n */\nexport type FieldTypeKind =\n\t| \"string\"\n\t| \"number\"\n\t| \"boolean\"\n\t| \"string-array\"\n\t| \"json\"\n\t| \"relation\"\n\t| \"relation-many\"\n\t| \"password\"\n\t| \"unknown\";\n\n/** Map a server field to its runtime type kind. */\nexport function fieldTypeKind(field: SchemaField): FieldTypeKind {\n\tconst opts = field.options ?? {};\n\tswitch (field.type) {\n\t\tcase \"text\":\n\t\tcase \"email\":\n\t\tcase \"url\":\n\t\tcase \"editor\":\n\t\tcase \"date\":\n\t\tcase \"datetime\":\n\t\tcase \"select\":\n\t\tcase \"file\":\n\t\t\treturn \"string\";\n\t\tcase \"number\":\n\t\t\treturn \"number\";\n\t\tcase \"bool\":\n\t\t\treturn \"boolean\";\n\t\tcase \"multi_select\":\n\t\tcase \"multi_file\":\n\t\t\treturn \"string-array\";\n\t\tcase \"json\":\n\t\tcase \"geo\":\n\t\t\treturn \"json\";\n\t\tcase \"relation\":\n\t\t\treturn (opts.maxSelect ?? 1) > 1 ? \"relation-many\" : \"relation\";\n\t\tcase \"password\":\n\t\t\treturn \"password\";\n\t\tdefault:\n\t\t\treturn \"unknown\";\n\t}\n}\n\n/**\n * Derive a TypeScript field type from a {@link SchemaField} — the runtime\n * counterpart to the codegen mapper. Lets consumers build typed clients\n * from a fetched schema without running the CLI.\n */\nexport function schemaFieldType(field: SchemaField): unknown {\n\tswitch (fieldTypeKind(field)) {\n\t\tcase \"string\":\n\t\t\treturn String;\n\t\tcase \"number\":\n\t\t\treturn Number;\n\t\tcase \"boolean\":\n\t\t\treturn Boolean;\n\t\tcase \"string-array\":\n\t\t\treturn [String] as const;\n\t\tcase \"relation\":\n\t\t\treturn String;\n\t\tcase \"relation-many\":\n\t\t\treturn [String] as const;\n\t\tcase \"json\":\n\t\t\treturn Object;\n\t\tcase \"password\":\n\t\t\treturn undefined;\n\t\tcase \"unknown\":\n\t\t\treturn undefined;\n\t}\n}\n","// ── Codegen ─────────────────────────────────────────────\n// Generates a `lazypock.types.ts` module from the live API schema.\n//\n// The generated file exports:\n// - One interface per collection (e.g. `PostsRecord`)\n// - A `LazypockCollections` map: { posts: PostsRecord; users: UsersRecord; ... }\n// - A `createClient()` factory pre-bound to those types, so\n// `client.collection(\"posts\").create({ title: \"x\" })` is fully type-checked.\n//\n// The CLI in `src/cli.ts` wires this to `GET /collections`.\n\nimport type { CollectionSchema, SchemaField } from \"./schema\";\nimport { fieldTypeScriptType } from \"./typegen\";\n\n/** Format a raw collection name into a valid TS identifier (PascalCase). */\nexport function collectionTypeName(name: string): string {\n\treturn name\n\t\t.split(/[^a-zA-Z0-9]+/)\n\t\t.filter(Boolean)\n\t\t.map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n\t\t.join(\"\")\n\t\t.replace(/^[0-9]/, \"_$&\");\n}\n\n/** Interface member name — sanitize hyphens/spaces but keep readability. */\nexport function fieldKey(name: string): string {\n\treturn name.replace(/[^a-zA-Z0-9_]/g, \"_\");\n}\n\n\n\n/**\n * Generate the full TypeScript source for the typed SDK module.\n *\n * @param collections Collections fetched from the API.\n * @param options Generation options.\n */\nexport function generateTypes(\n\tcollections: CollectionSchema[],\n\toptions: {\n\t\t/** Import specifier for the lazypock package (default `lazypock`). */\n\t\tpackageName?: string;\n\t\t/** Emit base record fields (id, created, updated, …). Default true. */\n\t\tincludeBaseFields?: boolean;\n\t\t/** Skip system collections (names starting with `_` or `users`). Default false. */\n\t\tskipSystem?: boolean;\n\t} = {},\n): string {\n\tconst {\n\t\tpackageName = \"lazypock\",\n\t\tincludeBaseFields = true,\n\t\tskipSystem = false,\n\t} = options;\n\n\tconst filtered = skipSystem\n\t\t? collections.filter(\n\t\t\t\t(c) => !c.system && !c.name.startsWith(\"_\") && c.name !== \"users\",\n\t\t\t)\n\t\t: collections;\n\n\tconst sections: string[] = [];\n\tsections.push(`// ── Auto-generated by lazypock-ts ──────────────────────────\n// Do not edit by hand. Regenerate with: npx lazypock-gen\n// Schema snapshot: ${new Date().toISOString()}`);\n\n\tif (includeBaseFields) {\n\t\tsections.push(`export interface BaseRecord {\n id: string;\n collectionId: string;\n collectionName: string;\n created: string;\n updated: string;\n}`);\n\t}\n\n\t// One interface per collection\n\tfor (const coll of filtered) {\n\t\tconst typeName = collectionTypeName(coll.name);\n\t\tconst fields = coll.fields ?? [];\n\t\tconst lines = fields.map((f) => memberLine(f)).filter((l) => l !== \"\");\n\t\tconst body = lines.join(\"\\n\");\n\t\tsections.push(\n\t\t\t`export interface ${typeName}Record${renderInterface({\n\t\t\t\textends: includeBaseFields ? \"BaseRecord\" : undefined,\n\t\t\t\tbody,\n\t\t\t})}`,\n\t\t);\n\t}\n\n\t// Auth collection type\n\tsections.push(`export interface AuthRecord extends BaseRecord {\n email: string;\n verified: boolean;\n}`);\n\n\t// Collections map\n\tconst mapEntries = filtered\n\t\t.map(\n\t\t\t(c) =>\n\t\t\t\t` \"${c.name}\": ${collectionTypeName(c.name)}Record${\n\t\t\t\t\tc.type === \"auth\" ? \" & AuthRecord\" : \"\"\n\t\t\t\t};`,\n\t\t)\n\t\t.join(\"\\n\");\n\tsections.push(`export interface LazypockCollections {\n${mapEntries}\n}`);\n\n\t// createClient factory\n\tsections.push(`import { LazypockClient, type LazypockClientOptions, type CollectionService } from \"${packageName}\";\n\n/**\n * Create a Lazypock client typed against this schema snapshot.\n * Collection access is fully type-checked:\n * client.collection(\"posts\").create({ title: \"x\" }) // title must exist\n */\nexport function createClient(options: LazypockClientOptions): TypedClient {\n return new TypedClient(options);\n}\n\nexport class TypedClient extends LazypockClient {\n override collection<K extends keyof LazypockCollections>(name: K): CollectionService<LazypockCollections[K]>;\n override collection(name: string): CollectionService<unknown> {\n return super.collection(name) as unknown as CollectionService<unknown>;\n }\n}`);\n\n\treturn sections.join(\"\\n\\n\") + \"\\n\";\n}\n\n/**\n * Render the interface body including the extends clause.\n * Empty body → ` extends BaseRecord {}` (valid TS).\n */\nfunction renderInterface(opts: { extends?: string; body: string }): string {\n\tconst ext = opts.extends ? ` extends ${opts.extends}` : \"\";\n\tif (!opts.body) return `${ext} {}`;\n\treturn `${ext} {\\n${opts.body}\\n}`;\n}\n\n/** Render a single interface member line for a field. */\nfunction memberLine(f: SchemaField): string {\n\tconst key = fieldKey(f.name);\n\tconst req = f.required || f.type === \"password\" ? \"\" : \"?\";\n\tconst type = fieldTypeScriptType(f);\n\t// `never` members (passwords) are omitted from read models.\n\tif (type === \"never\") return \"\";\n\treturn ` ${JSON.stringify(key)}${req}: ${type};`;\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.d.cts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|