@quadrokit/client 0.2.6 → 0.2.8

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 CHANGED
@@ -14,12 +14,12 @@ After install, the binary name is **`quadrokit-client`** (not the scoped package
14
14
 
15
15
  ```bash
16
16
  bunx quadrokit-client generate \
17
- --url 'http://localhost:7080/rest/$catalog' \
17
+ --url 'http://localhost:7080/rest/$catalog/$all' \
18
18
  --token YOUR_TOKEN \
19
19
  --out .quadrokit/generated
20
20
  ```
21
21
 
22
- - **`--url`**: HTTP(S) catalog URL or `file:///absolute/path/to/catalog.json`
22
+ - **`--url`**: HTTP(S) catalog URL or `file:///absolute/path/to/catalog.json`. Use **`/rest/$catalog/$all`** so the JSON includes dataclass **attributes**; plain `/rest/$catalog` only lists names (empty types). Defaults to `${VITE_4D_ORIGIN}/rest/$catalog/$all`.
23
23
  - **`--token`**: optional `Authorization: Bearer …` for protected catalog endpoints
24
24
  - **`--out`**: output directory (default `.quadrokit/generated`)
25
25
 
package/dist/cli.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { catalogFetchInit, defaultLoginUrlFromCatalogUrl, fetch4DAdminSessionCookie, vLog, wrapFetchError, } from './generate/catalog-session.mjs';
5
- import { writeGenerated } from './generate/codegen.mjs';
5
+ import { warnIfCatalogLacksAttributes, writeGenerated } from './generate/codegen.mjs';
6
6
  import { applyGenerateDotenv } from './generate/dotenv-cwd.mjs';
7
7
  function parseArgs(argv) {
8
8
  let command = '';
@@ -42,6 +42,14 @@ function parseArgs(argv) {
42
42
  }
43
43
  return { command, url, token, accessKey, loginUrl, out, verbose, insecureTls };
44
44
  }
45
+ /** 4D sometimes returns `dataClass` (summary) vs `dataClasses` (detail); align to `dataClasses`. */
46
+ function normalizeCatalogJson(raw) {
47
+ const o = raw;
48
+ if (o && typeof o === 'object' && !o.dataClasses && Array.isArray(o.dataClass)) {
49
+ return { ...o, dataClasses: o.dataClass };
50
+ }
51
+ return raw;
52
+ }
45
53
  function envFlag(name) {
46
54
  const v = process.env[name]?.trim().toLowerCase();
47
55
  return v === '1' || v === 'true' || v === 'yes';
@@ -60,7 +68,7 @@ async function loadCatalog(url, auth, debug) {
60
68
  vLog(debug, '📂', 'Local file', p);
61
69
  const text = await readFile(p, 'utf8');
62
70
  vLog(debug, '✅', 'Catalog JSON', `Read ${text.length} bytes from disk`);
63
- return JSON.parse(text);
71
+ return normalizeCatalogJson(JSON.parse(text));
64
72
  }
65
73
  const headers = {
66
74
  Accept: 'application/json',
@@ -109,7 +117,7 @@ async function main() {
109
117
  if (command !== 'generate') {
110
118
  console.error(`Usage: quadrokit-client generate [--url <catalog_url>] [auth] [options] [--out <dir>]
111
119
 
112
- --url defaults to \${VITE_4D_ORIGIN}/rest/\\$catalog (from env or .env in cwd), else http://127.0.0.1:7080/rest/\\$catalog
120
+ --url defaults to \${VITE_4D_ORIGIN}/rest/\\$catalog/\\$all (from env or .env in cwd), else http://127.0.0.1:7080/rest/\\$catalog/\\$all
113
121
 
114
122
  Auth (generator only; not used by the app runtime):
115
123
  --access-key <key> POST multipart accessKey to /api/login, then use 4DAdminSID for the catalog
@@ -125,8 +133,8 @@ Env / .env: VITE_4D_ORIGIN, QUADROKIT_ACCESS_KEY, QUADROKIT_LOGIN_URL, QUADROKIT
125
133
 
126
134
  Examples:
127
135
  quadrokit-client generate -v
128
- quadrokit-client generate --url https://localhost:7443/rest/\\$catalog --access-key MY_TOKEN --insecure-tls -v
129
- quadrokit-client generate --url http://localhost:7080/rest/\\$catalog --token secret
136
+ quadrokit-client generate --url https://localhost:7443/rest/\\$catalog/\\$all --access-key MY_TOKEN --insecure-tls -v
137
+ quadrokit-client generate --url http://localhost:7080/rest/\\$catalog/\\$all --token secret
130
138
  quadrokit-client generate --url file://./assets/catalog.json --out .quadrokit/generated
131
139
 
132
140
  bunx -p @quadrokit/client quadrokit-client generate
@@ -138,7 +146,7 @@ Examples:
138
146
  let url = parsed.url;
139
147
  if (!url) {
140
148
  const origin = (process.env.VITE_4D_ORIGIN || 'http://127.0.0.1:7080').replace(/\/$/, '');
141
- url = `${origin}/rest/$catalog`;
149
+ url = `${origin}/rest/$catalog/$all`;
142
150
  }
143
151
  const accessKey = accessKeyArg?.trim() || process.env.QUADROKIT_ACCESS_KEY?.trim() || undefined;
144
152
  const loginUrl = loginUrlArg?.trim() || process.env.QUADROKIT_LOGIN_URL?.trim() || undefined;
@@ -148,6 +156,7 @@ Examples:
148
156
  loginUrl,
149
157
  token: accessKey ? undefined : bearer,
150
158
  }, debug);
159
+ warnIfCatalogLacksAttributes(catalog);
151
160
  await writeGenerated(out, catalog);
152
161
  if (debug.verbose) {
153
162
  vLog(debug, '🎉', 'All set', `Generated client → ${out}`);
@@ -1,3 +1,5 @@
1
1
  import type { CatalogJson } from '@quadrokit/shared';
2
+ /** Warn when the catalog lists dataclasses but omits attributes (e.g. plain `GET /rest/$catalog` instead of `/rest/$catalog/$all`). */
3
+ export declare function warnIfCatalogLacksAttributes(catalog: CatalogJson): void;
2
4
  export declare function writeGenerated(outDir: string, catalog: CatalogJson): Promise<void>;
3
5
  //# sourceMappingURL=codegen.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/generate/codegen.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAsC,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAgTxF,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBxF"}
1
+ {"version":3,"file":"codegen.d.ts","sourceRoot":"","sources":["../../src/generate/codegen.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAsC,WAAW,EAAE,MAAM,mBAAmB,CAAA;AA8TxF,uIAAuI;AACvI,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAYvE;AAED,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBxF"}
@@ -104,8 +104,12 @@ function emitInterface(dc, _byName) {
104
104
  lines.push('}');
105
105
  return lines.join('\n');
106
106
  }
107
+ /** Include dataclasses unless explicitly not exposed (`exposed === false`). Omitted `exposed` is treated as true. */
108
+ function exposedDataClasses(catalog) {
109
+ return catalog.dataClasses?.filter((d) => d.exposed !== false) ?? [];
110
+ }
107
111
  function emitTypes(catalog) {
108
- const classes = catalog.dataClasses?.filter((d) => d.exposed) ?? [];
112
+ const classes = exposedDataClasses(catalog);
109
113
  const byName = new Map(classes.map((c) => [c.name, c]));
110
114
  const interfaces = classes.map((c) => emitInterface(c, byName));
111
115
  const pathTypes = classes.map((c) => {
@@ -114,10 +118,18 @@ function emitTypes(catalog) {
114
118
  const body = paths.length ? paths.map((p) => `'${p}'`).join('\n | ') : 'never';
115
119
  return `export type ${name} =\n | ${body};`;
116
120
  });
117
- return `/* eslint-disable */\n/* Auto-generated by @quadrokit/client — do not edit */\n\n${interfaces.join('\n\n')}\n\n${pathTypes.join('\n\n')}\n`;
121
+ const header = `/* eslint-disable */\n/* Auto-generated by @quadrokit/client — do not edit */\n`;
122
+ if (classes.length === 0) {
123
+ const raw = catalog.dataClasses?.length ?? 0;
124
+ const note = raw > 0
125
+ ? `\n/**\n * No entity types: all ${raw} data class(es) in the catalog have exposed: false.\n * Enable REST exposure for tables in 4D, then run quadrokit-client generate again.\n */\n`
126
+ : `\n/**\n * No entity types: the catalog has no dataClasses (or an empty list).\n */\n`;
127
+ return `${header}${note}\n`;
128
+ }
129
+ return `${header}\n${interfaces.join('\n\n')}\n\n${pathTypes.join('\n\n')}\n`;
118
130
  }
119
131
  function emitClient(catalog) {
120
- const classes = catalog.dataClasses?.filter((d) => d.exposed) ?? [];
132
+ const classes = exposedDataClasses(catalog);
121
133
  const dbName = catalog.__NAME ?? 'default';
122
134
  const hasAuthentify = catalog.methods?.some((m) => m.name === 'authentify' && m.applyTo === 'dataStore');
123
135
  const keyNamesRecord = Object.fromEntries(classes.map((x) => [x.name, keyNames(x)]));
@@ -275,6 +287,18 @@ export type QuadroClient = ReturnType<typeof createClient>;
275
287
  `;
276
288
  return header + metaExport + configInterface + body;
277
289
  }
290
+ /** Warn when the catalog lists dataclasses but omits attributes (e.g. plain `GET /rest/$catalog` instead of `/rest/$catalog/$all`). */
291
+ export function warnIfCatalogLacksAttributes(catalog) {
292
+ const classes = exposedDataClasses(catalog);
293
+ if (classes.length === 0) {
294
+ return;
295
+ }
296
+ const allLackAttrs = classes.every((c) => !(c.attributes && c.attributes.length > 0));
297
+ if (!allLackAttrs) {
298
+ return;
299
+ }
300
+ console.error('quadrokit: catalog has dataclass names but no attribute details. Fetch the full catalog: GET /rest/$catalog/$all (plain /rest/$catalog omits attributes). See 4D REST docs: $catalog/$all.');
301
+ }
278
302
  export async function writeGenerated(outDir, catalog) {
279
303
  await mkdir(outDir, { recursive: true });
280
304
  await writeFile(path.join(outDir, 'types.gen.ts'), emitTypes(catalog), 'utf8');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quadrokit/client",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Typed 4D REST client and catalog code generator for QuadroKit",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
@@ -29,7 +29,7 @@
29
29
  "generate:fixture": "bun run src/cli.ts generate --url file://../../assets/catalog.json --out ../../.quadrokit/generated-demo"
30
30
  },
31
31
  "dependencies": {
32
- "@quadrokit/shared": "^0.2.6",
32
+ "@quadrokit/shared": "^0.2.8",
33
33
  "undici": "^6.21.0"
34
34
  },
35
35
  "peerDependencies": {