@jini-ai/cms 0.5.4 → 0.5.5

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.
Files changed (113) hide show
  1. package/dist/navigation/agent-tools.js +20 -3
  2. package/dist/navigation/index.d.ts +3 -1
  3. package/dist/navigation/index.js +2 -0
  4. package/dist/navigation/menu-html.d.ts +31 -0
  5. package/dist/navigation/menu-html.js +53 -0
  6. package/dist/navigation/menu-service.d.ts +9 -3
  7. package/dist/navigation/menu-service.js +15 -3
  8. package/dist/navigation/tool-registrations.d.ts +5 -0
  9. package/dist/navigation/tool-registrations.js +32 -2
  10. package/dist/navigation/types.d.ts +6 -0
  11. package/dist/widgets/resolvers/menu.d.ts +8 -0
  12. package/dist/widgets/resolvers/menu.js +21 -6
  13. package/package.json +3 -3
  14. package/dist/http/settings/cms-adapter.d.ts +0 -20
  15. package/dist/http/settings/cms-adapter.js +0 -124
  16. package/dist/http/settings/contracts.d.ts +0 -135
  17. package/dist/http/settings/contracts.js +0 -2
  18. package/dist/http/settings/http.d.ts +0 -10
  19. package/dist/http/settings/http.js +0 -211
  20. package/dist/http/settings/index.d.ts +0 -5
  21. package/dist/http/settings/index.js +0 -4
  22. package/dist/http/settings/shared.d.ts +0 -68
  23. package/dist/http/settings/shared.js +0 -54
  24. package/dist/http/settings/stream.d.ts +0 -24
  25. package/dist/http/settings/stream.js +0 -147
  26. package/dist/settings/agent-tools.d.ts +0 -10
  27. package/dist/settings/agent-tools.js +0 -204
  28. package/dist/settings/agent-value-write-tools.d.ts +0 -22
  29. package/dist/settings/agent-value-write-tools.js +0 -154
  30. package/dist/settings/agent-writable-preferences.d.ts +0 -67
  31. package/dist/settings/agent-writable-preferences.js +0 -132
  32. package/dist/settings/agent-write-denylist.d.ts +0 -19
  33. package/dist/settings/agent-write-denylist.js +0 -15
  34. package/dist/settings/change-feed.d.ts +0 -83
  35. package/dist/settings/change-feed.js +0 -61
  36. package/dist/settings/definitions-dispatch.d.ts +0 -56
  37. package/dist/settings/definitions-dispatch.js +0 -86
  38. package/dist/settings/dictionaries/index.d.ts +0 -48
  39. package/dist/settings/dictionaries/index.js +0 -69
  40. package/dist/settings/dictionaries/settings-dialog.ar.d.ts +0 -3
  41. package/dist/settings/dictionaries/settings-dialog.ar.js +0 -27
  42. package/dist/settings/dictionaries/settings-dialog.bn.d.ts +0 -3
  43. package/dist/settings/dictionaries/settings-dialog.bn.js +0 -27
  44. package/dist/settings/dictionaries/settings-dialog.de.d.ts +0 -3
  45. package/dist/settings/dictionaries/settings-dialog.de.js +0 -27
  46. package/dist/settings/dictionaries/settings-dialog.es.d.ts +0 -3
  47. package/dist/settings/dictionaries/settings-dialog.es.js +0 -27
  48. package/dist/settings/dictionaries/settings-dialog.fa.d.ts +0 -3
  49. package/dist/settings/dictionaries/settings-dialog.fa.js +0 -27
  50. package/dist/settings/dictionaries/settings-dialog.fr.d.ts +0 -3
  51. package/dist/settings/dictionaries/settings-dialog.fr.js +0 -27
  52. package/dist/settings/dictionaries/settings-dialog.hi.d.ts +0 -3
  53. package/dist/settings/dictionaries/settings-dialog.hi.js +0 -27
  54. package/dist/settings/dictionaries/settings-dialog.hu.d.ts +0 -3
  55. package/dist/settings/dictionaries/settings-dialog.hu.js +0 -27
  56. package/dist/settings/dictionaries/settings-dialog.id.d.ts +0 -3
  57. package/dist/settings/dictionaries/settings-dialog.id.js +0 -27
  58. package/dist/settings/dictionaries/settings-dialog.it.d.ts +0 -3
  59. package/dist/settings/dictionaries/settings-dialog.it.js +0 -27
  60. package/dist/settings/dictionaries/settings-dialog.ja.d.ts +0 -3
  61. package/dist/settings/dictionaries/settings-dialog.ja.js +0 -27
  62. package/dist/settings/dictionaries/settings-dialog.ko.d.ts +0 -3
  63. package/dist/settings/dictionaries/settings-dialog.ko.js +0 -27
  64. package/dist/settings/dictionaries/settings-dialog.pl.d.ts +0 -3
  65. package/dist/settings/dictionaries/settings-dialog.pl.js +0 -27
  66. package/dist/settings/dictionaries/settings-dialog.pt-BR.d.ts +0 -3
  67. package/dist/settings/dictionaries/settings-dialog.pt-BR.js +0 -27
  68. package/dist/settings/dictionaries/settings-dialog.ru.d.ts +0 -3
  69. package/dist/settings/dictionaries/settings-dialog.ru.js +0 -27
  70. package/dist/settings/dictionaries/settings-dialog.th.d.ts +0 -3
  71. package/dist/settings/dictionaries/settings-dialog.th.js +0 -27
  72. package/dist/settings/dictionaries/settings-dialog.tr.d.ts +0 -3
  73. package/dist/settings/dictionaries/settings-dialog.tr.js +0 -27
  74. package/dist/settings/dictionaries/settings-dialog.uk.d.ts +0 -3
  75. package/dist/settings/dictionaries/settings-dialog.uk.js +0 -27
  76. package/dist/settings/dictionaries/settings-dialog.ur.d.ts +0 -3
  77. package/dist/settings/dictionaries/settings-dialog.ur.js +0 -27
  78. package/dist/settings/dictionaries/settings-dialog.zh-CN.d.ts +0 -3
  79. package/dist/settings/dictionaries/settings-dialog.zh-CN.js +0 -27
  80. package/dist/settings/dictionaries/settings-dialog.zh-TW.d.ts +0 -3
  81. package/dist/settings/dictionaries/settings-dialog.zh-TW.js +0 -27
  82. package/dist/settings/ensure-definitions.d.ts +0 -88
  83. package/dist/settings/ensure-definitions.js +0 -91
  84. package/dist/settings/errors.d.ts +0 -42
  85. package/dist/settings/errors.js +0 -46
  86. package/dist/settings/express/cms-adapter.d.ts +0 -20
  87. package/dist/settings/express/cms-adapter.js +0 -124
  88. package/dist/settings/express/contracts.d.ts +0 -135
  89. package/dist/settings/express/contracts.js +0 -2
  90. package/dist/settings/express/http.d.ts +0 -10
  91. package/dist/settings/express/http.js +0 -211
  92. package/dist/settings/express/shared.d.ts +0 -68
  93. package/dist/settings/express/shared.js +0 -54
  94. package/dist/settings/express/stream.d.ts +0 -24
  95. package/dist/settings/express/stream.js +0 -147
  96. package/dist/settings/ports.d.ts +0 -118
  97. package/dist/settings/ports.js +0 -2
  98. package/dist/settings/principal-lookup.d.ts +0 -9
  99. package/dist/settings/principal-lookup.js +0 -2
  100. package/dist/settings/purge-service.d.ts +0 -54
  101. package/dist/settings/purge-service.js +0 -106
  102. package/dist/settings/repo.memory.d.ts +0 -119
  103. package/dist/settings/repo.memory.js +0 -199
  104. package/dist/settings/settings.d.ts +0 -139
  105. package/dist/settings/settings.js +0 -259
  106. package/dist/settings/tool-registrations.d.ts +0 -51
  107. package/dist/settings/tool-registrations.js +0 -250
  108. package/dist/settings/types.d.ts +0 -125
  109. package/dist/settings/types.js +0 -3
  110. package/dist/settings/ui-tab-definitions.d.ts +0 -41
  111. package/dist/settings/ui-tab-definitions.js +0 -148
  112. package/dist/settings/write-service.d.ts +0 -320
  113. package/dist/settings/write-service.js +0 -683
@@ -1,54 +0,0 @@
1
- import type { Clock, UUID } from "@jini-ai/core/primitives";
2
- import type { SettingsRepoPort } from "./ports.js";
3
- import type { AuthorizeFn } from "./write-service.js";
4
- /**
5
- * @file `purgeTenantSettings` — the ledgered tenant/principal purge chokepoint.
6
- *
7
- * Purpose:
8
- * A host's value-table FKs (`setting_values_workspace`/`_user` -> `workspaces`)
9
- * are typically `ON DELETE RESTRICT`, not `CASCADE` — a raw workspace delete
10
- * cannot silently drop value rows without a ledgered revision. This module is
11
- * the explicit, ledgered alternative that requires: `authorize` once
12
- * -> enumerate every affected row -> append a redacted `op='purge'` revision
13
- * per row -> delete the row, all in ONE transaction. The `setting_revisions`
14
- * ledger itself is NEVER touched by a purge — only value rows are removed,
15
- * with their purge revision left behind. Repo write methods stay
16
- * package-private to this file and `write-service.ts`.
17
- *
18
- * Deviation from the outline's `SettingsWriteServiceDeps`: purge needs
19
- * neither `ids` (no new definition/value rows are created) nor `principals`
20
- * ( target-principal check is a write-time guard; purge only removes
21
- * pre-existing rows, it never writes a new user-scope row for an unverified
22
- * principal) — so it declares its own smaller `PurgeServiceDeps`.
23
- * See docs/decisions/DR-003-settings-ledger-invariants.md.
24
- */
25
- export interface PurgeServiceDeps {
26
- repo: SettingsRepoPort;
27
- clock: Clock;
28
- authorize: AuthorizeFn;
29
- }
30
- export interface PurgeTenantSettingsRequired {
31
- deps: PurgeServiceDeps;
32
- input: {
33
- workspaceId: UUID;
34
- /**
35
- * Omitted -> full tenant teardown: every `setting_values_workspace` row
36
- * for the workspace AND every `setting_values_user` row for every
37
- * principal in the workspace.
38
- * Present -> scope the purge to one principal's `setting_values_user`
39
- * rows only (GDPR single-subject erasure) — workspace-scope values and
40
- * every other principal's rows are left untouched.
41
- */
42
- principalId?: UUID;
43
- callerPrincipalId: UUID;
44
- };
45
- }
46
- /**
47
- * The purge chokepoint: authorize `settings.definitions.manage` once ->
48
- * enumerate the affected rows -> append a redacted `op='purge'` revision +
49
- * delete, per row, all in one transaction.
50
- */
51
- export declare function purgeTenantSettings(required: PurgeTenantSettingsRequired): Promise<{
52
- purgedCount: number;
53
- }>;
54
- //# sourceMappingURL=purge-service.d.ts.map
@@ -1,106 +0,0 @@
1
- import { nowIso as kernelNowIso } from "@jini-ai/core/primitives";
2
- import { ForbiddenError } from "./errors.js";
3
- import { invalidateWorkspaceSettingsCache } from "./settings.js";
4
- /**
5
- * The purge chokepoint: authorize `settings.definitions.manage` once ->
6
- * enumerate the affected rows -> append a redacted `op='purge'` revision +
7
- * delete, per row, all in one transaction.
8
- */
9
- export async function purgeTenantSettings(required) {
10
- const { deps, input } = required;
11
- const authResult = await deps.authorize({
12
- principalId: input.callerPrincipalId,
13
- permission: "settings.definitions.manage",
14
- workspaceId: input.workspaceId,
15
- entityType: "setting-workspace",
16
- entityId: input.workspaceId,
17
- });
18
- if (!authResult.allowed) {
19
- throw new ForbiddenError(`principal '${input.callerPrincipalId}' is not authorized for 'settings.definitions.manage' (${authResult.reason})`);
20
- }
21
- const result = await deps.repo.transaction(async () => {
22
- const now = kernelNowIso({ clock: deps.clock });
23
- let purgedCount = 0;
24
- if (input.principalId) {
25
- const userValues = await deps.repo.listUserValues({
26
- workspaceId: input.workspaceId,
27
- principalId: input.principalId,
28
- });
29
- for (const value of userValues) {
30
- await deps.repo.appendRevision({
31
- entityKind: "value",
32
- settingId: value.settingId,
33
- scope: "user",
34
- workspaceId: input.workspaceId,
35
- principalId: input.principalId,
36
- op: "purge",
37
- beforeJson: null,
38
- afterJson: null,
39
- defVersion: value.defVersion,
40
- actor: input.callerPrincipalId,
41
- originPluginId: null,
42
- changeSetId: null,
43
- createdAt: now,
44
- });
45
- await deps.repo.deleteUserValue({
46
- workspaceId: input.workspaceId,
47
- principalId: input.principalId,
48
- settingId: value.settingId,
49
- });
50
- purgedCount++;
51
- }
52
- return { purgedCount };
53
- }
54
- const workspaceValues = await deps.repo.listWorkspaceValues({ workspaceId: input.workspaceId });
55
- for (const value of workspaceValues) {
56
- await deps.repo.appendRevision({
57
- entityKind: "value",
58
- settingId: value.settingId,
59
- scope: "workspace",
60
- workspaceId: input.workspaceId,
61
- principalId: null,
62
- op: "purge",
63
- beforeJson: null,
64
- afterJson: null,
65
- defVersion: value.defVersion,
66
- actor: input.callerPrincipalId,
67
- originPluginId: null,
68
- changeSetId: null,
69
- createdAt: now,
70
- });
71
- await deps.repo.deleteWorkspaceValue({ workspaceId: input.workspaceId, settingId: value.settingId });
72
- purgedCount++;
73
- }
74
- const userValues = await deps.repo.listUserValuesByWorkspace({ workspaceId: input.workspaceId });
75
- for (const value of userValues) {
76
- const targetPrincipalId = value.principalId;
77
- if (!targetPrincipalId)
78
- continue; // defensive: every setting_values_user row carries a principal_id
79
- await deps.repo.appendRevision({
80
- entityKind: "value",
81
- settingId: value.settingId,
82
- scope: "user",
83
- workspaceId: input.workspaceId,
84
- principalId: targetPrincipalId,
85
- op: "purge",
86
- beforeJson: null,
87
- afterJson: null,
88
- defVersion: value.defVersion,
89
- actor: input.callerPrincipalId,
90
- originPluginId: null,
91
- changeSetId: null,
92
- createdAt: now,
93
- });
94
- await deps.repo.deleteUserValue({
95
- workspaceId: input.workspaceId,
96
- principalId: targetPrincipalId,
97
- settingId: value.settingId,
98
- });
99
- purgedCount++;
100
- }
101
- return { purgedCount };
102
- });
103
- invalidateWorkspaceSettingsCache({ repo: deps.repo, workspaceId: input.workspaceId }, { principalId: input.principalId });
104
- return result;
105
- }
106
- //# sourceMappingURL=purge-service.js.map
@@ -1,119 +0,0 @@
1
- import type { SettingsRepoPort } from "./ports.js";
2
- import type { SettingDefinitionRecord, SettingRevisionRecord, SettingValueRecord } from "./types.js";
3
- /**
4
- * @file In-memory adapter for `SettingsRepoPort` (the rule-of-two: in-memory + production adapters).
5
- *
6
- * Purpose:
7
- * Test/dev implementation. Mirrors the exact style of `identity/repo.memory.ts`:
8
- * constructor takes seed rows, methods filter/mutate internal arrays.
9
- *
10
- * `transaction` is a real snapshot/restore journal, not a no-op wrapper. It
11
- * used to be `async (fn) => fn()` on the reasoning that each individual
12
- * mutation is already synchronous and atomic in-process — true, but the
13
- * guarantee `SettingsRepoPort.transaction` actually sells is that a COMPOSITE
14
- * of several mutations is all-or-nothing. `resetNamespace` clears a whole
15
- * namespace inside one transaction; under a no-op wrapper a failure partway
16
- * through left the namespace half reset while the caller saw an error. This
17
- * adapter is a common wired default for dev/test composition, so that was
18
- * live in dev, and the contract suite could not see it because the rollback
19
- * assertion only ran against the SQLite adapter.
20
- */
21
- export declare class InMemorySettingsRepo implements SettingsRepoPort {
22
- private definitions;
23
- private globalValues;
24
- private workspaceValues;
25
- private userValues;
26
- private revisions;
27
- private nextSeq;
28
- /** True while `transaction` is open — see its doc for why this refuses
29
- * rather than nests. */
30
- private inTransaction;
31
- constructor(seed?: {
32
- definitions?: SettingDefinitionRecord[];
33
- globalValues?: SettingValueRecord[];
34
- workspaceValues?: SettingValueRecord[];
35
- userValues?: SettingValueRecord[];
36
- revisions?: SettingRevisionRecord[];
37
- });
38
- findActiveDefinition(required: {
39
- namespace: string;
40
- key: string;
41
- workspaceId: string | null;
42
- }): Promise<SettingDefinitionRecord | null>;
43
- findDefinitionBySettingId(required: {
44
- settingId: string;
45
- version?: number;
46
- }): Promise<SettingDefinitionRecord | null>;
47
- listActiveDefinitions(required: {
48
- workspaceId: string | null;
49
- }): Promise<SettingDefinitionRecord[]>;
50
- saveDefinition(record: SettingDefinitionRecord): Promise<void>;
51
- getGlobalValue(settingId: string): Promise<SettingValueRecord | null>;
52
- getWorkspaceValue(required: {
53
- workspaceId: string;
54
- settingId: string;
55
- }): Promise<SettingValueRecord | null>;
56
- getUserValue(required: {
57
- workspaceId: string;
58
- principalId: string;
59
- settingId: string;
60
- }): Promise<SettingValueRecord | null>;
61
- saveGlobalValue(record: SettingValueRecord): Promise<void>;
62
- saveWorkspaceValue(record: SettingValueRecord): Promise<void>;
63
- saveUserValue(record: SettingValueRecord): Promise<void>;
64
- listWorkspaceValues(required: {
65
- workspaceId: string;
66
- }): Promise<SettingValueRecord[]>;
67
- listUserValues(required: {
68
- workspaceId: string;
69
- principalId: string;
70
- }): Promise<SettingValueRecord[]>;
71
- listUserValuesByWorkspace(required: {
72
- workspaceId: string;
73
- }): Promise<SettingValueRecord[]>;
74
- deleteWorkspaceValue(required: {
75
- workspaceId: string;
76
- settingId: string;
77
- }): Promise<void>;
78
- deleteUserValue(required: {
79
- workspaceId: string;
80
- principalId: string;
81
- settingId: string;
82
- }): Promise<void>;
83
- appendRevision(record: Omit<SettingRevisionRecord, "seq">): Promise<number>;
84
- listRevisions(required: {
85
- settingId: string;
86
- }): Promise<SettingRevisionRecord[]>;
87
- listRevisionsSince(required: {
88
- sinceSeq: number;
89
- limit: number;
90
- workspaceId: string;
91
- }): Promise<SettingRevisionRecord[]>;
92
- maxRevisionSeq(): Promise<number>;
93
- /**
94
- * Runs `fn` atomically: every mutation it makes is discarded if it throws.
95
- *
96
- * A shallow copy of each array is enough because no method here mutates a
97
- * record in place — `save*` REPLACES the element at its index, `delete*`
98
- * reassigns a filtered array, `appendRevision` pushes. Restoring the arrays
99
- * therefore restores the rows too. If a future method starts editing a
100
- * record object in place, this must deep-copy instead.
101
- *
102
- * `nextSeq` is rolled back with the rest, matching SQLite: `sqlite_sequence`
103
- * is an ordinary table, so an AUTOINCREMENT counter bumped inside a
104
- * transaction is undone by its ROLLBACK. A seq handed out by a failed
105
- * transaction is safe to reuse precisely because that revision no longer
106
- * exists.
107
- *
108
- * **Deliberately NOT reentrant**, matching a SQLite adapter's `transaction`,
109
- * where `BEGIN IMMEDIATE` inside an open transaction is an error. Refusing
110
- * loudly is the point: an instance-level flag cannot tell "nested inside my
111
- * caller's transaction" from "an unrelated transaction that started while
112
- * mine was awaiting". Silently joining a stranger's transaction — and
113
- * inheriting its rollback — is a worse defect than refusing. A caller that
114
- * needs several writes to commit together opens ONE transaction and passes
115
- * `skipTransaction` to the inner writes, as `resetNamespace` does.
116
- */
117
- transaction<T>(fn: () => Promise<T>): Promise<T>;
118
- }
119
- //# sourceMappingURL=repo.memory.d.ts.map
@@ -1,199 +0,0 @@
1
- /**
2
- * @file In-memory adapter for `SettingsRepoPort` (the rule-of-two: in-memory + production adapters).
3
- *
4
- * Purpose:
5
- * Test/dev implementation. Mirrors the exact style of `identity/repo.memory.ts`:
6
- * constructor takes seed rows, methods filter/mutate internal arrays.
7
- *
8
- * `transaction` is a real snapshot/restore journal, not a no-op wrapper. It
9
- * used to be `async (fn) => fn()` on the reasoning that each individual
10
- * mutation is already synchronous and atomic in-process — true, but the
11
- * guarantee `SettingsRepoPort.transaction` actually sells is that a COMPOSITE
12
- * of several mutations is all-or-nothing. `resetNamespace` clears a whole
13
- * namespace inside one transaction; under a no-op wrapper a failure partway
14
- * through left the namespace half reset while the caller saw an error. This
15
- * adapter is a common wired default for dev/test composition, so that was
16
- * live in dev, and the contract suite could not see it because the rollback
17
- * assertion only ran against the SQLite adapter.
18
- */
19
- export class InMemorySettingsRepo {
20
- definitions;
21
- globalValues;
22
- workspaceValues;
23
- userValues;
24
- revisions;
25
- nextSeq;
26
- /** True while `transaction` is open — see its doc for why this refuses
27
- * rather than nests. */
28
- inTransaction = false;
29
- constructor(seed = {}) {
30
- this.definitions = [...(seed.definitions ?? [])];
31
- this.globalValues = [...(seed.globalValues ?? [])];
32
- this.workspaceValues = [...(seed.workspaceValues ?? [])];
33
- this.userValues = [...(seed.userValues ?? [])];
34
- this.revisions = [...(seed.revisions ?? [])];
35
- this.nextSeq = (seed.revisions?.reduce((max, r) => Math.max(max, r.seq), 0) ?? 0) + 1;
36
- }
37
- async findActiveDefinition(required) {
38
- // Matches active/alias/tombstone (the "current" row for this (ns,key,ws)
39
- // slot — callers decide how to react to each status, e.g. settings.ts
40
- // treats tombstone as read-absent but write-service.ts reports it
41
- // distinctly as DEFINITION_TOMBSTONED). Deliberately excludes
42
- // `deprecated`: those are stale prior-version rows superseded by a newer
43
- // active row at the same (ns,key,ws) slot (the retype mechanism), so
44
- // including them would make this lookup ambiguous.
45
- return (this.definitions.find((d) => d.namespace === required.namespace &&
46
- d.key === required.key &&
47
- d.workspaceId === required.workspaceId &&
48
- (d.status === "active" || d.status === "alias" || d.status === "tombstone")) ?? null);
49
- }
50
- async findDefinitionBySettingId(required) {
51
- const candidates = this.definitions.filter((d) => d.settingId === required.settingId);
52
- if (required.version != null) {
53
- return candidates.find((d) => d.version === required.version) ?? null;
54
- }
55
- return (candidates.find((d) => d.status === "active") ??
56
- candidates.sort((a, b) => b.version - a.version)[0] ??
57
- null);
58
- }
59
- async listActiveDefinitions(required) {
60
- return this.definitions.filter((d) => d.workspaceId === required.workspaceId && (d.status === "active" || d.status === "alias"));
61
- }
62
- async saveDefinition(record) {
63
- const index = this.definitions.findIndex((d) => d.settingId === record.settingId && d.version === record.version);
64
- if (index === -1) {
65
- this.definitions.push(record);
66
- return;
67
- }
68
- this.definitions[index] = record;
69
- }
70
- async getGlobalValue(settingId) {
71
- return this.globalValues.find((v) => v.settingId === settingId) ?? null;
72
- }
73
- async getWorkspaceValue(required) {
74
- return (this.workspaceValues.find((v) => v.workspaceId === required.workspaceId && v.settingId === required.settingId) ?? null);
75
- }
76
- async getUserValue(required) {
77
- return (this.userValues.find((v) => v.workspaceId === required.workspaceId &&
78
- v.principalId === required.principalId &&
79
- v.settingId === required.settingId) ?? null);
80
- }
81
- async saveGlobalValue(record) {
82
- const index = this.globalValues.findIndex((v) => v.settingId === record.settingId);
83
- if (index === -1) {
84
- this.globalValues.push(record);
85
- return;
86
- }
87
- this.globalValues[index] = record;
88
- }
89
- async saveWorkspaceValue(record) {
90
- const index = this.workspaceValues.findIndex((v) => v.workspaceId === record.workspaceId && v.settingId === record.settingId);
91
- if (index === -1) {
92
- this.workspaceValues.push(record);
93
- return;
94
- }
95
- this.workspaceValues[index] = record;
96
- }
97
- async saveUserValue(record) {
98
- const index = this.userValues.findIndex((v) => v.workspaceId === record.workspaceId &&
99
- v.principalId === record.principalId &&
100
- v.settingId === record.settingId);
101
- if (index === -1) {
102
- this.userValues.push(record);
103
- return;
104
- }
105
- this.userValues[index] = record;
106
- }
107
- async listWorkspaceValues(required) {
108
- return this.workspaceValues.filter((v) => v.workspaceId === required.workspaceId);
109
- }
110
- async listUserValues(required) {
111
- return this.userValues.filter((v) => v.workspaceId === required.workspaceId && v.principalId === required.principalId);
112
- }
113
- async listUserValuesByWorkspace(required) {
114
- return this.userValues.filter((v) => v.workspaceId === required.workspaceId);
115
- }
116
- async deleteWorkspaceValue(required) {
117
- this.workspaceValues = this.workspaceValues.filter((v) => !(v.workspaceId === required.workspaceId && v.settingId === required.settingId));
118
- }
119
- async deleteUserValue(required) {
120
- this.userValues = this.userValues.filter((v) => !(v.workspaceId === required.workspaceId &&
121
- v.principalId === required.principalId &&
122
- v.settingId === required.settingId));
123
- }
124
- async appendRevision(record) {
125
- const seq = this.nextSeq++;
126
- this.revisions.push({ ...record, seq });
127
- return seq;
128
- }
129
- async listRevisions(required) {
130
- return this.revisions.filter((r) => r.settingId === required.settingId).sort((a, b) => a.seq - b.seq);
131
- }
132
- async listRevisionsSince(required) {
133
- return this.revisions
134
- .filter((r) => r.seq > required.sinceSeq &&
135
- // Mirrors the SQL predicate exactly — see the port's doc. `null` means
136
- // platform-wide (every workspace resolves through it); anything else is one tenant's.
137
- (r.workspaceId === null || r.workspaceId === required.workspaceId))
138
- .sort((a, b) => a.seq - b.seq)
139
- .slice(0, required.limit);
140
- }
141
- async maxRevisionSeq() {
142
- return this.revisions.reduce((max, r) => (r.seq > max ? r.seq : max), 0);
143
- }
144
- /**
145
- * Runs `fn` atomically: every mutation it makes is discarded if it throws.
146
- *
147
- * A shallow copy of each array is enough because no method here mutates a
148
- * record in place — `save*` REPLACES the element at its index, `delete*`
149
- * reassigns a filtered array, `appendRevision` pushes. Restoring the arrays
150
- * therefore restores the rows too. If a future method starts editing a
151
- * record object in place, this must deep-copy instead.
152
- *
153
- * `nextSeq` is rolled back with the rest, matching SQLite: `sqlite_sequence`
154
- * is an ordinary table, so an AUTOINCREMENT counter bumped inside a
155
- * transaction is undone by its ROLLBACK. A seq handed out by a failed
156
- * transaction is safe to reuse precisely because that revision no longer
157
- * exists.
158
- *
159
- * **Deliberately NOT reentrant**, matching a SQLite adapter's `transaction`,
160
- * where `BEGIN IMMEDIATE` inside an open transaction is an error. Refusing
161
- * loudly is the point: an instance-level flag cannot tell "nested inside my
162
- * caller's transaction" from "an unrelated transaction that started while
163
- * mine was awaiting". Silently joining a stranger's transaction — and
164
- * inheriting its rollback — is a worse defect than refusing. A caller that
165
- * needs several writes to commit together opens ONE transaction and passes
166
- * `skipTransaction` to the inner writes, as `resetNamespace` does.
167
- */
168
- async transaction(fn) {
169
- if (this.inTransaction) {
170
- throw new Error("InMemorySettingsRepo.transaction is not reentrant, and cannot host a second overlapping transaction. " +
171
- "Open one transaction and pass `skipTransaction` to the inner writes (see resetNamespace).");
172
- }
173
- const snapshot = {
174
- definitions: [...this.definitions],
175
- globalValues: [...this.globalValues],
176
- workspaceValues: [...this.workspaceValues],
177
- userValues: [...this.userValues],
178
- revisions: [...this.revisions],
179
- nextSeq: this.nextSeq,
180
- };
181
- this.inTransaction = true;
182
- try {
183
- return await fn();
184
- }
185
- catch (error) {
186
- this.definitions = snapshot.definitions;
187
- this.globalValues = snapshot.globalValues;
188
- this.workspaceValues = snapshot.workspaceValues;
189
- this.userValues = snapshot.userValues;
190
- this.revisions = snapshot.revisions;
191
- this.nextSeq = snapshot.nextSeq;
192
- throw error;
193
- }
194
- finally {
195
- this.inTransaction = false;
196
- }
197
- }
198
- }
199
- //# sourceMappingURL=repo.memory.js.map
@@ -1,139 +0,0 @@
1
- import type { JsonValue } from "@jini-ai/core/primitives";
2
- import { DefinitionInvalidError, SecretNotSupportedError } from "./errors.js";
3
- import type { SettingsRepoPort } from "./ports.js";
4
- import { type SettingDefinitionRecord, type SettingOwnerKind, type SettingScopeContext, type SettingValueSchema } from "./types.js";
5
- /**
6
- * @file The settings resolver + pure definition-registration validation
7
- * ("one evaluator").
8
- *
9
- * Purpose:
10
- * `validateDefinitionInput` is pure (no I/O) — the write-side chokepoint in
11
- * `write-service.ts` calls it before persisting. `getEffective`/`getLayer`/
12
- * `resolveDefinition` are read-only against `SettingsRepoPort`. Neither
13
- * mutates anything; every mutation goes through `write-service.ts`.
14
- * See docs/decisions/DR-003-settings-ledger-invariants.md.
15
- */
16
- export interface DefinitionInput {
17
- namespace: string;
18
- key: string;
19
- ownerKind: SettingOwnerKind;
20
- workspaceId: string | null;
21
- ownerId?: string | null | undefined;
22
- schema: SettingValueSchema;
23
- defaultValue: JsonValue | null;
24
- scopes: number;
25
- secret: boolean;
26
- }
27
- /**
28
- * — the pure half of `registerDefinitions`.
29
- * Never throws; returns a discriminated result so the chokepoint decides how
30
- * to surface the failure (matches `ValueValidationFailedError`'s pattern of
31
- * keeping I/O out of validation).
32
- *
33
- * @complexity O(1) per definition.
34
- * @overallScore 100
35
- * See docs/decisions/DR-003-settings-ledger-invariants.md.
36
- */
37
- export declare function validateDefinitionInput(input: DefinitionInput): {
38
- valid: true;
39
- } | {
40
- valid: false;
41
- error: DefinitionInvalidError | SecretNotSupportedError;
42
- };
43
- export declare function validateValueAgainstSchema(schema: SettingValueSchema, value: JsonValue): boolean;
44
- export declare function registerCoercer({ tag, fn }: {
45
- tag: string;
46
- fn: (required: {
47
- value: JsonValue;
48
- }) => JsonValue;
49
- }, _optional?: Record<string, never>): void;
50
- /**
51
- * Bumps the namespace's definition-cache epoch (lazy invalidation) — every
52
- * previously-cached definition-cache entry for this
53
- * namespace (across every workspace) becomes unreachable on the next read,
54
- * without needing to enumerate tenants. Call after any write that changes a
55
- * definition's identity/shape/status in this namespace: register, rename
56
- * (both the old and new namespace), retype, deprecate, tombstone.
57
- */
58
- export declare function invalidateDefinitionNamespaceCache(repo: SettingsRepoPort, namespace: string): void;
59
- /**
60
- * Purge support — now a no-op, deliberately kept rather than deleted.
61
- *
62
- * This cleared the workspace- and user-scope layer-cache entries a tenant/principal teardown
63
- * invalidated, because a purge deletes an unbounded number of value rows across an unknown set of
64
- * namespaces and per-namespace single-key invalidation was not practical for it. With the layer
65
- * cache gone (see this module's cache header) there is nothing left to clear: the rows are deleted,
66
- * and the next read goes to the repo and sees them gone.
67
- *
68
- * Kept as an explicit no-op instead of removed because of what it guards. `purge-service.ts` calls
69
- * it as the last step of deleting a tenant's settings, and its correctness question — "can a purged
70
- * value still be read back?" — is exactly the kind that must be answered again if anyone
71
- * reintroduces caching. Deleting the call site would remove the place that question is asked. A
72
- * cache added without restoring a purge hook is a tenant-teardown leak, so this stays as the seam.
73
- */
74
- export declare function invalidateWorkspaceSettingsCache(_required: {
75
- repo: SettingsRepoPort;
76
- workspaceId: string;
77
- }, _optional?: {
78
- principalId?: string | undefined;
79
- }): void;
80
- /**
81
- * Follows an alias marker (depth <=1) to the current row and returns it as
82
- * stored — status intact, including `tombstone`. `resolveDefinition` (below)
83
- * is the read-path wrapper that collapses tombstone to typed-absent;
84
- * `write-service.ts` uses this raw form directly so it can report
85
- * `DEFINITION_TOMBSTONED` distinctly from "not found".
86
- * Malformed alias targets, cycles and chains deeper than one fail closed as absent.
87
- * See docs/decisions/DR-003-settings-ledger-invariants.md.
88
- */
89
- export declare function resolveDefinitionRaw(deps: {
90
- repo: SettingsRepoPort;
91
- }, input: {
92
- namespace: string;
93
- key: string;
94
- workspaceId: string | null;
95
- }): Promise<SettingDefinitionRecord | null>;
96
- /**
97
- * The read-path resolver: typed-absent (`null`) for a tombstoned or
98
- * missing key. Cached — keyed by
99
- * `(workspaceId, namespace, key, namespace-epoch)`, so it is workspace-
100
- * qualified by construction and invalidated wholesale by
101
- * `invalidateDefinitionNamespaceCache` on any definition-lifecycle write
102
- * (register/rename/retype/deprecate/tombstone). `resolveDefinitionRaw`
103
- * itself stays uncached — the write chokepoint (`write-service.ts`) calls it
104
- * directly so authorization/validation decisions are always made against
105
- * live data, never a cached read.
106
- */
107
- export declare function resolveDefinition(deps: {
108
- repo: SettingsRepoPort;
109
- }, input: {
110
- namespace: string;
111
- key: string;
112
- workspaceId: string | null;
113
- }): Promise<SettingDefinitionRecord | null>;
114
- export interface ResolvedSetting {
115
- value: JsonValue | null;
116
- sourceLayer: "user" | "workspace" | "global" | "default";
117
- defVersion: number;
118
- }
119
- /**
120
- * — total for a live key: never throws, never returns
121
- * undefined. Precedence `user ?? workspace ?? global ?? default`. A
122
- * `cleared` row is treated as absent at that layer (behavior.spec §1.2).
123
- *
124
- * Per-layer reads go straight to the repo (see this module's cache header for
125
- * why the per-layer value cache that used to wrap these three calls does not
126
- * exist here).
127
- *
128
- * @complexity O(1) — up to 3 layer reads + 1 definition read.
129
- * @overallScore 100
130
- * See docs/decisions/DR-003-settings-ledger-invariants.md.
131
- */
132
- export declare function getEffective(deps: {
133
- repo: SettingsRepoPort;
134
- }, input: {
135
- namespace: string;
136
- key: string;
137
- scopeContext: SettingScopeContext;
138
- }): Promise<ResolvedSetting | null>;
139
- //# sourceMappingURL=settings.d.ts.map