@proteinjs/db 1.46.0 → 1.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proteinjs/db",
3
- "version": "1.46.0",
3
+ "version": "1.47.0",
4
4
  "main": "./dist/generated/index.js",
5
5
  "types": "./dist/generated/index.d.ts",
6
6
  "exports": {
@@ -66,5 +66,5 @@
66
66
  "ts-jest": "29.1.1",
67
67
  "typescript": "5.2.2"
68
68
  },
69
- "gitHead": "93bcd1aaf8d85abf75c20478e324b6b5323baf2f"
69
+ "gitHead": "ed0fb780bf900daefb251880e49c330e7d818a1f"
70
70
  }
package/src/Table.ts CHANGED
@@ -101,6 +101,31 @@ export const addUpdateFieldValues = async (table: Table<any>, record: any) => {
101
101
  /**
102
102
  * primary key is `id`
103
103
  */
104
+ /**
105
+ * One act of a record table's LIST ACTIONS — the acts the table's title-row seat carries, on every
106
+ * form factor (the seat's create act is the `+` that opens the new-record form; its delete act
107
+ * removes the selected rows and shows once rows are selected). The framework performs the act by
108
+ * `kind`; the table declares who may see it and what it is called.
109
+ */
110
+ export type RecordTableAction = {
111
+ /** Which seat act this declaration configures. */
112
+ kind: 'create' | 'delete';
113
+ /**
114
+ * Who may see the act — the `Table.auth` Identity vocabulary. Undeclared → the act keeps the
115
+ * doors' verdict (`create` from the insert doors, `delete` from the delete doors). Declare when
116
+ * the act is a DOMAIN act rather than the generic operation: an invite is minted by its own
117
+ * service, so the generic insert doors stay closed by design and the create act names the
118
+ * identity the send act serves instead. The act itself stays gated where it runs (the form,
119
+ * the service); this door is the affordance's.
120
+ */
121
+ door?: Identity;
122
+ /**
123
+ * The act's name — tooltip and accessible label — when the generic verb is not what the act
124
+ * IS (an invite is SENT). Undeclared → "Create <table>" / "Delete selected rows".
125
+ */
126
+ label?: string;
127
+ };
128
+
104
129
  export abstract class Table<T extends Record> implements Loadable, CustomSerializableObject {
105
130
  public __serializerId = TableSerializerId;
106
131
  abstract name: string;
@@ -147,6 +172,15 @@ export abstract class Table<T extends Record> implements Loadable, CustomSeriali
147
172
  * redirect never needs a row load.
148
173
  */
149
174
  recordLink?: (row: Pick<T, 'id'>) => string;
175
+ /**
176
+ * The list actions the record table's title-row seat carries (see `RecordTableAction`).
177
+ * Undeclared, the seat derives the generic pair from the auth doors — create from the
178
+ * insert doors, delete-on-selection from the delete doors — exactly as every table did
179
+ * before this declaration existed. A declared entry CONFIGURES the act of its kind (door
180
+ * and/or label); kinds left undeclared keep the derivation. The seat, not the table, owns
181
+ * placement: every declared act renders in the same seat on desktop and phone.
182
+ */
183
+ actions?: RecordTableAction[];
150
184
  };
151
185
  };
152
186
  public auth?: {
@@ -79,20 +79,26 @@ export class MigrationTable extends Table<Migration> {
79
79
  service: { query: { permission: 'dev' }, update: { permission: 'dev' }, delete: { permission: 'dev' } },
80
80
  };
81
81
  /**
82
- * The row scan the founder actually reads (ruled 2026-09-02): WHICH migration (name — the
83
- * loader's class name), what it does, how the run went, WHEN it ran ("Ran at" = start_time,
84
- * the one timestamp that answers "when did it land"), how long, when it finished, and a
85
- * glimpse of its output (the mono JSON snippet; the record form carries the full value).
86
- * The declaration flags and the failure detail stay on the record form. Most recent run
87
- * first; never-run rows last — GoogleSQL orders NULL as the least value, so start_time DESC
88
- * places rows without a run after every real timestamp; ledger order (created) breaks ties.
82
+ * The row scan: WHICH migration (name — the loader's class name), what it does, how the run
83
+ * went, WHEN it ran ("Ran at" = start_time, the one timestamp that answers "when did it
84
+ * land"), how long, when it finished, and a glimpse of its output (the mono JSON snippet; the
85
+ * record form carries the full value). The declaration flags and the failure detail stay on
86
+ * the record form.
87
+ *
88
+ * Ordered as a ledger: newest first by `created` (id breaking ties — rows land in batches at
89
+ * one boot), the reverse of the order {@link MigrationRunner.runPendingMigrations} runs them
90
+ * in. Status plays no part: a row that never ran (`proposed`, a `manual` migration waiting
91
+ * for its operator) keeps its place among the rows that did, so the migrations that shipped
92
+ * together read together. An order by run time would push every never-run row below every
93
+ * run one (a never-run row has no start_time, and GoogleSQL orders NULL as the least value,
94
+ * last under DESC) — the pending manual migrations become the hardest rows to find.
89
95
  */
90
96
  public ui: Table<Migration>['ui'] = {
91
97
  recordTable: {
92
98
  columns: ['name', 'description', 'status', 'startTime', 'duration', 'endTime', 'output'],
93
99
  sort: [
94
- { field: 'startTime', desc: true },
95
- { field: 'created', desc: false },
100
+ { field: 'created', desc: true },
101
+ { field: 'id', desc: true },
96
102
  ],
97
103
  },
98
104
  };