@jini-ai/admin 0.3.0 → 0.3.7
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 +7 -4
- package/dist/core/data-table/types.d.ts +14 -0
- package/dist/core/data-table/types.d.ts.map +1 -0
- package/dist/core/data-table/types.js +8 -0
- package/dist/core/data-table/types.js.map +1 -0
- package/dist/core/entities/rules.d.ts +138 -0
- package/dist/core/entities/rules.d.ts.map +1 -0
- package/dist/core/entities/rules.js +344 -0
- package/dist/core/entities/rules.js.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/ports/entities.d.ts +302 -0
- package/dist/core/ports/entities.d.ts.map +1 -0
- package/dist/core/ports/entities.js +47 -0
- package/dist/core/ports/entities.js.map +1 -0
- package/dist/core/ports/index.d.ts +1 -0
- package/dist/core/ports/index.d.ts.map +1 -1
- package/dist/core/ports/menus.d.ts +43 -0
- package/dist/core/ports/menus.d.ts.map +1 -1
- package/dist/core/ports/menus.js +43 -0
- package/dist/core/ports/menus.js.map +1 -1
- package/dist/react/components/DataTable.d.ts +70 -8
- package/dist/react/components/DataTable.d.ts.map +1 -1
- package/dist/react/components/DataTable.js +58 -2
- package/dist/react/components/DataTable.js.map +1 -1
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.d.ts +1 -1
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.d.ts.map +1 -1
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.js +13 -12
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.js.map +1 -1
- package/dist/react/components/Sidebar.d.ts.map +1 -1
- package/dist/react/components/Sidebar.js +90 -2
- package/dist/react/components/Sidebar.js.map +1 -1
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +4 -3
- package/dist/react/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `AdminEntityPort` — the one port that is generic over the entity it fronts.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this port is shaped differently from every other one here
|
|
5
|
+
*
|
|
6
|
+
* Every other port in this directory names a fixed capability with a fixed row shape:
|
|
7
|
+
* `AdminMembersPort` returns `AdminMember`, `AdminSeoPort` returns `AdminSeoMeta`. That works
|
|
8
|
+
* because the capability is the same everywhere it is wired — a member is a member.
|
|
9
|
+
*
|
|
10
|
+
* An application's own entities are not like that. A recipe app has recipes and authors; a
|
|
11
|
+
* warehouse app has pallets and SKUs. There is no fixed row shape to write down, and force-fitting
|
|
12
|
+
* one onto an existing port is lossy in a way that shows up immediately: mapping an app's `Author`
|
|
13
|
+
* onto `AdminMember` has to invent a `status`, and drops every field (`bio`) the app actually has.
|
|
14
|
+
*
|
|
15
|
+
* So this port carries its own schema. An adapter declares an {@link AdminEntityDescriptor}, and
|
|
16
|
+
* the row type is *derived* from it ({@link AdminEntityRow}) rather than declared twice. A host
|
|
17
|
+
* screen reads the descriptor to decide what to render, which is what makes one screen able to
|
|
18
|
+
* render any entity with no per-entity code.
|
|
19
|
+
*
|
|
20
|
+
* ## What this port deliberately does NOT carry
|
|
21
|
+
*
|
|
22
|
+
* - **No `workspaceId`, and no other host-specific field.** If a field cannot be produced by an app
|
|
23
|
+
* with no database, it does not belong on a generic row. Multi-tenancy is the adapter's business,
|
|
24
|
+
* held in its own closure, invisible here. This is the single rule that keeps an adapter for a
|
|
25
|
+
* plain in-memory array as short as an adapter for a real backend.
|
|
26
|
+
* - **No response envelope.** `list` returns {@link AdminEntityPage}, `get` returns the row itself
|
|
27
|
+
* — never `{ entity }` or `{ items }`-plus-metadata. {@link AdminEntityPage} is not a
|
|
28
|
+
* counterexample: `nextCursor` is pagination state that has nowhere else to live, not a wrapper
|
|
29
|
+
* around the payload.
|
|
30
|
+
* - **No `expectedVersion` on `update`.** Every other write in this package carries one. It is
|
|
31
|
+
* omitted here because an in-memory store has no version column, and requiring one would make the
|
|
32
|
+
* smallest honest adapter impossible to write. An entity that wants optimistic concurrency
|
|
33
|
+
* declares a `version` field of kind `"integer"` and enforces it inside its own `update`. This is
|
|
34
|
+
* a real weakening relative to the other ports, stated rather than hidden, and worth revisiting
|
|
35
|
+
* the first time a genuinely multi-writer entity appears.
|
|
36
|
+
* - **No publication lifecycle.** `AdminEntry`-style `draft`/`published` belongs to a CMS entry, not
|
|
37
|
+
* to an arbitrary entity. An app that wants one declares a `status` field of kind `"text"`.
|
|
38
|
+
* - **No per-operation authorization seam** (no `canCreate`/`canDelete`). Consistent with every
|
|
39
|
+
* other port here, and with `AdminPanel.permissions` being affordance-only — but worth naming,
|
|
40
|
+
* because a generic CRUD surface over arbitrary application data is where that gap starts to
|
|
41
|
+
* matter more than it does on a fixed screen.
|
|
42
|
+
*
|
|
43
|
+
* Types only, like every file in this directory. The runtime half — registration, descriptor
|
|
44
|
+
* validation, and the erasure a generic screen consumes — lives in `../entities/rules.ts`.
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* The closed set of field kinds an entity may declare.
|
|
48
|
+
*
|
|
49
|
+
* **Closed deliberately**, unlike the reference-implementation-derived unions elsewhere in this
|
|
50
|
+
* directory (see `README.md`'s `T | (string & {})` idiom). A kind is not a label: each member here
|
|
51
|
+
* is a promise that a rendering layer knows how to display and edit it, and that a persistence
|
|
52
|
+
* layer knows its storage class. A host cannot add a seventh kind by widening a string — it would
|
|
53
|
+
* render as nothing. Adding a kind is a change to this package.
|
|
54
|
+
*
|
|
55
|
+
* `"media"` and `"enum"` are deliberately absent. `"media"` is the same shape as `"relation"` at
|
|
56
|
+
* rest (a foreign id stored as text) and differs only in needing a media *picker*, which needs
|
|
57
|
+
* `AdminMediaPort` wired; adding it before that picker exists would ship a kind indistinguishable
|
|
58
|
+
* from `"text"`. `"enum"` has a real unresolved question (does a queryable enum index the label or
|
|
59
|
+
* the ordinal?) and no forcing case.
|
|
60
|
+
*/
|
|
61
|
+
export type AdminEntityFieldKind = 'text' | 'integer' | 'real' | 'boolean' | 'datetime' | 'relation' | 'json';
|
|
62
|
+
/** Fields common to every kind. `relation` adds `target`; `json` narrows `queryable`. */
|
|
63
|
+
interface AdminEntityFieldBase {
|
|
64
|
+
/**
|
|
65
|
+
* The key this field occupies on a row.
|
|
66
|
+
*
|
|
67
|
+
* **No identifier grammar is imposed here, and that is deliberate.** The identifier grammar that
|
|
68
|
+
* gates a CMS content-type key (`^[a-z][a-z0-9_]{0,63}$`) is load-bearing where a field name
|
|
69
|
+
* reaches SQL DDL — it does not reach it here. Applying it to this descriptor would reject
|
|
70
|
+
* `authorId` and `createdAt`, i.e. would require an application to rename its own fields in order
|
|
71
|
+
* to get an admin. An app that is *also* persisted through the CMS maps names in its adapter,
|
|
72
|
+
* where the mapping is visible, rather than in its descriptor.
|
|
73
|
+
*
|
|
74
|
+
* `"id"` is reserved: every row already has one (see {@link AdminEntityRow}).
|
|
75
|
+
*/
|
|
76
|
+
readonly name: string;
|
|
77
|
+
/** Human-readable column/label text. Falls back to {@link AdminEntityFieldBase.name}. */
|
|
78
|
+
readonly label?: string;
|
|
79
|
+
/** When true, the row type makes this field non-optional — see {@link AdminEntityRow}. */
|
|
80
|
+
readonly required?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Whether a backend can filter or order on this field without a full scan.
|
|
83
|
+
*
|
|
84
|
+
* **Currently advisory in both directions, and nothing enforces either half.** A screen is
|
|
85
|
+
* expected to offer filter/sort controls only for `queryable` fields, and an adapter is expected
|
|
86
|
+
* to reject a filter on a non-queryable one rather than silently scanning — but this is a
|
|
87
|
+
* type-only port, so both are obligations rather than guarantees. An in-memory adapter (every
|
|
88
|
+
* app's first one) filters anything happily, so a filter that is a full scan in production looks
|
|
89
|
+
* correct in development. Read this flag as documentation of intent, not as a capability.
|
|
90
|
+
*/
|
|
91
|
+
readonly queryable?: boolean;
|
|
92
|
+
}
|
|
93
|
+
/** A value stored inline: text, a number, a flag, or an ISO-8601 timestamp. */
|
|
94
|
+
export type AdminEntityScalarField = AdminEntityFieldBase & {
|
|
95
|
+
readonly kind: 'text' | 'integer' | 'real' | 'boolean' | 'datetime';
|
|
96
|
+
};
|
|
97
|
+
/** A foreign id pointing at another registered entity. */
|
|
98
|
+
export type AdminEntityRelationField = AdminEntityFieldBase & {
|
|
99
|
+
readonly kind: 'relation';
|
|
100
|
+
/**
|
|
101
|
+
* The {@link AdminEntityDescriptor.name} this field points at. Checked against the registry at
|
|
102
|
+
* composition time (`createAdminEntityRegistry`), not by the type system — a descriptor does not
|
|
103
|
+
* know which other descriptors it will be registered beside.
|
|
104
|
+
*
|
|
105
|
+
* **No referential integrity is implied anywhere.** Nothing validates that the stored id names a
|
|
106
|
+
* live row: not this port, not a rendering layer, not a persistence layer. A deleted target
|
|
107
|
+
* leaves a dangling reference, and a screen's honest rendering of that is the raw id, visibly
|
|
108
|
+
* marked as unresolved — not a blank cell.
|
|
109
|
+
*/
|
|
110
|
+
readonly target: string;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* An arbitrary JSON value stored verbatim.
|
|
114
|
+
*
|
|
115
|
+
* Exists because no scalar kind can hold a list or a nested object, and an application's own
|
|
116
|
+
* entities routinely have one (a recipe's ingredients). Storage-only: never indexed, never
|
|
117
|
+
* filtered, never ordered — which is why `queryable` is typed `false | undefined` here, making
|
|
118
|
+
* `queryable: true` on a json field a compile error rather than a runtime rejection.
|
|
119
|
+
*/
|
|
120
|
+
export type AdminEntityJsonField = Omit<AdminEntityFieldBase, 'queryable'> & {
|
|
121
|
+
readonly kind: 'json';
|
|
122
|
+
readonly queryable?: false;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* One declared field. A discriminated union rather than a flat interface with optional extras, so
|
|
126
|
+
* `relation` can *require* its `target` instead of documenting that it needs one.
|
|
127
|
+
*/
|
|
128
|
+
export type AdminEntityField = AdminEntityScalarField | AdminEntityRelationField | AdminEntityJsonField;
|
|
129
|
+
/** What an entity is: a name, labels, a title field, and its fields. */
|
|
130
|
+
export interface AdminEntityDescriptor {
|
|
131
|
+
/**
|
|
132
|
+
* Stable machine name — the URL segment a generic screen routes on, and the value a
|
|
133
|
+
* {@link AdminEntityRelationField.target} names. Must equal the key this descriptor's port is
|
|
134
|
+
* registered under (enforced by `createAdminEntityRegistry`), so there is exactly one name for
|
|
135
|
+
* an entity rather than a registry key and a descriptor name that can drift apart.
|
|
136
|
+
*/
|
|
137
|
+
readonly name: string;
|
|
138
|
+
readonly labelSingular: string;
|
|
139
|
+
readonly labelPlural: string;
|
|
140
|
+
/**
|
|
141
|
+
* Which field renders as a row's human-readable title — in lists, and in every relation picker
|
|
142
|
+
* that points at this entity.
|
|
143
|
+
*
|
|
144
|
+
* **Cannot be constrained to "a `text` field on this same descriptor" by the type system** without
|
|
145
|
+
* a conditional type heavy enough to make every descriptor's errors unreadable, so it is checked
|
|
146
|
+
* at registration instead. A typo here is not a small bug: it makes every relation picker
|
|
147
|
+
* pointing at this entity show `undefined` for every row.
|
|
148
|
+
*/
|
|
149
|
+
readonly titleField: string;
|
|
150
|
+
readonly fields: readonly AdminEntityField[];
|
|
151
|
+
}
|
|
152
|
+
/** The runtime type each kind's value takes on a row. */
|
|
153
|
+
interface AdminEntityKindValue {
|
|
154
|
+
text: string;
|
|
155
|
+
datetime: string;
|
|
156
|
+
integer: number;
|
|
157
|
+
real: number;
|
|
158
|
+
boolean: boolean;
|
|
159
|
+
/** A foreign id — same storage class as `text`. */
|
|
160
|
+
relation: string;
|
|
161
|
+
/** Storage-only, so nothing may consume it without narrowing first. */
|
|
162
|
+
json: unknown;
|
|
163
|
+
}
|
|
164
|
+
type AdminEntityFieldValue<F extends AdminEntityField> = F extends {
|
|
165
|
+
kind: infer K extends AdminEntityFieldKind;
|
|
166
|
+
} ? K extends keyof AdminEntityKindValue ? AdminEntityKindValue[K] : never : never;
|
|
167
|
+
type AdminEntityRequiredNames<F extends AdminEntityField> = F extends {
|
|
168
|
+
required: true;
|
|
169
|
+
} ? F['name'] : never;
|
|
170
|
+
/**
|
|
171
|
+
* The row shape a descriptor describes — derived, never hand-written.
|
|
172
|
+
*
|
|
173
|
+
* Requires the descriptor to be declared `as const satisfies AdminEntityDescriptor`: without
|
|
174
|
+
* `as const` the field names widen to `string` and every key collapses. Fields declared
|
|
175
|
+
* `required: true` are non-optional; everything else is optional. `id` is always present and is
|
|
176
|
+
* never a declared field.
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* const recipe = {
|
|
180
|
+
* name: 'recipe', labelSingular: 'Recipe', labelPlural: 'Recipes', titleField: 'title',
|
|
181
|
+
* fields: [
|
|
182
|
+
* { name: 'title', kind: 'text', required: true },
|
|
183
|
+
* { name: 'authorId', kind: 'relation', target: 'author', required: true },
|
|
184
|
+
* { name: 'ingredients', kind: 'json' },
|
|
185
|
+
* ],
|
|
186
|
+
* } as const satisfies AdminEntityDescriptor;
|
|
187
|
+
* // AdminEntityRow<typeof recipe> is
|
|
188
|
+
* // { id: string; title: string; authorId: string; ingredients?: unknown }
|
|
189
|
+
*/
|
|
190
|
+
export type AdminEntityRow<D extends AdminEntityDescriptor> = {
|
|
191
|
+
readonly id: string;
|
|
192
|
+
} & {
|
|
193
|
+
readonly [F in D['fields'][number] as F['name'] & AdminEntityRequiredNames<F>]: AdminEntityFieldValue<F>;
|
|
194
|
+
} & {
|
|
195
|
+
readonly [F in D['fields'][number] as F['name'] & Exclude<F['name'], AdminEntityRequiredNames<F>>]?: AdminEntityFieldValue<F>;
|
|
196
|
+
};
|
|
197
|
+
/** One filter clause. `field` should name a `queryable` field — see that flag's own caveat. */
|
|
198
|
+
export interface AdminEntityFilter {
|
|
199
|
+
readonly field: string;
|
|
200
|
+
readonly op: 'eq' | 'neq' | 'lt' | 'lte' | 'gt' | 'gte';
|
|
201
|
+
readonly value: string | number | boolean;
|
|
202
|
+
}
|
|
203
|
+
export interface AdminEntityListQuery {
|
|
204
|
+
/** Maximum rows in the returned page. An adapter may return fewer; it must not return more. */
|
|
205
|
+
readonly limit?: number;
|
|
206
|
+
/**
|
|
207
|
+
* Opaque forward cursor, taken verbatim from a prior page's {@link AdminEntityPage.nextCursor}.
|
|
208
|
+
*
|
|
209
|
+
* **Not an offset.** An offset cannot be made stable under concurrent writes — a row inserted
|
|
210
|
+
* before the current position shifts every later page by one, silently duplicating or skipping a
|
|
211
|
+
* row. Every backend this port is likely to front can express keyset pagination over
|
|
212
|
+
* `(sortField, id)` instead.
|
|
213
|
+
*
|
|
214
|
+
* **Forward-only, by decision rather than by accident.** There is no `prevCursor`, because a
|
|
215
|
+
* backwards keyset walk needs a reversed comparator this port does not express. A screen that
|
|
216
|
+
* wants a "Previous" control holds the cursors it has already visited itself; that history is
|
|
217
|
+
* screen state and does not survive a reload.
|
|
218
|
+
*/
|
|
219
|
+
readonly cursor?: string;
|
|
220
|
+
readonly filter?: readonly AdminEntityFilter[];
|
|
221
|
+
/**
|
|
222
|
+
* Ordering, applied by the adapter over the whole collection *before* the page is cut.
|
|
223
|
+
*
|
|
224
|
+
* Ordering is a server concern and this is the only place it may be expressed. Sorting a page
|
|
225
|
+
* after it has been cut reorders the rows on screen and nothing else — correct-looking at one
|
|
226
|
+
* page, meaningless at two, and no value-comparison test detects the difference.
|
|
227
|
+
*/
|
|
228
|
+
readonly sort?: {
|
|
229
|
+
readonly field: string;
|
|
230
|
+
readonly direction: 'asc' | 'desc';
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
export interface AdminEntityPage<TRow> {
|
|
234
|
+
readonly items: readonly TRow[];
|
|
235
|
+
/**
|
|
236
|
+
* Cursor for the next page, or `null` on the last one.
|
|
237
|
+
*
|
|
238
|
+
* Required rather than optional — matching `AdminFormSubmissionPage`, this package's other
|
|
239
|
+
* cursor-paginated shape — because forgetting it silently caps every list at one page, which
|
|
240
|
+
* looks entirely correct until a collection outgrows one. `null` is a decision an adapter has to
|
|
241
|
+
* type out.
|
|
242
|
+
*/
|
|
243
|
+
readonly nextCursor: string | null;
|
|
244
|
+
/**
|
|
245
|
+
* Total rows matching the query, when the adapter can count cheaply.
|
|
246
|
+
*
|
|
247
|
+
* Genuinely optional, unlike `nextCursor`: omitting it degrades one label, and forcing a count
|
|
248
|
+
* query on every list read to satisfy the type would be worse than not having the label.
|
|
249
|
+
*/
|
|
250
|
+
readonly total?: number;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* CRUD over one application-defined entity.
|
|
254
|
+
*
|
|
255
|
+
* `D` is the descriptor *value's* type (`typeof recipeDescriptor`), not `AdminEntityDescriptor` —
|
|
256
|
+
* that is what lets `list`/`get`/`create` return rows typed from the declaration.
|
|
257
|
+
*
|
|
258
|
+
* Note that `AdminEntityPort<SomeSpecificDescriptor>` is **not** assignable to
|
|
259
|
+
* `AdminEntityPort<AdminEntityDescriptor>`: `create`/`update` take the row type as a parameter and
|
|
260
|
+
* are therefore contravariant in it. A screen generic over all entities consumes
|
|
261
|
+
* {@link AdminErasedEntityPort} instead, reached through `eraseEntityPort`.
|
|
262
|
+
*/
|
|
263
|
+
export interface AdminEntityPort<D extends AdminEntityDescriptor> {
|
|
264
|
+
readonly descriptor: D;
|
|
265
|
+
list(query?: AdminEntityListQuery): Promise<AdminEntityPage<AdminEntityRow<D>>>;
|
|
266
|
+
get(id: string): Promise<AdminEntityRow<D> | null>;
|
|
267
|
+
create(input: Omit<AdminEntityRow<D>, 'id'>): Promise<AdminEntityRow<D>>;
|
|
268
|
+
update(id: string, patch: Partial<Omit<AdminEntityRow<D>, 'id'>>): Promise<AdminEntityRow<D>>;
|
|
269
|
+
/**
|
|
270
|
+
* Optional. An entity that cannot be deleted omits it, and a generic screen renders no delete
|
|
271
|
+
* affordance — the same "absence means the capability does not exist" convention
|
|
272
|
+
* `AdminMembersPort` uses for its deliberately missing `enableMember`.
|
|
273
|
+
*/
|
|
274
|
+
remove?(id: string): Promise<void>;
|
|
275
|
+
}
|
|
276
|
+
/** A row with its field types erased, as a screen generic over all entities sees it. */
|
|
277
|
+
export type AdminEntityRowData = Record<string, unknown> & {
|
|
278
|
+
readonly id: string;
|
|
279
|
+
};
|
|
280
|
+
/**
|
|
281
|
+
* {@link AdminEntityPort} with its descriptor generic erased — what a screen that renders *any*
|
|
282
|
+
* entity actually holds. Produced only by `eraseEntityPort`; never implemented directly.
|
|
283
|
+
*/
|
|
284
|
+
export interface AdminErasedEntityPort {
|
|
285
|
+
readonly descriptor: AdminEntityDescriptor;
|
|
286
|
+
list(query?: AdminEntityListQuery): Promise<AdminEntityPage<AdminEntityRowData>>;
|
|
287
|
+
get(id: string): Promise<AdminEntityRowData | null>;
|
|
288
|
+
create(input: Record<string, unknown>): Promise<AdminEntityRowData>;
|
|
289
|
+
update(id: string, patch: Record<string, unknown>): Promise<AdminEntityRowData>;
|
|
290
|
+
remove?(id: string): Promise<void>;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* The assembled registry: entity name -> port, with each port's own type preserved.
|
|
294
|
+
*
|
|
295
|
+
* Same inference shape as `AdminClient` — a host's entities and any Jini-shipped ones would be
|
|
296
|
+
* indistinguishable here, and registering one is a line in an object literal.
|
|
297
|
+
*/
|
|
298
|
+
export type AdminEntityRegistry<TPorts extends Record<string, AdminEntityPort<AdminEntityDescriptor>>> = {
|
|
299
|
+
readonly [K in keyof TPorts]: TPorts[K];
|
|
300
|
+
};
|
|
301
|
+
export {};
|
|
302
|
+
//# sourceMappingURL=entities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entities.d.ts","sourceRoot":"","sources":["../../../src/core/ports/entities.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `AdminEntityPort` — the one port that is generic over the entity it fronts.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this port is shaped differently from every other one here
|
|
5
|
+
*
|
|
6
|
+
* Every other port in this directory names a fixed capability with a fixed row shape:
|
|
7
|
+
* `AdminMembersPort` returns `AdminMember`, `AdminSeoPort` returns `AdminSeoMeta`. That works
|
|
8
|
+
* because the capability is the same everywhere it is wired — a member is a member.
|
|
9
|
+
*
|
|
10
|
+
* An application's own entities are not like that. A recipe app has recipes and authors; a
|
|
11
|
+
* warehouse app has pallets and SKUs. There is no fixed row shape to write down, and force-fitting
|
|
12
|
+
* one onto an existing port is lossy in a way that shows up immediately: mapping an app's `Author`
|
|
13
|
+
* onto `AdminMember` has to invent a `status`, and drops every field (`bio`) the app actually has.
|
|
14
|
+
*
|
|
15
|
+
* So this port carries its own schema. An adapter declares an {@link AdminEntityDescriptor}, and
|
|
16
|
+
* the row type is *derived* from it ({@link AdminEntityRow}) rather than declared twice. A host
|
|
17
|
+
* screen reads the descriptor to decide what to render, which is what makes one screen able to
|
|
18
|
+
* render any entity with no per-entity code.
|
|
19
|
+
*
|
|
20
|
+
* ## What this port deliberately does NOT carry
|
|
21
|
+
*
|
|
22
|
+
* - **No `workspaceId`, and no other host-specific field.** If a field cannot be produced by an app
|
|
23
|
+
* with no database, it does not belong on a generic row. Multi-tenancy is the adapter's business,
|
|
24
|
+
* held in its own closure, invisible here. This is the single rule that keeps an adapter for a
|
|
25
|
+
* plain in-memory array as short as an adapter for a real backend.
|
|
26
|
+
* - **No response envelope.** `list` returns {@link AdminEntityPage}, `get` returns the row itself
|
|
27
|
+
* — never `{ entity }` or `{ items }`-plus-metadata. {@link AdminEntityPage} is not a
|
|
28
|
+
* counterexample: `nextCursor` is pagination state that has nowhere else to live, not a wrapper
|
|
29
|
+
* around the payload.
|
|
30
|
+
* - **No `expectedVersion` on `update`.** Every other write in this package carries one. It is
|
|
31
|
+
* omitted here because an in-memory store has no version column, and requiring one would make the
|
|
32
|
+
* smallest honest adapter impossible to write. An entity that wants optimistic concurrency
|
|
33
|
+
* declares a `version` field of kind `"integer"` and enforces it inside its own `update`. This is
|
|
34
|
+
* a real weakening relative to the other ports, stated rather than hidden, and worth revisiting
|
|
35
|
+
* the first time a genuinely multi-writer entity appears.
|
|
36
|
+
* - **No publication lifecycle.** `AdminEntry`-style `draft`/`published` belongs to a CMS entry, not
|
|
37
|
+
* to an arbitrary entity. An app that wants one declares a `status` field of kind `"text"`.
|
|
38
|
+
* - **No per-operation authorization seam** (no `canCreate`/`canDelete`). Consistent with every
|
|
39
|
+
* other port here, and with `AdminPanel.permissions` being affordance-only — but worth naming,
|
|
40
|
+
* because a generic CRUD surface over arbitrary application data is where that gap starts to
|
|
41
|
+
* matter more than it does on a fixed screen.
|
|
42
|
+
*
|
|
43
|
+
* Types only, like every file in this directory. The runtime half — registration, descriptor
|
|
44
|
+
* validation, and the erasure a generic screen consumes — lives in `../entities/rules.ts`.
|
|
45
|
+
*/
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=entities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entities.js","sourceRoot":"","sources":["../../../src/core/ports/entities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG"}
|
|
@@ -18,4 +18,5 @@ export type { AdminSeoAnalysis, AdminSeoIssue, AdminSeoMeta, AdminSeoOpenGraph,
|
|
|
18
18
|
export type { AdminRedirect, AdminRedirectCreateInput, AdminRedirectHitStats, AdminRedirectImportFailure, AdminRedirectImportResult, AdminRedirectListFilter, AdminRedirectsPort, AdminRedirectUpdatePatch, RedirectMatchType, RedirectSource, RedirectStatus, RedirectStatusCode, } from './redirects.js';
|
|
19
19
|
export type { AdminAssignMenuLocationResult, AdminDeleteMenuResult, AdminMenu, AdminMenuBinding, AdminMenuCreateInput, AdminMenuCustomTarget, AdminMenuEntryTarget, AdminMenuItem, AdminMenuItemAttrs, AdminMenuRouteTarget, AdminMenusPort, AdminMenuTarget, AdminMenuTermTarget, AdminMenuUpdateTreeInput, AdminMenuUrlTarget, MenuStatus, NavTargetKind, } from './menus.js';
|
|
20
20
|
export type { AdminFormCreateInput, AdminFormDefinition, AdminFormField, AdminFormNotifyConfig, AdminFormsPort, AdminFormSubmission, AdminFormSubmissionPage, AdminFormUpdatePatch, FormDefinitionStatus, FormFieldType, } from './forms.js';
|
|
21
|
+
export type { AdminEntityDescriptor, AdminEntityField, AdminEntityFieldKind, AdminEntityFilter, AdminEntityJsonField, AdminEntityListQuery, AdminEntityPage, AdminEntityPort, AdminEntityRegistry, AdminEntityRelationField, AdminEntityRow, AdminEntityRowData, AdminEntityScalarField, AdminErasedEntityPort, } from './entities.js';
|
|
21
22
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/ports/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC9D,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAClE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC7D,YAAY,EACV,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,GACrB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,mBAAmB,EACnB,qBAAqB,EACrB,2BAA2B,EAC3B,iBAAiB,EACjB,mBAAmB,EACnB,aAAa,EACb,uBAAuB,EACvB,wBAAwB,EACxB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,wBAAwB,EACxB,+BAA+B,EAC/B,4BAA4B,EAC5B,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,EACvB,gBAAgB,EAChB,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAClG,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,SAAS,GACV,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,uBAAuB,EACvB,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,UAAU,CAAC;AAElB,YAAY,EACV,aAAa,EACb,wBAAwB,EACxB,qBAAqB,EACrB,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACvB,kBAAkB,EAClB,wBAAwB,EACxB,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AAExB,YAAY,EACV,6BAA6B,EAC7B,qBAAqB,EACrB,SAAS,EACT,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,EACpB,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,wBAAwB,EACxB,kBAAkB,EAClB,UAAU,EACV,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,cAAc,EACd,mBAAmB,EACnB,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,GACd,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/ports/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC9D,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAClE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC7D,YAAY,EACV,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,YAAY,EACZ,oBAAoB,GACrB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,iBAAiB,EACjB,cAAc,EACd,iBAAiB,EACjB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,mBAAmB,EACnB,qBAAqB,EACrB,2BAA2B,EAC3B,iBAAiB,EACjB,mBAAmB,EACnB,aAAa,EACb,uBAAuB,EACvB,wBAAwB,EACxB,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,wBAAwB,EACxB,+BAA+B,EAC/B,4BAA4B,EAC5B,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,EACvB,gBAAgB,EAChB,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,2BAA2B,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAClG,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzE,YAAY,EACV,iBAAiB,EACjB,iBAAiB,EACjB,WAAW,EACX,SAAS,GACV,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,uBAAuB,EACvB,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,UAAU,CAAC;AAElB,YAAY,EACV,aAAa,EACb,wBAAwB,EACxB,qBAAqB,EACrB,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACvB,kBAAkB,EAClB,wBAAwB,EACxB,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AAExB,YAAY,EACV,6BAA6B,EAC7B,qBAAqB,EACrB,SAAS,EACT,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,EACpB,aAAa,EACb,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,wBAAwB,EACxB,kBAAkB,EAClB,UAAU,EACV,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,cAAc,EACd,mBAAmB,EACnB,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,qBAAqB,EACrB,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,wBAAwB,EACxB,cAAc,EACd,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,eAAe,CAAC"}
|
|
@@ -25,6 +25,20 @@
|
|
|
25
25
|
* `kind` values (with `AdminMenuCustomTarget` as the `default` arm) is the straightforward way to
|
|
26
26
|
* stay correct.
|
|
27
27
|
*
|
|
28
|
+
* **Migration hazard, measured 2026-09-05.** This is not merely a style preference — it means an
|
|
29
|
+
* exhaustive `switch` with NO `default` arm cannot compile against `AdminMenuTarget` at all, and
|
|
30
|
+
* that is exactly the shape a host arrives with. Both existing admin apps' `targetForKind`
|
|
31
|
+
* (`apps/admin/src/features/menus/MenuEditor.tsx`) switch over their own local CLOSED four-member
|
|
32
|
+
* union with no `default`, and rely on exhaustiveness for the return type. Compiling that function
|
|
33
|
+
* against this port's union emits `TS2366` (lacks ending return statement) plus a `TS2339` per
|
|
34
|
+
* field access. Confirmed by compiling the real function against this union directly.
|
|
35
|
+
*
|
|
36
|
+
* The hazard is pre-existing and belongs to `AdminMenuCustomTarget`, not to any later change: the
|
|
37
|
+
* identical probe run against the union with and without an extra member emits a character-for-
|
|
38
|
+
* character identical error set. Recorded here because it will surface the first time anyone wires
|
|
39
|
+
* a host onto this port, and the natural instinct will be to blame whatever change is in flight at
|
|
40
|
+
* that moment. The fix is a `default` arm in the host, not a change to this file.
|
|
41
|
+
*
|
|
28
42
|
* **This is a deliberately deferred decision, not a settled one — record it as such.** `entryRef`/
|
|
29
43
|
* `termRef` encode a host content model (an "entry" and a "taxonomy term" are the reference
|
|
30
44
|
* implementation's own vocabulary for its content system, not a vendor-neutral abstraction this
|
|
@@ -39,6 +53,35 @@
|
|
|
39
53
|
* instead of one, and a genuine vendor-neutral reference shape (rather than another bespoke
|
|
40
54
|
* `kind`-per-host member) becomes the right call.
|
|
41
55
|
*
|
|
56
|
+
* ### Status of that trigger, re-checked 2026-09-05 — NOT fired
|
|
57
|
+
*
|
|
58
|
+
* A second host now exists in this workspace, and that is NOT the trigger. The trigger is a second
|
|
59
|
+
* host needing a fifth KIND. Recording the difference here because a promotion was very nearly
|
|
60
|
+
* approved on the weaker reading. Four findings, all verified against source rather than assumed:
|
|
61
|
+
*
|
|
62
|
+
* 1. **Zero importers.** Nothing anywhere imports `AdminMenuTarget`, `NavTargetKind`, or any target
|
|
63
|
+
* variant from this package. In this repo they are re-exported by `core/index.ts` and
|
|
64
|
+
* `core/ports/index.ts` and consumed by nothing.
|
|
65
|
+
* 2. **Both admin apps declare their own.** Each ships a local, *closed*
|
|
66
|
+
* `{ kind: "entryRef" | "termRef" | "url" | "route" }` in its own `src/lib/api.ts` — an HTTP
|
|
67
|
+
* DTO, not this port. The two are byte-identical to each other: the second is a fork of the
|
|
68
|
+
* first, so they are one data point, not two.
|
|
69
|
+
* 3. **The other `NavTargetKind` is a different type.** `@jini-ai/cms/navigation` exports its own
|
|
70
|
+
* `NavTargetKind` (closed, plus a separate `ReservedNavTargetKind`). A host importing that one
|
|
71
|
+
* is not exercising this port.
|
|
72
|
+
* 4. **The union is already open.** `AdminMenuCustomTarget`'s `kind: string & {}` already lets any
|
|
73
|
+
* host express any kind today. Nothing is blocked while the decision stays deferred.
|
|
74
|
+
*
|
|
75
|
+
* So no fifth kind has been needed, and adding a named generic variant now would be designing from
|
|
76
|
+
* zero validating examples — the exact failure the paragraph above exists to prevent. Considered
|
|
77
|
+
* and rejected on 2026-09-05 for that reason. Re-check this section, not the prose above it,
|
|
78
|
+
* before concluding the trigger has fired.
|
|
79
|
+
*
|
|
80
|
+
* Note for whoever revisits: wiring `AdminMenusPort` into a host is the experiment that ANSWERS the
|
|
81
|
+
* type question, and it is not blocked by it. A host that wires the port and finds
|
|
82
|
+
* `entryRef`/`termRef` fit vindicates the deferral; one that needs a product/collection kind fires
|
|
83
|
+
* the trigger with a real second example to design from.
|
|
84
|
+
*
|
|
42
85
|
* ## `deleteMenu` is a two-rung ladder driven by call count, not by `force` alone
|
|
43
86
|
*
|
|
44
87
|
* A single method models a two-step deletion flow: the FIRST call against a non-trashed menu
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menus.d.ts","sourceRoot":"","sources":["../../../src/core/ports/menus.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"menus.d.ts","sourceRoot":"","sources":["../../../src/core/ports/menus.ts"],"names":[],"mappings":"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"}
|
package/dist/core/ports/menus.js
CHANGED
|
@@ -25,6 +25,20 @@
|
|
|
25
25
|
* `kind` values (with `AdminMenuCustomTarget` as the `default` arm) is the straightforward way to
|
|
26
26
|
* stay correct.
|
|
27
27
|
*
|
|
28
|
+
* **Migration hazard, measured 2026-09-05.** This is not merely a style preference — it means an
|
|
29
|
+
* exhaustive `switch` with NO `default` arm cannot compile against `AdminMenuTarget` at all, and
|
|
30
|
+
* that is exactly the shape a host arrives with. Both existing admin apps' `targetForKind`
|
|
31
|
+
* (`apps/admin/src/features/menus/MenuEditor.tsx`) switch over their own local CLOSED four-member
|
|
32
|
+
* union with no `default`, and rely on exhaustiveness for the return type. Compiling that function
|
|
33
|
+
* against this port's union emits `TS2366` (lacks ending return statement) plus a `TS2339` per
|
|
34
|
+
* field access. Confirmed by compiling the real function against this union directly.
|
|
35
|
+
*
|
|
36
|
+
* The hazard is pre-existing and belongs to `AdminMenuCustomTarget`, not to any later change: the
|
|
37
|
+
* identical probe run against the union with and without an extra member emits a character-for-
|
|
38
|
+
* character identical error set. Recorded here because it will surface the first time anyone wires
|
|
39
|
+
* a host onto this port, and the natural instinct will be to blame whatever change is in flight at
|
|
40
|
+
* that moment. The fix is a `default` arm in the host, not a change to this file.
|
|
41
|
+
*
|
|
28
42
|
* **This is a deliberately deferred decision, not a settled one — record it as such.** `entryRef`/
|
|
29
43
|
* `termRef` encode a host content model (an "entry" and a "taxonomy term" are the reference
|
|
30
44
|
* implementation's own vocabulary for its content system, not a vendor-neutral abstraction this
|
|
@@ -39,6 +53,35 @@
|
|
|
39
53
|
* instead of one, and a genuine vendor-neutral reference shape (rather than another bespoke
|
|
40
54
|
* `kind`-per-host member) becomes the right call.
|
|
41
55
|
*
|
|
56
|
+
* ### Status of that trigger, re-checked 2026-09-05 — NOT fired
|
|
57
|
+
*
|
|
58
|
+
* A second host now exists in this workspace, and that is NOT the trigger. The trigger is a second
|
|
59
|
+
* host needing a fifth KIND. Recording the difference here because a promotion was very nearly
|
|
60
|
+
* approved on the weaker reading. Four findings, all verified against source rather than assumed:
|
|
61
|
+
*
|
|
62
|
+
* 1. **Zero importers.** Nothing anywhere imports `AdminMenuTarget`, `NavTargetKind`, or any target
|
|
63
|
+
* variant from this package. In this repo they are re-exported by `core/index.ts` and
|
|
64
|
+
* `core/ports/index.ts` and consumed by nothing.
|
|
65
|
+
* 2. **Both admin apps declare their own.** Each ships a local, *closed*
|
|
66
|
+
* `{ kind: "entryRef" | "termRef" | "url" | "route" }` in its own `src/lib/api.ts` — an HTTP
|
|
67
|
+
* DTO, not this port. The two are byte-identical to each other: the second is a fork of the
|
|
68
|
+
* first, so they are one data point, not two.
|
|
69
|
+
* 3. **The other `NavTargetKind` is a different type.** `@jini-ai/cms/navigation` exports its own
|
|
70
|
+
* `NavTargetKind` (closed, plus a separate `ReservedNavTargetKind`). A host importing that one
|
|
71
|
+
* is not exercising this port.
|
|
72
|
+
* 4. **The union is already open.** `AdminMenuCustomTarget`'s `kind: string & {}` already lets any
|
|
73
|
+
* host express any kind today. Nothing is blocked while the decision stays deferred.
|
|
74
|
+
*
|
|
75
|
+
* So no fifth kind has been needed, and adding a named generic variant now would be designing from
|
|
76
|
+
* zero validating examples — the exact failure the paragraph above exists to prevent. Considered
|
|
77
|
+
* and rejected on 2026-09-05 for that reason. Re-check this section, not the prose above it,
|
|
78
|
+
* before concluding the trigger has fired.
|
|
79
|
+
*
|
|
80
|
+
* Note for whoever revisits: wiring `AdminMenusPort` into a host is the experiment that ANSWERS the
|
|
81
|
+
* type question, and it is not blocked by it. A host that wires the port and finds
|
|
82
|
+
* `entryRef`/`termRef` fit vindicates the deferral; one that needs a product/collection kind fires
|
|
83
|
+
* the trigger with a real second example to design from.
|
|
84
|
+
*
|
|
42
85
|
* ## `deleteMenu` is a two-rung ladder driven by call count, not by `force` alone
|
|
43
86
|
*
|
|
44
87
|
* A single method models a two-step deletion flow: the FIRST call against a non-trashed menu
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menus.js","sourceRoot":"","sources":["../../../src/core/ports/menus.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"menus.js","sourceRoot":"","sources":["../../../src/core/ports/menus.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8GG"}
|