irgen 0.2.1 → 0.2.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +2 -1
  3. package/dist/cli.js +30 -4
  4. package/dist/dsl/frontend-runtime.d.ts +110 -7
  5. package/dist/dsl/frontend-runtime.js +18 -0
  6. package/dist/emit/frontend/frontend-components.d.ts +6 -0
  7. package/dist/emit/frontend/frontend-components.js +1428 -0
  8. package/dist/emit/frontend/frontend-helpers.d.ts +15 -0
  9. package/dist/emit/frontend/frontend-helpers.js +219 -0
  10. package/dist/emit/frontend/frontend-package.d.ts +2 -0
  11. package/dist/emit/frontend/frontend-package.js +106 -0
  12. package/dist/emit/frontend/frontend-pages.d.ts +3 -0
  13. package/dist/emit/frontend/frontend-pages.js +51 -0
  14. package/dist/emit/frontend/frontend-pwa.d.ts +2 -0
  15. package/dist/emit/frontend/frontend-pwa.js +77 -0
  16. package/dist/emit/frontend/frontend-react.js +400 -1684
  17. package/dist/emit/frontend/frontend-shared.d.ts +4 -0
  18. package/dist/emit/frontend/frontend-shared.js +89 -0
  19. package/dist/emit/frontend/frontend-tailwind.d.ts +2 -0
  20. package/dist/emit/frontend/frontend-tailwind.js +27 -0
  21. package/dist/emit/frontend/frontend-vite.d.ts +3 -0
  22. package/dist/emit/frontend/frontend-vite.js +29 -0
  23. package/dist/emit/frontend/runtime-emitter.js +224 -5
  24. package/dist/emit/frontend/runtime-template.d.ts +7 -1
  25. package/dist/emit/frontend/runtime-template.js +104 -10
  26. package/dist/index.js +29 -3
  27. package/dist/ir/decl/frontend.raw.schema.d.ts +1953 -85
  28. package/dist/ir/decl/frontend.raw.schema.js +37 -3
  29. package/dist/ir/decl/normalize.schema.d.ts +2476 -116
  30. package/dist/ir/domain/frontend/index.d.ts +36 -1
  31. package/dist/ir/target/backend.policy.d.ts +30 -30
  32. package/dist/ir/target/frontend.policy.d.ts +101 -0
  33. package/dist/ir/target/frontend.policy.js +16 -0
  34. package/dist/lowering/frontend/macros/index.d.ts +2 -0
  35. package/dist/lowering/frontend/macros/index.js +15 -0
  36. package/dist/lowering/frontend/macros/pages.d.ts +3 -0
  37. package/dist/lowering/frontend/macros/pages.js +119 -0
  38. package/dist/lowering/frontend/macros/pricing.d.ts +2 -0
  39. package/dist/lowering/frontend/macros/pricing.js +22 -0
  40. package/dist/lowering/frontend/macros/registry.d.ts +5 -0
  41. package/dist/lowering/frontend/macros/registry.js +13 -0
  42. package/dist/lowering/frontend/macros/tablePage.d.ts +2 -0
  43. package/dist/lowering/frontend/macros/tablePage.js +101 -0
  44. package/dist/lowering/frontend.js +96 -27
  45. package/package.json +7 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  All notable changes to the `irgen` project will be documented in this file.
4
4
 
5
+ ## [0.2.2] - 2026-01-18
6
+
7
+ ### Core Extensions & Architecture (Phase 10)
8
+ - **Universal Action Model**: Introduced `ActionSpec` type and `onClick` support for buttons, enabling a consistent "invoke operation" or "navigate" behavior across all UI components.
9
+ - **Operation-Backed Forms**: Updated `DeclFormSchema` to support `operationId` in submission configuration, allowing forms to bind directly to backend operations instead of hardcoded URLs.
10
+ - **Micro-Frontend Support**: Added `macro` field to `DeclComponentSchema`, enabling the definition of "Page Templates" (like `TablePage`, `EditorPage`) as single high-level components with props.
11
+ - **Dependency Declaration**: Added `requiredComponentKeys` to `DeclFrontendAppSchema`, allowing extensions and presets to explicitly declare their UI component dependencies.
12
+ - **Active Runtime Signals**: Implemented functional execution of `toast`, `redirect`, `openUrl`, and `downloadAs` signals in the headless runtime (`BaseRuntime`), replacing passive data with active behavior.
13
+ - **Frontend auth contract**: Added `frontend.auth` to declare login/logout operations, login path, and nav visibility rules in a deterministic, policy-driven way.
14
+ - **Form lifecycle upgrades**: Added `form.load` with args/when/mapFields, plus `form.submit.label` for configurable submit text.
15
+ - **Table UX contracts**: Added `table.rowNavigateTo` (row click navigation) and `table.rowActions` with per-row actions and optional confirm dialogs.
16
+ - **Runtime logic evaluation**: Frontend runtime now evaluates logic expressions for path params, action arguments, and field mapping, enabling dynamic behaviors without recompilation.
17
+ - **Frontend build hooks**: Added `build.copyTo`/`build.postbuild` policy to emit postbuild copy scripts in frontend outputs (`copyToPublic` is deprecated).
18
+ - **Component UI variants**: Added `component.props.uiVariant` (`header`/`inline`) and `component.props.layoutVariant` (`header`) to control header-style layouts without card wrappers.
19
+ - **Visual policy (best-effort)**: Emitter reads `policies.frontend.visual` knobs (`navLayout`, `contentWidth`, `density`) without adding schema validation.
20
+ - **Topbar controls (best-effort)**: `visual.topbarControls` controls right-side navbar items (search/notifications/theme/avatar), avatar visibility, and optional custom links.
21
+ - **Branding controls (best-effort)**: `visual.brand` can hide/show logos and override logo src/text/icon in topbar/sidebar.
22
+ - **Nav overrides (best-effort)**: `visual.navItems` can define separate menus for topbar and sidebar, and hide topbar.
23
+ - **Footer/search overrides (best-effort)**: `visual.footerLinks` and `visual.search` allow replacing footer links and search copy without core edits.
24
+ - **Footer layout (best-effort)**: `visual.footer` can disable or compact footer and override footer text.
25
+ - **Form styling (best-effort)**: `visual.form` allows overriding form label/input/error/button/form classes.
26
+ - **Button styling (best-effort)**: `visual.button` allows overriding base/variant button classes.
27
+ - **Table styling (best-effort)**: `visual.table` allows overriding table container/row/cell/action classes.
28
+ - **Tabs styling (best-effort)**: `visual.tabs` allows overriding tabs container/header/button/panel classes.
29
+ - **Marketing blocks (best-effort)**: `visual.marketing` allows overriding hero/features/logos/etc container/title classes.
30
+ - **Card styling (best-effort)**: `visual.cards` allows overriding card/empty/placeholder classes.
31
+ - **Prose styling (best-effort)**: `visual.prose` allows overriding markdown/prose wrapper class.
32
+ - **Motion styling (best-effort)**: `visual.motion` allows overriding page enter + hover/alert/tag motion classes.
33
+ - **Copy overrides (best-effort)**: `visual.copy` allows overriding empty/placeholder/table/tab strings plus common UI labels, including runtime error text.
34
+ - **Token overrides (best-effort)**: `visual.tokens` allows overriding typography/spacing/radius/shadow/color/motion tokens.
35
+ - **Icon overrides (best-effort)**: `visual.icons` allows overriding default UI chrome icons, including docs section + docs item + nav + footer + search modal + row action icons.
36
+ - **SSR fix**: ensure `App.tsx` imports React when using `React.createElement` in generated UI.
37
+ - **Breakpoint overrides (best-effort)**: `visual.breakpoints` allows overriding responsive layout classes (padding/sidebar/docs grid).
38
+ - **Docs/background overrides (best-effort)**: `visual.docs` and `visual.background` control docs labels/sidebar/TOC and decorative gradients.
39
+ - **Labels/avatar overrides (best-effort)**: `visual.labels.sidebarLabel` and `visual.topbarControls.avatar.src` replace hardcoded sidebar label and avatar URL.
40
+
5
41
  ## [0.2.1] - 2026-01-11
6
42
 
7
43
  ### GitHub Pages (SSG/Hybrid)
package/README.md CHANGED
@@ -38,6 +38,7 @@ irgen focuses on **architecture and determinism**, not convenience shortcuts. ir
38
38
  - **Multi-app Support**: Set `basePath` to deploy multiple apps (e.g., `/admin`, `/docs`) under a single project or domain.
39
39
  - **Form Logic & UX**: JSONLogic predicates, dependency-tracked hooks, async select with debounce/pagination/search.
40
40
  - **Submission Pipeline**: Lifecycle hooks, confirm dialogs, response handling, and draft persistence.
41
+ - **Auth + Table Contracts**: `frontend.auth` for login/logout flows; `form.load` for prefill; `table.rowNavigateTo` + `table.rowActions` for row navigation/actions.
41
42
 
42
43
  ### Static Site (HTML-first)
43
44
  - **HTML-only output**: emits final HTML (no React hydration), JS is strictly progressive enhancement.
@@ -149,7 +150,7 @@ See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for details on:
149
150
  - Generation Gap Pattern
150
151
  - Port & Adapters (Hexagonal)
151
152
  - Frontend Runtime & IR
152
- - Frontend SSG plan: [docs/FRONTEND-SSG-PLAN.md](docs/FRONTEND-SSG-PLAN.md)
153
+ - Macro System: [docs/MACRO-SYSTEM.md](docs/MACRO-SYSTEM.md)
153
154
 
154
155
  ## Roadmap
155
156
  - [x] Separation of Concerns (Generated vs User space)
package/dist/cli.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import path from "node:path";
3
+ import fs from "node:fs";
3
4
  import { pathToFileURL } from "node:url";
5
+ import { createRequire } from "node:module";
4
6
  import { aggregateDecls } from "./dsl/aggregator.js";
5
7
  import { registerBuiltins, runMapper } from "./mappers/index.js";
6
8
  // Guard: require a modern Node (tsx loader relies on module.register)
@@ -186,13 +188,37 @@ Examples:
186
188
  return;
187
189
  const { createExtensionContext } = await import("./extensions/context.js");
188
190
  const ctx = createExtensionContext();
191
+ const require = createRequire(import.meta.url);
192
+ const pickExtensionFn = (mod) => {
193
+ const candidate = (mod?.default ?? mod?.extension ?? mod);
194
+ if (typeof candidate === "function")
195
+ return candidate;
196
+ if (candidate && typeof candidate === "object") {
197
+ if (typeof candidate.default === "function")
198
+ return candidate.default;
199
+ if (typeof candidate.extension === "function")
200
+ return candidate.extension;
201
+ }
202
+ return null;
203
+ };
204
+ const resolveExtensionModule = (spec) => {
205
+ const isPathLike = path.isAbsolute(spec) || spec.startsWith(".") || spec.startsWith("/") || /^[A-Za-z]:[\\/]/.test(spec);
206
+ if (isPathLike) {
207
+ const abs = path.isAbsolute(spec) ? spec : path.resolve(process.cwd(), spec);
208
+ if (!fs.existsSync(abs)) {
209
+ throw new Error(`extension module not found: ${spec}`);
210
+ }
211
+ return pathToFileURL(abs).href;
212
+ }
213
+ const resolved = require.resolve(spec, { paths: [process.cwd()] });
214
+ return pathToFileURL(resolved).href;
215
+ };
189
216
  for (const modPath of extModules) {
190
- const abs = path.isAbsolute(modPath) ? modPath : path.resolve(process.cwd(), modPath);
191
- const modUrl = pathToFileURL(abs).href;
217
+ const modUrl = resolveExtensionModule(modPath);
192
218
  const imported = await import(modUrl);
193
- const fn = (imported.default ?? imported.extension ?? imported);
219
+ const fn = pickExtensionFn(imported);
194
220
  if (typeof fn === "function") {
195
- fn(ctx, imported.options ?? undefined);
221
+ await fn(ctx, imported.options ?? undefined);
196
222
  }
197
223
  else {
198
224
  console.warn(`extension module ${modPath} did not export a function`);
@@ -1,5 +1,5 @@
1
1
  import { DeclFrontendApp, DeclComponent } from "../ir/decl/frontend.raw.schema.js";
2
- export type RuntimeComponent = Omit<DeclComponent, "agentChat" | "cliUsage" | "table"> & {
2
+ export type RuntimeComponent = Omit<DeclComponent, "agentChat" | "cliUsage" | "table" | "macro"> & {
3
3
  field: (fieldName: string, type: string, label?: string, validators?: Record<string, any>, config?: any) => void;
4
4
  prop: (key: string, value: string) => void;
5
5
  agentChat: (data: {
@@ -39,21 +39,25 @@ export type RuntimeComponent = Omit<DeclComponent, "agentChat" | "cliUsage" | "t
39
39
  render?: string;
40
40
  }>;
41
41
  }) => void;
42
+ useMacro: (type: string, props?: Record<string, any>) => void;
42
43
  };
43
44
  type FrontendOptions = {
44
45
  basePath?: string;
45
46
  pwa?: DeclFrontendApp["pwa"];
47
+ auth?: DeclFrontendApp["auth"];
46
48
  meta?: Record<string, any>;
47
49
  policies?: Record<string, any>;
48
50
  datasources?: DeclFrontendApp["datasources"];
49
51
  operations?: DeclFrontendApp["operations"];
50
52
  resources?: DeclFrontendApp["resources"];
53
+ requiredComponentKeys?: string[];
51
54
  };
52
55
  export declare function datasource(id: string, config: any): void;
53
56
  export declare function operation(id: string, config: any): void;
54
57
  export declare function resource(id: string, config: any): void;
55
58
  export declare function meta(key: string, value: unknown): void;
56
59
  export declare function policy(target: string, value: Record<string, any>): void;
60
+ export declare function requiredComponents(keys: string[]): void;
57
61
  export declare function page(pName: string, opts: {
58
62
  path: string;
59
63
  hideHeader?: boolean;
@@ -72,6 +76,7 @@ type FrontendCallbackArgs = {
72
76
  resource: typeof resource;
73
77
  meta: typeof meta;
74
78
  policy: typeof policy;
79
+ requiredComponents: typeof requiredComponents;
75
80
  };
76
81
  export declare function frontend(name: string, optsOrFn: FrontendOptions | ((a: FrontendCallbackArgs) => void), maybeFn?: (a: FrontendCallbackArgs) => void): {
77
82
  type: "frontend";
@@ -130,7 +135,7 @@ export declare function frontend(name: string, optsOrFn: FrontendOptions | ((a:
130
135
  components: {
131
136
  type: "component";
132
137
  name: string;
133
- props?: Record<string, string> | undefined;
138
+ props?: Record<string, any> | undefined;
134
139
  form?: {
135
140
  fields: {
136
141
  type: "number" | "date" | "text" | "select" | "textarea" | "checkbox" | "radio" | "datetime" | "time" | "url" | "phone" | "file" | "slider" | "currency" | "tags" | "signature" | "daterange" | "email" | "password";
@@ -193,6 +198,8 @@ export declare function frontend(name: string, optsOrFn: FrontendOptions | ((a:
193
198
  }[];
194
199
  submit?: {
195
200
  url?: string | undefined;
201
+ label?: string | undefined;
202
+ operationId?: string | undefined;
196
203
  method?: "POST" | "PUT" | "PATCH" | undefined;
197
204
  successMessage?: string | undefined;
198
205
  errorMessage?: string | undefined;
@@ -203,6 +210,14 @@ export declare function frontend(name: string, optsOrFn: FrontendOptions | ((a:
203
210
  onError?: any;
204
211
  redirect?: string | undefined;
205
212
  } | undefined;
213
+ load?: {
214
+ operationId: string;
215
+ onSuccess?: any;
216
+ onError?: any;
217
+ args?: any;
218
+ when?: any;
219
+ mapFields?: Record<string, any> | undefined;
220
+ } | undefined;
206
221
  } | undefined;
207
222
  entityRef?: string | undefined;
208
223
  content?: string | undefined;
@@ -236,16 +251,40 @@ export declare function frontend(name: string, optsOrFn: FrontendOptions | ((a:
236
251
  button?: {
237
252
  label: string;
238
253
  icon?: string | undefined;
254
+ onClick?: {
255
+ kind: "invoke";
256
+ operationId: string;
257
+ confirmMessage?: string | undefined;
258
+ args?: any;
259
+ } | {
260
+ kind: "navigate";
261
+ confirmMessage?: string | undefined;
262
+ to?: any;
263
+ } | undefined;
239
264
  variant?: "primary" | "secondary" | "ghost" | undefined;
240
265
  } | undefined;
241
266
  table?: {
267
+ operationId?: string | undefined;
242
268
  columns?: {
243
269
  header: string;
244
270
  accessor: string;
245
271
  render?: string | undefined;
246
272
  }[] | undefined;
247
273
  resourceId?: string | undefined;
248
- operationId?: string | undefined;
274
+ rowNavigateTo?: string | undefined;
275
+ rowActions?: {
276
+ label: string;
277
+ onClick?: {
278
+ kind: "invoke";
279
+ operationId: string;
280
+ confirmMessage?: string | undefined;
281
+ args?: any;
282
+ } | {
283
+ kind: "navigate";
284
+ confirmMessage?: string | undefined;
285
+ to?: any;
286
+ } | undefined;
287
+ }[] | undefined;
249
288
  } | undefined;
250
289
  themeToggle?: boolean | undefined;
251
290
  codeBlock?: {
@@ -253,6 +292,7 @@ export declare function frontend(name: string, optsOrFn: FrontendOptions | ((a:
253
292
  language: string;
254
293
  showLineNumbers: boolean;
255
294
  } | undefined;
295
+ macro?: string | undefined;
256
296
  marketing?: {
257
297
  kind: "hero" | "features" | "testimonials" | "faq" | "logos" | "cta" | "stats" | "timeline";
258
298
  align?: "left" | "center" | undefined;
@@ -270,8 +310,18 @@ export declare function frontend(name: string, optsOrFn: FrontendOptions | ((a:
270
310
  }[] | undefined;
271
311
  actions?: {
272
312
  label: string;
273
- href: string;
274
313
  icon?: string | undefined;
314
+ href?: string | undefined;
315
+ onClick?: {
316
+ kind: "invoke";
317
+ operationId: string;
318
+ confirmMessage?: string | undefined;
319
+ args?: any;
320
+ } | {
321
+ kind: "navigate";
322
+ confirmMessage?: string | undefined;
323
+ to?: any;
324
+ } | undefined;
275
325
  variant?: "primary" | "secondary" | "ghost" | undefined;
276
326
  }[] | undefined;
277
327
  badge?: string | undefined;
@@ -285,7 +335,7 @@ export declare function frontend(name: string, optsOrFn: FrontendOptions | ((a:
285
335
  components: {
286
336
  type: "component";
287
337
  name: string;
288
- props?: Record<string, string> | undefined;
338
+ props?: Record<string, any> | undefined;
289
339
  form?: {
290
340
  fields: {
291
341
  type: "number" | "date" | "text" | "select" | "textarea" | "checkbox" | "radio" | "datetime" | "time" | "url" | "phone" | "file" | "slider" | "currency" | "tags" | "signature" | "daterange" | "email" | "password";
@@ -348,6 +398,8 @@ export declare function frontend(name: string, optsOrFn: FrontendOptions | ((a:
348
398
  }[];
349
399
  submit?: {
350
400
  url?: string | undefined;
401
+ label?: string | undefined;
402
+ operationId?: string | undefined;
351
403
  method?: "POST" | "PUT" | "PATCH" | undefined;
352
404
  successMessage?: string | undefined;
353
405
  errorMessage?: string | undefined;
@@ -358,6 +410,14 @@ export declare function frontend(name: string, optsOrFn: FrontendOptions | ((a:
358
410
  onError?: any;
359
411
  redirect?: string | undefined;
360
412
  } | undefined;
413
+ load?: {
414
+ operationId: string;
415
+ onSuccess?: any;
416
+ onError?: any;
417
+ args?: any;
418
+ when?: any;
419
+ mapFields?: Record<string, any> | undefined;
420
+ } | undefined;
361
421
  } | undefined;
362
422
  entityRef?: string | undefined;
363
423
  content?: string | undefined;
@@ -391,16 +451,40 @@ export declare function frontend(name: string, optsOrFn: FrontendOptions | ((a:
391
451
  button?: {
392
452
  label: string;
393
453
  icon?: string | undefined;
454
+ onClick?: {
455
+ kind: "invoke";
456
+ operationId: string;
457
+ confirmMessage?: string | undefined;
458
+ args?: any;
459
+ } | {
460
+ kind: "navigate";
461
+ confirmMessage?: string | undefined;
462
+ to?: any;
463
+ } | undefined;
394
464
  variant?: "primary" | "secondary" | "ghost" | undefined;
395
465
  } | undefined;
396
466
  table?: {
467
+ operationId?: string | undefined;
397
468
  columns?: {
398
469
  header: string;
399
470
  accessor: string;
400
471
  render?: string | undefined;
401
472
  }[] | undefined;
402
473
  resourceId?: string | undefined;
403
- operationId?: string | undefined;
474
+ rowNavigateTo?: string | undefined;
475
+ rowActions?: {
476
+ label: string;
477
+ onClick?: {
478
+ kind: "invoke";
479
+ operationId: string;
480
+ confirmMessage?: string | undefined;
481
+ args?: any;
482
+ } | {
483
+ kind: "navigate";
484
+ confirmMessage?: string | undefined;
485
+ to?: any;
486
+ } | undefined;
487
+ }[] | undefined;
404
488
  } | undefined;
405
489
  themeToggle?: boolean | undefined;
406
490
  codeBlock?: {
@@ -408,6 +492,7 @@ export declare function frontend(name: string, optsOrFn: FrontendOptions | ((a:
408
492
  language: string;
409
493
  showLineNumbers: boolean;
410
494
  } | undefined;
495
+ macro?: string | undefined;
411
496
  marketing?: {
412
497
  kind: "hero" | "features" | "testimonials" | "faq" | "logos" | "cta" | "stats" | "timeline";
413
498
  align?: "left" | "center" | undefined;
@@ -425,8 +510,18 @@ export declare function frontend(name: string, optsOrFn: FrontendOptions | ((a:
425
510
  }[] | undefined;
426
511
  actions?: {
427
512
  label: string;
428
- href: string;
429
513
  icon?: string | undefined;
514
+ href?: string | undefined;
515
+ onClick?: {
516
+ kind: "invoke";
517
+ operationId: string;
518
+ confirmMessage?: string | undefined;
519
+ args?: any;
520
+ } | {
521
+ kind: "navigate";
522
+ confirmMessage?: string | undefined;
523
+ to?: any;
524
+ } | undefined;
430
525
  variant?: "primary" | "secondary" | "ghost" | undefined;
431
526
  }[] | undefined;
432
527
  badge?: string | undefined;
@@ -480,6 +575,14 @@ export declare function frontend(name: string, optsOrFn: FrontendOptions | ((a:
480
575
  purpose?: string | undefined;
481
576
  }[] | undefined;
482
577
  } | undefined;
578
+ auth?: {
579
+ enabled?: boolean | undefined;
580
+ loginPath?: string | undefined;
581
+ meOperationId?: string | undefined;
582
+ logoutOperationId?: string | undefined;
583
+ hideLoginWhenAuthed?: boolean | undefined;
584
+ } | undefined;
585
+ requiredComponentKeys?: string[] | undefined;
483
586
  };
484
587
  export declare function loadFrontendDsl(entry: string): Promise<DeclFrontendApp>;
485
588
  export {};
@@ -31,6 +31,7 @@ const RUNTIME_HELPERS = [
31
31
  "enableThemeToggle",
32
32
  "code",
33
33
  "table",
34
+ "useMacro",
34
35
  ];
35
36
  function stripRuntimeHelpers(comp) {
36
37
  for (const key of RUNTIME_HELPERS) {
@@ -60,6 +61,10 @@ export function policy(target, value) {
60
61
  assert(_global.__IR_CURRENT_FRONTEND, "policy() harus di dalam frontend()");
61
62
  mergePolicy(target, value);
62
63
  }
64
+ export function requiredComponents(keys) {
65
+ assert(_global.__IR_CURRENT_FRONTEND, "requiredComponents() harus di dalam frontend()");
66
+ _global.__IR_CURRENT_FRONTEND.requiredComponentKeys = keys;
67
+ }
63
68
  export function page(pName, opts, cb) {
64
69
  assert(_global.__IR_CURRENT_FRONTEND, "page() harus di dalam frontend()");
65
70
  const page = {
@@ -104,6 +109,11 @@ export function page(pName, opts, cb) {
104
109
  comp.codeBlock = { snippet, language, showLineNumbers: options?.showLineNumbers ?? true };
105
110
  };
106
111
  comp.table = function (data) { comp.table = data; };
112
+ comp.useMacro = function (type, props) {
113
+ comp.macro = type;
114
+ if (props)
115
+ comp.props = props;
116
+ };
107
117
  if (typeof cCb === "function")
108
118
  cCb(comp);
109
119
  stripRuntimeHelpers(comp);
@@ -151,6 +161,11 @@ export function component(name, cb) {
151
161
  comp.codeBlock = { snippet, language, showLineNumbers: options?.showLineNumbers ?? true };
152
162
  };
153
163
  comp.table = function (data) { comp.table = data; };
164
+ comp.useMacro = function (type, props) {
165
+ comp.macro = type;
166
+ if (props)
167
+ comp.props = props;
168
+ };
154
169
  if (typeof cb === "function")
155
170
  cb(comp);
156
171
  stripRuntimeHelpers(comp);
@@ -176,6 +191,8 @@ export function frontend(name, optsOrFn, maybeFn) {
176
191
  operations: opts?.operations ?? [],
177
192
  resources: opts?.resources ?? [],
178
193
  ...(opts?.pwa ? { pwa: opts.pwa } : {}),
194
+ ...(opts?.auth ? { auth: opts.auth } : {}),
195
+ ...(opts?.requiredComponentKeys ? { requiredComponentKeys: opts.requiredComponentKeys } : {}),
179
196
  meta: { ...baseMeta },
180
197
  };
181
198
  if (opts?.policies) {
@@ -191,6 +208,7 @@ export function frontend(name, optsOrFn, maybeFn) {
191
208
  component,
192
209
  meta,
193
210
  policy,
211
+ requiredComponents,
194
212
  });
195
213
  const parsed = DeclFrontendAppSchema.parse(_global.__IR_CURRENT_FRONTEND);
196
214
  _global.__IR_CURRENT_FRONTEND = parsed;
@@ -0,0 +1,6 @@
1
+ import type { Project } from "ts-morph";
2
+ import type { FrontendComponent, FrontendMarketing } from "../../ir/domain/frontend.js";
3
+ import type { FrontendTargetIR } from "../../ir/target/frontend.js";
4
+ import type { FrontendPolicy } from "../../ir/target/frontend.policy.js";
5
+ export declare function emitComponent(project: Project, frontendDir: string, component: FrontendComponent, ir: FrontendTargetIR): void;
6
+ export declare function emitMarketingComponent(writer: any, m: FrontendMarketing, policy: FrontendPolicy, actionHandlers?: Array<string | null>): void;