@olenbetong/appframe-vite 6.10.0 → 6.11.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.
package/README.md CHANGED
@@ -1,300 +1,300 @@
1
- # @olenbetong/appframe-vite
2
-
3
- Tools to develop and build Vite applications that run inside Appframe articles.
4
-
5
- - Provides a Vite plugin you add in `vite.config.*`.
6
- - Proxies API and static routes to an Appframe server during `pnpm start`.
7
- - Logs in automatically and refreshes the session on an interval.
8
- - Serves real article HTML from Appframe in dev, but swaps production assets for your local entry.
9
- - Caches i18n strings locally to speed up development loads.
10
- - Shapes production builds to Appframe paths and filenames, with optional React externals.
11
-
12
- ## Installation
13
-
14
- ```sh
15
- pnpm add -D @olenbetong/appframe-vite
16
- ```
17
-
18
- ## Usage (Vite)
19
-
20
- ```ts
21
- // vite.config.mjs or vite.config.ts
22
- import appframe from "@olenbetong/appframe-vite";
23
- import { defineConfig } from "vite";
24
-
25
- export default defineConfig({
26
- plugins: [appframe()],
27
- });
28
- ```
29
-
30
- Then run your app:
31
-
32
- ```sh
33
- pnpm --filter <app> start
34
- ```
35
-
36
- Make sure your Appframe credentials are available as environment variables:
37
-
38
- - `APPFRAME_LOGIN`: username
39
- - `APPFRAME_PWD`: password
40
-
41
- On first run, the plugin logs in, configures Vite’s proxy for Appframe routes, and serves the article HTML with your local entry injected.
42
-
43
- ### MUI X license key
44
-
45
- The plugin injects the MUI X Premium license key into the bundle as the global `__MUI_X_LICENSE_KEY__`, for both `serve` and `build`. `@olenbetong/appframe-ds/grid` reads it and registers the license itself, so an app using the Designsystemet `AfGrid` does not need its own `LicenseInfo.setLicenseKey()` call.
46
-
47
- The key is resolved from, in order:
48
-
49
- 1. the `MUI_X_LICENSE_KEY` environment variable (CI, a local `.env`, or the shell),
50
- 2. the `MUI_X_LICENSE_KEY` constant declared in the app's `src/shared/licenses.ts`.
51
-
52
- The fallback keeps existing apps working with no changes; setting the environment variable is the forward path for getting the key out of the repository.
53
-
54
- ## Project configuration (package.json)
55
-
56
- This package reads `package.json.appframe` to know what to proxy and how to build.
57
-
58
- ```jsonc
59
- {
60
- "appframe": {
61
- "article": {
62
- "id": "my-article", // Required
63
- "altNames": ["my-article-dev"], // Optional: alternate root paths
64
- },
65
- "deploy": { "hostname": "dev.obet.no" }, // Default server if proxy not set
66
- "proxy": {
67
- "hostname": "dev.obet.no", // Optional override for dev proxy
68
- "routes": ["^/custom/.*"], // Optional: extra proxied routes
69
- },
70
- "build": {
71
- "platform": true, // Import React and the af packages from the site's platform bundle
72
- "externals": true, // Legacy: map React and ReactDOM to globals
73
- },
74
- },
75
- }
76
- ```
77
-
78
- ### `build.platform`
79
-
80
- Apps on the 2026 site templates (`ob.2026.application`) set `"platform": true`. The production
81
- build then leaves `react`, `react/jsx-runtime`, `react/jsx-dev-runtime`, `react-dom`,
82
- `react-dom/client`, `@olenbetong/appframe-core` and `@olenbetong/appframe-data` as bare imports,
83
- which the template's import map resolves to `@olenbetong/synergi-platform` — one cached React
84
- per page, shared with the site chrome. The `af.data` global redirect (`dataGlobal`) then only
85
- applies to the dev server, which has no import map. The build also writes `dist/platform.json`
86
- with the platform version the app was built alongside (the workspace's
87
- `packages/synergi-platform`) and the versions it compiled against; `scripts/app-deploy.ts` pins
88
- the article to that platform version (its `PlatformHead` and `PlatformStyles` blocks) and
89
- refuses to deploy when the article is not on a 2026 template, that version is not served on the
90
- target site, or its manifest carries other versions than the app (override with
91
- `--allow-platform-mismatch`).
92
-
93
- ## What it does (at a glance)
94
-
95
- - Dev server
96
- - Proxies Appframe routes (e.g. `/api`, `/file`, `/lib`, etc.) to the configured hostname.
97
- - Performs an initial login and refreshes the session every 5 minutes to prevent 403 errors.
98
- - Serves the real article HTML from Appframe, but removes production assets and injects your local entry (`/src/index.*`).
99
- - Caches localized strings under `node_modules/.appframe/localizeCache.json` and injects them into the page to avoid many small HTTP 1.1 requests.
100
- - Build
101
- - Enables `manifest` and `sourcemap`.
102
- - Writes filenames to Appframe paths, using the article ID, e.g.:
103
- - `file/article/script/<ARTICLE_ID>/main.[hash].min.js`
104
- - `file/article/style/<ARTICLE_ID>/style.[hash].min.css`
105
- - With `build.platform`, leaves React and the af packages as bare imports for the template's import map and writes `dist/platform.json`; the legacy `build.externals` maps `react` and `react-dom` to `React` and `ReactDOM` globals instead.
106
- - Adds a Rollup visualizer report at `dist/stats.html`.
107
- - Resolve
108
- - Adds the alias `~/` to `/src/` so imports like `~/components/Button` resolve to `src/components/Button`.
109
- - DevTools API (see below)
110
-
111
- ## DevTools
112
-
113
- In dev mode the plugin exposes a small JSON API at `/__appframe_devtools__/api` for the **Appframe DevTools
114
- browser extension** (`packages/appframe-devtools` in the SynergiWeb repo). The extension adds an "Appframe"
115
- panel to Chrome/Edge DevTools with a live view of the app's data objects, an editor for `resources.yaml`
116
- and a browser for the data API catalog. Nothing is injected into the page — the toolbar that older
117
- versions added to every dev-served page is gone.
118
-
119
- | Endpoint | Purpose |
120
- | --------------------------- | --------------------------------------------------------------------------------------------------------------------- |
121
- | `GET /api/config` | Discovery: `{ appframe: true, apiVersion, hostname, articleId, articleHostname, appName, appVersion, pluginVersion }` |
122
- | `GET /api/resources` | Parsed `resources.yaml` |
123
- | `POST /api/resources` | Add an entry (`type: "dataObject" \| "procedure"` plus the entry fields) |
124
- | `PUT /api/resources/:id` | Replace an entry |
125
- | `DELETE /api/resources/:id` | Remove an entry and its generated output file |
126
- | `POST /api/catalog` | Register a `dbObjectId` as a data API resource on dev, stage and prod |
127
- | `GET /api/release` | Current session (`session: null` when none) and the capabilities derived from the app's scripts |
128
- | `POST /api/release` | Start a job: `{ kind: "release", dry?, force? }` or `{ kind: "deploy", target, copyDatasources? }`; 409 while running |
129
- | `DELETE /api/release` | Cancel the running job |
130
- | `POST /api/release/input` | Answer one of the script's questions (`{ name, value }`: the release type and notes, or the apply confirmation) |
131
- | `GET /api/release/events` | Server-sent events: the session snapshot, then every step, log line and question as it happens |
132
-
133
- The release job runs whatever `scripts.release` is in the app's package.json, from the app directory, with
134
- `--ui` appended. The deploy job runs `pnpm run build`, optionally `pnpm run cd` (or `copy-datasources`) and
135
- `pnpm run deploy` with `APPFRAME_DEPLOY_HOSTNAME` set to the target, which `app-deploy.ts` and
136
- `app-copy-datasources.ts` honour over the hostnames in package.json. The script is expected to follow the protocol in `scripts/lib/releaseUi.ts` (JSON event lines
137
- prefixed with `@@appframe-devtools ` on stdout, answers as JSON lines on stdin).
138
-
139
- Disable it with `appframe({ devtools: false })` when there is no article context (Storybook does this).
140
- The extension page has host permissions for `localhost`, so the API needs no CORS headers.
141
-
142
- ## CLI — `appframe-vite`
143
-
144
- The package ships a CLI (`appframe-vite`) alongside the Vite plugin.
145
-
146
- ### `generate-types`
147
-
148
- Generates TypeScript type definitions from the article's data objects and procedures:
149
-
150
- ```sh
151
- appframe-vite generate-types
152
- ```
153
-
154
- ### `resources generate`
155
-
156
- Reads `resources.yaml` and regenerates all data object / procedure files:
157
-
158
- ```sh
159
- appframe-vite resources generate
160
- # custom config file path:
161
- appframe-vite resources generate --config path/to/resources.yaml
162
- ```
163
-
164
- ### `resources add`
165
-
166
- Interactive wizard — prompts for resource, ID, output path, permissions, fields, and more, then appends an entry to `resources.yaml` and generates the output file:
167
-
168
- ```sh
169
- appframe-vite resources add
170
- ```
171
-
172
- ### `resources edit [id]`
173
-
174
- Interactive editor — pre-fills the wizard with the current values from `resources.yaml` and regenerates the file on save:
175
-
176
- ```sh
177
- appframe-vite resources edit dsAccountGroups
178
- # or without an id (shows a searchable list):
179
- appframe-vite resources edit
180
- ```
181
-
182
- ### `resources migrate [id]`
183
-
184
- Migrates data objects and procedures that are configured on the article in the appdesigner over to `resources.yaml`. The article's data sources and their fields are read from the CMS, converted to `resources.yaml` entries, and the output files are generated.
185
-
186
- Each migrated view/procedure is also registered as a Data API resource if needed (the same thing `af resources add` does), because `af.data.generateApiDataObject` and `af.ProcedureAPI` go through `/api/data` instead of the article's data sources.
187
-
188
- ```sh
189
- # migrate everything on the article
190
- appframe-vite resources migrate
191
- # migrate a single data object or procedure
192
- appframe-vite resources migrate dsVouchers
193
- # preview without changing anything
194
- appframe-vite resources migrate --dry-run
195
- ```
196
-
197
- Options:
198
-
199
- | Option | Description |
200
- | ------------------------ | -------------------------------------------------------------- |
201
- | `-c, --config <path>` | Path to the resources config file (default: `resources.yaml`) |
202
- | `-o, --output-dir <dir>` | Directory for the generated files (default: `src/data`) |
203
- | `--dry-run` | Show what would be migrated without writing anything |
204
- | `--no-generate` | Only update `resources.yaml`, do not generate the output files |
205
-
206
- Existing `resources.yaml` entries with the same id are updated in place. Field aliases have no `resources.yaml` equivalent and are reported as warnings.
207
-
208
- After migrating, import the generated files from your app, remove the data objects and procedures from the article in the appdesigner, and run `pnpm generate-types`.
209
-
210
- ## `resources.yaml` config file
211
-
212
- Place `resources.yaml` at the project root to declare all data objects and procedures for an app. The Vite dev server watches this file and auto-regenerates all output files when it changes.
213
-
214
- **Example:**
215
-
216
- ```yaml
217
- dataObjects:
218
- - id: dsAccountGroups
219
- resource: atbv_Accounting_SubsidiaryLedgerGroups
220
- global: true
221
- types: true
222
- maxRecords: -1
223
- expose: true
224
- fields:
225
- - Domain
226
- - SubsidiaryLedgerGroup
227
- - PrimKey
228
- - Description
229
- output: src/data/dsAccountGroups.ts
230
-
231
- - id: dsSubsidiaryLedger
232
- resource: atbv_Accounting_SubsidiaryLedger
233
- global: true
234
- types: true
235
- permissions: IUD
236
- output: src/data/dsSubsidiaryLedger.ts
237
-
238
- - id: dsProjectLocations
239
- resource: aviw_Accounting_ProjectsLocations
240
- global: true
241
- types: true
242
- master: dsProjects
243
- linkFields:
244
- - Domain
245
- - ProjectID
246
- output: src/data/dsProjectLocations.ts
247
-
248
- procedures:
249
- - id: procCreateCustomer
250
- resource: astp_Accounting_SubsidiaryLedger_Create
251
- global: true
252
- types: true
253
- expose: true
254
- output: src/data/procCreateCustomer.ts
255
- ```
256
-
257
- **Supported fields per entry:**
258
-
259
- | Field | Type | Description |
260
- | ------------- | -------------------- | ------------------------------------------------------------------------------- |
261
- | `id` | `string` | Variable name used in generated code (e.g. `dsAccountGroups`) |
262
- | `resource` | `string` | Database object ID (e.g. `atbv_Accounting_SubsidiaryLedgerGroups`) |
263
- | `output` | `string` | Output file path relative to project root |
264
- | `global` | `boolean` | Use `af.data.generateApiDataObject` / `new af.ProcedureAPI` globals |
265
- | `types` | `boolean` | Emit TypeScript type definitions |
266
- | `permissions` | `string` | Permissions: I = insert, U = update, D = delete (e.g. `IUD`) |
267
- | `maxRecords` | `number` | Max records to fetch (default `50`; `-1` for all) |
268
- | `sortOrder` | `string \| string[]` | Sort order, e.g. `Created:Desc` or `[Created:Desc, Name]` |
269
- | `master` | `string` | Master data object name (or `name:importPath`) |
270
- | `linkFields` | `string \| string[]` | Fields linking child to master |
271
- | `expose` | `boolean \| string` | Expose on `af.article.dataObjects` / `af.article.procedures` |
272
- | `dynamic` | `boolean` | Enable dynamic loading |
273
- | `unique` | `string` | Unique table name for update/delete |
274
- | `overrides` | `string \| string[]` | Type overrides, e.g. `MyField:string[]` |
275
- | `distinct` | `boolean` | Fetch distinct rows |
276
- | `aggregates` | `string \| string[]` | Aggregate bindings, e.g. `Qty:SUM` |
277
- | `groupBy` | `string \| string[]` | Group-by fields |
278
- | `where` | `string` | Initial where clause |
279
- | `fields` | `string \| string[]` | Fields to include (all if omitted) |
280
- | `transaction` | `boolean` | Procedures only. `false` stops the framework wrapping the call in a transaction |
281
- | `timeout` | `number` | Procedures only. Milliseconds before the call is abandoned (default `30000`) |
282
-
283
- A top-level `server` key can override the hostname (defaults to `appframe.proxy.hostname` from `package.json`).
284
-
285
- **Master/child relationships:** set both `master` and `linkFields` on the child object. The generated data object will include `masterDataObject` and `linkFields` in the `generateApiDataObject(...)` options.
286
-
287
- ## `@olenbetong/appframe-vite/resources` export
288
-
289
- Shared code generation utilities for Node.js consumers:
290
-
291
- ```ts
292
- import {
293
- fetchAndGenerate,
294
- buildYamlConfig,
295
- parseYamlConfig,
296
- writeGeneratedFile,
297
- getCustomImportPath,
298
- type CLIOptions,
299
- } from "@olenbetong/appframe-vite/resources";
300
- ```
1
+ # @olenbetong/appframe-vite
2
+
3
+ Tools to develop and build Vite applications that run inside Appframe articles.
4
+
5
+ - Provides a Vite plugin you add in `vite.config.*`.
6
+ - Proxies API and static routes to an Appframe server during `pnpm start`.
7
+ - Logs in automatically and refreshes the session on an interval.
8
+ - Serves real article HTML from Appframe in dev, but swaps production assets for your local entry.
9
+ - Caches i18n strings locally to speed up development loads.
10
+ - Shapes production builds to Appframe paths and filenames, with optional React externals.
11
+
12
+ ## Installation
13
+
14
+ ```sh
15
+ pnpm add -D @olenbetong/appframe-vite
16
+ ```
17
+
18
+ ## Usage (Vite)
19
+
20
+ ```ts
21
+ // vite.config.mjs or vite.config.ts
22
+ import appframe from "@olenbetong/appframe-vite";
23
+ import { defineConfig } from "vite";
24
+
25
+ export default defineConfig({
26
+ plugins: [appframe()],
27
+ });
28
+ ```
29
+
30
+ Then run your app:
31
+
32
+ ```sh
33
+ pnpm --filter <app> start
34
+ ```
35
+
36
+ Make sure your Appframe credentials are available as environment variables:
37
+
38
+ - `APPFRAME_LOGIN`: username
39
+ - `APPFRAME_PWD`: password
40
+
41
+ On first run, the plugin logs in, configures Vite’s proxy for Appframe routes, and serves the article HTML with your local entry injected.
42
+
43
+ ### MUI X license key
44
+
45
+ The plugin injects the MUI X Premium license key into the bundle as the global `__MUI_X_LICENSE_KEY__`, for both `serve` and `build`. `@olenbetong/appframe-ds/grid` reads it and registers the license itself, so an app using the Designsystemet `AfGrid` does not need its own `LicenseInfo.setLicenseKey()` call.
46
+
47
+ The key is resolved from, in order:
48
+
49
+ 1. the `MUI_X_LICENSE_KEY` environment variable (CI, a local `.env`, or the shell),
50
+ 2. the `MUI_X_LICENSE_KEY` constant declared in the app's `src/shared/licenses.ts`.
51
+
52
+ The fallback keeps existing apps working with no changes; setting the environment variable is the forward path for getting the key out of the repository.
53
+
54
+ ## Project configuration (package.json)
55
+
56
+ This package reads `package.json.appframe` to know what to proxy and how to build.
57
+
58
+ ```jsonc
59
+ {
60
+ "appframe": {
61
+ "article": {
62
+ "id": "my-article", // Required
63
+ "altNames": ["my-article-dev"], // Optional: alternate root paths
64
+ },
65
+ "deploy": { "hostname": "dev.obet.no" }, // Default server if proxy not set
66
+ "proxy": {
67
+ "hostname": "dev.obet.no", // Optional override for dev proxy
68
+ "routes": ["^/custom/.*"], // Optional: extra proxied routes
69
+ },
70
+ "build": {
71
+ "platform": true, // Import React and the af packages from the site's platform bundle
72
+ "externals": true, // Legacy: map React and ReactDOM to globals
73
+ },
74
+ },
75
+ }
76
+ ```
77
+
78
+ ### `build.platform`
79
+
80
+ Apps on the 2026 site templates (`ob.2026.application`) set `"platform": true`. The production
81
+ build then leaves `react`, `react/jsx-runtime`, `react/jsx-dev-runtime`, `react-dom`,
82
+ `react-dom/client`, `@olenbetong/appframe-core` and `@olenbetong/appframe-data` as bare imports,
83
+ which the template's import map resolves to `@olenbetong/synergi-platform` — one cached React
84
+ per page, shared with the site chrome. The `af.data` global redirect (`dataGlobal`) then only
85
+ applies to the dev server, which has no import map. The build also writes `dist/platform.json`
86
+ with the platform version the app was built alongside (the workspace's
87
+ `packages/synergi-platform`) and the versions it compiled against; `scripts/app-deploy.ts` pins
88
+ the article to that platform version (its `PlatformHead` and `PlatformStyles` blocks) and
89
+ refuses to deploy when the article is not on a 2026 template, that version is not served on the
90
+ target site, or its manifest carries other versions than the app (override with
91
+ `--allow-platform-mismatch`).
92
+
93
+ ## What it does (at a glance)
94
+
95
+ - Dev server
96
+ - Proxies Appframe routes (e.g. `/api`, `/file`, `/lib`, etc.) to the configured hostname.
97
+ - Performs an initial login and refreshes the session every 5 minutes to prevent 403 errors.
98
+ - Serves the real article HTML from Appframe, but removes production assets and injects your local entry (`/src/index.*`).
99
+ - Caches localized strings under `node_modules/.appframe/localizeCache.json` and injects them into the page to avoid many small HTTP 1.1 requests.
100
+ - Build
101
+ - Enables `manifest` and `sourcemap`.
102
+ - Writes filenames to Appframe paths, using the article ID, e.g.:
103
+ - `file/article/script/<ARTICLE_ID>/main.[hash].min.js`
104
+ - `file/article/style/<ARTICLE_ID>/style.[hash].min.css`
105
+ - With `build.platform`, leaves React and the af packages as bare imports for the template's import map and writes `dist/platform.json`; the legacy `build.externals` maps `react` and `react-dom` to `React` and `ReactDOM` globals instead.
106
+ - Adds a Rollup visualizer report at `dist/stats.html`.
107
+ - Resolve
108
+ - Adds the alias `~/` to `/src/` so imports like `~/components/Button` resolve to `src/components/Button`.
109
+ - DevTools API (see below)
110
+
111
+ ## DevTools
112
+
113
+ In dev mode the plugin exposes a small JSON API at `/__appframe_devtools__/api` for the **Appframe DevTools
114
+ browser extension** (`packages/appframe-devtools` in the SynergiWeb repo). The extension adds an "Appframe"
115
+ panel to Chrome/Edge DevTools with a live view of the app's data objects, an editor for `resources.yaml`
116
+ and a browser for the data API catalog. Nothing is injected into the page — the toolbar that older
117
+ versions added to every dev-served page is gone.
118
+
119
+ | Endpoint | Purpose |
120
+ | --------------------------- | --------------------------------------------------------------------------------------------------------------------- |
121
+ | `GET /api/config` | Discovery: `{ appframe: true, apiVersion, hostname, articleId, articleHostname, appName, appVersion, pluginVersion }` |
122
+ | `GET /api/resources` | Parsed `resources.yaml` |
123
+ | `POST /api/resources` | Add an entry (`type: "dataObject" \| "procedure"` plus the entry fields) |
124
+ | `PUT /api/resources/:id` | Replace an entry |
125
+ | `DELETE /api/resources/:id` | Remove an entry and its generated output file |
126
+ | `POST /api/catalog` | Register a `dbObjectId` as a data API resource on dev, stage and prod |
127
+ | `GET /api/release` | Current session (`session: null` when none) and the capabilities derived from the app's scripts |
128
+ | `POST /api/release` | Start a job: `{ kind: "release", dry?, force? }` or `{ kind: "deploy", target, copyDatasources? }`; 409 while running |
129
+ | `DELETE /api/release` | Cancel the running job |
130
+ | `POST /api/release/input` | Answer one of the script's questions (`{ name, value }`: the release type and notes, or the apply confirmation) |
131
+ | `GET /api/release/events` | Server-sent events: the session snapshot, then every step, log line and question as it happens |
132
+
133
+ The release job runs whatever `scripts.release` is in the app's package.json, from the app directory, with
134
+ `--ui` appended. The deploy job runs `pnpm run build`, optionally `pnpm run cd` (or `copy-datasources`) and
135
+ `pnpm run deploy` with `APPFRAME_DEPLOY_HOSTNAME` set to the target, which `app-deploy.ts` and
136
+ `app-copy-datasources.ts` honour over the hostnames in package.json. The script is expected to follow the protocol in `scripts/lib/releaseUi.ts` (JSON event lines
137
+ prefixed with `@@appframe-devtools ` on stdout, answers as JSON lines on stdin).
138
+
139
+ Disable it with `appframe({ devtools: false })` when there is no article context (Storybook does this).
140
+ The extension page has host permissions for `localhost`, so the API needs no CORS headers.
141
+
142
+ ## CLI — `appframe-vite`
143
+
144
+ The package ships a CLI (`appframe-vite`) alongside the Vite plugin.
145
+
146
+ ### `generate-types`
147
+
148
+ Generates TypeScript type definitions from the article's data objects and procedures:
149
+
150
+ ```sh
151
+ appframe-vite generate-types
152
+ ```
153
+
154
+ ### `resources generate`
155
+
156
+ Reads `resources.yaml` and regenerates all data object / procedure files:
157
+
158
+ ```sh
159
+ appframe-vite resources generate
160
+ # custom config file path:
161
+ appframe-vite resources generate --config path/to/resources.yaml
162
+ ```
163
+
164
+ ### `resources add`
165
+
166
+ Interactive wizard — prompts for resource, ID, output path, permissions, fields, and more, then appends an entry to `resources.yaml` and generates the output file:
167
+
168
+ ```sh
169
+ appframe-vite resources add
170
+ ```
171
+
172
+ ### `resources edit [id]`
173
+
174
+ Interactive editor — pre-fills the wizard with the current values from `resources.yaml` and regenerates the file on save:
175
+
176
+ ```sh
177
+ appframe-vite resources edit dsAccountGroups
178
+ # or without an id (shows a searchable list):
179
+ appframe-vite resources edit
180
+ ```
181
+
182
+ ### `resources migrate [id]`
183
+
184
+ Migrates data objects and procedures that are configured on the article in the appdesigner over to `resources.yaml`. The article's data sources and their fields are read from the CMS, converted to `resources.yaml` entries, and the output files are generated.
185
+
186
+ Each migrated view/procedure is also registered as a Data API resource if needed (the same thing `af resources add` does), because `af.data.generateApiDataObject` and `af.ProcedureAPI` go through `/api/data` instead of the article's data sources.
187
+
188
+ ```sh
189
+ # migrate everything on the article
190
+ appframe-vite resources migrate
191
+ # migrate a single data object or procedure
192
+ appframe-vite resources migrate dsVouchers
193
+ # preview without changing anything
194
+ appframe-vite resources migrate --dry-run
195
+ ```
196
+
197
+ Options:
198
+
199
+ | Option | Description |
200
+ | ------------------------ | -------------------------------------------------------------- |
201
+ | `-c, --config <path>` | Path to the resources config file (default: `resources.yaml`) |
202
+ | `-o, --output-dir <dir>` | Directory for the generated files (default: `src/data`) |
203
+ | `--dry-run` | Show what would be migrated without writing anything |
204
+ | `--no-generate` | Only update `resources.yaml`, do not generate the output files |
205
+
206
+ Existing `resources.yaml` entries with the same id are updated in place. Field aliases have no `resources.yaml` equivalent and are reported as warnings.
207
+
208
+ After migrating, import the generated files from your app, remove the data objects and procedures from the article in the appdesigner, and run `pnpm generate-types`.
209
+
210
+ ## `resources.yaml` config file
211
+
212
+ Place `resources.yaml` at the project root to declare all data objects and procedures for an app. The Vite dev server watches this file and auto-regenerates all output files when it changes.
213
+
214
+ **Example:**
215
+
216
+ ```yaml
217
+ dataObjects:
218
+ - id: dsAccountGroups
219
+ resource: atbv_Accounting_SubsidiaryLedgerGroups
220
+ global: true
221
+ types: true
222
+ maxRecords: -1
223
+ expose: true
224
+ fields:
225
+ - Domain
226
+ - SubsidiaryLedgerGroup
227
+ - PrimKey
228
+ - Description
229
+ output: src/data/dsAccountGroups.ts
230
+
231
+ - id: dsSubsidiaryLedger
232
+ resource: atbv_Accounting_SubsidiaryLedger
233
+ global: true
234
+ types: true
235
+ permissions: IUD
236
+ output: src/data/dsSubsidiaryLedger.ts
237
+
238
+ - id: dsProjectLocations
239
+ resource: aviw_Accounting_ProjectsLocations
240
+ global: true
241
+ types: true
242
+ master: dsProjects
243
+ linkFields:
244
+ - Domain
245
+ - ProjectID
246
+ output: src/data/dsProjectLocations.ts
247
+
248
+ procedures:
249
+ - id: procCreateCustomer
250
+ resource: astp_Accounting_SubsidiaryLedger_Create
251
+ global: true
252
+ types: true
253
+ expose: true
254
+ output: src/data/procCreateCustomer.ts
255
+ ```
256
+
257
+ **Supported fields per entry:**
258
+
259
+ | Field | Type | Description |
260
+ | ------------- | -------------------- | ------------------------------------------------------------------------------- |
261
+ | `id` | `string` | Variable name used in generated code (e.g. `dsAccountGroups`) |
262
+ | `resource` | `string` | Database object ID (e.g. `atbv_Accounting_SubsidiaryLedgerGroups`) |
263
+ | `output` | `string` | Output file path relative to project root |
264
+ | `global` | `boolean` | Use `af.data.generateApiDataObject` / `new af.ProcedureAPI` globals |
265
+ | `types` | `boolean` | Emit TypeScript type definitions |
266
+ | `permissions` | `string` | Permissions: I = insert, U = update, D = delete (e.g. `IUD`) |
267
+ | `maxRecords` | `number` | Max records to fetch (default `50`; `-1` for all) |
268
+ | `sortOrder` | `string \| string[]` | Sort order, e.g. `Created:Desc` or `[Created:Desc, Name]` |
269
+ | `master` | `string` | Master data object name (or `name:importPath`) |
270
+ | `linkFields` | `string \| string[]` | Fields linking child to master |
271
+ | `expose` | `boolean \| string` | Expose on `af.article.dataObjects` / `af.article.procedures` |
272
+ | `dynamic` | `boolean` | Enable dynamic loading |
273
+ | `unique` | `string` | Unique table name for update/delete |
274
+ | `overrides` | `string \| string[]` | Type overrides, e.g. `MyField:string[]` |
275
+ | `distinct` | `boolean` | Fetch distinct rows |
276
+ | `aggregates` | `string \| string[]` | Aggregate bindings, e.g. `Qty:SUM` |
277
+ | `groupBy` | `string \| string[]` | Group-by fields |
278
+ | `where` | `string` | Initial where clause |
279
+ | `fields` | `string \| string[]` | Fields to include (all if omitted) |
280
+ | `transaction` | `boolean` | Procedures only. `false` stops the framework wrapping the call in a transaction |
281
+ | `timeout` | `number` | Procedures only. Milliseconds before the call is abandoned (default `30000`) |
282
+
283
+ A top-level `server` key can override the hostname (defaults to `appframe.proxy.hostname` from `package.json`).
284
+
285
+ **Master/child relationships:** set both `master` and `linkFields` on the child object. The generated data object will include `masterDataObject` and `linkFields` in the `generateApiDataObject(...)` options.
286
+
287
+ ## `@olenbetong/appframe-vite/resources` export
288
+
289
+ Shared code generation utilities for Node.js consumers:
290
+
291
+ ```ts
292
+ import {
293
+ fetchAndGenerate,
294
+ buildYamlConfig,
295
+ parseYamlConfig,
296
+ writeGeneratedFile,
297
+ getCustomImportPath,
298
+ type CLIOptions,
299
+ } from "@olenbetong/appframe-vite/resources";
300
+ ```
package/lib/build.js CHANGED
@@ -27,6 +27,9 @@ export const PLATFORM_EXTERNALS = [
27
27
  "@olenbetong/appframe-ds/mdi",
28
28
  "@olenbetong/appframe-ds/progress",
29
29
  "@olenbetong/appframe-ds/report-downloader",
30
+ "pdfjs-dist",
31
+ "react-pdf",
32
+ "pdf-lib",
30
33
  ];
31
34
  /**
32
35
  * The IIFE global each platform specifier is published under (`buildConfig.entries` in the
@@ -49,6 +52,9 @@ export const PLATFORM_GLOBALS = {
49
52
  "@olenbetong/appframe-ds/mdi": "SynergiPlatform.appframeDsMdi",
50
53
  "@olenbetong/appframe-ds/progress": "SynergiPlatform.appframeDsProgress",
51
54
  "@olenbetong/appframe-ds/report-downloader": "SynergiPlatform.appframeDsReportDownloader",
55
+ "pdfjs-dist": "SynergiPlatform.pdfjs",
56
+ "react-pdf": "SynergiPlatform.reactPdf",
57
+ "pdf-lib": "SynergiPlatform.pdfLib",
52
58
  };
53
59
  export function usesPlatform(appframe) {
54
60
  return appframe?.build?.platform === true;
@@ -68,6 +74,9 @@ export const PLATFORM_UPSTREAM = [
68
74
  "@olenbetong/appframe-data",
69
75
  "@olenbetong/appframe-react",
70
76
  "@olenbetong/appframe-ds",
77
+ "pdfjs-dist",
78
+ "react-pdf",
79
+ "pdf-lib",
71
80
  ];
72
81
  /**
73
82
  * The platform package's version as the app sees it: its own copy under node_modules when it
@@ -302,10 +302,10 @@ export function getProcedureDefinition(name, procDefinition, options) {
302
302
  output.push(`export type ${paramTypeName} = null | undefined | Record<string, unknown>;\n`);
303
303
  }
304
304
  }
305
- output.push(`export const ${procName} = new ${options.global ? "af." : ""}ProcedureAPI${options.types ? `<${paramTypeName}, ${options.typesJsonReturnType ?? "{ Table?: unknown[] }"}>` : ""}({
306
- procedureId: "${name}",
307
- parameters: ${JSON.stringify(parameters, null, 2)},
308
- timeout: ${options.timeout ?? 30000}${options.transaction === false ? ",\n transaction: false" : ""}
305
+ output.push(`export const ${procName} = new ${options.global ? "af." : ""}ProcedureAPI${options.types ? `<${paramTypeName}, ${options.typesJsonReturnType ?? "{ Table?: unknown[] }"}>` : ""}({
306
+ procedureId: "${name}",
307
+ parameters: ${JSON.stringify(parameters, null, 2)},
308
+ timeout: ${options.timeout ?? 30000}${options.transaction === false ? ",\n transaction: false" : ""}
309
309
  });`);
310
310
  if (options.expose) {
311
311
  output.push("");
@@ -389,7 +389,7 @@ export function getDataObjectDefinition(name, viewDefinition, options) {
389
389
  }
390
390
  fieldTypes += ` ${field.name}: ${type};\n`;
391
391
  }
392
- types = `export type ${typeName} = {
392
+ types = `export type ${typeName} = {
393
393
  ${fieldTypes}}`;
394
394
  }
395
395
  if (options.global) {
@@ -473,9 +473,9 @@ ${fieldTypes}}`;
473
473
  parametersOption.push(`distinctRows: true`);
474
474
  }
475
475
  dsOptions.push(`parameters: {\n\t\t${parametersOption.join(",\n\t\t")}\n\t}`);
476
- output += `export const ${options.id} = ${api}({
477
- ${dsOptions.join(",\n\t")}
478
- });
476
+ output += `export const ${options.id} = ${api}({
477
+ ${dsOptions.join(",\n\t")}
478
+ });
479
479
  `;
480
480
  if (options.expose) {
481
481
  let id = typeof options.expose === "string" ? options.expose : options.id;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@olenbetong/appframe-vite",
3
- "version": "6.10.0",
3
+ "version": "6.11.0",
4
4
  "description": "Tools to use and deploy Vite applications to Appframe",
5
5
  "main": "./lib/index.js",
6
6
  "type": "module",
@@ -37,14 +37,14 @@
37
37
  "fuzzy": "^0.1.3",
38
38
  "inquirer": "^14.2.2",
39
39
  "inquirer-autocomplete-standalone": "^0.8.1",
40
- "jsdom": "30.0.1",
40
+ "jsdom": "30.1.0",
41
41
  "rollup-plugin-visualizer": "^7.1.1",
42
42
  "yaml": "^2.9.1",
43
43
  "@olenbetong/appframe-data": "1.7.2"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/jsdom": "^30.0.0",
47
- "@types/node": "26.5.1",
47
+ "@types/node": "26.6.1",
48
48
  "typescript": "7.0.2",
49
49
  "vite": "8.3.0"
50
50
  },