@seifer-webapp-factory/user-settings 0.1.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/README.md +122 -0
- package/backend/templates/config/config-fragment.ts +22 -0
- package/backend/templates/nestjs/tokens.ts +15 -0
- package/backend/templates/nestjs/user-settings.controller.ts +119 -0
- package/backend/templates/nestjs/user-settings.module.ts +107 -0
- package/backend/templates/persistence/migrations/0001_user_settings.sql +16 -0
- package/backend/templates/persistence/migrations/index.ts +37 -0
- package/backend/templates/persistence/pg-settings-store.ts +62 -0
- package/backend/templates/security/security.ts +79 -0
- package/dist/backend/src/descriptors.d.ts +11 -0
- package/dist/backend/src/descriptors.d.ts.map +1 -0
- package/dist/backend/src/descriptors.js +38 -0
- package/dist/backend/src/descriptors.js.map +1 -0
- package/dist/backend/src/errors.d.ts +18 -0
- package/dist/backend/src/errors.d.ts.map +1 -0
- package/dist/backend/src/errors.js +29 -0
- package/dist/backend/src/errors.js.map +1 -0
- package/dist/backend/src/index.d.ts +10 -0
- package/dist/backend/src/index.d.ts.map +1 -0
- package/dist/backend/src/index.js +9 -0
- package/dist/backend/src/index.js.map +1 -0
- package/dist/backend/src/ports.d.ts +58 -0
- package/dist/backend/src/ports.d.ts.map +1 -0
- package/dist/backend/src/ports.js +2 -0
- package/dist/backend/src/ports.js.map +1 -0
- package/dist/backend/src/services.d.ts +26 -0
- package/dist/backend/src/services.d.ts.map +1 -0
- package/dist/backend/src/services.js +104 -0
- package/dist/backend/src/services.js.map +1 -0
- package/dist/contract/config.d.ts +35 -0
- package/dist/contract/config.d.ts.map +1 -0
- package/dist/contract/config.js +20 -0
- package/dist/contract/config.js.map +1 -0
- package/dist/contract/endpoints.d.ts +188 -0
- package/dist/contract/endpoints.d.ts.map +1 -0
- package/dist/contract/endpoints.js +21 -0
- package/dist/contract/endpoints.js.map +1 -0
- package/dist/contract/errors.d.ts +22 -0
- package/dist/contract/errors.d.ts.map +1 -0
- package/dist/contract/errors.js +26 -0
- package/dist/contract/errors.js.map +1 -0
- package/dist/contract/events.d.ts +38 -0
- package/dist/contract/events.d.ts.map +1 -0
- package/dist/contract/events.js +7 -0
- package/dist/contract/events.js.map +1 -0
- package/dist/contract/index.d.ts +11 -0
- package/dist/contract/index.d.ts.map +1 -0
- package/dist/contract/index.js +11 -0
- package/dist/contract/index.js.map +1 -0
- package/dist/contract/schemas.d.ts +255 -0
- package/dist/contract/schemas.d.ts.map +1 -0
- package/dist/contract/schemas.js +54 -0
- package/dist/contract/schemas.js.map +1 -0
- package/dist/frontend/src/client.d.ts +22 -0
- package/dist/frontend/src/client.d.ts.map +1 -0
- package/dist/frontend/src/client.js +64 -0
- package/dist/frontend/src/client.js.map +1 -0
- package/dist/frontend/src/descriptors.d.ts +12 -0
- package/dist/frontend/src/descriptors.d.ts.map +1 -0
- package/dist/frontend/src/descriptors.js +44 -0
- package/dist/frontend/src/descriptors.js.map +1 -0
- package/dist/frontend/src/index.d.ts +16 -0
- package/dist/frontend/src/index.d.ts.map +1 -0
- package/dist/frontend/src/index.js +12 -0
- package/dist/frontend/src/index.js.map +1 -0
- package/dist/frontend/src/ssr.d.ts +43 -0
- package/dist/frontend/src/ssr.d.ts.map +1 -0
- package/dist/frontend/src/ssr.js +90 -0
- package/dist/frontend/src/ssr.js.map +1 -0
- package/dist/frontend/src/store.d.ts +46 -0
- package/dist/frontend/src/store.d.ts.map +1 -0
- package/dist/frontend/src/store.js +111 -0
- package/dist/frontend/src/store.js.map +1 -0
- package/dist/frontend/src/useSettings.d.ts +46 -0
- package/dist/frontend/src/useSettings.d.ts.map +1 -0
- package/dist/frontend/src/useSettings.js +100 -0
- package/dist/frontend/src/useSettings.js.map +1 -0
- package/dist/manifest.d.ts +59 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +45 -0
- package/dist/manifest.js.map +1 -0
- package/dist/scaffolder/core/errors.d.ts +24 -0
- package/dist/scaffolder/core/errors.d.ts.map +1 -0
- package/dist/scaffolder/core/errors.js +38 -0
- package/dist/scaffolder/core/errors.js.map +1 -0
- package/dist/scaffolder/core/materialize.d.ts +40 -0
- package/dist/scaffolder/core/materialize.d.ts.map +1 -0
- package/dist/scaffolder/core/materialize.js +38 -0
- package/dist/scaffolder/core/materialize.js.map +1 -0
- package/dist/scaffolder/core/ports.d.ts +28 -0
- package/dist/scaffolder/core/ports.d.ts.map +1 -0
- package/dist/scaffolder/core/ports.js +28 -0
- package/dist/scaffolder/core/ports.js.map +1 -0
- package/dist/scaffolder/core/presence.d.ts +21 -0
- package/dist/scaffolder/core/presence.d.ts.map +1 -0
- package/dist/scaffolder/core/presence.js +20 -0
- package/dist/scaffolder/core/presence.js.map +1 -0
- package/dist/scaffolder/index.d.ts +20 -0
- package/dist/scaffolder/index.d.ts.map +1 -0
- package/dist/scaffolder/index.js +6 -0
- package/dist/scaffolder/index.js.map +1 -0
- package/frontend/templates/i18n/en.json +20 -0
- package/frontend/templates/i18n/nl.json +20 -0
- package/frontend/templates/pages/settings.vue +62 -0
- package/frontend/templates/runtime.ts +19 -0
- package/package.json +79 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../backend/src/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAarE,sFAAsF;AACtF,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;gBACzB,IAAI,EAAE,qBAAqB,EAAE,OAAO,GAAE,MAAuB;CAM1E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG,uBAAuB,GAAG,SAAS,CAE/E"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Generieke, veilige boodschappen per code (nooit rauwe details). */
|
|
2
|
+
const MESSAGES = {
|
|
3
|
+
unknown_key: 'Onbekende instellingssleutel',
|
|
4
|
+
validation_failed: 'Validatie mislukt',
|
|
5
|
+
consent_namespace_rejected: "Namespace 'consent' hoort bij de account-module, niet bij user-settings",
|
|
6
|
+
stale_write: 'De meegestuurde baseVersion is verouderd',
|
|
7
|
+
unauthenticated: 'Niet geauthenticeerd',
|
|
8
|
+
csrf_failed: 'CSRF-controle mislukt',
|
|
9
|
+
store_unavailable: 'De instellingen-store is niet beschikbaar',
|
|
10
|
+
};
|
|
11
|
+
/** Fout van de mechanism-laag, gedragen door een contract-`UserSettingsErrorCode`. */
|
|
12
|
+
export class UserSettingsModuleError extends Error {
|
|
13
|
+
code;
|
|
14
|
+
constructor(code, message = MESSAGES[code]) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'UserSettingsModuleError';
|
|
17
|
+
this.code = code;
|
|
18
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Vertaal een reeds-getypte module-fout door; geef `undefined` terug voor een onbekende fout (die laat de
|
|
23
|
+
* aanroeper bubbelen). Bestaat voor pariteit met de siblings; user-settings heeft geen kit-fouten om te
|
|
24
|
+
* mappen.
|
|
25
|
+
*/
|
|
26
|
+
export function asModuleError(err) {
|
|
27
|
+
return err instanceof UserSettingsModuleError ? err : undefined;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../backend/src/errors.ts"],"names":[],"mappings":"AAOA,sEAAsE;AACtE,MAAM,QAAQ,GAA0C;IACtD,WAAW,EAAE,8BAA8B;IAC3C,iBAAiB,EAAE,mBAAmB;IACtC,0BAA0B,EAAE,yEAAyE;IACrG,WAAW,EAAE,0CAA0C;IACvD,eAAe,EAAE,sBAAsB;IACvC,WAAW,EAAE,uBAAuB;IACpC,iBAAiB,EAAE,2CAA2C;CAC/D,CAAC;AAEF,sFAAsF;AACtF,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IACvC,IAAI,CAAwB;IACrC,YAAY,IAA2B,EAAE,UAAkB,QAAQ,CAAC,IAAI,CAAC;QACvE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;QACtC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACpD,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,GAAY;IACxC,OAAO,GAAG,YAAY,uBAAuB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AAClE,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* user-settings — backend-mechanism (flow-services). De verticale, framework-vrije servicelaag voor de
|
|
3
|
+
* server-adapter achter het gedeelde contract. Surface (NestJS-controller/module/migratie/pg-store) leeft
|
|
4
|
+
* in `backend/templates` en consumeert deze barrel.
|
|
5
|
+
*/
|
|
6
|
+
export { getSettings, putSetting, mergeSettings } from './services.js';
|
|
7
|
+
export { UserSettingsModuleError, asModuleError } from './errors.js';
|
|
8
|
+
export { assertValidDescriptors, resolveDescriptor, validateValue } from './descriptors.js';
|
|
9
|
+
export type { Clock, StoredSetting, UpsertSetting, SettingsServerStore, SubjectProvider, UserSettingsModuleConfig, UserSettingsModuleDeps, } from './ports.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../backend/src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEvE,OAAO,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAErE,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAE5F,YAAY,EACV,KAAK,EACL,aAAa,EACb,aAAa,EACb,mBAAmB,EACnB,eAAe,EACf,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* user-settings — backend-mechanism (flow-services). De verticale, framework-vrije servicelaag voor de
|
|
3
|
+
* server-adapter achter het gedeelde contract. Surface (NestJS-controller/module/migratie/pg-store) leeft
|
|
4
|
+
* in `backend/templates` en consumeert deze barrel.
|
|
5
|
+
*/
|
|
6
|
+
export { getSettings, putSetting, mergeSettings } from './services.js';
|
|
7
|
+
export { UserSettingsModuleError, asModuleError } from './errors.js';
|
|
8
|
+
export { assertValidDescriptors, resolveDescriptor, validateValue } from './descriptors.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../backend/src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEvE,OAAO,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAErE,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* De poorten & de dependency-bundle van de user-settings-mechanism-laag.
|
|
3
|
+
*
|
|
4
|
+
* De flow-services zijn framework-vrij en krijgen ALLE afhankelijkheden via één {@link UserSettingsModuleDeps}-
|
|
5
|
+
* bundel. Anders dan account leunt deze module NIET op de auth- of privacy-kit: de server-adapter heeft
|
|
6
|
+
* alleen een keyed store, een subject-provider, events en een klok nodig. Determinisme loopt via de
|
|
7
|
+
* geïnjecteerde {@link Clock} (nooit `Date.now`).
|
|
8
|
+
*/
|
|
9
|
+
import type { SettingsSchema, UserSettingsConfig, UserSettingsEventSink } from '../../contract/index.js';
|
|
10
|
+
/** Deterministische klok-poort (ms-epoch). Lokaal getypt zodat de module geen auth-kit-dep nodig heeft. */
|
|
11
|
+
export type Clock = () => number;
|
|
12
|
+
/** Eén server-persistente instelling: gekeyd op (subject, key), server-beheerde monotone `version`. */
|
|
13
|
+
export interface StoredSetting {
|
|
14
|
+
readonly key: string;
|
|
15
|
+
readonly namespace: string;
|
|
16
|
+
readonly value: unknown;
|
|
17
|
+
readonly version: number;
|
|
18
|
+
readonly updatedAt: number;
|
|
19
|
+
/** Versioned tombstone: een delete is een write met `deleted: true`, nooit een fysieke row-delete. */
|
|
20
|
+
readonly deleted: boolean;
|
|
21
|
+
}
|
|
22
|
+
/** Wat de service aan de store aanbiedt om te upserten; de store stempelt zelf de nieuwe `version`. */
|
|
23
|
+
export interface UpsertSetting {
|
|
24
|
+
readonly key: string;
|
|
25
|
+
readonly namespace: string;
|
|
26
|
+
readonly value: unknown;
|
|
27
|
+
readonly updatedAt: number;
|
|
28
|
+
readonly deleted?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* `SettingsServerStore`-poort (requires-port `SettingsStore`, server-kant): de host levert een adapter over
|
|
32
|
+
* `persistence` + de `user_settings`-migratie. `upsert` MOET de `version` server-side monotoon ophogen per
|
|
33
|
+
* (subject, key) — dat is de ordenings-autoriteit die client-klok-skew omzeilt.
|
|
34
|
+
*/
|
|
35
|
+
export interface SettingsServerStore {
|
|
36
|
+
all(subjectId: string): Promise<readonly StoredSetting[]>;
|
|
37
|
+
get(subjectId: string, key: string): Promise<StoredSetting | null>;
|
|
38
|
+
upsert(subjectId: string, entry: UpsertSetting): Promise<StoredSetting>;
|
|
39
|
+
}
|
|
40
|
+
/** `SubjectProvider`-poort: resolve het geauthenticeerde subject-id (alleen server-adapter). */
|
|
41
|
+
export interface SubjectProvider {
|
|
42
|
+
resolve(request: unknown): Promise<string | null>;
|
|
43
|
+
}
|
|
44
|
+
/** Configuratie van de mechanism-laag: het host-descriptor-schema + het gevalideerde config-fragment. */
|
|
45
|
+
export interface UserSettingsModuleConfig {
|
|
46
|
+
/** De host-gedeclareerde keys; drijft unknown-key- en waarde-validatie in de services. */
|
|
47
|
+
descriptors: SettingsSchema;
|
|
48
|
+
/** Uit het contract-config-fragment: renderKeys, cookiePrefix, serverSync, onboardingScope. */
|
|
49
|
+
policy: UserSettingsConfig;
|
|
50
|
+
}
|
|
51
|
+
/** De volledige afhankelijkheids-bundel die in elke flow-service wordt geïnjecteerd. */
|
|
52
|
+
export interface UserSettingsModuleDeps {
|
|
53
|
+
store: SettingsServerStore;
|
|
54
|
+
events: UserSettingsEventSink;
|
|
55
|
+
clock: Clock;
|
|
56
|
+
config: UserSettingsModuleConfig;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=ports.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ports.d.ts","sourceRoot":"","sources":["../../../backend/src/ports.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAEzG,2GAA2G;AAC3G,MAAM,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC;AAEjC,uGAAuG;AACvG,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,sGAAsG;IACtG,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,uGAAuG;AACvG,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC,CAAC;IAC1D,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IACnE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CACzE;AAED,gGAAgG;AAChG,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACnD;AAED,yGAAyG;AACzG,MAAM,WAAW,wBAAwB;IACvC,0FAA0F;IAC1F,WAAW,EAAE,cAAc,CAAC;IAC5B,+FAA+F;IAC/F,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAED,wFAAwF;AACxF,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,mBAAmB,CAAC;IAC3B,MAAM,EAAE,qBAAqB,CAAC;IAC9B,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,wBAAwB,CAAC;CAClC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ports.js","sourceRoot":"","sources":["../../../backend/src/ports.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* De backend-mechanism-laag: framework-vrije flow-services voor de server-adapter. Elke functie krijgt de
|
|
3
|
+
* {@link UserSettingsModuleDeps}-bundel + het gevalideerde request (en het subject-id), stempelt de
|
|
4
|
+
* server-beheerde monotone `version`, handhaaft de per-key LWW-merge en emit het juiste domein-event
|
|
5
|
+
* (nooit met een waarde erin). NestJS-controllers zijn *surface* en horen hier niet thuis.
|
|
6
|
+
*/
|
|
7
|
+
import type { MergeRequest, MergeResponse, PutSettingRequest, SettingsMapResponse, SettingsRecord } from '../../contract/index.js';
|
|
8
|
+
import type { UserSettingsModuleDeps } from './ports.js';
|
|
9
|
+
/** Lees de volledige keyed map van het subject. Tombstones komen mee als `deleted: true`. */
|
|
10
|
+
export declare function getSettings(deps: UserSettingsModuleDeps, subjectId: string): Promise<SettingsMapResponse>;
|
|
11
|
+
/**
|
|
12
|
+
* Upsert één key. Onbekende key → `unknown_key`; ongeldige waarde → `validation_failed`. Als de client een
|
|
13
|
+
* `baseVersion` meestuurt die achterloopt op de server → `stale_write` (409): de client heeft tegen
|
|
14
|
+
* verouderde state geschreven en moet opnieuw ophalen. Zonder `baseVersion` wint de write onvoorwaardelijk
|
|
15
|
+
* (receive-order LWW). De store hoogt de `version` monotoon op. Emit `setting.updated` (zonder waarde).
|
|
16
|
+
*/
|
|
17
|
+
export declare function putSetting(deps: UserSettingsModuleDeps, subjectId: string, key: string, req: PutSettingRequest): Promise<SettingsRecord>;
|
|
18
|
+
/**
|
|
19
|
+
* Voeg de anonieme client-map samen in de server-store. Conflict-policy (onderzoek-default): een key die de
|
|
20
|
+
* account nog NIET heeft wordt overgenomen (`client_filled`); een key die de account wél heeft blijft staan
|
|
21
|
+
* (`server_won`); een onbekende/ongeldige key wordt overgeslagen (`rejected`) — nooit stil weggegooid.
|
|
22
|
+
* Deterministisch en klok-onafhankelijk. `opId` maakt de aanroep idempotent op de surface-laag (dedupe).
|
|
23
|
+
* Emit één `settings.merged` met de aangeraakte keys + conflicten (nooit waarden).
|
|
24
|
+
*/
|
|
25
|
+
export declare function mergeSettings(deps: UserSettingsModuleDeps, subjectId: string, req: MergeRequest): Promise<MergeResponse>;
|
|
26
|
+
//# sourceMappingURL=services.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../../../backend/src/services.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAEV,YAAY,EACZ,aAAa,EACb,iBAAiB,EAEjB,mBAAmB,EACnB,cAAc,EACf,MAAM,yBAAyB,CAAC;AAGjC,OAAO,KAAK,EAAiB,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAIxE,6FAA6F;AAC7F,wBAAsB,WAAW,CAAC,IAAI,EAAE,sBAAsB,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAK/G;AAID;;;;;GAKG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,sBAAsB,EAC5B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAAC,cAAc,CAAC,CA0BzB;AAID;;;;;;GAMG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,sBAAsB,EAC5B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,YAAY,GAChB,OAAO,CAAC,aAAa,CAAC,CA4CxB"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { resolveDescriptor, validateValue } from './descriptors.js';
|
|
2
|
+
import { UserSettingsModuleError } from './errors.js';
|
|
3
|
+
// --- Lezen ---
|
|
4
|
+
/** Lees de volledige keyed map van het subject. Tombstones komen mee als `deleted: true`. */
|
|
5
|
+
export async function getSettings(deps, subjectId) {
|
|
6
|
+
const stored = await deps.store.all(subjectId);
|
|
7
|
+
const settings = {};
|
|
8
|
+
for (const record of stored)
|
|
9
|
+
settings[record.key] = toRecord(record);
|
|
10
|
+
return { settings };
|
|
11
|
+
}
|
|
12
|
+
// --- Eén key schrijven (optimistic concurrency via baseVersion) ---
|
|
13
|
+
/**
|
|
14
|
+
* Upsert één key. Onbekende key → `unknown_key`; ongeldige waarde → `validation_failed`. Als de client een
|
|
15
|
+
* `baseVersion` meestuurt die achterloopt op de server → `stale_write` (409): de client heeft tegen
|
|
16
|
+
* verouderde state geschreven en moet opnieuw ophalen. Zonder `baseVersion` wint de write onvoorwaardelijk
|
|
17
|
+
* (receive-order LWW). De store hoogt de `version` monotoon op. Emit `setting.updated` (zonder waarde).
|
|
18
|
+
*/
|
|
19
|
+
export async function putSetting(deps, subjectId, key, req) {
|
|
20
|
+
const descriptor = resolveDescriptor(deps.config.descriptors, key);
|
|
21
|
+
const value = validateValue(descriptor, req.value);
|
|
22
|
+
const current = await deps.store.get(subjectId, key);
|
|
23
|
+
if (current !== null && req.baseVersion !== undefined && req.baseVersion < current.version) {
|
|
24
|
+
throw new UserSettingsModuleError('stale_write');
|
|
25
|
+
}
|
|
26
|
+
const saved = await deps.store.upsert(subjectId, {
|
|
27
|
+
key,
|
|
28
|
+
namespace: descriptor.namespace,
|
|
29
|
+
value,
|
|
30
|
+
updatedAt: deps.clock(),
|
|
31
|
+
deleted: false,
|
|
32
|
+
});
|
|
33
|
+
deps.events.emit({
|
|
34
|
+
name: 'setting.updated',
|
|
35
|
+
occurredAt: nowIso(deps),
|
|
36
|
+
subjectId,
|
|
37
|
+
key,
|
|
38
|
+
namespace: descriptor.namespace,
|
|
39
|
+
version: saved.version,
|
|
40
|
+
});
|
|
41
|
+
return toRecord(saved);
|
|
42
|
+
}
|
|
43
|
+
// --- Guest→login merge (per-key, idempotent) ---
|
|
44
|
+
/**
|
|
45
|
+
* Voeg de anonieme client-map samen in de server-store. Conflict-policy (onderzoek-default): een key die de
|
|
46
|
+
* account nog NIET heeft wordt overgenomen (`client_filled`); een key die de account wél heeft blijft staan
|
|
47
|
+
* (`server_won`); een onbekende/ongeldige key wordt overgeslagen (`rejected`) — nooit stil weggegooid.
|
|
48
|
+
* Deterministisch en klok-onafhankelijk. `opId` maakt de aanroep idempotent op de surface-laag (dedupe).
|
|
49
|
+
* Emit één `settings.merged` met de aangeraakte keys + conflicten (nooit waarden).
|
|
50
|
+
*/
|
|
51
|
+
export async function mergeSettings(deps, subjectId, req) {
|
|
52
|
+
const conflicts = [];
|
|
53
|
+
const mergedKeys = [];
|
|
54
|
+
for (const [key, entry] of Object.entries(req.entries)) {
|
|
55
|
+
const descriptor = deps.config.descriptors.find((d) => d.key === key);
|
|
56
|
+
if (descriptor === undefined) {
|
|
57
|
+
conflicts.push({ key, resolution: 'rejected' });
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const parsed = descriptor.schema.safeParse(entry.value);
|
|
61
|
+
if (!parsed.success) {
|
|
62
|
+
conflicts.push({ key, resolution: 'rejected' });
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const current = await deps.store.get(subjectId, key);
|
|
66
|
+
if (current !== null) {
|
|
67
|
+
// De account heeft deze key al → server wint (default policy).
|
|
68
|
+
conflicts.push({ key, resolution: 'server_won' });
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
await deps.store.upsert(subjectId, {
|
|
72
|
+
key,
|
|
73
|
+
namespace: descriptor.namespace,
|
|
74
|
+
value: parsed.data,
|
|
75
|
+
updatedAt: deps.clock(),
|
|
76
|
+
deleted: entry.deleted ?? false,
|
|
77
|
+
});
|
|
78
|
+
mergedKeys.push(key);
|
|
79
|
+
conflicts.push({ key, resolution: 'client_filled' });
|
|
80
|
+
}
|
|
81
|
+
deps.events.emit({
|
|
82
|
+
name: 'settings.merged',
|
|
83
|
+
occurredAt: nowIso(deps),
|
|
84
|
+
subjectId,
|
|
85
|
+
mergedKeys,
|
|
86
|
+
conflicts: conflicts.filter((c) => c.resolution === 'server_won').map((c) => c.key),
|
|
87
|
+
});
|
|
88
|
+
const settings = (await getSettings(deps, subjectId)).settings;
|
|
89
|
+
return { settings, conflicts };
|
|
90
|
+
}
|
|
91
|
+
// --- Interne helpers ---
|
|
92
|
+
function toRecord(record) {
|
|
93
|
+
return {
|
|
94
|
+
value: record.value,
|
|
95
|
+
version: record.version,
|
|
96
|
+
updatedAt: record.updatedAt,
|
|
97
|
+
...(record.deleted ? { deleted: true } : {}),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/** ISO-8601 tijdstip uit de geïnjecteerde klok (deterministisch, geen `Date.now`). */
|
|
101
|
+
function nowIso(deps) {
|
|
102
|
+
return new Date(deps.clock()).toISOString();
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=services.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"services.js","sourceRoot":"","sources":["../../../backend/src/services.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* user-settings — config-fragment (host levert dit via de config-kit). Elke knop is divergentie-niveau 1.
|
|
4
|
+
* Defaults resolven tot een werkende, veilige module. `serverSync` staat AAN wanneer auth aanwezig is
|
|
5
|
+
* (locked decision): cross-device follow-me incl. onboarding; een no-auth product selecteert de
|
|
6
|
+
* server-adapter nooit (client-only, geen backend) ongeacht deze vlag.
|
|
7
|
+
*/
|
|
8
|
+
export declare const userSettingsConfigSchema: z.ZodObject<{
|
|
9
|
+
/** Server-persistentie aan wanneer auth aanwezig is (default), of expliciet uit voor client-only. */
|
|
10
|
+
serverSync: z.ZodDefault<z.ZodBoolean>;
|
|
11
|
+
/** Prefix voor de render-key cookies. */
|
|
12
|
+
cookiePrefix: z.ZodDefault<z.ZodString>;
|
|
13
|
+
/** De render-kritieke keys die altijd op een server-leesbare cookie gaan (SSR-veilig, first-paint). */
|
|
14
|
+
renderKeys: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
15
|
+
/** Levensduur (dagen) van de render-key cookies. */
|
|
16
|
+
cookieMaxAgeDays: z.ZodDefault<z.ZodNumber>;
|
|
17
|
+
/** Onboarding-scope: 'server' (follow-me over devices, default) of 'device' (localStorage). */
|
|
18
|
+
onboardingScope: z.ZodDefault<z.ZodEnum<["server", "device"]>>;
|
|
19
|
+
}, "strip", z.ZodTypeAny, {
|
|
20
|
+
serverSync: boolean;
|
|
21
|
+
cookiePrefix: string;
|
|
22
|
+
renderKeys: string[];
|
|
23
|
+
cookieMaxAgeDays: number;
|
|
24
|
+
onboardingScope: "server" | "device";
|
|
25
|
+
}, {
|
|
26
|
+
serverSync?: boolean | undefined;
|
|
27
|
+
cookiePrefix?: string | undefined;
|
|
28
|
+
renderKeys?: string[] | undefined;
|
|
29
|
+
cookieMaxAgeDays?: number | undefined;
|
|
30
|
+
onboardingScope?: "server" | "device" | undefined;
|
|
31
|
+
}>;
|
|
32
|
+
export type UserSettingsConfig = z.infer<typeof userSettingsConfigSchema>;
|
|
33
|
+
/** Ongevalideerde host-invoer (alle velden optioneel; defaults vullen de rest). */
|
|
34
|
+
export type UserSettingsConfigInput = z.input<typeof userSettingsConfigSchema>;
|
|
35
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../contract/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB;IACnC,qGAAqG;;IAErG,yCAAyC;;IAEzC,uGAAuG;;IAEvG,oDAAoD;;IAEpD,+FAA+F;;;;;;;;;;;;;;EAE/F,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAC1E,mFAAmF;AACnF,MAAM,MAAM,uBAAuB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* user-settings — config-fragment (host levert dit via de config-kit). Elke knop is divergentie-niveau 1.
|
|
4
|
+
* Defaults resolven tot een werkende, veilige module. `serverSync` staat AAN wanneer auth aanwezig is
|
|
5
|
+
* (locked decision): cross-device follow-me incl. onboarding; een no-auth product selecteert de
|
|
6
|
+
* server-adapter nooit (client-only, geen backend) ongeacht deze vlag.
|
|
7
|
+
*/
|
|
8
|
+
export const userSettingsConfigSchema = z.object({
|
|
9
|
+
/** Server-persistentie aan wanneer auth aanwezig is (default), of expliciet uit voor client-only. */
|
|
10
|
+
serverSync: z.boolean().default(true),
|
|
11
|
+
/** Prefix voor de render-key cookies. */
|
|
12
|
+
cookiePrefix: z.string().min(1).default('us_'),
|
|
13
|
+
/** De render-kritieke keys die altijd op een server-leesbare cookie gaan (SSR-veilig, first-paint). */
|
|
14
|
+
renderKeys: z.array(z.string().min(1)).default(['theme', 'locale', 'dir']),
|
|
15
|
+
/** Levensduur (dagen) van de render-key cookies. */
|
|
16
|
+
cookieMaxAgeDays: z.number().int().positive().default(365),
|
|
17
|
+
/** Onboarding-scope: 'server' (follow-me over devices, default) of 'device' (localStorage). */
|
|
18
|
+
onboardingScope: z.enum(['server', 'device']).default('server'),
|
|
19
|
+
});
|
|
20
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../contract/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,qGAAqG;IACrG,UAAU,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACrC,yCAAyC;IACzC,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;IAC9C,uGAAuG;IACvG,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC1E,oDAAoD;IACpD,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC;IAC1D,+FAA+F;IAC/F,eAAe,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;CAChE,CAAC,CAAC"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { UserSettingsErrorCode } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* De machine-leesbare endpoint-catalogus (alleen relevant wanneer de server-adapter draait). Backend
|
|
5
|
+
* leidt hier route + validatie uit af; frontend leidt hier zijn getypte client uit af. Eén bron, geen
|
|
6
|
+
* drift — de manifest `backendRoutes` wordt hieruit afgeleid (nooit met de hand overgetypt).
|
|
7
|
+
*/
|
|
8
|
+
export interface EndpointDescriptor<Req extends z.ZodTypeAny | null, Res extends z.ZodTypeAny> {
|
|
9
|
+
readonly method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
10
|
+
readonly path: string;
|
|
11
|
+
readonly request: Req;
|
|
12
|
+
readonly response: Res;
|
|
13
|
+
/** Foutcodes die dit endpoint kan retourneren (uit de gedeelde taxonomie). */
|
|
14
|
+
readonly errors: readonly UserSettingsErrorCode[];
|
|
15
|
+
/** Vereist een geldig CSRF-token (cookie-transport). */
|
|
16
|
+
readonly csrf: boolean;
|
|
17
|
+
/** Vereist een geauthenticeerde sessie (via de host SubjectProvider). */
|
|
18
|
+
readonly auth: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare const USER_SETTINGS_ENDPOINTS: {
|
|
21
|
+
readonly getSettings: {
|
|
22
|
+
readonly method: "GET";
|
|
23
|
+
readonly path: "/settings";
|
|
24
|
+
readonly request: null;
|
|
25
|
+
readonly response: z.ZodObject<{
|
|
26
|
+
settings: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
27
|
+
value: z.ZodUnknown;
|
|
28
|
+
version: z.ZodNumber;
|
|
29
|
+
updatedAt: z.ZodNumber;
|
|
30
|
+
deleted: z.ZodOptional<z.ZodBoolean>;
|
|
31
|
+
}, "strip", z.ZodTypeAny, {
|
|
32
|
+
version: number;
|
|
33
|
+
updatedAt: number;
|
|
34
|
+
value?: unknown;
|
|
35
|
+
deleted?: boolean | undefined;
|
|
36
|
+
}, {
|
|
37
|
+
version: number;
|
|
38
|
+
updatedAt: number;
|
|
39
|
+
value?: unknown;
|
|
40
|
+
deleted?: boolean | undefined;
|
|
41
|
+
}>>;
|
|
42
|
+
}, "strip", z.ZodTypeAny, {
|
|
43
|
+
settings: Record<string, {
|
|
44
|
+
version: number;
|
|
45
|
+
updatedAt: number;
|
|
46
|
+
value?: unknown;
|
|
47
|
+
deleted?: boolean | undefined;
|
|
48
|
+
}>;
|
|
49
|
+
}, {
|
|
50
|
+
settings: Record<string, {
|
|
51
|
+
version: number;
|
|
52
|
+
updatedAt: number;
|
|
53
|
+
value?: unknown;
|
|
54
|
+
deleted?: boolean | undefined;
|
|
55
|
+
}>;
|
|
56
|
+
}>;
|
|
57
|
+
readonly errors: readonly ["unauthenticated", "store_unavailable"];
|
|
58
|
+
readonly csrf: false;
|
|
59
|
+
readonly auth: true;
|
|
60
|
+
};
|
|
61
|
+
readonly putSetting: {
|
|
62
|
+
readonly method: "PUT";
|
|
63
|
+
readonly path: "/settings/:key";
|
|
64
|
+
readonly request: z.ZodObject<{
|
|
65
|
+
value: z.ZodUnknown;
|
|
66
|
+
baseVersion: z.ZodOptional<z.ZodNumber>;
|
|
67
|
+
}, "strip", z.ZodTypeAny, {
|
|
68
|
+
value?: unknown;
|
|
69
|
+
baseVersion?: number | undefined;
|
|
70
|
+
}, {
|
|
71
|
+
value?: unknown;
|
|
72
|
+
baseVersion?: number | undefined;
|
|
73
|
+
}>;
|
|
74
|
+
readonly response: z.ZodObject<{
|
|
75
|
+
value: z.ZodUnknown;
|
|
76
|
+
version: z.ZodNumber;
|
|
77
|
+
updatedAt: z.ZodNumber;
|
|
78
|
+
deleted: z.ZodOptional<z.ZodBoolean>;
|
|
79
|
+
}, "strip", z.ZodTypeAny, {
|
|
80
|
+
version: number;
|
|
81
|
+
updatedAt: number;
|
|
82
|
+
value?: unknown;
|
|
83
|
+
deleted?: boolean | undefined;
|
|
84
|
+
}, {
|
|
85
|
+
version: number;
|
|
86
|
+
updatedAt: number;
|
|
87
|
+
value?: unknown;
|
|
88
|
+
deleted?: boolean | undefined;
|
|
89
|
+
}>;
|
|
90
|
+
readonly errors: readonly ["unknown_key", "validation_failed", "stale_write", "unauthenticated", "csrf_failed", "store_unavailable"];
|
|
91
|
+
readonly csrf: true;
|
|
92
|
+
readonly auth: true;
|
|
93
|
+
};
|
|
94
|
+
readonly mergeSettings: {
|
|
95
|
+
readonly method: "POST";
|
|
96
|
+
readonly path: "/settings/merge";
|
|
97
|
+
readonly request: z.ZodObject<{
|
|
98
|
+
opId: z.ZodString;
|
|
99
|
+
entries: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
100
|
+
value: z.ZodUnknown;
|
|
101
|
+
version: z.ZodNumber;
|
|
102
|
+
updatedAt: z.ZodNumber;
|
|
103
|
+
deleted: z.ZodOptional<z.ZodBoolean>;
|
|
104
|
+
}, "strip", z.ZodTypeAny, {
|
|
105
|
+
version: number;
|
|
106
|
+
updatedAt: number;
|
|
107
|
+
value?: unknown;
|
|
108
|
+
deleted?: boolean | undefined;
|
|
109
|
+
}, {
|
|
110
|
+
version: number;
|
|
111
|
+
updatedAt: number;
|
|
112
|
+
value?: unknown;
|
|
113
|
+
deleted?: boolean | undefined;
|
|
114
|
+
}>>;
|
|
115
|
+
}, "strip", z.ZodTypeAny, {
|
|
116
|
+
entries: Record<string, {
|
|
117
|
+
version: number;
|
|
118
|
+
updatedAt: number;
|
|
119
|
+
value?: unknown;
|
|
120
|
+
deleted?: boolean | undefined;
|
|
121
|
+
}>;
|
|
122
|
+
opId: string;
|
|
123
|
+
}, {
|
|
124
|
+
entries: Record<string, {
|
|
125
|
+
version: number;
|
|
126
|
+
updatedAt: number;
|
|
127
|
+
value?: unknown;
|
|
128
|
+
deleted?: boolean | undefined;
|
|
129
|
+
}>;
|
|
130
|
+
opId: string;
|
|
131
|
+
}>;
|
|
132
|
+
readonly response: z.ZodObject<{
|
|
133
|
+
settings: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
134
|
+
value: z.ZodUnknown;
|
|
135
|
+
version: z.ZodNumber;
|
|
136
|
+
updatedAt: z.ZodNumber;
|
|
137
|
+
deleted: z.ZodOptional<z.ZodBoolean>;
|
|
138
|
+
}, "strip", z.ZodTypeAny, {
|
|
139
|
+
version: number;
|
|
140
|
+
updatedAt: number;
|
|
141
|
+
value?: unknown;
|
|
142
|
+
deleted?: boolean | undefined;
|
|
143
|
+
}, {
|
|
144
|
+
version: number;
|
|
145
|
+
updatedAt: number;
|
|
146
|
+
value?: unknown;
|
|
147
|
+
deleted?: boolean | undefined;
|
|
148
|
+
}>>;
|
|
149
|
+
conflicts: z.ZodArray<z.ZodObject<{
|
|
150
|
+
key: z.ZodString;
|
|
151
|
+
resolution: z.ZodEnum<["server_won", "client_filled", "rejected"]>;
|
|
152
|
+
}, "strip", z.ZodTypeAny, {
|
|
153
|
+
key: string;
|
|
154
|
+
resolution: "server_won" | "client_filled" | "rejected";
|
|
155
|
+
}, {
|
|
156
|
+
key: string;
|
|
157
|
+
resolution: "server_won" | "client_filled" | "rejected";
|
|
158
|
+
}>, "many">;
|
|
159
|
+
}, "strip", z.ZodTypeAny, {
|
|
160
|
+
settings: Record<string, {
|
|
161
|
+
version: number;
|
|
162
|
+
updatedAt: number;
|
|
163
|
+
value?: unknown;
|
|
164
|
+
deleted?: boolean | undefined;
|
|
165
|
+
}>;
|
|
166
|
+
conflicts: {
|
|
167
|
+
key: string;
|
|
168
|
+
resolution: "server_won" | "client_filled" | "rejected";
|
|
169
|
+
}[];
|
|
170
|
+
}, {
|
|
171
|
+
settings: Record<string, {
|
|
172
|
+
version: number;
|
|
173
|
+
updatedAt: number;
|
|
174
|
+
value?: unknown;
|
|
175
|
+
deleted?: boolean | undefined;
|
|
176
|
+
}>;
|
|
177
|
+
conflicts: {
|
|
178
|
+
key: string;
|
|
179
|
+
resolution: "server_won" | "client_filled" | "rejected";
|
|
180
|
+
}[];
|
|
181
|
+
}>;
|
|
182
|
+
readonly errors: readonly ["validation_failed", "unauthenticated", "csrf_failed", "store_unavailable"];
|
|
183
|
+
readonly csrf: true;
|
|
184
|
+
readonly auth: true;
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
export type UserSettingsEndpointName = keyof typeof USER_SETTINGS_ENDPOINTS;
|
|
188
|
+
//# sourceMappingURL=endpoints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoints.d.ts","sourceRoot":"","sources":["../../contract/endpoints.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQ7B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAEzD;;;;GAIG;AACH,MAAM,WAAW,kBAAkB,CAAC,GAAG,SAAS,CAAC,CAAC,UAAU,GAAG,IAAI,EAAE,GAAG,SAAS,CAAC,CAAC,UAAU;IAC3F,QAAQ,CAAC,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC;IACvB,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAClD,wDAAwD;IACxD,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkBsD,CAAC;AAE3F,MAAM,MAAM,wBAAwB,GAAG,MAAM,OAAO,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { settingsMapResponseSchema, putSettingRequestSchema, putSettingResponseSchema, mergeRequestSchema, mergeResponseSchema, } from './schemas.js';
|
|
2
|
+
export const USER_SETTINGS_ENDPOINTS = {
|
|
3
|
+
getSettings: {
|
|
4
|
+
method: 'GET', path: '/settings', request: null,
|
|
5
|
+
response: settingsMapResponseSchema,
|
|
6
|
+
errors: ['unauthenticated', 'store_unavailable'], csrf: false, auth: true,
|
|
7
|
+
},
|
|
8
|
+
putSetting: {
|
|
9
|
+
method: 'PUT', path: '/settings/:key', request: putSettingRequestSchema,
|
|
10
|
+
response: putSettingResponseSchema,
|
|
11
|
+
errors: ['unknown_key', 'validation_failed', 'stale_write', 'unauthenticated', 'csrf_failed', 'store_unavailable'],
|
|
12
|
+
csrf: true, auth: true,
|
|
13
|
+
},
|
|
14
|
+
mergeSettings: {
|
|
15
|
+
method: 'POST', path: '/settings/merge', request: mergeRequestSchema,
|
|
16
|
+
response: mergeResponseSchema,
|
|
17
|
+
errors: ['validation_failed', 'unauthenticated', 'csrf_failed', 'store_unavailable'],
|
|
18
|
+
csrf: true, auth: true,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=endpoints.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoints.js","sourceRoot":"","sources":["../../contract/endpoints.ts"],"names":[],"mappings":"AACA,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,wBAAwB,EACxB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,cAAc,CAAC;AAqBtB,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,WAAW,EAAE;QACX,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI;QAC/C,QAAQ,EAAE,yBAAyB;QACnC,MAAM,EAAE,CAAC,iBAAiB,EAAE,mBAAmB,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI;KAC1E;IACD,UAAU,EAAE;QACV,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,uBAAuB;QACvE,QAAQ,EAAE,wBAAwB;QAClC,MAAM,EAAE,CAAC,aAAa,EAAE,mBAAmB,EAAE,aAAa,EAAE,iBAAiB,EAAE,aAAa,EAAE,mBAAmB,CAAC;QAClH,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;KACvB;IACD,aAAa,EAAE;QACb,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,kBAAkB;QACpE,QAAQ,EAAE,mBAAmB;QAC7B,MAAM,EAAE,CAAC,mBAAmB,EAAE,iBAAiB,EAAE,aAAa,EAAE,mBAAmB,CAAC;QACpF,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;KACvB;CACuF,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error-taxonomie + i18n-sleutels. Eén gedeelde foutcodelijst die beide helften (backend mapping → HTTP,
|
|
3
|
+
* frontend weergave → i18n) uit het contract afleiden. Foutteksten lekken nooit waarden of PII.
|
|
4
|
+
*/
|
|
5
|
+
export declare const USER_SETTINGS_ERROR_CODES: readonly ["unknown_key", "validation_failed", "consent_namespace_rejected", "stale_write", "unauthenticated", "csrf_failed", "store_unavailable"];
|
|
6
|
+
export type UserSettingsErrorCode = (typeof USER_SETTINGS_ERROR_CODES)[number];
|
|
7
|
+
export interface UserSettingsErrorDescriptor {
|
|
8
|
+
/** HTTP-status waarnaar de backend deze fout mapt. */
|
|
9
|
+
readonly httpStatus: number;
|
|
10
|
+
/** i18n-sleutel die de frontend toont (nooit rauwe details). */
|
|
11
|
+
readonly i18nKey: string;
|
|
12
|
+
}
|
|
13
|
+
/** Enige bron van de code→HTTP + code→i18n-mapping. */
|
|
14
|
+
export declare const USER_SETTINGS_ERROR_TAXONOMY: Readonly<Record<UserSettingsErrorCode, UserSettingsErrorDescriptor>>;
|
|
15
|
+
/** Contract-vorm van een foutrespons (body). Bevat nooit stacktraces, waarden of PII. */
|
|
16
|
+
export interface UserSettingsErrorBody {
|
|
17
|
+
readonly code: UserSettingsErrorCode;
|
|
18
|
+
readonly message: string;
|
|
19
|
+
/** Optioneel: veld-gebonden validatiefouten (paden), zonder ingevoerde waarden. */
|
|
20
|
+
readonly fields?: Readonly<Record<string, string[]>>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../contract/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,eAAO,MAAM,yBAAyB,mJAQ5B,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE/E,MAAM,WAAW,2BAA2B;IAC1C,sDAAsD;IACtD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,gEAAgE;IAChE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,uDAAuD;AACvD,eAAO,MAAM,4BAA4B,EAAE,QAAQ,CAAC,MAAM,CAAC,qBAAqB,EAAE,2BAA2B,CAAC,CAW1G,CAAC;AAEL,yFAAyF;AACzF,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,mFAAmF;IACnF,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;CACtD"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error-taxonomie + i18n-sleutels. Eén gedeelde foutcodelijst die beide helften (backend mapping → HTTP,
|
|
3
|
+
* frontend weergave → i18n) uit het contract afleiden. Foutteksten lekken nooit waarden of PII.
|
|
4
|
+
*/
|
|
5
|
+
export const USER_SETTINGS_ERROR_CODES = [
|
|
6
|
+
'unknown_key',
|
|
7
|
+
'validation_failed',
|
|
8
|
+
'consent_namespace_rejected',
|
|
9
|
+
'stale_write',
|
|
10
|
+
'unauthenticated',
|
|
11
|
+
'csrf_failed',
|
|
12
|
+
'store_unavailable',
|
|
13
|
+
];
|
|
14
|
+
/** Enige bron van de code→HTTP + code→i18n-mapping. */
|
|
15
|
+
export const USER_SETTINGS_ERROR_TAXONOMY = Object.freeze({
|
|
16
|
+
unknown_key: { httpStatus: 422, i18nKey: 'userSettings.error.unknown_key' },
|
|
17
|
+
validation_failed: { httpStatus: 422, i18nKey: 'userSettings.error.validation_failed' },
|
|
18
|
+
consent_namespace_rejected: { httpStatus: 422, i18nKey: 'userSettings.error.consent_namespace_rejected' },
|
|
19
|
+
// stale_write: de baseVersion is ouder dan de server. Voor scalar-prefs mag de write alsnog winnen
|
|
20
|
+
// (receive-order LWW), maar de code wordt blootgesteld voor optimistic-concurrency-detectie.
|
|
21
|
+
stale_write: { httpStatus: 409, i18nKey: 'userSettings.error.stale_write' },
|
|
22
|
+
unauthenticated: { httpStatus: 401, i18nKey: 'userSettings.error.unauthenticated' },
|
|
23
|
+
csrf_failed: { httpStatus: 403, i18nKey: 'userSettings.error.csrf_failed' },
|
|
24
|
+
store_unavailable: { httpStatus: 503, i18nKey: 'userSettings.error.store_unavailable' },
|
|
25
|
+
});
|
|
26
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../contract/errors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,aAAa;IACb,mBAAmB;IACnB,4BAA4B;IAC5B,aAAa;IACb,iBAAiB;IACjB,aAAa;IACb,mBAAmB;CACX,CAAC;AAWX,uDAAuD;AACvD,MAAM,CAAC,MAAM,4BAA4B,GACvC,MAAM,CAAC,MAAM,CAAC;IACZ,WAAW,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAE,gCAAgC,EAAE;IAC3E,iBAAiB,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAE,sCAAsC,EAAE;IACvF,0BAA0B,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAE,+CAA+C,EAAE;IACzG,mGAAmG;IACnG,6FAA6F;IAC7F,WAAW,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAE,gCAAgC,EAAE;IAC3E,eAAe,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAE,oCAAoC,EAAE;IACnF,WAAW,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAE,gCAAgC,EAAE;IAC3E,iBAAiB,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAE,sCAAsC,EAAE;CACxF,CAAC,CAAC"}
|