@jini-ai/cms 0.3.10 → 0.4.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/CHANGELOG.md +59 -0
- package/README.md +47 -19
- package/dist/content-types/agent-tools.d.ts +3 -56
- package/dist/content-types/agent-tools.js +22 -6
- package/dist/content-types/cleanup.d.ts +18 -13
- package/dist/content-types/cleanup.js +16 -11
- package/dist/content-types/errors.d.ts +57 -24
- package/dist/content-types/errors.js +56 -39
- package/dist/content-types/field-defs.d.ts +8 -4
- package/dist/content-types/field-defs.js +19 -14
- package/dist/content-types/index-provisioning.d.ts +61 -47
- package/dist/content-types/index-provisioning.js +90 -66
- package/dist/content-types/index.d.ts +4 -3
- package/dist/content-types/lifecycle-dispatch.d.ts +11 -5
- package/dist/content-types/lifecycle-dispatch.js +5 -4
- package/dist/content-types/lifecycle.d.ts +23 -18
- package/dist/content-types/lifecycle.js +71 -64
- package/dist/content-types/repo.memory.d.ts +5 -4
- package/dist/content-types/repo.memory.js +3 -2
- package/dist/content-types/tool-registrations.d.ts +6 -8
- package/dist/content-types/tool-registrations.js +24 -28
- package/dist/content-types/types.d.ts +17 -13
- package/dist/content-types/types.js +11 -5
- package/dist/content-types/write-service.d.ts +20 -22
- package/dist/content-types/write-service.js +78 -75
- package/dist/core/authorization.d.ts +15 -0
- package/dist/core/authorization.js +2 -0
- package/dist/core/commands/change-set.d.ts +15 -7
- package/dist/core/commands/command.d.ts +45 -26
- package/dist/core/commands/command.js +73 -63
- package/dist/core/entity-liveness.d.ts +11 -7
- package/dist/core/entity-liveness.js +5 -4
- package/dist/core/index.d.ts +12 -5
- package/dist/core/index.js +10 -4
- package/dist/core/ports.d.ts +25 -24
- package/dist/core/ports.js +1 -0
- package/dist/core/tools/args.d.ts +49 -0
- package/dist/core/tools/args.js +46 -0
- package/dist/core/tools/index.d.ts +3 -0
- package/dist/core/tools/index.js +3 -0
- package/dist/entries/agent-tools.d.ts +1 -73
- package/dist/entries/agent-tools.js +19 -13
- package/dist/entries/errors.d.ts +28 -14
- package/dist/entries/errors.js +17 -11
- package/dist/entries/field-validation.d.ts +5 -3
- package/dist/entries/field-validation.js +7 -5
- package/dist/entries/index.d.ts +2 -2
- package/dist/entries/lifecycle-dispatch.d.ts +5 -2
- package/dist/entries/lifecycle-dispatch.js +2 -1
- package/dist/entries/list.d.ts +4 -2
- package/dist/entries/list.js +4 -3
- package/dist/entries/repo.memory.d.ts +12 -10
- package/dist/entries/repo.memory.js +11 -10
- package/dist/entries/tool-registrations.d.ts +6 -8
- package/dist/entries/tool-registrations.js +42 -41
- package/dist/entries/types.d.ts +0 -7
- package/dist/entries/write-service.d.ts +28 -29
- package/dist/entries/write-service.js +114 -105
- package/dist/http/settings/cms-adapter.d.ts +20 -0
- package/dist/http/settings/cms-adapter.js +124 -0
- package/dist/http/settings/contracts.d.ts +135 -0
- package/dist/http/settings/contracts.js +2 -0
- package/dist/http/settings/http.d.ts +10 -0
- package/dist/http/settings/http.js +211 -0
- package/dist/http/settings/index.d.ts +5 -0
- package/dist/http/settings/index.js +4 -0
- package/dist/http/settings/shared.d.ts +68 -0
- package/dist/http/settings/shared.js +54 -0
- package/dist/http/settings/stream.d.ts +24 -0
- package/dist/http/settings/stream.js +147 -0
- package/dist/media/agent-tools.d.ts +1 -66
- package/dist/media/agent-tools.js +5 -1
- package/dist/media/blob-gc-lock.d.ts +6 -2
- package/dist/media/blob-gc-lock.js +4 -2
- package/dist/media/blob-gc.d.ts +64 -51
- package/dist/media/blob-gc.js +62 -52
- package/dist/media/blob-key.d.ts +1 -1
- package/dist/media/content-type-sniffer.d.ts +3 -1
- package/dist/media/content-type-sniffer.js +1 -1
- package/dist/media/html-attributes.d.ts +9 -5
- package/dist/media/html-attributes.js +8 -6
- package/dist/media/image-transformer.js +1 -1
- package/dist/media/image-transformer.sharp.d.ts +6 -0
- package/dist/media/image-transformer.sharp.js +12 -6
- package/dist/media/import/fetch-image.d.ts +24 -0
- package/dist/media/import/fetch-image.js +137 -0
- package/dist/media/import/index.d.ts +3 -0
- package/dist/media/import/index.js +3 -0
- package/dist/media/import/ports.d.ts +38 -0
- package/dist/media/index.d.ts +7 -1
- package/dist/media/index.js +1 -0
- package/dist/media/media-service.d.ts +14 -10
- package/dist/media/media-service.js +57 -52
- package/dist/media/ports.d.ts +3 -2
- package/dist/media/rendition-service.d.ts +3 -3
- package/dist/media/rendition-service.js +38 -37
- package/dist/media/repo.memory.d.ts +18 -7
- package/dist/media/repo.memory.js +12 -6
- package/dist/media/tool-registrations.d.ts +16 -15
- package/dist/media/tool-registrations.js +84 -72
- package/dist/media/transform-lock.d.ts +22 -12
- package/dist/media/transform-lock.js +14 -13
- package/dist/media/transform-registry.d.ts +3 -3
- package/dist/media/transform-registry.js +19 -18
- package/dist/media/transform-types.d.ts +8 -3
- package/dist/media/transform-types.js +10 -5
- package/dist/media/types.d.ts +21 -4
- package/dist/media/types.js +15 -2
- package/dist/navigation/agent-tools.d.ts +1 -80
- package/dist/navigation/agent-tools.js +15 -3
- package/dist/navigation/contracts.d.ts +1 -1
- package/dist/navigation/index.d.ts +2 -2
- package/dist/navigation/menu-service.d.ts +57 -40
- package/dist/navigation/menu-service.js +80 -69
- package/dist/navigation/ports.d.ts +1 -1
- package/dist/navigation/read-model.d.ts +3 -1
- package/dist/navigation/read-model.js +2 -2
- package/dist/navigation/reconcile.d.ts +2 -2
- package/dist/navigation/reconcile.js +2 -1
- package/dist/navigation/repo.memory.d.ts +7 -3
- package/dist/navigation/repo.memory.js +4 -2
- package/dist/navigation/resolver.d.ts +4 -1
- package/dist/navigation/resolver.js +1 -1
- package/dist/navigation/tool-registrations.d.ts +6 -8
- package/dist/navigation/tool-registrations.js +44 -42
- package/dist/navigation/types.d.ts +1 -1
- package/dist/presentation/presentation.d.ts +11 -3
- package/dist/presentation/presentation.js +13 -6
- package/dist/presentation/repo.memory.d.ts +6 -2
- package/dist/presentation/repo.memory.js +3 -2
- package/dist/settings/agent-tools.d.ts +2 -75
- package/dist/settings/agent-tools.js +49 -45
- package/dist/settings/agent-value-write-tools.d.ts +22 -0
- package/dist/settings/agent-value-write-tools.js +154 -0
- package/dist/settings/agent-writable-preferences.js +8 -20
- package/dist/settings/agent-write-denylist.d.ts +19 -0
- package/dist/settings/agent-write-denylist.js +15 -0
- package/dist/settings/change-feed.d.ts +11 -2
- package/dist/settings/change-feed.js +4 -4
- package/dist/settings/definitions-dispatch.d.ts +8 -3
- package/dist/settings/definitions-dispatch.js +5 -5
- package/dist/settings/dictionaries/index.d.ts +5 -2
- package/dist/settings/dictionaries/index.js +2 -2
- package/dist/settings/dictionaries/settings-dialog.ar.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.bn.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.de.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.es.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.fa.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.fr.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.hi.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.hu.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.id.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.it.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ja.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ko.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.pl.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.pt-BR.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ru.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.th.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.tr.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.uk.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.ur.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.zh-CN.js +0 -1
- package/dist/settings/dictionaries/settings-dialog.zh-TW.js +0 -1
- package/dist/settings/ensure-definitions.d.ts +5 -5
- package/dist/settings/errors.d.ts +7 -1
- package/dist/settings/errors.js +7 -1
- package/dist/settings/index.d.ts +4 -1
- package/dist/settings/index.js +1 -0
- package/dist/settings/ports.d.ts +6 -5
- package/dist/settings/principal-lookup.d.ts +9 -0
- package/dist/settings/principal-lookup.js +2 -0
- package/dist/settings/purge-service.d.ts +5 -4
- package/dist/settings/purge-service.js +3 -2
- package/dist/settings/settings.d.ts +22 -7
- package/dist/settings/settings.js +47 -26
- package/dist/settings/tool-registrations.d.ts +25 -32
- package/dist/settings/tool-registrations.js +66 -56
- package/dist/settings/types.d.ts +1 -1
- package/dist/settings/ui-tab-definitions.d.ts +1 -1
- package/dist/settings/write-service.d.ts +32 -26
- package/dist/settings/write-service.js +26 -20
- package/dist/taxonomy/agent-tools.d.ts +1 -66
- package/dist/taxonomy/agent-tools.js +25 -15
- package/dist/taxonomy/content-lookup.js +1 -1
- package/dist/taxonomy/index.d.ts +2 -2
- package/dist/taxonomy/merge-term.d.ts +7 -4
- package/dist/taxonomy/merge-term.js +7 -5
- package/dist/taxonomy/repo.memory.d.ts +42 -19
- package/dist/taxonomy/repo.memory.js +14 -12
- package/dist/taxonomy/validation-chain.d.ts +39 -18
- package/dist/taxonomy/validation-chain.js +35 -25
- package/dist/taxonomy/write-service.d.ts +106 -45
- package/dist/taxonomy/write-service.js +221 -179
- package/dist/trash/adapters/record-store.d.ts +52 -0
- package/dist/trash/adapters/record-store.js +36 -0
- package/dist/trash/cursor.d.ts +20 -0
- package/dist/trash/cursor.js +36 -0
- package/dist/trash/follow-ups.d.ts +76 -0
- package/dist/trash/follow-ups.js +46 -0
- package/dist/trash/index.d.ts +8 -0
- package/dist/trash/index.js +8 -0
- package/dist/trash/ports.d.ts +308 -0
- package/dist/trash/ports.js +2 -0
- package/dist/trash/repo.memory.d.ts +50 -0
- package/dist/trash/repo.memory.js +114 -0
- package/dist/trash/sweeper.d.ts +77 -0
- package/dist/trash/sweeper.js +127 -0
- package/dist/trash/write-service.d.ts +100 -0
- package/dist/trash/write-service.js +242 -0
- package/dist/workspace/agent-tools.d.ts +1 -59
- package/dist/workspace/agent-tools.js +27 -12
- package/dist/workspace/create.d.ts +31 -6
- package/dist/workspace/create.js +18 -5
- package/dist/workspace/delete.d.ts +18 -13
- package/dist/workspace/delete.js +20 -12
- package/dist/workspace/index.d.ts +1 -1
- package/dist/workspace/repo.memory.d.ts +19 -4
- package/dist/workspace/repo.memory.js +26 -4
- package/dist/workspace/tool-registrations.d.ts +2 -2
- package/dist/workspace/tool-registrations.js +13 -13
- package/dist/workspace/update.d.ts +6 -5
- package/dist/workspace/update.js +9 -8
- package/package.json +44 -38
- package/dist/identity/admin-crud-service.d.ts +0 -280
- package/dist/identity/admin-crud-service.js +0 -468
- package/dist/identity/agent-tool-input.d.ts +0 -58
- package/dist/identity/agent-tool-input.js +0 -126
- package/dist/identity/agent-tools.d.ts +0 -101
- package/dist/identity/agent-tools.js +0 -290
- package/dist/identity/auth-service.d.ts +0 -118
- package/dist/identity/auth-service.js +0 -170
- package/dist/identity/authorize.d.ts +0 -90
- package/dist/identity/authorize.js +0 -94
- package/dist/identity/grant-service.d.ts +0 -198
- package/dist/identity/grant-service.js +0 -361
- package/dist/identity/hasher.d.ts +0 -39
- package/dist/identity/hasher.js +0 -39
- package/dist/identity/index.d.ts +0 -62
- package/dist/identity/index.js +0 -54
- package/dist/identity/password-policy.d.ts +0 -57
- package/dist/identity/password-policy.js +0 -66
- package/dist/identity/permission-migrations.d.ts +0 -86
- package/dist/identity/permission-migrations.js +0 -67
- package/dist/identity/permissions.d.ts +0 -35
- package/dist/identity/permissions.js +0 -603
- package/dist/identity/ports.d.ts +0 -201
- package/dist/identity/repo.memory.d.ts +0 -164
- package/dist/identity/repo.memory.js +0 -224
- package/dist/identity/seed.d.ts +0 -54
- package/dist/identity/seed.js +0 -429
- package/dist/identity/tool-registrations.d.ts +0 -50
- package/dist/identity/tool-registrations.js +0 -419
- package/dist/identity/types.d.ts +0 -185
- package/dist/identity/types.js +0 -64
- package/dist/identity/username.d.ts +0 -12
- package/dist/identity/username.js +0 -14
- /package/dist/{identity → media/import}/ports.js +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Marker adapter for record stores in compositions without column SQL.
|
|
3
|
+
* Persistent adapters must flip marker columns without parsing payloads so corrupt rows remain
|
|
4
|
+
* removable. A host backed by memory records supplies the same three operations through its store.
|
|
5
|
+
* This adapter has no trash-index knowledge: the service owns indexing and transactions, and this
|
|
6
|
+
* supplies only the domain half of the marker flip.
|
|
7
|
+
*/
|
|
8
|
+
import type { TrashAdapter } from "../ports.js";
|
|
9
|
+
/** The narrowest shape a marker flip needs. Host in-memory repositories supply it. */
|
|
10
|
+
export interface TrashRecordStore<T extends {
|
|
11
|
+
version: number;
|
|
12
|
+
}> {
|
|
13
|
+
findById(required: {
|
|
14
|
+
workspaceId: string;
|
|
15
|
+
id: string;
|
|
16
|
+
}): Promise<T | null>;
|
|
17
|
+
save(required: {
|
|
18
|
+
record: T;
|
|
19
|
+
}): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export interface RecordStoreTrashAdapterDeps<T extends {
|
|
22
|
+
version: number;
|
|
23
|
+
}> {
|
|
24
|
+
entityType: string;
|
|
25
|
+
store: TrashRecordStore<T>;
|
|
26
|
+
/** Pure: set the hidden marker and stamp the caller-supplied timestamp. */
|
|
27
|
+
hidden(required: {
|
|
28
|
+
record: T;
|
|
29
|
+
at: string;
|
|
30
|
+
}): T;
|
|
31
|
+
/** Pure: clear the marker, preserving the stored payload. */
|
|
32
|
+
shown(required: {
|
|
33
|
+
record: T;
|
|
34
|
+
at: string;
|
|
35
|
+
}): T;
|
|
36
|
+
/** Detect repeated flips so a no-op cannot re-fire transactional follow-ups. */
|
|
37
|
+
isHidden(required: {
|
|
38
|
+
record: T;
|
|
39
|
+
}): boolean;
|
|
40
|
+
}
|
|
41
|
+
/** @complexity O(1) to build; every method is one store read plus at most one store write. */
|
|
42
|
+
export declare function createRecordStoreTrashAdapter<T extends {
|
|
43
|
+
version: number;
|
|
44
|
+
}>(deps: RecordStoreTrashAdapterDeps<T>,
|
|
45
|
+
/** Without physical deletion, purge stands down and never claims a removal it did not make. */
|
|
46
|
+
optional?: {
|
|
47
|
+
hardDelete?: (required: {
|
|
48
|
+
workspaceId: string;
|
|
49
|
+
id: string;
|
|
50
|
+
}) => Promise<void>;
|
|
51
|
+
}): TrashAdapter;
|
|
52
|
+
//# sourceMappingURL=record-store.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** @complexity O(1) to build; every method is one store read plus at most one store write. */
|
|
2
|
+
export function createRecordStoreTrashAdapter(deps,
|
|
3
|
+
/** Without physical deletion, purge stands down and never claims a removal it did not make. */
|
|
4
|
+
optional = {}) {
|
|
5
|
+
async function flip(required, direction) {
|
|
6
|
+
const existing = await deps.store.findById({ workspaceId: required.workspaceId, id: required.entityId });
|
|
7
|
+
if (!existing)
|
|
8
|
+
return { ok: false, reason: "not-found" };
|
|
9
|
+
if (required.expectedVersion !== null && existing.version !== required.expectedVersion) {
|
|
10
|
+
return { ok: false, reason: "version-changed" };
|
|
11
|
+
}
|
|
12
|
+
const alreadyThere = direction === "hide" ? deps.isHidden({ record: existing }) : !deps.isHidden({ record: existing });
|
|
13
|
+
if (alreadyThere)
|
|
14
|
+
return { ok: true, version: existing.version, noop: true };
|
|
15
|
+
const moved = direction === "hide" ? deps.hidden({ record: existing, at: required.at }) : deps.shown({ record: existing, at: required.at });
|
|
16
|
+
const next = { ...moved, version: existing.version + 1 };
|
|
17
|
+
await deps.store.save({ record: next });
|
|
18
|
+
return { ok: true, version: next.version };
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
entityType: deps.entityType,
|
|
22
|
+
hide: (required) => flip(required, "hide"),
|
|
23
|
+
unhide: (required) => flip(required, "unhide"),
|
|
24
|
+
async purge(required) {
|
|
25
|
+
const existing = await deps.store.findById({ workspaceId: required.workspaceId, id: required.entityId });
|
|
26
|
+
if (!existing)
|
|
27
|
+
return "already-gone";
|
|
28
|
+
if (required.expectedVersion !== null && existing.version !== required.expectedVersion)
|
|
29
|
+
return "version-changed";
|
|
30
|
+
await optional.hardDelete?.({ workspaceId: required.workspaceId, id: required.entityId });
|
|
31
|
+
const after = await deps.store.findById({ workspaceId: required.workspaceId, id: required.entityId });
|
|
32
|
+
return after === null ? "purged" : "version-changed";
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=record-store.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** @complexity O(1). */
|
|
2
|
+
export declare function encodeTrashCursor(required: {
|
|
3
|
+
trashedAt: string;
|
|
4
|
+
id: string;
|
|
5
|
+
}): string;
|
|
6
|
+
/**
|
|
7
|
+
* Decodes a cursor produced by {@link encodeTrashCursor}.
|
|
8
|
+
*
|
|
9
|
+
* Returns `null` for anything unparseable rather than throwing — a stale or hand-edited cursor
|
|
10
|
+
* degrades to "start from the top", which is the safe reading for a list of deleted things.
|
|
11
|
+
*
|
|
12
|
+
* @complexity O(1).
|
|
13
|
+
*/
|
|
14
|
+
export declare function decodeTrashCursor({ cursor }: {
|
|
15
|
+
cursor: string | null | undefined;
|
|
16
|
+
}): {
|
|
17
|
+
trashedAt: string;
|
|
18
|
+
id: string;
|
|
19
|
+
} | null;
|
|
20
|
+
//# sourceMappingURL=cursor.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Keyset cursor codec for the Trash list, shared by both `TrashRepoPort` adapters so the
|
|
3
|
+
* persistent repo and the in-memory test double cannot drift on pagination.
|
|
4
|
+
*
|
|
5
|
+
* Keyset on `(trashed_at, id)` rather than OFFSET: the sweeper deletes rows underneath a paging
|
|
6
|
+
* reader, and OFFSET would silently skip an item every time a row above the window disappeared.
|
|
7
|
+
*/
|
|
8
|
+
const SEPARATOR = "\u0000";
|
|
9
|
+
/** @complexity O(1). */
|
|
10
|
+
export function encodeTrashCursor(required) {
|
|
11
|
+
return Buffer.from(`${required.trashedAt}${SEPARATOR}${required.id}`, "utf8").toString("base64url");
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Decodes a cursor produced by {@link encodeTrashCursor}.
|
|
15
|
+
*
|
|
16
|
+
* Returns `null` for anything unparseable rather than throwing — a stale or hand-edited cursor
|
|
17
|
+
* degrades to "start from the top", which is the safe reading for a list of deleted things.
|
|
18
|
+
*
|
|
19
|
+
* @complexity O(1).
|
|
20
|
+
*/
|
|
21
|
+
export function decodeTrashCursor({ cursor }) {
|
|
22
|
+
if (!cursor)
|
|
23
|
+
return null;
|
|
24
|
+
let decoded;
|
|
25
|
+
try {
|
|
26
|
+
decoded = Buffer.from(cursor, "base64url").toString("utf8");
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const at = decoded.indexOf(SEPARATOR);
|
|
32
|
+
if (at <= 0 || at === decoded.length - 1)
|
|
33
|
+
return null;
|
|
34
|
+
return { trashedAt: decoded.slice(0, at), id: decoded.slice(at + 1) };
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `withFollowUps` — lets one Trash type finish a hide, restore or purge its own generic marker
|
|
3
|
+
* flip cannot express, without teaching the generic table adapter anything about that type. T1's
|
|
4
|
+
* generalization of the A0 `withRestoreFollowUp` sketch (plan §2 T1 item 6) into one decorator with
|
|
5
|
+
* three optional hooks, so T5/T6 (menus, terms, taxonomies) register their own event/revision
|
|
6
|
+
* follow-ups the same way `widget` already does, rather than each writing a bespoke wrapper.
|
|
7
|
+
*
|
|
8
|
+
* Every hook:
|
|
9
|
+
* - runs inside the op's own transaction — `TrashPort.trash`/`restore`/`purgeSelected` (`write-
|
|
10
|
+
* service.ts`) already wrap the adapter call in `deps.transaction`, and this decorator's hooks run
|
|
11
|
+
* from inside that same call, so a hook backed by the same host database connection
|
|
12
|
+
* joins it automatically (the transaction runner must be reentrant);
|
|
13
|
+
* - runs only on an OK/`"purged"` result that actually changed something — a no-op outcome
|
|
14
|
+
* (`not-found`, `version-changed`, `blocked`, `already-gone`) never fires one, because nothing
|
|
15
|
+
* changed, and neither does `hide`/`unhide`'s own idempotent "already in the target state" branch,
|
|
16
|
+
* which also reports `ok: true` with no transition; `TrashMarkerResult.noop` (`ports.ts`) is what
|
|
17
|
+
* tells that branch apart from a real one, since both otherwise share the same `ok: true` shape
|
|
18
|
+
* (T1c, `follow-ups.test.ts` — the two were conflated here until that dispatch's direct test of
|
|
19
|
+
* this file caught it: a re-hide/re-unhide of an already-trashed/already-live row used to refire
|
|
20
|
+
* the hook);
|
|
21
|
+
* - propagates a throw, which rolls the whole op back (the state change and the hook are one unit).
|
|
22
|
+
*
|
|
23
|
+
* `afterPurge` is paired with `beforePurge`: the row is gone the instant `purge` reports `"purged"`
|
|
24
|
+
* (a DELETE is visible to later reads in the SAME transaction even before COMMIT), so anything a
|
|
25
|
+
* purge hook needs to see about the row's pre-purge state (a taxonomy revision's
|
|
26
|
+
* `previousStateJson`) has to be read BEFORE the delete runs. `beforePurge`'s return value is handed
|
|
27
|
+
* back to `afterPurge` untouched — this file never interprets it, keeping every hook's own shape a
|
|
28
|
+
* type-specific concern for its host composition, not for this shared service.
|
|
29
|
+
*/
|
|
30
|
+
import type { TrashAdapter } from "./ports.js";
|
|
31
|
+
/** Finishes one hide. Only fires when `hide` actually transitioned the row (never on the idempotent
|
|
32
|
+
* "already trashed" branch, and never when `hide` returned `blocked`/`not-found`/`version-changed`). */
|
|
33
|
+
export type HideFollowUp = (required: {
|
|
34
|
+
workspaceId: string;
|
|
35
|
+
entityId: string;
|
|
36
|
+
at: string;
|
|
37
|
+
}) => Promise<void>;
|
|
38
|
+
/** Finishes one restore. Receives the Trash row's stored `priorMarker`; never called without one —
|
|
39
|
+
* same contract the A0 `RestoreFollowUp` (widget adoption) already relied on. */
|
|
40
|
+
export type UnhideFollowUp = (required: {
|
|
41
|
+
workspaceId: string;
|
|
42
|
+
entityId: string;
|
|
43
|
+
priorMarker: string;
|
|
44
|
+
at: string;
|
|
45
|
+
}) => Promise<void>;
|
|
46
|
+
/** Reads whatever "prior state" a purge follow-up needs, before the row is deleted. Runs even when
|
|
47
|
+
* the purge that follows turns out NOT to succeed (a version race) — it is a read, not a write, so
|
|
48
|
+
* that costs nothing beyond one extra query on an already-rare path. */
|
|
49
|
+
export type BeforePurge = (required: {
|
|
50
|
+
workspaceId: string;
|
|
51
|
+
entityId: string;
|
|
52
|
+
}) => Promise<unknown>;
|
|
53
|
+
/** Finishes one purge. `priorState` is exactly what `beforePurge` returned (`undefined` when the
|
|
54
|
+
* caller registered no `beforePurge`). Fires only on a `"purged"` outcome. */
|
|
55
|
+
export type AfterPurge = (required: {
|
|
56
|
+
workspaceId: string;
|
|
57
|
+
entityId: string;
|
|
58
|
+
priorState: unknown;
|
|
59
|
+
}) => Promise<void>;
|
|
60
|
+
export interface TrashFollowUpHooks {
|
|
61
|
+
afterHide?: HideFollowUp;
|
|
62
|
+
afterUnhide?: UnhideFollowUp;
|
|
63
|
+
beforePurge?: BeforePurge;
|
|
64
|
+
afterPurge?: AfterPurge;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Wraps `adapter` so its `hide`/`unhide`/`purge` also run whichever of `hooks`'s optional callbacks
|
|
68
|
+
* applies, per the OK/success rule in the file header. An entry with no hooks at all should not call
|
|
69
|
+
* this — host composition only wraps entries that have at least one.
|
|
70
|
+
*
|
|
71
|
+
* @complexity O(1) beyond the adapter's own methods and whichever hook fires.
|
|
72
|
+
*/
|
|
73
|
+
export declare function withFollowUps(required: {
|
|
74
|
+
adapter: TrashAdapter;
|
|
75
|
+
}, hooks?: TrashFollowUpHooks): TrashAdapter;
|
|
76
|
+
//# sourceMappingURL=follow-ups.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps `adapter` so its `hide`/`unhide`/`purge` also run whichever of `hooks`'s optional callbacks
|
|
3
|
+
* applies, per the OK/success rule in the file header. An entry with no hooks at all should not call
|
|
4
|
+
* this — host composition only wraps entries that have at least one.
|
|
5
|
+
*
|
|
6
|
+
* @complexity O(1) beyond the adapter's own methods and whichever hook fires.
|
|
7
|
+
*/
|
|
8
|
+
export function withFollowUps(required, hooks = {}) {
|
|
9
|
+
const { adapter } = required;
|
|
10
|
+
return {
|
|
11
|
+
entityType: adapter.entityType,
|
|
12
|
+
async hide(hideRequired, hideOptional) {
|
|
13
|
+
const result = await adapter.hide(hideRequired, hideOptional);
|
|
14
|
+
// Idempotent flips also report ok:true; noop is the signal that no transition occurred.
|
|
15
|
+
if (result.ok && !result.noop && hooks.afterHide) {
|
|
16
|
+
await hooks.afterHide({ workspaceId: hideRequired.workspaceId, entityId: hideRequired.entityId, at: hideRequired.at });
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
},
|
|
20
|
+
async unhide(unhideRequired, unhideOptional) {
|
|
21
|
+
const result = await adapter.unhide(unhideRequired, unhideOptional);
|
|
22
|
+
const priorMarker = unhideOptional?.priorMarker;
|
|
23
|
+
// The same noop guard prevents an already-live row from re-firing a restore follow-up.
|
|
24
|
+
if (result.ok && !result.noop && typeof priorMarker === "string" && hooks.afterUnhide) {
|
|
25
|
+
await hooks.afterUnhide({
|
|
26
|
+
workspaceId: unhideRequired.workspaceId,
|
|
27
|
+
entityId: unhideRequired.entityId,
|
|
28
|
+
priorMarker,
|
|
29
|
+
at: unhideRequired.at,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
},
|
|
34
|
+
async purge(purgeRequired, purgeOptional) {
|
|
35
|
+
const priorState = hooks.beforePurge
|
|
36
|
+
? await hooks.beforePurge({ workspaceId: purgeRequired.workspaceId, entityId: purgeRequired.entityId })
|
|
37
|
+
: undefined;
|
|
38
|
+
const result = await adapter.purge(purgeRequired, purgeOptional);
|
|
39
|
+
if (result === "purged" && hooks.afterPurge) {
|
|
40
|
+
await hooks.afterPurge({ workspaceId: purgeRequired.workspaceId, entityId: purgeRequired.entityId, priorState });
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=follow-ups.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./ports.js";
|
|
2
|
+
export * from "./write-service.js";
|
|
3
|
+
export * from "./sweeper.js";
|
|
4
|
+
export * from "./follow-ups.js";
|
|
5
|
+
export * from "./repo.memory.js";
|
|
6
|
+
export * from "./cursor.js";
|
|
7
|
+
export * from "./adapters/record-store.js";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from "./ports.js";
|
|
2
|
+
export * from "./write-service.js";
|
|
3
|
+
export * from "./sweeper.js";
|
|
4
|
+
export * from "./follow-ups.js";
|
|
5
|
+
export * from "./repo.memory.js";
|
|
6
|
+
export * from "./cursor.js";
|
|
7
|
+
export * from "./adapters/record-store.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Column/record contracts for snapshot-only trash listing and atomic marker/index changes.
|
|
3
|
+
* A trash operation never round-trips an entity payload, and listing never reads the entity at all.
|
|
4
|
+
* Parsing a payload merely to flip its status fails on the corrupt rows a user most wants gone.
|
|
5
|
+
* `trash` requires a caller-held display snapshot. Adapters expose only hide/unhide/purge, with no
|
|
6
|
+
* describe method: a read that can throw must never become a dependency of trash listing.
|
|
7
|
+
*/
|
|
8
|
+
export type TrashEntityType = string;
|
|
9
|
+
/** The two display strings captured from columns at trash time. Never derived by the port. */
|
|
10
|
+
export interface TrashDisplay {
|
|
11
|
+
title: string;
|
|
12
|
+
subtitle?: string | null;
|
|
13
|
+
}
|
|
14
|
+
export interface TrashActor {
|
|
15
|
+
principalId: string;
|
|
16
|
+
pluginId?: string | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Outcome of a marker flip.
|
|
20
|
+
*
|
|
21
|
+
* `version` is the entity's version AFTER the flip — that is what lands in
|
|
22
|
+
* `trashed_items.entity_version` and what the sweeper's compare-and-delete later checks, so a
|
|
23
|
+
* restore (which bumps the version again) always wins a race against a purge. `null` for a domain
|
|
24
|
+
* with no version column.
|
|
25
|
+
*
|
|
26
|
+
* `priorMarker` is OPTIONAL and additive (migration 0072): a status-marker adapter (`menu`, `term`,
|
|
27
|
+
* `taxonomy`) reports the status the row carried immediately before `hide` flipped it to `trash`, so
|
|
28
|
+
* `restore` can put it back exactly rather than to a fixed fallback. A caller that omits the key
|
|
29
|
+
* entirely (timestamp adapters) keeps the exact `{ ok: true, version }` shape — do not set this to
|
|
30
|
+
* `undefined`; leave the key out. `trash.contract.test.ts`'s pinned `assert.deepEqual(trashed, {
|
|
31
|
+
* ok: true, version: 2 })` depends on that: Node's `assert.deepEqual` treats an extra own-enumerable
|
|
32
|
+
* property, even one valued `undefined`, as a mismatch.
|
|
33
|
+
*
|
|
34
|
+
* `noop` is OPTIONAL and additive (T1c, `follow-ups.test.ts`): host table adapters' `hide`/`unhide`
|
|
35
|
+
* set it to `true` on their idempotent "already in the target state" branch — the ONE signal that
|
|
36
|
+
* distinguishes that branch from a real transition, both of which otherwise report the same
|
|
37
|
+
* `ok: true`. `withFollowUps` (`@jini-ai/cms/trash`) reads it to decide whether a hook actually finished
|
|
38
|
+
* something. Same leave-the-key-out rule as `priorMarker`: a real transition leaves it absent, so exact-shape assertions remain
|
|
39
|
+
* valid, so their pinned exact-shape assertions are
|
|
40
|
+
* unaffected.
|
|
41
|
+
*
|
|
42
|
+
* `"blocked"` (T1 item 2, migration-free — no new column) is a THIRD failure reason, alongside
|
|
43
|
+
* `"not-found"`/`"version-changed"`: the row exists and the version matches, but the host's
|
|
44
|
+
* declared blocker found rows that must move or be deleted first (a term with child terms). `code`
|
|
45
|
+
* and `count` are always present together on this branch — the host registry defines their meaning.
|
|
46
|
+
*/
|
|
47
|
+
export type TrashMarkerResult = {
|
|
48
|
+
ok: true;
|
|
49
|
+
version: number | null;
|
|
50
|
+
priorMarker?: string | null;
|
|
51
|
+
noop?: true;
|
|
52
|
+
} | {
|
|
53
|
+
ok: false;
|
|
54
|
+
reason: "not-found" | "version-changed";
|
|
55
|
+
} | {
|
|
56
|
+
ok: false;
|
|
57
|
+
reason: "blocked";
|
|
58
|
+
code: string;
|
|
59
|
+
count: number;
|
|
60
|
+
};
|
|
61
|
+
/** Outcome of a physical row removal. */
|
|
62
|
+
export type TrashPurgeOutcome = "purged" | "version-changed" | "already-gone";
|
|
63
|
+
/**
|
|
64
|
+
* One per domain. Built and registered into a plain `Map` at the composition root and resolved
|
|
65
|
+
* **at call time**, never at registration time: this codebase's module registries (`ToolRegistry`,
|
|
66
|
+
* routing's `phaseRegistry`) are append-only with no unregister, so anything that filters at
|
|
67
|
+
* registration runs exactly once. Two real bugs already came from that.
|
|
68
|
+
*
|
|
69
|
+
* Persistent host implementations MUST be column-only SQL. No payload parse, no ORM entity hydration, no domain
|
|
70
|
+
* write-service call that re-validates a record.
|
|
71
|
+
*/
|
|
72
|
+
export interface TrashAdapter {
|
|
73
|
+
readonly entityType: TrashEntityType;
|
|
74
|
+
/**
|
|
75
|
+
* Move the domain's own marker to hidden.
|
|
76
|
+
*
|
|
77
|
+
* `at` is supplied by the caller rather than read from an adapter-local clock so the marker's
|
|
78
|
+
* timestamp is byte-identical to the one the calling domain writes into its revision ledger.
|
|
79
|
+
*/
|
|
80
|
+
hide(required: {
|
|
81
|
+
workspaceId: string;
|
|
82
|
+
entityId: string;
|
|
83
|
+
at: string;
|
|
84
|
+
expectedVersion: number | null;
|
|
85
|
+
}, optional?: {
|
|
86
|
+
actor?: TrashActor;
|
|
87
|
+
}): Promise<TrashMarkerResult>;
|
|
88
|
+
/**
|
|
89
|
+
* Move it back. Same no-parse rule as {@link TrashAdapter.hide}.
|
|
90
|
+
*
|
|
91
|
+
* `priorMarker` (migration 0072) is what {@link TrashItem.priorMarker} stored at trash time —
|
|
92
|
+
* additive and optional, so every adapter's existing signature stays structurally compatible. A
|
|
93
|
+
* status-marker adapter restores to `priorMarker ?? restoreFallback`; a timestamp-marker adapter
|
|
94
|
+
* (timestamp adapters) ignores it, since clearing the marker column needs no prior value.
|
|
95
|
+
*/
|
|
96
|
+
unhide(required: {
|
|
97
|
+
workspaceId: string;
|
|
98
|
+
entityId: string;
|
|
99
|
+
at: string;
|
|
100
|
+
expectedVersion: number | null;
|
|
101
|
+
}, optional?: {
|
|
102
|
+
priorMarker?: string | null;
|
|
103
|
+
actor?: TrashActor;
|
|
104
|
+
}): Promise<TrashMarkerResult>;
|
|
105
|
+
/** Physically remove the row. Compare-and-delete on `expectedVersion`. */
|
|
106
|
+
purge(required: {
|
|
107
|
+
workspaceId: string;
|
|
108
|
+
entityId: string;
|
|
109
|
+
expectedVersion: number | null;
|
|
110
|
+
}, optional?: {
|
|
111
|
+
actor?: TrashActor;
|
|
112
|
+
}): Promise<TrashPurgeOutcome>;
|
|
113
|
+
}
|
|
114
|
+
/** One row of the Trash list. Rendered entirely from the snapshot — no entity read. */
|
|
115
|
+
export interface TrashItem {
|
|
116
|
+
id: string;
|
|
117
|
+
workspaceId: string;
|
|
118
|
+
entityType: TrashEntityType;
|
|
119
|
+
entityId: string;
|
|
120
|
+
trashedAt: string;
|
|
121
|
+
purgeAfter: string;
|
|
122
|
+
actorPrincipalId: string;
|
|
123
|
+
actorPluginId: string | null;
|
|
124
|
+
displayTitle: string;
|
|
125
|
+
displaySubtitle: string | null;
|
|
126
|
+
entityVersion: number | null;
|
|
127
|
+
priorMarker: string | null;
|
|
128
|
+
}
|
|
129
|
+
export interface TrashPage {
|
|
130
|
+
items: TrashItem[];
|
|
131
|
+
/** Keyset cursor on `(trashed_at, id)`; `null` when the page is the last one. */
|
|
132
|
+
nextCursor: string | null;
|
|
133
|
+
}
|
|
134
|
+
/** Per-item outcome of {@link TrashPort.purgeSelected}. */
|
|
135
|
+
export type PurgeItemOutcome = "purged" | "already-gone" | "version-changed" | "not-found" | "adapter-unavailable" | "forbidden";
|
|
136
|
+
/**
|
|
137
|
+
* Decides whether the caller may permanently destroy ONE already-resolved trash row.
|
|
138
|
+
*
|
|
139
|
+
* Purge addresses rows by trash row id, and the per-kind permission has to be checked against the
|
|
140
|
+
* kind the STORED row actually has. A caller that supplied the kind alongside the id could name
|
|
141
|
+
* `comment` for a post's row and destroy a post holding only `comments.moderate`. So the gate is
|
|
142
|
+
* handed the row `purgeSelected` looked up, inside the same call that then purges it — which closes
|
|
143
|
+
* the read-then-act window as well as the escalation.
|
|
144
|
+
*
|
|
145
|
+
* Returning `false` (rather than throwing) keeps a denial a per-item outcome: one forbidden row in
|
|
146
|
+
* a hand-ticked selection must not abort the rows the caller may destroy.
|
|
147
|
+
*/
|
|
148
|
+
export type TrashItemAuthorizer = (required: {
|
|
149
|
+
item: TrashItem;
|
|
150
|
+
}) => Promise<boolean>;
|
|
151
|
+
export interface PurgeReport {
|
|
152
|
+
purged: number;
|
|
153
|
+
results: {
|
|
154
|
+
id: string;
|
|
155
|
+
outcome: PurgeItemOutcome;
|
|
156
|
+
}[];
|
|
157
|
+
}
|
|
158
|
+
/** Outcome of a restore. `adapter-unavailable` is honest degradation, not a throw: the row lists
|
|
159
|
+
* from its snapshot even when its plugin has been uninstalled. */
|
|
160
|
+
export type RestoreOutcome = "restored" | "not-found" | "version-changed" | "adapter-unavailable";
|
|
161
|
+
export interface TrashPort {
|
|
162
|
+
/**
|
|
163
|
+
* Hide the entity AND index it, as ONE transaction. There is deliberately no public seam that
|
|
164
|
+
* performs only one of the two writes — a failure between them would leave an item that is in the
|
|
165
|
+
* Trash and still live on the site, or hidden with no Trash row, which is unrecoverable from the UI.
|
|
166
|
+
*/
|
|
167
|
+
trash(required: {
|
|
168
|
+
workspaceId: string;
|
|
169
|
+
entityType: TrashEntityType;
|
|
170
|
+
entityId: string;
|
|
171
|
+
actor: TrashActor;
|
|
172
|
+
display: TrashDisplay;
|
|
173
|
+
at: string;
|
|
174
|
+
expectedVersion: number | null;
|
|
175
|
+
}, optional?: {
|
|
176
|
+
priorMarker?: string | null;
|
|
177
|
+
}): Promise<TrashMarkerResult>;
|
|
178
|
+
restore(required: {
|
|
179
|
+
workspaceId: string;
|
|
180
|
+
entityType: TrashEntityType;
|
|
181
|
+
entityId: string;
|
|
182
|
+
at: string;
|
|
183
|
+
}, optional?: {
|
|
184
|
+
actor?: TrashActor;
|
|
185
|
+
}): Promise<RestoreOutcome>;
|
|
186
|
+
/** Excludes `purge_after <= now`: a dormant site shows nothing expired on its first render, with
|
|
187
|
+
* no wait for a sweep. */
|
|
188
|
+
list(required: {
|
|
189
|
+
workspaceId: string;
|
|
190
|
+
now: string;
|
|
191
|
+
limit: number;
|
|
192
|
+
}, optional?: {
|
|
193
|
+
entityTypes?: readonly TrashEntityType[];
|
|
194
|
+
cursor?: string | null;
|
|
195
|
+
}): Promise<TrashPage>;
|
|
196
|
+
/**
|
|
197
|
+
* Human-confirmed: the admin Trash screen and `permanent-delete` tools may reach this port only
|
|
198
|
+
* after confirmation. Tool input cannot self-confirm; see the confirmed-call contract tests.
|
|
199
|
+
*
|
|
200
|
+
* `authorizeItem` is REQUIRED, not optional. A permission check a call site may omit is a
|
|
201
|
+
* permission check some call site eventually omits, and this operation is the one that cannot be
|
|
202
|
+
* undone. See {@link TrashItemAuthorizer} for why the gate takes the resolved row.
|
|
203
|
+
*/
|
|
204
|
+
purgeSelected(required: {
|
|
205
|
+
workspaceId: string;
|
|
206
|
+
ids: readonly string[];
|
|
207
|
+
actor: TrashActor;
|
|
208
|
+
authorizeItem: TrashItemAuthorizer;
|
|
209
|
+
}): Promise<PurgeReport>;
|
|
210
|
+
}
|
|
211
|
+
export type RemoveEntity = (required: {
|
|
212
|
+
workspaceId: string;
|
|
213
|
+
id: string;
|
|
214
|
+
display: TrashDisplay;
|
|
215
|
+
at: string;
|
|
216
|
+
expectedVersion: number | null;
|
|
217
|
+
actor: TrashActor;
|
|
218
|
+
}, optional?: {
|
|
219
|
+
priorMarker?: string | null;
|
|
220
|
+
}) => Promise<TrashMarkerResult>;
|
|
221
|
+
/**
|
|
222
|
+
* The function a domain's delete path receives to DROP an index row.
|
|
223
|
+
*
|
|
224
|
+
* Needed by any domain that can leave the trashable state by a route other than a Trash-screen
|
|
225
|
+
* restore or purge: a comment approved back out of `trash` by ordinary moderation, a media asset
|
|
226
|
+
* hard-purged from the admin delete rung. Without it the index row outlives the condition it
|
|
227
|
+
* records, and the Trash screen offers a Restore or a permanent delete for something that is either
|
|
228
|
+
* live again or already gone.
|
|
229
|
+
*
|
|
230
|
+
* NOT a half of {@link TrashPort.trash}. It writes no marker and hides nothing — it only forgets a
|
|
231
|
+
* removal that some other owner has already undone or completed.
|
|
232
|
+
*/
|
|
233
|
+
export type ForgetRemovedEntity = (required: {
|
|
234
|
+
workspaceId: string;
|
|
235
|
+
id: string;
|
|
236
|
+
}) => Promise<void>;
|
|
237
|
+
/** A claimed batch of due rows, handed to the sweeper. */
|
|
238
|
+
export interface TrashSweepClaim {
|
|
239
|
+
id: string;
|
|
240
|
+
workspaceId: string;
|
|
241
|
+
entityType: TrashEntityType;
|
|
242
|
+
entityId: string;
|
|
243
|
+
entityVersion: number | null;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Storage for `trashed_items`. Adapters: host persistence + in-memory reference adapter.
|
|
247
|
+
*
|
|
248
|
+
* Every method is column-only. Nothing here ever reaches into a domain table.
|
|
249
|
+
*/
|
|
250
|
+
export interface TrashRepoPort {
|
|
251
|
+
/** Idempotent by `(workspace_id, entity_type, entity_id)` — re-trashing is a no-op, not a
|
|
252
|
+
* duplicate row. */
|
|
253
|
+
insert(required: {
|
|
254
|
+
row: TrashItem;
|
|
255
|
+
}): Promise<void>;
|
|
256
|
+
findByEntity(required: {
|
|
257
|
+
workspaceId: string;
|
|
258
|
+
entityType: TrashEntityType;
|
|
259
|
+
entityId: string;
|
|
260
|
+
}): Promise<TrashItem | null>;
|
|
261
|
+
findByIds(required: {
|
|
262
|
+
workspaceId: string;
|
|
263
|
+
ids: readonly string[];
|
|
264
|
+
}): Promise<TrashItem[]>;
|
|
265
|
+
/** Removes the index row. This IS the restore — there is no second marker to clear. */
|
|
266
|
+
deleteById(required: {
|
|
267
|
+
workspaceId: string;
|
|
268
|
+
id: string;
|
|
269
|
+
}): Promise<void>;
|
|
270
|
+
deleteByEntity(required: {
|
|
271
|
+
workspaceId: string;
|
|
272
|
+
entityType: TrashEntityType;
|
|
273
|
+
entityId: string;
|
|
274
|
+
}): Promise<void>;
|
|
275
|
+
list(required: {
|
|
276
|
+
workspaceId: string;
|
|
277
|
+
now: string;
|
|
278
|
+
limit: number;
|
|
279
|
+
}, optional?: {
|
|
280
|
+
entityTypes?: readonly TrashEntityType[];
|
|
281
|
+
cursor?: string | null;
|
|
282
|
+
}): Promise<TrashPage>;
|
|
283
|
+
/** Atomic claim of due, unleased rows across EVERY workspace in the file (the `purge_after`
|
|
284
|
+
* index is global for exactly this query). */
|
|
285
|
+
claimDue(required: {
|
|
286
|
+
now: string;
|
|
287
|
+
leaseOwner: string;
|
|
288
|
+
leaseUntil: string;
|
|
289
|
+
limit: number;
|
|
290
|
+
}): Promise<TrashSweepClaim[]>;
|
|
291
|
+
/** Explicitly release a claim. The built-in sweep retains stood-down leases for retry backoff. */
|
|
292
|
+
releaseLease(required: {
|
|
293
|
+
id: string;
|
|
294
|
+
}): Promise<void>;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Marker/index writes and follow-ups must share one reentrant transaction. A domain may already
|
|
298
|
+
* hold a transaction around its marker/revision writes; nesting must join rather than open another
|
|
299
|
+
* BEGIN, and a propagated failure must roll back the entire unit.
|
|
300
|
+
*/
|
|
301
|
+
export type TransactionRunner = <T>(required: {
|
|
302
|
+
work: () => Promise<T>;
|
|
303
|
+
}) => Promise<T>;
|
|
304
|
+
/** Host-owned allowlist evaluated at call time; listing still uses snapshots for missing domains. */
|
|
305
|
+
export type TrashEntityPolicy = (required: {
|
|
306
|
+
entityType: TrashEntityType;
|
|
307
|
+
}) => boolean;
|
|
308
|
+
//# sourceMappingURL=ports.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { TrashEntityType, TrashItem, TrashPage, TrashRepoPort, TrashSweepClaim } from "./ports.js";
|
|
2
|
+
export declare class InMemoryTrashRepo implements TrashRepoPort {
|
|
3
|
+
constructor(_required: Record<string, never>);
|
|
4
|
+
private readonly rows;
|
|
5
|
+
private readonly leases;
|
|
6
|
+
/** Test-only view of everything stored, including expired rows the list hides. */
|
|
7
|
+
all(_required: Record<string, never>): TrashItem[];
|
|
8
|
+
/** @complexity O(n) over stored rows (the identity scan); n is a test fixture. */
|
|
9
|
+
insert({ row }: {
|
|
10
|
+
row: TrashItem;
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
findByEntity(required: {
|
|
13
|
+
workspaceId: string;
|
|
14
|
+
entityType: TrashEntityType;
|
|
15
|
+
entityId: string;
|
|
16
|
+
}): Promise<TrashItem | null>;
|
|
17
|
+
findByIds(required: {
|
|
18
|
+
workspaceId: string;
|
|
19
|
+
ids: readonly string[];
|
|
20
|
+
}): Promise<TrashItem[]>;
|
|
21
|
+
deleteById(required: {
|
|
22
|
+
workspaceId: string;
|
|
23
|
+
id: string;
|
|
24
|
+
}): Promise<void>;
|
|
25
|
+
deleteByEntity(required: {
|
|
26
|
+
workspaceId: string;
|
|
27
|
+
entityType: TrashEntityType;
|
|
28
|
+
entityId: string;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
/** @complexity O(n log n) — sorts the workspace's rows; fixture-sized by construction. */
|
|
31
|
+
list(required: {
|
|
32
|
+
workspaceId: string;
|
|
33
|
+
now: string;
|
|
34
|
+
limit: number;
|
|
35
|
+
}, optional?: {
|
|
36
|
+
entityTypes?: readonly TrashEntityType[];
|
|
37
|
+
cursor?: string | null;
|
|
38
|
+
}): Promise<TrashPage>;
|
|
39
|
+
/** @complexity O(n log n). */
|
|
40
|
+
claimDue(required: {
|
|
41
|
+
now: string;
|
|
42
|
+
leaseOwner: string;
|
|
43
|
+
leaseUntil: string;
|
|
44
|
+
limit: number;
|
|
45
|
+
}): Promise<TrashSweepClaim[]>;
|
|
46
|
+
releaseLease(required: {
|
|
47
|
+
id: string;
|
|
48
|
+
}): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=repo.memory.d.ts.map
|