@versionone/agility-sdk 1.0.0-alpha.0 → 1.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -7
- package/dist/index.cjs +97 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -5
- package/dist/index.d.ts +39 -5
- package/dist/index.js +97 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -301,6 +301,8 @@ interface AssetType {
|
|
|
301
301
|
/** The attribute that carries the short name (Number on workitems). */
|
|
302
302
|
ShortName?: AttributeTokenRef;
|
|
303
303
|
Attributes: Record<AttributeToken, AttributeDefinition>;
|
|
304
|
+
/** The operations the type supports, by bare name (Delete, Inactivate, …); delivered keyed by token (Story.Delete). */
|
|
305
|
+
Operations?: string[];
|
|
304
306
|
}
|
|
305
307
|
type AssetTypeMap$1 = Record<AssetTypeToken, AssetType>;
|
|
306
308
|
interface Metamodel {
|
|
@@ -772,6 +774,12 @@ declare class Schema implements AttributeLookup {
|
|
|
772
774
|
assetType(token: AssetTypeToken): AssetType | null | undefined;
|
|
773
775
|
/** An attribute by bare name; undefined when the type is not loaded, null when it has no such attribute. */
|
|
774
776
|
attribute(type: AssetTypeToken, name: string): AttributeDefinition | null | undefined;
|
|
777
|
+
/**
|
|
778
|
+
* Can the type do this operation (Delete, Reactivate, …)? Undefined when
|
|
779
|
+
* the type is not loaded or its definition carries no operations, so a
|
|
780
|
+
* caller can tell "no" from "don't know".
|
|
781
|
+
*/
|
|
782
|
+
canOperate(type: AssetTypeToken, op: string): boolean | undefined;
|
|
775
783
|
/** The tokens loaded so far. */
|
|
776
784
|
get loaded(): AssetTypeToken[];
|
|
777
785
|
/**
|
|
@@ -803,6 +811,12 @@ declare class Schema implements AttributeLookup {
|
|
|
803
811
|
* which were skipped by the guard; and a reverse batch that undoes what
|
|
804
812
|
* succeeded, best effort, with a note for anything it cannot.
|
|
805
813
|
*
|
|
814
|
+
* Closing a workitem cascades: Core's Inactivate and QuickClose also close
|
|
815
|
+
* its open descendants, and Reactivate reopens only the asset it names. So
|
|
816
|
+
* before a batch that closes, the receipt records each closing target's
|
|
817
|
+
* open descendants; after it, the ones the close actually closed; and the
|
|
818
|
+
* reverse reopens the parent, then those, top down.
|
|
819
|
+
*
|
|
806
820
|
* The snapshot keeps the endpoint's spelling (strings, relations as OID
|
|
807
821
|
* tokens) on purpose: it is what a reverse batch writes back.
|
|
808
822
|
*/
|
|
@@ -867,18 +881,29 @@ interface Reverse {
|
|
|
867
881
|
commands: Command[];
|
|
868
882
|
notes: string[];
|
|
869
883
|
}
|
|
884
|
+
/** What reverseOf can know beyond the response, each optional. */
|
|
885
|
+
interface ReverseContext {
|
|
886
|
+
/** Per closing target, its open descendants before the batch (Receipt.openDescendants). */
|
|
887
|
+
openDescendants?: Record<Oid, Oid[]>;
|
|
888
|
+
/** Per closing target, the descendants the close closed (Receipt.cascaded); undefined when they could not be read back. */
|
|
889
|
+
cascaded?: Record<Oid, Oid[]>;
|
|
890
|
+
/** Can this type do this operation? Undefined is "don't know", and the command is kept. */
|
|
891
|
+
canOperate?: (type: AssetTypeToken, op: string) => boolean | undefined;
|
|
892
|
+
}
|
|
870
893
|
/**
|
|
871
894
|
* A batch that undoes what the response says succeeded — best effort. Only
|
|
872
895
|
* what the response confirms is reversed (membership in it, not command
|
|
873
896
|
* index, decides): updated attributes put back from the snapshot, the
|
|
874
|
-
* inverse operation where one is known,
|
|
875
|
-
*
|
|
897
|
+
* inverse operation where one is known (and, for a close, the descendants
|
|
898
|
+
* it closed reopened after it, top down), created assets deleted (newest
|
|
899
|
+
* first). An operation a type does not have, by `context.canOperate`, is
|
|
900
|
+
* a note rather than a command the server would refuse.
|
|
876
901
|
*/
|
|
877
902
|
declare function reverseOf(described: Described, before: Record<Oid, Record<string, unknown>>, done: {
|
|
878
903
|
created: Oid[];
|
|
879
904
|
modified: Oid[];
|
|
880
905
|
operated: Oid[];
|
|
881
|
-
}): Reverse;
|
|
906
|
+
}, context?: ReverseContext): Reverse;
|
|
882
907
|
interface Receipt {
|
|
883
908
|
receipt: 1;
|
|
884
909
|
request: Command[];
|
|
@@ -908,8 +933,14 @@ interface Receipt {
|
|
|
908
933
|
} & Named)[];
|
|
909
934
|
/** ChangeDate of everything the batch touched, just after it: the stamp a later reverse checks for drift. */
|
|
910
935
|
after: Record<Oid, string>;
|
|
911
|
-
/** Touched assets the guard left alone: changed or gone since the snapshot. */
|
|
936
|
+
/** Touched assets the guard left alone: changed or gone since the snapshot. Not the ones whose command failed: those are in `failed`. */
|
|
912
937
|
skipped: Oid[];
|
|
938
|
+
/** Touched assets whose command the server reports as failed (outcome.failures says why). */
|
|
939
|
+
failed: Oid[];
|
|
940
|
+
/** Per asset the batch closes (Inactivate, QuickClose): its open descendants at the snapshot, top down. */
|
|
941
|
+
openDescendants: Record<Oid, Oid[]>;
|
|
942
|
+
/** Per asset the batch closed: the descendants the close closed with it, read back after the send. */
|
|
943
|
+
cascaded: Record<Oid, Oid[]>;
|
|
913
944
|
reverse: Reverse;
|
|
914
945
|
/** What the record could not do and why: a name lookup that failed, deferred commands sent unsnapshotted. */
|
|
915
946
|
notes: string[];
|
|
@@ -985,7 +1016,10 @@ declare function reversible(transport: Transport, receipt: Pick<Receipt, "revers
|
|
|
985
1016
|
* pattern first, so it cannot carry grammar. The ID stays a variable.
|
|
986
1017
|
* - Moment is NOT a stamp: `Moment=<an earlier moment of this asset>`
|
|
987
1018
|
* selects the asset as it was then (as-of), and an update scoped by it
|
|
988
|
-
* lands on the current asset. A Moment guard guards nothing.
|
|
1019
|
+
* lands on the current asset. A Moment guard guards nothing. Proven for
|
|
1020
|
+
* writes on 2026-11-11: an update filtered on a stale Moment previews as
|
|
1021
|
+
* one command and applies, overwriting the change made in between
|
|
1022
|
+
* ("why not Moment, on a write"; versionone/VersionOne.SDK.JavaScript#51).
|
|
989
1023
|
* Every type has ChangeDate except the Ideas product's (Idea*), which keeps
|
|
990
1024
|
* no history and cannot be guarded.
|
|
991
1025
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -301,6 +301,8 @@ interface AssetType {
|
|
|
301
301
|
/** The attribute that carries the short name (Number on workitems). */
|
|
302
302
|
ShortName?: AttributeTokenRef;
|
|
303
303
|
Attributes: Record<AttributeToken, AttributeDefinition>;
|
|
304
|
+
/** The operations the type supports, by bare name (Delete, Inactivate, …); delivered keyed by token (Story.Delete). */
|
|
305
|
+
Operations?: string[];
|
|
304
306
|
}
|
|
305
307
|
type AssetTypeMap$1 = Record<AssetTypeToken, AssetType>;
|
|
306
308
|
interface Metamodel {
|
|
@@ -772,6 +774,12 @@ declare class Schema implements AttributeLookup {
|
|
|
772
774
|
assetType(token: AssetTypeToken): AssetType | null | undefined;
|
|
773
775
|
/** An attribute by bare name; undefined when the type is not loaded, null when it has no such attribute. */
|
|
774
776
|
attribute(type: AssetTypeToken, name: string): AttributeDefinition | null | undefined;
|
|
777
|
+
/**
|
|
778
|
+
* Can the type do this operation (Delete, Reactivate, …)? Undefined when
|
|
779
|
+
* the type is not loaded or its definition carries no operations, so a
|
|
780
|
+
* caller can tell "no" from "don't know".
|
|
781
|
+
*/
|
|
782
|
+
canOperate(type: AssetTypeToken, op: string): boolean | undefined;
|
|
775
783
|
/** The tokens loaded so far. */
|
|
776
784
|
get loaded(): AssetTypeToken[];
|
|
777
785
|
/**
|
|
@@ -803,6 +811,12 @@ declare class Schema implements AttributeLookup {
|
|
|
803
811
|
* which were skipped by the guard; and a reverse batch that undoes what
|
|
804
812
|
* succeeded, best effort, with a note for anything it cannot.
|
|
805
813
|
*
|
|
814
|
+
* Closing a workitem cascades: Core's Inactivate and QuickClose also close
|
|
815
|
+
* its open descendants, and Reactivate reopens only the asset it names. So
|
|
816
|
+
* before a batch that closes, the receipt records each closing target's
|
|
817
|
+
* open descendants; after it, the ones the close actually closed; and the
|
|
818
|
+
* reverse reopens the parent, then those, top down.
|
|
819
|
+
*
|
|
806
820
|
* The snapshot keeps the endpoint's spelling (strings, relations as OID
|
|
807
821
|
* tokens) on purpose: it is what a reverse batch writes back.
|
|
808
822
|
*/
|
|
@@ -867,18 +881,29 @@ interface Reverse {
|
|
|
867
881
|
commands: Command[];
|
|
868
882
|
notes: string[];
|
|
869
883
|
}
|
|
884
|
+
/** What reverseOf can know beyond the response, each optional. */
|
|
885
|
+
interface ReverseContext {
|
|
886
|
+
/** Per closing target, its open descendants before the batch (Receipt.openDescendants). */
|
|
887
|
+
openDescendants?: Record<Oid, Oid[]>;
|
|
888
|
+
/** Per closing target, the descendants the close closed (Receipt.cascaded); undefined when they could not be read back. */
|
|
889
|
+
cascaded?: Record<Oid, Oid[]>;
|
|
890
|
+
/** Can this type do this operation? Undefined is "don't know", and the command is kept. */
|
|
891
|
+
canOperate?: (type: AssetTypeToken, op: string) => boolean | undefined;
|
|
892
|
+
}
|
|
870
893
|
/**
|
|
871
894
|
* A batch that undoes what the response says succeeded — best effort. Only
|
|
872
895
|
* what the response confirms is reversed (membership in it, not command
|
|
873
896
|
* index, decides): updated attributes put back from the snapshot, the
|
|
874
|
-
* inverse operation where one is known,
|
|
875
|
-
*
|
|
897
|
+
* inverse operation where one is known (and, for a close, the descendants
|
|
898
|
+
* it closed reopened after it, top down), created assets deleted (newest
|
|
899
|
+
* first). An operation a type does not have, by `context.canOperate`, is
|
|
900
|
+
* a note rather than a command the server would refuse.
|
|
876
901
|
*/
|
|
877
902
|
declare function reverseOf(described: Described, before: Record<Oid, Record<string, unknown>>, done: {
|
|
878
903
|
created: Oid[];
|
|
879
904
|
modified: Oid[];
|
|
880
905
|
operated: Oid[];
|
|
881
|
-
}): Reverse;
|
|
906
|
+
}, context?: ReverseContext): Reverse;
|
|
882
907
|
interface Receipt {
|
|
883
908
|
receipt: 1;
|
|
884
909
|
request: Command[];
|
|
@@ -908,8 +933,14 @@ interface Receipt {
|
|
|
908
933
|
} & Named)[];
|
|
909
934
|
/** ChangeDate of everything the batch touched, just after it: the stamp a later reverse checks for drift. */
|
|
910
935
|
after: Record<Oid, string>;
|
|
911
|
-
/** Touched assets the guard left alone: changed or gone since the snapshot. */
|
|
936
|
+
/** Touched assets the guard left alone: changed or gone since the snapshot. Not the ones whose command failed: those are in `failed`. */
|
|
912
937
|
skipped: Oid[];
|
|
938
|
+
/** Touched assets whose command the server reports as failed (outcome.failures says why). */
|
|
939
|
+
failed: Oid[];
|
|
940
|
+
/** Per asset the batch closes (Inactivate, QuickClose): its open descendants at the snapshot, top down. */
|
|
941
|
+
openDescendants: Record<Oid, Oid[]>;
|
|
942
|
+
/** Per asset the batch closed: the descendants the close closed with it, read back after the send. */
|
|
943
|
+
cascaded: Record<Oid, Oid[]>;
|
|
913
944
|
reverse: Reverse;
|
|
914
945
|
/** What the record could not do and why: a name lookup that failed, deferred commands sent unsnapshotted. */
|
|
915
946
|
notes: string[];
|
|
@@ -985,7 +1016,10 @@ declare function reversible(transport: Transport, receipt: Pick<Receipt, "revers
|
|
|
985
1016
|
* pattern first, so it cannot carry grammar. The ID stays a variable.
|
|
986
1017
|
* - Moment is NOT a stamp: `Moment=<an earlier moment of this asset>`
|
|
987
1018
|
* selects the asset as it was then (as-of), and an update scoped by it
|
|
988
|
-
* lands on the current asset. A Moment guard guards nothing.
|
|
1019
|
+
* lands on the current asset. A Moment guard guards nothing. Proven for
|
|
1020
|
+
* writes on 2026-11-11: an update filtered on a stale Moment previews as
|
|
1021
|
+
* one command and applies, overwriting the change made in between
|
|
1022
|
+
* ("why not Moment, on a write"; versionone/VersionOne.SDK.JavaScript#51).
|
|
989
1023
|
* Every type has ChangeDate except the Ideas product's (Idea*), which keeps
|
|
990
1024
|
* no history and cannot be guarded.
|
|
991
1025
|
*
|
package/dist/index.js
CHANGED
|
@@ -827,9 +827,46 @@ async function snapshot(transport, described) {
|
|
|
827
827
|
}
|
|
828
828
|
return before;
|
|
829
829
|
}
|
|
830
|
-
var INVERSE = { Inactivate: "Reactivate", Reactivate: "Inactivate", Delete: "Undelete", Undelete: "Delete" };
|
|
831
|
-
|
|
830
|
+
var INVERSE = { Inactivate: "Reactivate", QuickClose: "Reactivate", Reactivate: "Inactivate", Delete: "Undelete", Undelete: "Delete" };
|
|
831
|
+
var CASCADING = /* @__PURE__ */ new Set(["Inactivate", "QuickClose"]);
|
|
832
|
+
var CASCADE_DEPTH = 8;
|
|
833
|
+
async function openDescendantsOf(transport, schema, roots) {
|
|
834
|
+
const out = {};
|
|
835
|
+
const rootOf = new Map(roots.map((r) => [momentless(r), momentless(r)]));
|
|
836
|
+
const seen = new Set(rootOf.keys());
|
|
837
|
+
let frontier = [...rootOf.keys()];
|
|
838
|
+
for (let depth = 0; depth < CASCADE_DEPTH && frontier.length; depth += 1) {
|
|
839
|
+
const children = /* @__PURE__ */ new Map();
|
|
840
|
+
for (const [type, list3] of byType(frontier)) {
|
|
841
|
+
await schema.load(type);
|
|
842
|
+
if (!schema.attribute(type, "Children")) continue;
|
|
843
|
+
for (const part of chunks(list3)) {
|
|
844
|
+
const rows = await query(transport, { from: type, select: ["Children"], filter: ["ID=$oids"], with: { $oids: part }, page: { size: part.length, start: 0 } });
|
|
845
|
+
for (const r of rows) {
|
|
846
|
+
const parent = momentless(r._oid);
|
|
847
|
+
for (const c of [].concat(plain(r.Children) ?? [])) {
|
|
848
|
+
const child = momentless(String(c));
|
|
849
|
+
if (seen.has(child)) continue;
|
|
850
|
+
seen.add(child);
|
|
851
|
+
children.set(child, rootOf.get(parent));
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
const states = children.size ? await statesOf(transport, [...children.keys()]) : {};
|
|
857
|
+
frontier = [];
|
|
858
|
+
for (const [child, root] of children) {
|
|
859
|
+
if (states[child] === void 0 || isClosed(states[child])) continue;
|
|
860
|
+
rootOf.set(child, root);
|
|
861
|
+
(out[root] ??= []).push(child);
|
|
862
|
+
frontier.push(child);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
return out;
|
|
866
|
+
}
|
|
867
|
+
function reverseOf(described, before, done, context = {}) {
|
|
832
868
|
const commands = [], notes = [];
|
|
869
|
+
const cannot = (oid, op) => context.canOperate?.(typeOfOid(oid), op) === false;
|
|
833
870
|
const modified = new Set(done.modified.map(momentless)), operated = new Set(done.operated.map(momentless));
|
|
834
871
|
for (const u of described.updates) {
|
|
835
872
|
if (!modified.has(u.oid)) continue;
|
|
@@ -854,10 +891,32 @@ function reverseOf(described, before, done) {
|
|
|
854
891
|
for (const e of described.executes) {
|
|
855
892
|
if (!operated.has(e.oid)) continue;
|
|
856
893
|
const inverse = INVERSE[e.op];
|
|
857
|
-
if (inverse)
|
|
858
|
-
|
|
894
|
+
if (!inverse) {
|
|
895
|
+
notes.push(`${e.oid}: ${e.op} has no known inverse (was ${String(before[e.oid]?.AssetState ?? "?")})`);
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
if (cannot(e.oid, inverse)) {
|
|
899
|
+
notes.push(`${e.oid}: ${typeOfOid(e.oid)} has no ${inverse} operation, so its ${e.op} is not reversed`);
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
commands.push({ from: e.oid, execute: inverse });
|
|
903
|
+
if (!CASCADING.has(e.op)) continue;
|
|
904
|
+
const open = context.openDescendants?.[e.oid] ?? [];
|
|
905
|
+
const closed = context.cascaded ? context.cascaded[e.oid] ?? [] : void 0;
|
|
906
|
+
if (closed === void 0) {
|
|
907
|
+
if (open.length) notes.push(`${e.oid}: ${e.op} may also have closed ${open.length} open descendant${open.length === 1 ? "" : "s"} (${open.join(", ")}); they could not be read back, so they are not reopened`);
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
for (const d of closed) {
|
|
911
|
+
if (cannot(d, "Reactivate")) notes.push(`${d}: closed with ${e.oid}, but ${typeOfOid(d)} has no Reactivate operation`);
|
|
912
|
+
else commands.push({ from: d, execute: "Reactivate" });
|
|
913
|
+
}
|
|
914
|
+
if (closed.length) notes.push(`${e.oid}: ${e.op} also closed ${closed.join(", ")}; reopened after it`);
|
|
915
|
+
}
|
|
916
|
+
for (const oid of [...done.created].reverse().map(momentless)) {
|
|
917
|
+
if (cannot(oid, "Delete")) notes.push(`${oid}: ${typeOfOid(oid)} has no Delete operation, so it is not deleted${typeOfOid(oid) === "Conversation" ? " (the server removes a Conversation once its last Expression is deleted)" : ""}`);
|
|
918
|
+
else commands.push({ from: oid, execute: "Delete" });
|
|
859
919
|
}
|
|
860
|
-
for (const oid of [...done.created].reverse()) commands.push({ from: momentless(oid), execute: "Delete" });
|
|
861
920
|
for (const d of described.deferred ?? []) notes.push(`command ${d.index} (${d.alias}): an aliased ${d.kind} of ${d.from}, sent as written with no snapshot; not reversed`);
|
|
862
921
|
if (commands.length) notes.unshift("restores the state recorded at snapshotAt; anything that changed between then and sentAt is not reflected, and a scalar restored to null is cleared, not unset");
|
|
863
922
|
return { commands, notes };
|
|
@@ -892,12 +951,14 @@ async function runBatch(transport, schema, commands, options = {}) {
|
|
|
892
951
|
const described = describe(commands, projected);
|
|
893
952
|
const touched = touchedBy(projected);
|
|
894
953
|
const names = await namesOf(transport, schema, touched, noteFailure);
|
|
895
|
-
const receipt = { receipt: 1, request: commands, previewedAt, preview: projected, described, names: Object.fromEntries(names), before: {}, sent: false, guarded: false, created: [], touched: [], after: {}, skipped: [], reverse: { commands: [], notes: [] }, notes };
|
|
954
|
+
const receipt = { receipt: 1, request: commands, previewedAt, preview: projected, described, names: Object.fromEntries(names), before: {}, sent: false, guarded: false, created: [], touched: [], after: {}, skipped: [], failed: [], openDescendants: {}, cascaded: {}, reverse: { commands: [], notes: [] }, notes };
|
|
896
955
|
const nothing = !described.creates.length && !described.updates.length && !described.executes.length && !described.queries.length && !described.deferred.length;
|
|
897
956
|
if (nothing) return receipt;
|
|
898
957
|
for (const d of described.deferred) notes.push(`command ${d.index} (${d.alias}): an aliased ${d.kind}, which preview does not expand; sent as written, not snapshotted, not reversible`);
|
|
899
958
|
receipt.snapshotAt = now();
|
|
900
959
|
receipt.before = await snapshot(transport, described);
|
|
960
|
+
const closing = described.executes.filter((e) => CASCADING.has(e.op)).map((e) => e.oid);
|
|
961
|
+
if (closing.length) receipt.openDescendants = await openDescendantsOf(transport, schema, closing);
|
|
901
962
|
if (options.dryRun) return receipt;
|
|
902
963
|
if (options.confirm && !await options.confirm(receipt)) return receipt;
|
|
903
964
|
let unstamped = [];
|
|
@@ -929,13 +990,29 @@ async function runBatch(transport, schema, commands, options = {}) {
|
|
|
929
990
|
const unnamed = { number: null, name: null };
|
|
930
991
|
receipt.created = done.created.map((oid, i) => ({ alias: described.creates[i]?.alias ?? null, type: described.creates[i]?.type ?? typeOfOid(oid), oid, ...unnamed }));
|
|
931
992
|
receipt.touched = hit.map((oid) => ({ oid, ...names.get(oid) ?? unnamed }));
|
|
932
|
-
|
|
933
|
-
|
|
993
|
+
const sentAs = receipt.sentCommands ?? [];
|
|
994
|
+
const oidOfSent = (i) => {
|
|
995
|
+
const body = i === null ? void 0 : sentAs[i] ? bodyOf3(sentAs[i]) : void 0;
|
|
996
|
+
const id = body?.with?.$oid ?? body?.from;
|
|
997
|
+
return typeof id === "string" && id.includes(":") ? momentless(id) : null;
|
|
998
|
+
};
|
|
999
|
+
receipt.failed = [...new Set(done.failures.map((f) => oidOfSent(f.index)).filter((oid) => !!oid && touched.includes(oid) && !hit.includes(oid)))];
|
|
1000
|
+
receipt.skipped = useGuard ? [.../* @__PURE__ */ new Set([...unstamped, ...touched.filter((oid) => !hit.includes(oid) && !receipt.failed.includes(oid))])] : [];
|
|
1001
|
+
for (const type of new Set([...done.created, ...hit].map((oid) => typeOfOid(momentless(oid))))) await schema.load(type).catch(() => null);
|
|
1002
|
+
const canOperate = (type, op) => schema.canOperate(type, op);
|
|
1003
|
+
const operatedNow = new Set(done.operated.map(momentless));
|
|
1004
|
+
const closed = Object.keys(receipt.openDescendants).filter((oid) => operatedNow.has(oid) && receipt.openDescendants[oid].length);
|
|
1005
|
+
receipt.reverse = reverseOf(described, receipt.before, done, { openDescendants: receipt.openDescendants, cascaded: closed.length ? void 0 : {}, canOperate });
|
|
934
1006
|
try {
|
|
1007
|
+
if (closed.length) {
|
|
1008
|
+
const states = await statesOf(transport, closed.flatMap((oid) => receipt.openDescendants[oid]));
|
|
1009
|
+
receipt.cascaded = Object.fromEntries(closed.map((oid) => [oid, receipt.openDescendants[oid].filter((d) => isClosed(states[d]))]));
|
|
1010
|
+
receipt.reverse = reverseOf(described, receipt.before, done, { openDescendants: receipt.openDescendants, cascaded: receipt.cascaded, canOperate });
|
|
1011
|
+
}
|
|
935
1012
|
const more = await namesOf(transport, schema, [...done.created, ...hit.filter((oid) => !names.has(oid))], noteFailure);
|
|
936
1013
|
receipt.created = receipt.created.map((c) => ({ ...c, ...more.get(c.oid) ?? unnamed }));
|
|
937
1014
|
receipt.touched = receipt.touched.map((t) => names.has(t.oid) ? t : { ...t, ...more.get(t.oid) ?? unnamed });
|
|
938
|
-
const all = [...done.created, ...hit];
|
|
1015
|
+
const all = [.../* @__PURE__ */ new Set([...done.created, ...hit, ...Object.values(receipt.cascaded).flat()])];
|
|
939
1016
|
receipt.after = all.length ? await stampsOf(transport, all) : {};
|
|
940
1017
|
} catch (error) {
|
|
941
1018
|
throw new BatchRecordError(error, receipt);
|
|
@@ -1099,7 +1176,8 @@ function toAssetType(doc) {
|
|
|
1099
1176
|
...doc.DefaultOrderBy ? { DefaultOrderBy: doc.DefaultOrderBy.tokenref } : {},
|
|
1100
1177
|
...doc.DefaultDisplayBy ? { DefaultDisplayBy: doc.DefaultDisplayBy.tokenref } : {},
|
|
1101
1178
|
...doc.ShortName ? { ShortName: doc.ShortName.tokenref } : {},
|
|
1102
|
-
Attributes: Object.fromEntries(Object.entries(doc.Attributes ?? {}).map(([key, value]) => [key, toAttributeDefinition(value)]))
|
|
1179
|
+
Attributes: Object.fromEntries(Object.entries(doc.Attributes ?? {}).map(([key, value]) => [key, toAttributeDefinition(value)])),
|
|
1180
|
+
...doc.Operations ? { Operations: Object.keys(doc.Operations).map((token) => token.slice(token.indexOf(".") + 1)) } : {}
|
|
1103
1181
|
};
|
|
1104
1182
|
}
|
|
1105
1183
|
function toMetamodel(doc) {
|
|
@@ -1163,6 +1241,15 @@ var Schema = class _Schema {
|
|
|
1163
1241
|
attribute(type, name) {
|
|
1164
1242
|
return attributeOf(this, type, name);
|
|
1165
1243
|
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Can the type do this operation (Delete, Reactivate, …)? Undefined when
|
|
1246
|
+
* the type is not loaded or its definition carries no operations, so a
|
|
1247
|
+
* caller can tell "no" from "don't know".
|
|
1248
|
+
*/
|
|
1249
|
+
canOperate(type, op) {
|
|
1250
|
+
const t = this.assetType(type);
|
|
1251
|
+
return t?.Operations ? t.Operations.includes(op) : void 0;
|
|
1252
|
+
}
|
|
1166
1253
|
/** The tokens loaded so far. */
|
|
1167
1254
|
get loaded() {
|
|
1168
1255
|
return [...this.types.entries()].filter(([, t]) => t !== null).map(([token]) => token);
|