@ai-matrx/records 0.57.0 → 0.57.1
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/CHANGELOG.md +25 -0
- package/dist/core/index.cjs +94 -17
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +36 -3
- package/dist/core/index.d.ts +36 -3
- package/dist/core/index.js +94 -17
- package/dist/core/index.js.map +1 -1
- package/dist/index.cjs +6 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +84 -11
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +56 -2
- package/dist/react/index.d.ts +56 -2
- package/dist/react/index.js +84 -11
- package/dist/react/index.js.map +1 -1
- package/dist/{types-DC2tYa4w.d.cts → types-Bsamaf-a.d.cts} +39 -2
- package/dist/{types-DC2tYa4w.d.ts → types-Bsamaf-a.d.ts} +39 -2
- package/dist/use-cases/index.d.cts +2 -2
- package/dist/use-cases/index.d.ts +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog — @ai-matrx/records
|
|
2
2
|
|
|
3
|
+
## 0.57.1 (unpublished — lane RECORDS-UI-FIX, guide re-walk 2026-09-23)
|
|
4
|
+
|
|
5
|
+
* **A refusal is not an app error.** The default error sink printed every refusal through
|
|
6
|
+
`console.error`, and matrx-frontend's error capture counts every `console.error`, so one
|
|
7
|
+
refused value lit a red "2 errors" pill. The codes a person causes (`refused_by_rule`,
|
|
8
|
+
`stale_write`, `invalid_argument`, `already_exists`, `missing_reference`) now go to
|
|
9
|
+
`console.info`; everything else stays `console.error`. New exports (core):
|
|
10
|
+
`isAPersonsRefusal`, `A_PERSONS_REFUSAL`. Test: `src/__tests__/a-refusal-is-never-an-app-error.test.ts`.
|
|
11
|
+
* **Every screen hears the writes made on its page.** `recordUpdate`, `recordDelete`,
|
|
12
|
+
`recordRestore`, `restoreVersion` and `valueRestore` announce the record they wrote on
|
|
13
|
+
success. New exports: core `announceRecordChanged` / `onRecordChanged`, react
|
|
14
|
+
`useRecordChangeRevision(recordId)`; `useRecord` re-reads on it.
|
|
15
|
+
|
|
16
|
+
## Unreleased — STORE-RULE-GAPS (2026-09-23)
|
|
17
|
+
|
|
18
|
+
* `FieldRule` gains `example` (a pattern's "how to write it") and `min` (a length's shortest);
|
|
19
|
+
`value` is optional for a min-only length rule. `NewFieldDeclaration.allow_other` (sent as
|
|
20
|
+
`config.allow_other`) and `FieldPatch.allow_other`; `allowsOtherValues(field)`.
|
|
21
|
+
`PredictedRefusal.detail`.
|
|
22
|
+
* The mirror predicts the store's new sentences word for word ("… has to be at least 20
|
|
23
|
+
characters long"; a pattern with an example hints "Enter it like …" with `detail.example`),
|
|
24
|
+
and no longer predicts a refusal for a list value that is not a uuid — the store holds option
|
|
25
|
+
KEYS (CHOICE-VALUE) and a list that takes other values adds an unknown word.
|
|
26
|
+
* Needs matrx-frontend `storerulegaps_an_example_a_shortest_length_and_other_choices.sql`.
|
|
27
|
+
|
|
3
28
|
## 0.57.0
|
|
4
29
|
|
|
5
30
|
**The grid primitives are live, so their doors are in the catalogue.** (published by lane
|
package/dist/core/index.cjs
CHANGED
|
@@ -2199,6 +2199,7 @@ __export(core_exports, {
|
|
|
2199
2199
|
ARCHIVE_LANES: () => ARCHIVE_LANES,
|
|
2200
2200
|
ARCHIVE_LANE_LABEL: () => ARCHIVE_LANE_LABEL,
|
|
2201
2201
|
ARCHIVE_LANE_MEANS: () => ARCHIVE_LANE_MEANS,
|
|
2202
|
+
A_PERSONS_REFUSAL: () => A_PERSONS_REFUSAL,
|
|
2202
2203
|
COMPUTE_ON: () => COMPUTE_ON,
|
|
2203
2204
|
CONTEXT_POLICIES: () => CONTEXT_POLICIES,
|
|
2204
2205
|
DECLARABLE_FIELD_KINDS: () => DECLARABLE_FIELD_KINDS,
|
|
@@ -2245,6 +2246,8 @@ __export(core_exports, {
|
|
|
2245
2246
|
VALUE_ALTERNATE_KEYS: () => VALUE_ALTERNATE_KEYS,
|
|
2246
2247
|
VALUE_ENVELOPE_KEYS: () => VALUE_ENVELOPE_KEYS,
|
|
2247
2248
|
actionRefusals: () => actionRefusals,
|
|
2249
|
+
allowsOtherValues: () => allowsOtherValues,
|
|
2250
|
+
announceRecordChanged: () => announceRecordChanged,
|
|
2248
2251
|
archivedByLine: () => archivedByLine,
|
|
2249
2252
|
archivedWhen: () => archivedWhen,
|
|
2250
2253
|
asPermissionLevel: () => asPermissionLevel,
|
|
@@ -2277,6 +2280,7 @@ __export(core_exports, {
|
|
|
2277
2280
|
importXlsx: () => importXlsx,
|
|
2278
2281
|
indexedDbStore: () => indexedDbStore,
|
|
2279
2282
|
intersectIds: () => intersectIds,
|
|
2283
|
+
isAPersonsRefusal: () => isAPersonsRefusal,
|
|
2280
2284
|
isArchiveLane: () => isArchiveLane,
|
|
2281
2285
|
isDecided: () => isDecided,
|
|
2282
2286
|
isOwnOp: () => isOwnOp,
|
|
@@ -2291,6 +2295,7 @@ __export(core_exports, {
|
|
|
2291
2295
|
mintClientKey: () => mintClientKey,
|
|
2292
2296
|
mintOpId: () => mintOpId,
|
|
2293
2297
|
ok: () => ok,
|
|
2298
|
+
onRecordChanged: () => onRecordChanged,
|
|
2294
2299
|
openCaptureQueue: () => openCaptureQueue,
|
|
2295
2300
|
optionKey: () => optionKey,
|
|
2296
2301
|
optionLabel: () => optionLabel,
|
|
@@ -2439,6 +2444,11 @@ function highestLevel(left, right) {
|
|
|
2439
2444
|
return ORDINAL[left] >= ORDINAL[right] ? left : right;
|
|
2440
2445
|
}
|
|
2441
2446
|
|
|
2447
|
+
// src/field.ts
|
|
2448
|
+
function allowsOtherValues(field) {
|
|
2449
|
+
return field.type === "list" && field.config?.["allow_other"] === true;
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2442
2452
|
// src/choice.ts
|
|
2443
2453
|
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2444
2454
|
function optionKey(option) {
|
|
@@ -5697,6 +5707,22 @@ function staleWriteDetail(error) {
|
|
|
5697
5707
|
};
|
|
5698
5708
|
}
|
|
5699
5709
|
|
|
5710
|
+
// src/core/recordChanges.ts
|
|
5711
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
5712
|
+
function announceRecordChanged(recordId) {
|
|
5713
|
+
for (const listener of [...listeners]) {
|
|
5714
|
+
try {
|
|
5715
|
+
listener(recordId);
|
|
5716
|
+
} catch (thrown) {
|
|
5717
|
+
console.error("[@ai-matrx/records] a record-change listener threw", thrown);
|
|
5718
|
+
}
|
|
5719
|
+
}
|
|
5720
|
+
}
|
|
5721
|
+
function onRecordChanged(listener) {
|
|
5722
|
+
listeners.add(listener);
|
|
5723
|
+
return () => listeners.delete(listener);
|
|
5724
|
+
}
|
|
5725
|
+
|
|
5700
5726
|
// src/core/query.ts
|
|
5701
5727
|
var DEFAULT_PAGE_SIZE = 50;
|
|
5702
5728
|
function planQuery(query) {
|
|
@@ -5738,12 +5764,22 @@ function intersectIds(sets) {
|
|
|
5738
5764
|
|
|
5739
5765
|
// src/core/client.ts
|
|
5740
5766
|
var DEFAULT_SCHEMA = "custom";
|
|
5767
|
+
var A_PERSONS_REFUSAL = /* @__PURE__ */ new Set([
|
|
5768
|
+
"refused_by_rule",
|
|
5769
|
+
"stale_write",
|
|
5770
|
+
"invalid_argument",
|
|
5771
|
+
"already_exists",
|
|
5772
|
+
"missing_reference"
|
|
5773
|
+
]);
|
|
5774
|
+
function isAPersonsRefusal(code) {
|
|
5775
|
+
return A_PERSONS_REFUSAL.has(code);
|
|
5776
|
+
}
|
|
5741
5777
|
function sink(config) {
|
|
5742
|
-
return config.onError ?? ((e) =>
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
console.error(
|
|
5746
|
-
)
|
|
5778
|
+
return config.onError ?? ((e) => {
|
|
5779
|
+
const line = `[@ai-matrx/records] ${e.where}: ${e.code} \u2014 ${e.message}${e.hint ? ` (${e.hint})` : ""}`;
|
|
5780
|
+
if (isAPersonsRefusal(e.code)) console.info(line);
|
|
5781
|
+
else console.error(line);
|
|
5782
|
+
});
|
|
5747
5783
|
}
|
|
5748
5784
|
function createRecordsClient(config) {
|
|
5749
5785
|
const schema = config.schema ?? DEFAULT_SCHEMA;
|
|
@@ -5850,21 +5886,26 @@ function createRecordsClient(config) {
|
|
|
5850
5886
|
},
|
|
5851
5887
|
"recordUpdate"
|
|
5852
5888
|
);
|
|
5889
|
+
if (result.ok) announceRecordChanged(record_id);
|
|
5853
5890
|
return result;
|
|
5854
5891
|
},
|
|
5855
5892
|
async recordDelete({ record_id }) {
|
|
5856
|
-
|
|
5893
|
+
const result = await callDoor(
|
|
5857
5894
|
DOORS.recordDelete,
|
|
5858
5895
|
{ p_organization_id: org, p_record_id: record_id },
|
|
5859
5896
|
"recordDelete"
|
|
5860
5897
|
);
|
|
5898
|
+
if (result.ok) announceRecordChanged(record_id);
|
|
5899
|
+
return result;
|
|
5861
5900
|
},
|
|
5862
5901
|
async recordRestore({ record_id }) {
|
|
5863
|
-
|
|
5902
|
+
const result = await callDoor(
|
|
5864
5903
|
DOORS.recordRestore,
|
|
5865
5904
|
{ p_organization_id: org, p_record_id: record_id },
|
|
5866
5905
|
"recordRestore"
|
|
5867
5906
|
);
|
|
5907
|
+
if (result.ok) announceRecordChanged(record_id);
|
|
5908
|
+
return result;
|
|
5868
5909
|
},
|
|
5869
5910
|
async tableArchive({ table_id, chunk, includeTable }) {
|
|
5870
5911
|
return callDoor(
|
|
@@ -5914,6 +5955,24 @@ function createRecordsClient(config) {
|
|
|
5914
5955
|
})
|
|
5915
5956
|
);
|
|
5916
5957
|
},
|
|
5958
|
+
async tableArchived({ table_id }) {
|
|
5959
|
+
const kernel = await callDoor(DOORS.tableKernelId, {}, "tableArchived.kernel");
|
|
5960
|
+
if (!kernel.ok) return err(kernel.error);
|
|
5961
|
+
const PAGE = 200;
|
|
5962
|
+
for (let page = 0; page < 25; page += 1) {
|
|
5963
|
+
const answered = await this.listArchived({
|
|
5964
|
+
table_id: kernel.data,
|
|
5965
|
+
lane: "org",
|
|
5966
|
+
limit: PAGE,
|
|
5967
|
+
offset: page * PAGE
|
|
5968
|
+
});
|
|
5969
|
+
if (!answered.ok) return err(answered.error);
|
|
5970
|
+
const hit = answered.data.rows.find((row) => row.id === table_id);
|
|
5971
|
+
if (hit) return ok(hit);
|
|
5972
|
+
if (answered.data.total !== null) return ok(null);
|
|
5973
|
+
}
|
|
5974
|
+
return ok(null);
|
|
5975
|
+
},
|
|
5917
5976
|
async listArchived({ table_id, lane = DEFAULT_ARCHIVE_LANE, limit = 50, offset = 0 }) {
|
|
5918
5977
|
const door = await callDoor(
|
|
5919
5978
|
DOORS.readRecordsArchived,
|
|
@@ -6115,9 +6174,11 @@ function createRecordsClient(config) {
|
|
|
6115
6174
|
);
|
|
6116
6175
|
},
|
|
6117
6176
|
async fieldDeclare({ table_id, spec }) {
|
|
6177
|
+
const { allow_other, ...rest } = spec;
|
|
6178
|
+
const p_spec = allow_other === void 0 ? rest : { ...rest, config: { ...rest.config ?? {}, allow_other } };
|
|
6118
6179
|
return callDoor(
|
|
6119
6180
|
DOORS.fieldDeclare,
|
|
6120
|
-
{ p_organization_id: org, p_table_id: table_id, p_spec
|
|
6181
|
+
{ p_organization_id: org, p_table_id: table_id, p_spec },
|
|
6121
6182
|
"fieldDeclare"
|
|
6122
6183
|
);
|
|
6123
6184
|
},
|
|
@@ -7085,14 +7146,16 @@ function createRecordsClient(config) {
|
|
|
7085
7146
|
);
|
|
7086
7147
|
},
|
|
7087
7148
|
async restoreVersion({ record_id, version }) {
|
|
7088
|
-
|
|
7149
|
+
const result = await callDoor(
|
|
7089
7150
|
DOORS.restoreVersion,
|
|
7090
7151
|
{ p_organization_id: org, p_record_id: record_id, p_version: version },
|
|
7091
7152
|
"restoreVersion"
|
|
7092
7153
|
);
|
|
7154
|
+
if (result.ok) announceRecordChanged(record_id);
|
|
7155
|
+
return result;
|
|
7093
7156
|
},
|
|
7094
7157
|
async valueRestore({ record_id, field_key, version }) {
|
|
7095
|
-
|
|
7158
|
+
const answered = await callDoor(
|
|
7096
7159
|
DOORS.valueRestore,
|
|
7097
7160
|
{
|
|
7098
7161
|
p_organization_id: org,
|
|
@@ -7102,6 +7165,8 @@ function createRecordsClient(config) {
|
|
|
7102
7165
|
},
|
|
7103
7166
|
"valueRestore"
|
|
7104
7167
|
);
|
|
7168
|
+
if (answered.ok) announceRecordChanged(record_id);
|
|
7169
|
+
return answered;
|
|
7105
7170
|
},
|
|
7106
7171
|
async commentThread({ record_id, include_resolved }) {
|
|
7107
7172
|
return callDoor(
|
|
@@ -7883,12 +7948,30 @@ function ruleRefusal(field, rule, item, values) {
|
|
|
7883
7948
|
}
|
|
7884
7949
|
return null;
|
|
7885
7950
|
case "length":
|
|
7886
|
-
if (typeof item
|
|
7951
|
+
if (typeof item !== "string") return null;
|
|
7952
|
+
if (rule.value !== void 0 && rule.value !== null && rule.value !== "" && item.length > Number(rule.value)) {
|
|
7887
7953
|
return { ...base, message: `${label} is longer than ${rule.value} characters`, hint: "FLD-3." };
|
|
7888
7954
|
}
|
|
7955
|
+
if (typeof rule.min === "number" && item !== "" && item.length < rule.min) {
|
|
7956
|
+
return {
|
|
7957
|
+
...base,
|
|
7958
|
+
message: `${label} has to be at least ${rule.min} characters long`,
|
|
7959
|
+
hint: "FLD-3.",
|
|
7960
|
+
detail: { field_key: field.key, rule: "length", min: rule.min }
|
|
7961
|
+
};
|
|
7962
|
+
}
|
|
7889
7963
|
return null;
|
|
7890
7964
|
case "pattern":
|
|
7891
7965
|
if (typeof item === "string" && !new RegExp(String(rule.value)).test(item)) {
|
|
7966
|
+
const example = typeof rule.example === "string" ? rule.example.trim() : "";
|
|
7967
|
+
if (example !== "") {
|
|
7968
|
+
return {
|
|
7969
|
+
...base,
|
|
7970
|
+
message: `${label} is not written the way this field expects`,
|
|
7971
|
+
hint: `Enter it like ${example}.`,
|
|
7972
|
+
detail: { field_key: field.key, rule: "pattern", example }
|
|
7973
|
+
};
|
|
7974
|
+
}
|
|
7892
7975
|
return { ...base, message: `${label} is not written the way this field expects`, hint: "FLD-3." };
|
|
7893
7976
|
}
|
|
7894
7977
|
return null;
|
|
@@ -7973,12 +8056,6 @@ function predictValueRefusals(fields, values, recordType) {
|
|
|
7973
8056
|
message: `${label} takes one of its choices, and it was given a ${jsonType(item)}`,
|
|
7974
8057
|
hint: "FLD-5 / FLD-6: a list field stores the id of the option RECORD, because every pick-list is already a Table."
|
|
7975
8058
|
});
|
|
7976
|
-
} else if (!UUID2.test(item)) {
|
|
7977
|
-
found.push({
|
|
7978
|
-
...base,
|
|
7979
|
-
message: `${label} was given a choice that is not one of its choices`,
|
|
7980
|
-
hint: `REC-51: option membership. ${label} stores the id of an option record, and what it was given is not an id at all.`
|
|
7981
|
-
});
|
|
7982
8059
|
}
|
|
7983
8060
|
} else if (field.type === "relation") {
|
|
7984
8061
|
if (typeof item !== "string") {
|