@shamar/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/actions.d.ts +39 -0
  2. package/dist/actions.d.ts.map +1 -0
  3. package/dist/actions.js +97 -0
  4. package/dist/actions.js.map +1 -0
  5. package/dist/alignment.d.ts +13 -0
  6. package/dist/alignment.d.ts.map +1 -0
  7. package/dist/alignment.js +34 -0
  8. package/dist/alignment.js.map +1 -0
  9. package/dist/currency.d.ts +16 -0
  10. package/dist/currency.d.ts.map +1 -0
  11. package/dist/currency.js +52 -0
  12. package/dist/currency.js.map +1 -0
  13. package/dist/form.d.ts +281 -0
  14. package/dist/form.d.ts.map +1 -0
  15. package/dist/form.js +756 -0
  16. package/dist/form.js.map +1 -0
  17. package/dist/index.d.ts +19 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +19 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/infolist.d.ts +90 -0
  22. package/dist/infolist.d.ts.map +1 -0
  23. package/dist/infolist.js +273 -0
  24. package/dist/infolist.js.map +1 -0
  25. package/dist/labels.d.ts +6 -0
  26. package/dist/labels.d.ts.map +1 -0
  27. package/dist/labels.js +26 -0
  28. package/dist/labels.js.map +1 -0
  29. package/dist/panel.d.ts +20 -0
  30. package/dist/panel.d.ts.map +1 -0
  31. package/dist/panel.js +46 -0
  32. package/dist/panel.js.map +1 -0
  33. package/dist/permissions.d.ts +43 -0
  34. package/dist/permissions.d.ts.map +1 -0
  35. package/dist/permissions.js +152 -0
  36. package/dist/permissions.js.map +1 -0
  37. package/dist/policy-types.d.ts +23 -0
  38. package/dist/policy-types.d.ts.map +1 -0
  39. package/dist/policy-types.js +8 -0
  40. package/dist/policy-types.js.map +1 -0
  41. package/dist/reactivity.d.ts +23 -0
  42. package/dist/reactivity.d.ts.map +1 -0
  43. package/dist/reactivity.js +68 -0
  44. package/dist/reactivity.js.map +1 -0
  45. package/dist/record-value.d.ts +31 -0
  46. package/dist/record-value.d.ts.map +1 -0
  47. package/dist/record-value.js +118 -0
  48. package/dist/record-value.js.map +1 -0
  49. package/dist/registry.d.ts +16 -0
  50. package/dist/registry.d.ts.map +1 -0
  51. package/dist/registry.js +49 -0
  52. package/dist/registry.js.map +1 -0
  53. package/dist/relation.d.ts +28 -0
  54. package/dist/relation.d.ts.map +1 -0
  55. package/dist/relation.js +69 -0
  56. package/dist/relation.js.map +1 -0
  57. package/dist/resource.d.ts +100 -0
  58. package/dist/resource.d.ts.map +1 -0
  59. package/dist/resource.js +165 -0
  60. package/dist/resource.js.map +1 -0
  61. package/dist/schemas.d.ts +193 -0
  62. package/dist/schemas.d.ts.map +1 -0
  63. package/dist/schemas.js +501 -0
  64. package/dist/schemas.js.map +1 -0
  65. package/dist/table.d.ts +71 -0
  66. package/dist/table.d.ts.map +1 -0
  67. package/dist/table.js +172 -0
  68. package/dist/table.js.map +1 -0
  69. package/dist/types.d.ts +474 -0
  70. package/dist/types.d.ts.map +1 -0
  71. package/dist/types.js +2 -0
  72. package/dist/types.js.map +1 -0
  73. package/dist/validation.d.ts +27 -0
  74. package/dist/validation.d.ts.map +1 -0
  75. package/dist/validation.js +182 -0
  76. package/dist/validation.js.map +1 -0
  77. package/package.json +30 -0
@@ -0,0 +1,152 @@
1
+ import { POLICY_ABILITIES } from './policy-types.js';
2
+ /** Build a permission key, e.g. `products:create`. */
3
+ export function permissionKey(resource, ability) {
4
+ return `${resource}:${ability}`;
5
+ }
6
+ /**
7
+ * Check a full permission name (`companies:viewAny`, `companies:*`, `*`).
8
+ * Wildcards: `*`, `{resource}:*`, `*:{ability}`.
9
+ */
10
+ export function can(user, permission) {
11
+ if (!user)
12
+ return false;
13
+ const permissions = user.permissions ?? [];
14
+ if (permissions.includes('*') || permissions.includes(permission)) {
15
+ return true;
16
+ }
17
+ const [resource, ability] = permission.split(':');
18
+ if (!resource || !ability)
19
+ return false;
20
+ if (permissions.includes(`${resource}:*`))
21
+ return true;
22
+ if (ability !== '*' && permissions.includes(`*:${ability}`))
23
+ return true;
24
+ return false;
25
+ }
26
+ export function isAdmin(user) {
27
+ if (!user)
28
+ return false;
29
+ if (can(user, '*'))
30
+ return true;
31
+ return Boolean(user.roleIds?.includes('admin') || user.roles?.includes('admin'));
32
+ }
33
+ export function userHasPermission(user, slug, ability) {
34
+ if (!user)
35
+ return false;
36
+ return (can(user, '*') ||
37
+ can(user, `${slug}:*`) ||
38
+ can(user, `${slug}:${ability}`) ||
39
+ can(user, `*:${ability}`));
40
+ }
41
+ /** True if the user has any of the named permissions (wildcard-aware). */
42
+ export function canAny(user, permissions) {
43
+ return permissions.some((entry) => can(user, entry));
44
+ }
45
+ export function hasExplicitPermissions(permissions) {
46
+ return Array.isArray(permissions) && permissions.length > 0;
47
+ }
48
+ /** Wildcard grant covers a more specific permission candidate. */
49
+ export function covers(wildcard, candidate) {
50
+ if (!wildcard || !candidate || wildcard === candidate)
51
+ return false;
52
+ if (wildcard === '*')
53
+ return true;
54
+ if (wildcard.endsWith(':*')) {
55
+ const prefix = wildcard.slice(0, -1);
56
+ return candidate.startsWith(prefix);
57
+ }
58
+ if (wildcard.startsWith('*:')) {
59
+ const suffix = wildcard.slice(2);
60
+ return candidate.endsWith(`:${suffix}`);
61
+ }
62
+ return false;
63
+ }
64
+ /**
65
+ * Whether a granted permission covers a required ability.
66
+ */
67
+ export function matchesPermission(granted, required) {
68
+ const grants = [...granted];
69
+ if (grants.length === 0)
70
+ return false;
71
+ if (grants.includes('*'))
72
+ return true;
73
+ if (grants.includes(required))
74
+ return true;
75
+ for (const grant of grants) {
76
+ if (covers(grant, required))
77
+ return true;
78
+ }
79
+ return false;
80
+ }
81
+ /**
82
+ * Normalize resource `permissions()` entries into full `{slug}:{ability}` names.
83
+ */
84
+ export function normalizeCustomPermissions(slug, entries) {
85
+ const out = [];
86
+ for (const entry of entries) {
87
+ const raw = typeof entry === 'string' ? entry : entry.name;
88
+ const label = typeof entry === 'string' ? undefined : entry.label;
89
+ if (!raw?.trim())
90
+ continue;
91
+ const name = raw === '*' || raw.includes(':') ? raw.trim() : `${slug}:${raw.trim()}`;
92
+ out.push(label ? { name, label } : { name });
93
+ }
94
+ return out;
95
+ }
96
+ function humanizePolicyAbility(ability) {
97
+ if (ability === '*')
98
+ return 'All';
99
+ if (ability === 'viewAny')
100
+ return 'View any';
101
+ if (ability === 'view')
102
+ return 'View';
103
+ if (ability === 'create')
104
+ return 'Create';
105
+ if (ability === 'edit')
106
+ return 'Update';
107
+ if (ability === 'delete')
108
+ return 'Delete';
109
+ return ability.charAt(0).toUpperCase() + ability.slice(1);
110
+ }
111
+ /**
112
+ * Build the full permission catalog from registered resources.
113
+ * Upsert these rows on app boot — never create permissions manually in admin.
114
+ */
115
+ export function buildPermissionCatalog(registry) {
116
+ const entries = [
117
+ {
118
+ name: '*',
119
+ resource: '*',
120
+ ability: '*',
121
+ label: 'Superuser (all)',
122
+ },
123
+ ];
124
+ for (const meta of registry.all()) {
125
+ for (const ability of POLICY_ABILITIES) {
126
+ const name = permissionKey(meta.slug, ability);
127
+ entries.push({
128
+ name,
129
+ resource: meta.slug,
130
+ ability,
131
+ label: `${meta.label} — ${humanizePolicyAbility(ability)}`,
132
+ });
133
+ }
134
+ entries.push({
135
+ name: permissionKey(meta.slug, '*'),
136
+ resource: meta.slug,
137
+ ability: '*',
138
+ label: `${meta.label} — All`,
139
+ });
140
+ for (const custom of meta.customPermissions ?? []) {
141
+ const colon = custom.name.indexOf(':');
142
+ entries.push({
143
+ name: custom.name,
144
+ resource: colon > 0 ? custom.name.slice(0, colon) : meta.slug,
145
+ ability: colon > 0 ? custom.name.slice(colon + 1) : custom.name,
146
+ label: custom.label ?? custom.name,
147
+ });
148
+ }
149
+ }
150
+ return entries;
151
+ }
152
+ //# sourceMappingURL=permissions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissions.js","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD,sDAAsD;AACtD,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,OAAe;IAC7D,OAAO,GAAG,QAAQ,IAAI,OAAO,EAAE,CAAC;AAClC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,GAAG,CAAC,IAAmC,EAAE,UAAkB;IACzE,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC;IAC3C,IAAI,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAClE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAClD,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,WAAW,CAAC,QAAQ,CAAC,GAAG,QAAQ,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACvD,IAAI,OAAO,KAAK,GAAG,IAAI,WAAW,CAAC,QAAQ,CAAC,KAAK,OAAO,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IACzE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,IAAmC;IACzD,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAChC,OAAO,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AACnF,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,IAAmC,EACnC,IAAY,EACZ,OAAsB;IAEtB,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,OAAO,CACL,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC;QACd,GAAG,CAAC,IAAI,EAAE,GAAG,IAAI,IAAI,CAAC;QACtB,GAAG,CAAC,IAAI,EAAE,GAAG,IAAI,IAAI,OAAO,EAAE,CAAC;QAC/B,GAAG,CAAC,IAAI,EAAE,KAAK,OAAO,EAAE,CAAC,CAC1B,CAAC;AACJ,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,MAAM,CAAC,IAAmC,EAAE,WAAqB;IAC/E,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,WAA6B;IAClE,OAAO,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC;AAC9D,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,MAAM,CAAC,QAAgB,EAAE,SAAiB;IACxD,IAAI,CAAC,QAAQ,IAAI,CAAC,SAAS,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACpE,IAAI,QAAQ,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IAClC,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACrC,OAAO,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IACD,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QACjC,OAAO,SAAS,CAAC,QAAQ,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAyB,EAAE,QAAgB;IAC3E,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC;IAC5B,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACtC,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAE3C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;IAC3C,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,0BAA0B,CACxC,IAAY,EACZ,OAAyD;IAEzD,MAAM,GAAG,GAA4C,EAAE,CAAC;IACxD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,GAAG,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;QAC3D,MAAM,KAAK,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;QAClE,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE;YAAE,SAAS;QAC3B,MAAM,IAAI,GACR,GAAG,KAAK,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC;QAC1E,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AASD,SAAS,qBAAqB,CAAC,OAAe;IAC5C,IAAI,OAAO,KAAK,GAAG;QAAE,OAAO,KAAK,CAAC;IAClC,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,UAAU,CAAC;IAC7C,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,MAAM,CAAC;IACtC,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC1C,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,QAAQ,CAAC;IACxC,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC1C,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAA0B;IAC/D,MAAM,OAAO,GAA6B;QACxC;YACE,IAAI,EAAE,GAAG;YACT,QAAQ,EAAE,GAAG;YACb,OAAO,EAAE,GAAG;YACZ,KAAK,EAAE,iBAAiB;SACzB;KACF,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC;QAClC,KAAK,MAAM,OAAO,IAAI,gBAAgB,EAAE,CAAC;YACvC,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC/C,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI;gBACJ,QAAQ,EAAE,IAAI,CAAC,IAAI;gBACnB,OAAO;gBACP,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,MAAM,qBAAqB,CAAC,OAAO,CAAC,EAAE;aAC3D,CAAC,CAAC;QACL,CAAC;QAED,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC;YACnC,QAAQ,EAAE,IAAI,CAAC,IAAI;YACnB,OAAO,EAAE,GAAG;YACZ,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,QAAQ;SAC7B,CAAC,CAAC;QAEH,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,iBAAiB,IAAI,EAAE,EAAE,CAAC;YAClD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACvC,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,QAAQ,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI;gBAC7D,OAAO,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI;gBAC/D,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI;aACnC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { ShamarUser } from './types.js';
2
+ /** Loom / Filament policy abilities. */
3
+ export type PolicyAbility = 'viewAny' | 'view' | 'create' | 'edit' | 'delete';
4
+ /** Equality filters applied to list queries (record-level scoping). */
5
+ export interface QueryScope {
6
+ equals?: Record<string, unknown>;
7
+ }
8
+ /**
9
+ * Policy class contract — Loom-style static methods.
10
+ * Assign to `Resource.policy` or register in `auth.policies`.
11
+ */
12
+ export type PolicyClass = {
13
+ new (...args: unknown[]): unknown;
14
+ ownerField?: string;
15
+ viewAny?(user: ShamarUser, slug?: string): boolean;
16
+ view?(user: ShamarUser, record: Record<string, unknown>, slug?: string): boolean;
17
+ create?(user: ShamarUser, slug?: string): boolean;
18
+ edit?(user: ShamarUser, record: Record<string, unknown>, slug?: string): boolean;
19
+ delete?(user: ShamarUser, record: Record<string, unknown>, slug?: string): boolean;
20
+ scopeList?(user: ShamarUser, slug?: string): QueryScope | undefined;
21
+ };
22
+ export declare const POLICY_ABILITIES: PolicyAbility[];
23
+ //# sourceMappingURL=policy-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-types.d.ts","sourceRoot":"","sources":["../src/policy-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,wCAAwC;AACxC,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE9E,uEAAuE;AACvE,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACnD,IAAI,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACjF,MAAM,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAClD,IAAI,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACjF,MAAM,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACnF,SAAS,CAAC,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;CACrE,CAAC;AAEF,eAAO,MAAM,gBAAgB,EAAE,aAAa,EAM3C,CAAC"}
@@ -0,0 +1,8 @@
1
+ export const POLICY_ABILITIES = [
2
+ 'viewAny',
3
+ 'view',
4
+ 'create',
5
+ 'edit',
6
+ 'delete',
7
+ ];
8
+ //# sourceMappingURL=policy-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy-types.js","sourceRoot":"","sources":["../src/policy-types.ts"],"names":[],"mappings":"AAyBA,MAAM,CAAC,MAAM,gBAAgB,GAAoB;IAC/C,SAAS;IACT,MAAM;IACN,QAAQ;IACR,MAAM;IACN,QAAQ;CACT,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { FieldContext, FormOperation } from './types.js';
2
+ export declare function createFieldContext(options: {
3
+ state: Record<string, unknown>;
4
+ record?: Record<string, unknown> | null;
5
+ operation: FormOperation;
6
+ changed?: string;
7
+ }): FieldContext;
8
+ /** Snapshot of mutable state after Get/Set mutations. */
9
+ export declare function readContextState(ctx: FieldContext): Record<string, unknown>;
10
+ export declare function evaluateAfterStateUpdated(field: {
11
+ name: string;
12
+ afterStateUpdated?: (ctx: FieldContext) => void | Promise<void>;
13
+ }, options: {
14
+ state: Record<string, unknown>;
15
+ record?: Record<string, unknown> | null;
16
+ operation: FormOperation;
17
+ }): Promise<Record<string, unknown>>;
18
+ export declare function resolveClosure<T>(value: T | ((ctx: FieldContext) => T) | undefined, ctx: FieldContext, fallback?: T): T | undefined;
19
+ export declare function resolveGridItemStyle(item: {
20
+ columnSpan?: number | 'full';
21
+ columnStart?: number;
22
+ }, sectionColumns?: number): string;
23
+ //# sourceMappingURL=reactivity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reactivity.d.ts","sourceRoot":"","sources":["../src/reactivity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE9D,wBAAgB,kBAAkB,CAAC,OAAO,EAAE;IAC1C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxC,SAAS,EAAE,aAAa,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,YAAY,CAgBf;AAED,yDAAyD;AACzD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAO3E;AAED,wBAAsB,yBAAyB,CAC7C,KAAK,EAAE;IACL,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjE,EACD,OAAO,EAAE;IACP,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxC,SAAS,EAAE,aAAa,CAAC;CAC1B,GACA,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAqBlC;AAED,wBAAgB,cAAc,CAAC,CAAC,EAC9B,KAAK,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,YAAY,KAAK,CAAC,CAAC,GAAG,SAAS,EACjD,GAAG,EAAE,YAAY,EACjB,QAAQ,CAAC,EAAE,CAAC,GACX,CAAC,GAAG,SAAS,CAMf;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5D,cAAc,SAAI,GACjB,MAAM,CAYR"}
@@ -0,0 +1,68 @@
1
+ export function createFieldContext(options) {
2
+ const state = { ...options.state };
3
+ return {
4
+ get state() {
5
+ return options.changed != null ? state[options.changed] : state;
6
+ },
7
+ record: options.record ?? null,
8
+ operation: options.operation,
9
+ get(name) {
10
+ return state[name];
11
+ },
12
+ set(name, value) {
13
+ state[name] = value;
14
+ },
15
+ };
16
+ }
17
+ /** Snapshot of mutable state after Get/Set mutations. */
18
+ export function readContextState(ctx) {
19
+ // FieldContext.set mutates the closed-over object; re-read via known keys by
20
+ // wrapping set/get — callers should prefer evaluateAfterStateUpdated return.
21
+ const out = {};
22
+ // No enumerable store on ctx; evaluate hooks return patched state explicitly.
23
+ void out;
24
+ return out;
25
+ }
26
+ export async function evaluateAfterStateUpdated(field, options) {
27
+ const state = { ...options.state };
28
+ const ctx = {
29
+ get state() {
30
+ return state[field.name];
31
+ },
32
+ record: options.record ?? null,
33
+ operation: options.operation,
34
+ get(name) {
35
+ return state[name];
36
+ },
37
+ set(name, value) {
38
+ state[name] = value;
39
+ },
40
+ };
41
+ if (field.afterStateUpdated) {
42
+ await field.afterStateUpdated(ctx);
43
+ }
44
+ return state;
45
+ }
46
+ export function resolveClosure(value, ctx, fallback) {
47
+ if (value === undefined)
48
+ return fallback;
49
+ if (typeof value === 'function') {
50
+ return value(ctx);
51
+ }
52
+ return value;
53
+ }
54
+ export function resolveGridItemStyle(item, sectionColumns = 2) {
55
+ const parts = [];
56
+ if (item.columnStart != null) {
57
+ parts.push(`grid-column-start: ${item.columnStart}`);
58
+ }
59
+ if (item.columnSpan === 'full') {
60
+ parts.push(`grid-column: 1 / -1`);
61
+ }
62
+ else if (typeof item.columnSpan === 'number' && item.columnSpan > 1) {
63
+ const span = Math.min(item.columnSpan, sectionColumns);
64
+ parts.push(`grid-column: span ${span} / span ${span}`);
65
+ }
66
+ return parts.join('; ');
67
+ }
68
+ //# sourceMappingURL=reactivity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reactivity.js","sourceRoot":"","sources":["../src/reactivity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { RelationConfig, ResourceMeta } from './types.js';
2
+ /**
3
+ * Read a value from a record using dot notation.
4
+ * `company.name` → `record.company.name`
5
+ * `categories.name` → maps `name` across `record.categories[]`
6
+ */
7
+ export declare function getRecordValue(record: Record<string, unknown>, path: string): unknown;
8
+ /** Singularize a resource slug for path matching (`companies` → `company`). */
9
+ export declare function singularizeResourceSlug(slug: string): string;
10
+ export interface RelationDisplayBinding {
11
+ /** Full dot path, e.g. `company.name`. */
12
+ path: string;
13
+ /** First segment, e.g. `company` — nested key on the hydrated record. */
14
+ root: string;
15
+ /** Remaining segments joined, e.g. `name`. */
16
+ attribute: string;
17
+ /** Form FK / M2M field on the parent record, e.g. `companyId`. */
18
+ fieldName: string;
19
+ relation: RelationConfig;
20
+ }
21
+ /** Collect column/entry names that use dot notation. */
22
+ export declare function collectDisplayPaths(meta: ResourceMeta): string[];
23
+ /**
24
+ * Map a dot path to a form relationship field on the same resource.
25
+ * `company.name` → `companyId` BelongsTo `companies`
26
+ * `categories.name` → `categoryIds` ManyToMany `categories`
27
+ */
28
+ export declare function resolveRelationDisplayBinding(meta: ResourceMeta, path: string): RelationDisplayBinding | null;
29
+ /** Resolve all display bindings for a resource meta. */
30
+ export declare function resolveRelationDisplayBindings(meta: ResourceMeta): RelationDisplayBinding[];
31
+ //# sourceMappingURL=record-value.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-value.d.ts","sourceRoot":"","sources":["../src/record-value.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE5E;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,EAAE,MAAM,GACX,OAAO,CA8BT;AAED,+EAA+E;AAC/E,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAU5D;AAED,MAAM,WAAW,sBAAsB;IACrC,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED,wDAAwD;AACxD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,EAAE,CAShE;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,YAAY,EAClB,IAAI,EAAE,MAAM,GACX,sBAAsB,GAAG,IAAI,CAuB/B;AAiBD,wDAAwD;AACxD,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,YAAY,GACjB,sBAAsB,EAAE,CAY1B"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Read a value from a record using dot notation.
3
+ * `company.name` → `record.company.name`
4
+ * `categories.name` → maps `name` across `record.categories[]`
5
+ */
6
+ export function getRecordValue(record, path) {
7
+ if (!path.includes('.')) {
8
+ return record[path];
9
+ }
10
+ const parts = path.split('.').filter(Boolean);
11
+ if (parts.length === 0)
12
+ return undefined;
13
+ let current = record;
14
+ for (const part of parts) {
15
+ if (current == null)
16
+ return undefined;
17
+ if (Array.isArray(current)) {
18
+ const mapped = current
19
+ .map((item) => {
20
+ if (item == null)
21
+ return undefined;
22
+ if (typeof item === 'object') {
23
+ return item[part];
24
+ }
25
+ return part === 'value' || part === 'id' ? item : undefined;
26
+ })
27
+ .filter((value) => value != null && value !== '');
28
+ return mapped.length > 0 ? mapped : undefined;
29
+ }
30
+ if (typeof current !== 'object')
31
+ return undefined;
32
+ current = current[part];
33
+ }
34
+ return current;
35
+ }
36
+ /** Singularize a resource slug for path matching (`companies` → `company`). */
37
+ export function singularizeResourceSlug(slug) {
38
+ const trimmed = slug.trim();
39
+ if (!trimmed)
40
+ return trimmed;
41
+ if (trimmed.endsWith('ies') && trimmed.length > 3) {
42
+ return `${trimmed.slice(0, -3)}y`;
43
+ }
44
+ if (trimmed.endsWith('s') && trimmed.length > 1) {
45
+ return trimmed.slice(0, -1);
46
+ }
47
+ return trimmed;
48
+ }
49
+ /** Collect column/entry names that use dot notation. */
50
+ export function collectDisplayPaths(meta) {
51
+ const names = new Set();
52
+ for (const column of meta.columns) {
53
+ if (column.name.includes('.'))
54
+ names.add(column.name);
55
+ }
56
+ for (const entry of meta.infolist.entries) {
57
+ if (entry.name.includes('.'))
58
+ names.add(entry.name);
59
+ }
60
+ return [...names];
61
+ }
62
+ /**
63
+ * Map a dot path to a form relationship field on the same resource.
64
+ * `company.name` → `companyId` BelongsTo `companies`
65
+ * `categories.name` → `categoryIds` ManyToMany `categories`
66
+ */
67
+ export function resolveRelationDisplayBinding(meta, path) {
68
+ if (!path.includes('.'))
69
+ return null;
70
+ const segments = path.split('.').filter(Boolean);
71
+ if (segments.length < 2)
72
+ return null;
73
+ const root = segments[0];
74
+ const attribute = segments.slice(1).join('.');
75
+ for (const field of meta.fields) {
76
+ if (!field.relation)
77
+ continue;
78
+ if (!relationPathMatchesField(root, field))
79
+ continue;
80
+ return {
81
+ path,
82
+ root,
83
+ attribute,
84
+ fieldName: field.name,
85
+ relation: field.relation,
86
+ };
87
+ }
88
+ return null;
89
+ }
90
+ function relationPathMatchesField(root, field) {
91
+ const relation = field.relation;
92
+ if (!relation)
93
+ return false;
94
+ const resource = relation.resource;
95
+ const singular = singularizeResourceSlug(resource);
96
+ const fieldRoot = field.name.replace(/Ids?$/, '');
97
+ if (root === resource || root === singular)
98
+ return true;
99
+ if (root === field.name || root === fieldRoot)
100
+ return true;
101
+ if (relation.kind === 'belongsTo' && root === field.name.replace(/Id$/, ''))
102
+ return true;
103
+ return false;
104
+ }
105
+ /** Resolve all display bindings for a resource meta. */
106
+ export function resolveRelationDisplayBindings(meta) {
107
+ const seen = new Set();
108
+ const bindings = [];
109
+ for (const path of collectDisplayPaths(meta)) {
110
+ const binding = resolveRelationDisplayBinding(meta, path);
111
+ if (!binding || seen.has(binding.path))
112
+ continue;
113
+ seen.add(binding.path);
114
+ bindings.push(binding);
115
+ }
116
+ return bindings;
117
+ }
118
+ //# sourceMappingURL=record-value.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-value.js","sourceRoot":"","sources":["../src/record-value.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { ResourceMeta } from './types.js';
2
+ import type { Resource } from './resource.js';
3
+ export declare class ResourceRegistry {
4
+ private readonly meta;
5
+ private readonly classes;
6
+ constructor(resourceClasses: Array<typeof Resource>);
7
+ all(): ResourceMeta[];
8
+ get(slug: string): ResourceMeta | undefined;
9
+ require(slug: string): ResourceMeta;
10
+ resourceClass(slug: string): typeof Resource | undefined;
11
+ navigationGroups(): Array<{
12
+ name: string;
13
+ items: ResourceMeta[];
14
+ }>;
15
+ }
16
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAmC;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;gBAElD,eAAe,EAAE,KAAK,CAAC,OAAO,QAAQ,CAAC;IAWnD,GAAG,IAAI,YAAY,EAAE;IAIrB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAI3C,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY;IAMnC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,QAAQ,GAAG,SAAS;IAIxD,gBAAgB,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC;CAkBnE"}
@@ -0,0 +1,49 @@
1
+ export class ResourceRegistry {
2
+ meta = new Map();
3
+ classes = new Map();
4
+ constructor(resourceClasses) {
5
+ for (const ResourceClass of resourceClasses) {
6
+ const configured = ResourceClass.configure();
7
+ if (this.meta.has(configured.slug)) {
8
+ throw new Error(`Duplicate Shamar resource slug: ${configured.slug}`);
9
+ }
10
+ this.meta.set(configured.slug, configured);
11
+ this.classes.set(configured.slug, ResourceClass);
12
+ }
13
+ }
14
+ all() {
15
+ return [...this.meta.values()];
16
+ }
17
+ get(slug) {
18
+ return this.meta.get(slug);
19
+ }
20
+ require(slug) {
21
+ const meta = this.get(slug);
22
+ if (!meta)
23
+ throw new Error(`Unknown Shamar resource: ${slug}`);
24
+ return meta;
25
+ }
26
+ resourceClass(slug) {
27
+ return this.classes.get(slug);
28
+ }
29
+ navigationGroups() {
30
+ const groups = new Map();
31
+ for (const meta of this.all()) {
32
+ const group = meta.navigationGroup ?? 'General';
33
+ const items = groups.get(group) ?? [];
34
+ items.push(meta);
35
+ groups.set(group, items);
36
+ }
37
+ return [...groups.entries()].map(([name, items]) => ({
38
+ name,
39
+ items: items.sort((a, b) => {
40
+ const sortA = a.navigationSort ?? Number.POSITIVE_INFINITY;
41
+ const sortB = b.navigationSort ?? Number.POSITIVE_INFINITY;
42
+ if (sortA !== sortB)
43
+ return sortA - sortB;
44
+ return a.label.localeCompare(b.label);
45
+ }),
46
+ }));
47
+ }
48
+ }
49
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAGA,MAAM,OAAO,gBAAgB;IACV,IAAI,GAAG,IAAI,GAAG,EAAwB,CAAC;IACvC,OAAO,GAAG,IAAI,GAAG,EAA2B,CAAC;IAE9D,YAAY,eAAuC;QACjD,KAAK,MAAM,aAAa,IAAI,eAAe,EAAE,CAAC;YAC5C,MAAM,UAAU,GAAG,aAAa,CAAC,SAAS,EAAE,CAAC;YAC7C,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnC,MAAM,IAAI,KAAK,CAAC,mCAAmC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;YACxE,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;YAC3C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAED,GAAG;QACD,OAAO,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACjC,CAAC;IAED,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED,OAAO,CAAC,IAAY;QAClB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,IAAI,EAAE,CAAC,CAAC;QAC/D,OAAO,IAAI,CAAC;IACd,CAAC;IAED,aAAa,CAAC,IAAY;QACxB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAED,gBAAgB;QACd,MAAM,MAAM,GAAG,IAAI,GAAG,EAA0B,CAAC;QACjD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,IAAI,SAAS,CAAC;YAChD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YACtC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjB,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC3B,CAAC;QACD,OAAO,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;YACnD,IAAI;YACJ,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;gBACzB,MAAM,KAAK,GAAG,CAAC,CAAC,cAAc,IAAI,MAAM,CAAC,iBAAiB,CAAC;gBAC3D,MAAM,KAAK,GAAG,CAAC,CAAC,cAAc,IAAI,MAAM,CAAC,iBAAiB,CAAC;gBAC3D,IAAI,KAAK,KAAK,KAAK;oBAAE,OAAO,KAAK,GAAG,KAAK,CAAC;gBAC1C,OAAO,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YACxC,CAAC,CAAC;SACH,CAAC,CAAC,CAAC;IACN,CAAC;CACF"}
@@ -0,0 +1,28 @@
1
+ import type { FieldConfig, RelationConfig, RelationKind, RelationWidget } from './types.js';
2
+ export interface RelationshipOptions {
3
+ kind?: RelationKind;
4
+ foreignKey?: string;
5
+ widget?: RelationWidget;
6
+ createOption?: boolean;
7
+ createAndEditOption?: boolean;
8
+ preloadLimit?: number;
9
+ groupBy?: string;
10
+ checkboxColumns?: number;
11
+ checkboxFramed?: boolean;
12
+ cascadeWildcards?: boolean;
13
+ /** Dehydrated value attribute (default `id`). Use `name` for ability keys. */
14
+ valueAttribute?: string;
15
+ }
16
+ /** Default widget for each relation kind. */
17
+ export declare function defaultRelationWidget(kind: RelationKind): RelationWidget;
18
+ /**
19
+ * Build a normalized {@link RelationConfig} from Filament-style arguments.
20
+ */
21
+ export declare function buildRelationConfig(resource: string, titleAttribute: string, options?: RelationshipOptions): RelationConfig;
22
+ /**
23
+ * Apply relationship config onto a field, adjusting type/multiple/dehydrated defaults.
24
+ */
25
+ export declare function applyRelationship(config: FieldConfig, resource: string, titleAttribute: string, options?: RelationshipOptions): void;
26
+ /** Title attribute used for labels / quick-create. */
27
+ export declare function relationTitleAttribute(relation: RelationConfig): string;
28
+ //# sourceMappingURL=relation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relation.d.ts","sourceRoot":"","sources":["../src/relation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACd,YAAY,EACZ,cAAc,EACf,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,mBAAmB;IAClC,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,6CAA6C;AAC7C,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,YAAY,GAAG,cAAc,CAUxE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE,mBAAwB,GAChC,cAAc,CAkBhB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE,mBAAwB,GAChC,IAAI,CA0BN;AAED,sDAAsD;AACtD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CAEvE"}
@@ -0,0 +1,69 @@
1
+ /** Default widget for each relation kind. */
2
+ export function defaultRelationWidget(kind) {
3
+ switch (kind) {
4
+ case 'hasMany':
5
+ return 'table';
6
+ case 'manyToMany':
7
+ return 'combobox';
8
+ case 'belongsTo':
9
+ default:
10
+ return 'combobox';
11
+ }
12
+ }
13
+ /**
14
+ * Build a normalized {@link RelationConfig} from Filament-style arguments.
15
+ */
16
+ export function buildRelationConfig(resource, titleAttribute, options = {}) {
17
+ const kind = options.kind ?? 'belongsTo';
18
+ return {
19
+ kind,
20
+ resource,
21
+ labelField: titleAttribute,
22
+ titleAttribute,
23
+ foreignKey: options.foreignKey,
24
+ widget: options.widget ?? defaultRelationWidget(kind),
25
+ createOption: options.createOption,
26
+ createAndEditOption: options.createAndEditOption,
27
+ preloadLimit: options.preloadLimit,
28
+ groupBy: options.groupBy,
29
+ checkboxColumns: options.checkboxColumns,
30
+ checkboxFramed: options.checkboxFramed,
31
+ cascadeWildcards: options.cascadeWildcards,
32
+ valueAttribute: options.valueAttribute,
33
+ };
34
+ }
35
+ /**
36
+ * Apply relationship config onto a field, adjusting type/multiple/dehydrated defaults.
37
+ */
38
+ export function applyRelationship(config, resource, titleAttribute, options = {}) {
39
+ const relation = buildRelationConfig(resource, titleAttribute, options);
40
+ config.relation = relation;
41
+ config.searchable = config.searchable ?? true;
42
+ if (relation.kind === 'manyToMany') {
43
+ config.multiple = true;
44
+ }
45
+ if (relation.kind === 'hasMany') {
46
+ // Children own the FK; parent field is managed via widgets, not dehydrated.
47
+ config.dehydrated = false;
48
+ config.multiple = true;
49
+ }
50
+ if (relation.widget === 'radio') {
51
+ config.type = 'radio';
52
+ }
53
+ else if (relation.widget === 'checkboxList') {
54
+ config.type = 'checkboxList';
55
+ config.multiple = true;
56
+ }
57
+ else if (relation.widget === 'table') {
58
+ config.type = 'relationTable';
59
+ config.multiple = true;
60
+ }
61
+ else if (config.type !== 'radio' && config.type !== 'checkboxList') {
62
+ config.type = 'relation';
63
+ }
64
+ }
65
+ /** Title attribute used for labels / quick-create. */
66
+ export function relationTitleAttribute(relation) {
67
+ return relation.titleAttribute ?? relation.labelField;
68
+ }
69
+ //# sourceMappingURL=relation.js.map