@burojs/context 0.2.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.
@@ -0,0 +1,197 @@
1
+ type PermissionStatus = 'loading' | 'ready' | 'error';
2
+ /**
3
+ * Permission resolver returned by user's permissions slice.
4
+ * Default behavior contract:
5
+ * loading: visible=true, enabled=false (UI shows controls in disabled state)
6
+ * error: visible=false, enabled=false (fail-closed)
7
+ * ready: normal evaluation
8
+ */
9
+ interface PermissionMap {
10
+ status: PermissionStatus;
11
+ can: (action: string, resource: string, record?: unknown) => boolean;
12
+ reason: (action: string, resource: string, record?: unknown) => string | undefined;
13
+ }
14
+
15
+ type SliceKind = 'static' | 'fromAuth' | 'picker' | 'async';
16
+ /**
17
+ * Common shape for all slice definitions.
18
+ * Specific kinds extend this with kind-specific fields (defined in slices/*.ts).
19
+ */
20
+ interface SliceDefinition<TName extends string = string, TValue = unknown> {
21
+ name: TName;
22
+ kind: SliceKind;
23
+ deps: string[];
24
+ /** Phantom — for type inference of slice value types. */
25
+ __valueType?: TValue;
26
+ }
27
+
28
+ interface Store<TState extends Record<string, unknown>> {
29
+ snapshot: () => TState;
30
+ setSlice: <K extends keyof TState>(key: K, value: TState[K]) => void;
31
+ subscribe: (fn: () => void, selector?: (state: TState) => unknown) => () => void;
32
+ }
33
+
34
+ interface Atom<T> {
35
+ get: () => T;
36
+ set: (value: T) => void;
37
+ subscribe: (fn: () => void) => () => void;
38
+ }
39
+ declare function createAtom<T>(initial: T): Atom<T>;
40
+
41
+ declare function createStore<TState extends Record<string, unknown>>(initial: TState): Store<TState>;
42
+
43
+ /**
44
+ * Proxy-based read tracking. The selector function reads from a Proxy
45
+ * wrapping the target; every property access is recorded with dotted-path keys.
46
+ *
47
+ * Used by store.subscribe to know exactly which slice keys a subscriber depends on,
48
+ * enabling point-wise re-invocation only when relevant slices change.
49
+ */
50
+ interface TrackedReadResult<T> {
51
+ result: T;
52
+ deps: Set<string>;
53
+ }
54
+ declare function trackReads<TCtx, TResult>(ctx: TCtx, selector: (c: TCtx) => TResult): TrackedReadResult<TResult>;
55
+
56
+ interface DagNode {
57
+ name: string;
58
+ deps: string[];
59
+ }
60
+ /**
61
+ * Kahn-style topological sort with cycle detection.
62
+ * Throws on cycles (with path) and on missing dependencies.
63
+ */
64
+ declare function topoSort<N extends DagNode>(nodes: N[]): N[];
65
+
66
+ interface StaticSliceDefinition<TName extends string, TValue> extends SliceDefinition<TName, TValue> {
67
+ kind: 'static';
68
+ value: () => TValue;
69
+ }
70
+ interface StaticSliceConfig<TValue> {
71
+ value: TValue | (() => TValue);
72
+ }
73
+ declare function defineStaticSlice<TName extends string, TValue>(name: TName, config: StaticSliceConfig<TValue>): StaticSliceDefinition<TName, TValue>;
74
+
75
+ type AuthInvalidationEvent = 'login' | 'logout' | 'refresh';
76
+ interface FromAuthSliceDefinition<TName extends string, TValue> extends SliceDefinition<TName, TValue> {
77
+ kind: 'fromAuth';
78
+ extract: (rawAuthUser: unknown) => TValue;
79
+ invalidateOn: AuthInvalidationEvent[];
80
+ }
81
+ interface FromAuthSliceConfig<TValue> {
82
+ extract: (rawAuthUser: never) => TValue;
83
+ invalidateOn?: AuthInvalidationEvent[];
84
+ }
85
+ declare function defineFromAuthSlice<TName extends string, TValue>(name: TName, config: FromAuthSliceConfig<TValue>): FromAuthSliceDefinition<TName, TValue>;
86
+
87
+ type PickerPersistMode = 'localStorage' | 'sessionStorage' | 'url';
88
+ /**
89
+ * Picker target — minimal contract. Real ResourceDef from @burojs/core
90
+ * has full shape; here we accept anything thunk-returning a name.
91
+ *
92
+ * SCOPE-ISOLATION CONTRACT:
93
+ * The picker-slice NEVER reads `target.scope` or `target.requireScope`.
94
+ *
95
+ * ⚠️ This comment used to also list `picker.label` / `picker.searchFields`
96
+ * / `picker.listColumns` / `picker.filters` as properties the slice reads.
97
+ * It never read them: `definePickerSlice` is a pure descriptor factory and
98
+ * touches none of them. The `ResourceConfig.picker` block those names came
99
+ * from was deleted in the core-flexibility program (sub-project D) as dead
100
+ * API — it had no reader anywhere in the repo, and only survived a type
101
+ * error because `define-resource.ts` copied it through with an `as any`.
102
+ * Resolver emits a dev-warning if target has those set.
103
+ */
104
+ interface PickerSliceDefinition<TName extends string, TValue> extends SliceDefinition<TName, TValue> {
105
+ kind: 'picker';
106
+ resource: () => unknown;
107
+ filter?: (ctx: never) => Record<string, unknown>;
108
+ resetWhen?: string[];
109
+ persist?: PickerPersistMode;
110
+ required: boolean;
111
+ }
112
+ interface PickerSliceConfig {
113
+ resource: () => unknown;
114
+ dependsOn?: string[];
115
+ filter?: (ctx: never) => Record<string, unknown>;
116
+ resetWhen?: string[];
117
+ persist?: PickerPersistMode;
118
+ required?: boolean;
119
+ }
120
+ declare function definePickerSlice<TName extends string, TValue = unknown>(name: TName, config: PickerSliceConfig): PickerSliceDefinition<TName, TValue>;
121
+
122
+ interface AsyncSliceDefinition<TName extends string, TValue> extends SliceDefinition<TName, TValue> {
123
+ kind: 'async';
124
+ fetch: (ctx: never) => Promise<TValue>;
125
+ cacheKey?: (ctx: never) => string;
126
+ staleTime: number;
127
+ }
128
+ interface AsyncSliceConfig<TValue> {
129
+ dependsOn?: string[];
130
+ fetch: (ctx: never) => Promise<TValue>;
131
+ cacheKey?: (ctx: never) => string;
132
+ /** ms; 0 = always refetch on invalidation */
133
+ staleTime?: number;
134
+ }
135
+ declare function defineAsyncSlice<TName extends string, TValue>(name: TName, config: AsyncSliceConfig<TValue>): AsyncSliceDefinition<TName, TValue>;
136
+
137
+ interface SliceBuilder {
138
+ static: <TValue>(config: StaticSliceConfig<TValue>) => StaticSliceDefinition<string, TValue>;
139
+ fromAuth: <TValue>(config: FromAuthSliceConfig<TValue>) => FromAuthSliceDefinition<string, TValue>;
140
+ picker: <TValue = unknown>(config: PickerSliceConfig) => PickerSliceDefinition<string, TValue>;
141
+ async: <TValue>(config: AsyncSliceConfig<TValue>) => AsyncSliceDefinition<string, TValue>;
142
+ }
143
+ declare function defineContextSlices<T extends Record<string, SliceDefinition>>(factory: (s: SliceBuilder) => T): T;
144
+
145
+ interface Resolver<TSlices extends Record<string, SliceDefinition>> {
146
+ boot: () => Promise<void>;
147
+ snapshot: () => {
148
+ [K in keyof TSlices]: unknown;
149
+ };
150
+ setSlice: <K extends keyof TSlices>(key: K, value: unknown) => void;
151
+ subscribe: (fn: () => void, selector?: (s: never) => unknown) => () => void;
152
+ feedAuth: (rawAuthUser: unknown) => void;
153
+ flushAsync: () => Promise<void>;
154
+ }
155
+ declare function createResolver<TSlices extends Record<string, SliceDefinition>>(slices: TSlices): Resolver<TSlices>;
156
+
157
+ type Scope = Record<string, unknown> | null;
158
+ interface ScopeChainEntry {
159
+ name: string;
160
+ scope: (ctx: never) => Scope;
161
+ }
162
+ /**
163
+ * Shallow merge two scope objects. Patch wins on conflict.
164
+ * null on either side is treated as "no contribution".
165
+ */
166
+ declare function mergeScope(base: Scope, patch: Scope): Scope;
167
+ /**
168
+ * Cascade scope down a parent → child chain.
169
+ * Each entry's scope() is invoked with ctx; results are merged left-to-right.
170
+ * Later entries override earlier ones (child wins).
171
+ */
172
+ declare function cascadeScope(chain: ScopeChainEntry[], ctx: never): Scope;
173
+ interface RequireScopeOk {
174
+ ok: true;
175
+ }
176
+ interface RequireScopeFail {
177
+ ok: false;
178
+ missing: string[];
179
+ }
180
+ type RequireScopeResult = RequireScopeOk | RequireScopeFail;
181
+ /**
182
+ * Verify all required keys are present (and not undefined) in scope.
183
+ */
184
+ declare function checkRequireScope(scope: Scope, required: string[]): RequireScopeResult;
185
+
186
+ declare function createPendingPermissions(): PermissionMap;
187
+ declare function createErrorPermissions(error: unknown): PermissionMap;
188
+ interface ReadyPermissionsConfig {
189
+ can: (action: string, resource: string, record?: unknown) => boolean;
190
+ reason?: (action: string, resource: string, record?: unknown) => string | undefined;
191
+ }
192
+ declare function createReadyPermissions(config: ReadyPermissionsConfig): PermissionMap;
193
+
194
+ declare const PACKAGE_NAME: "@burojs/context";
195
+ declare const PACKAGE_VERSION: "0.0.0";
196
+
197
+ export { type AsyncSliceConfig, type AsyncSliceDefinition, type Atom, type AuthInvalidationEvent, type DagNode, type FromAuthSliceConfig, type FromAuthSliceDefinition, PACKAGE_NAME, PACKAGE_VERSION, type PermissionMap, type PermissionStatus, type PickerPersistMode, type PickerSliceConfig, type PickerSliceDefinition, type ReadyPermissionsConfig, type RequireScopeFail, type RequireScopeOk, type RequireScopeResult, type Resolver, type Scope, type ScopeChainEntry, type SliceBuilder, type SliceDefinition, type SliceKind, type StaticSliceConfig, type StaticSliceDefinition, type Store, type TrackedReadResult, cascadeScope, checkRequireScope, createAtom, createErrorPermissions, createPendingPermissions, createReadyPermissions, createResolver, createStore, defineAsyncSlice, defineContextSlices, defineFromAuthSlice, definePickerSlice, defineStaticSlice, mergeScope, topoSort, trackReads };
@@ -0,0 +1,197 @@
1
+ type PermissionStatus = 'loading' | 'ready' | 'error';
2
+ /**
3
+ * Permission resolver returned by user's permissions slice.
4
+ * Default behavior contract:
5
+ * loading: visible=true, enabled=false (UI shows controls in disabled state)
6
+ * error: visible=false, enabled=false (fail-closed)
7
+ * ready: normal evaluation
8
+ */
9
+ interface PermissionMap {
10
+ status: PermissionStatus;
11
+ can: (action: string, resource: string, record?: unknown) => boolean;
12
+ reason: (action: string, resource: string, record?: unknown) => string | undefined;
13
+ }
14
+
15
+ type SliceKind = 'static' | 'fromAuth' | 'picker' | 'async';
16
+ /**
17
+ * Common shape for all slice definitions.
18
+ * Specific kinds extend this with kind-specific fields (defined in slices/*.ts).
19
+ */
20
+ interface SliceDefinition<TName extends string = string, TValue = unknown> {
21
+ name: TName;
22
+ kind: SliceKind;
23
+ deps: string[];
24
+ /** Phantom — for type inference of slice value types. */
25
+ __valueType?: TValue;
26
+ }
27
+
28
+ interface Store<TState extends Record<string, unknown>> {
29
+ snapshot: () => TState;
30
+ setSlice: <K extends keyof TState>(key: K, value: TState[K]) => void;
31
+ subscribe: (fn: () => void, selector?: (state: TState) => unknown) => () => void;
32
+ }
33
+
34
+ interface Atom<T> {
35
+ get: () => T;
36
+ set: (value: T) => void;
37
+ subscribe: (fn: () => void) => () => void;
38
+ }
39
+ declare function createAtom<T>(initial: T): Atom<T>;
40
+
41
+ declare function createStore<TState extends Record<string, unknown>>(initial: TState): Store<TState>;
42
+
43
+ /**
44
+ * Proxy-based read tracking. The selector function reads from a Proxy
45
+ * wrapping the target; every property access is recorded with dotted-path keys.
46
+ *
47
+ * Used by store.subscribe to know exactly which slice keys a subscriber depends on,
48
+ * enabling point-wise re-invocation only when relevant slices change.
49
+ */
50
+ interface TrackedReadResult<T> {
51
+ result: T;
52
+ deps: Set<string>;
53
+ }
54
+ declare function trackReads<TCtx, TResult>(ctx: TCtx, selector: (c: TCtx) => TResult): TrackedReadResult<TResult>;
55
+
56
+ interface DagNode {
57
+ name: string;
58
+ deps: string[];
59
+ }
60
+ /**
61
+ * Kahn-style topological sort with cycle detection.
62
+ * Throws on cycles (with path) and on missing dependencies.
63
+ */
64
+ declare function topoSort<N extends DagNode>(nodes: N[]): N[];
65
+
66
+ interface StaticSliceDefinition<TName extends string, TValue> extends SliceDefinition<TName, TValue> {
67
+ kind: 'static';
68
+ value: () => TValue;
69
+ }
70
+ interface StaticSliceConfig<TValue> {
71
+ value: TValue | (() => TValue);
72
+ }
73
+ declare function defineStaticSlice<TName extends string, TValue>(name: TName, config: StaticSliceConfig<TValue>): StaticSliceDefinition<TName, TValue>;
74
+
75
+ type AuthInvalidationEvent = 'login' | 'logout' | 'refresh';
76
+ interface FromAuthSliceDefinition<TName extends string, TValue> extends SliceDefinition<TName, TValue> {
77
+ kind: 'fromAuth';
78
+ extract: (rawAuthUser: unknown) => TValue;
79
+ invalidateOn: AuthInvalidationEvent[];
80
+ }
81
+ interface FromAuthSliceConfig<TValue> {
82
+ extract: (rawAuthUser: never) => TValue;
83
+ invalidateOn?: AuthInvalidationEvent[];
84
+ }
85
+ declare function defineFromAuthSlice<TName extends string, TValue>(name: TName, config: FromAuthSliceConfig<TValue>): FromAuthSliceDefinition<TName, TValue>;
86
+
87
+ type PickerPersistMode = 'localStorage' | 'sessionStorage' | 'url';
88
+ /**
89
+ * Picker target — minimal contract. Real ResourceDef from @burojs/core
90
+ * has full shape; here we accept anything thunk-returning a name.
91
+ *
92
+ * SCOPE-ISOLATION CONTRACT:
93
+ * The picker-slice NEVER reads `target.scope` or `target.requireScope`.
94
+ *
95
+ * ⚠️ This comment used to also list `picker.label` / `picker.searchFields`
96
+ * / `picker.listColumns` / `picker.filters` as properties the slice reads.
97
+ * It never read them: `definePickerSlice` is a pure descriptor factory and
98
+ * touches none of them. The `ResourceConfig.picker` block those names came
99
+ * from was deleted in the core-flexibility program (sub-project D) as dead
100
+ * API — it had no reader anywhere in the repo, and only survived a type
101
+ * error because `define-resource.ts` copied it through with an `as any`.
102
+ * Resolver emits a dev-warning if target has those set.
103
+ */
104
+ interface PickerSliceDefinition<TName extends string, TValue> extends SliceDefinition<TName, TValue> {
105
+ kind: 'picker';
106
+ resource: () => unknown;
107
+ filter?: (ctx: never) => Record<string, unknown>;
108
+ resetWhen?: string[];
109
+ persist?: PickerPersistMode;
110
+ required: boolean;
111
+ }
112
+ interface PickerSliceConfig {
113
+ resource: () => unknown;
114
+ dependsOn?: string[];
115
+ filter?: (ctx: never) => Record<string, unknown>;
116
+ resetWhen?: string[];
117
+ persist?: PickerPersistMode;
118
+ required?: boolean;
119
+ }
120
+ declare function definePickerSlice<TName extends string, TValue = unknown>(name: TName, config: PickerSliceConfig): PickerSliceDefinition<TName, TValue>;
121
+
122
+ interface AsyncSliceDefinition<TName extends string, TValue> extends SliceDefinition<TName, TValue> {
123
+ kind: 'async';
124
+ fetch: (ctx: never) => Promise<TValue>;
125
+ cacheKey?: (ctx: never) => string;
126
+ staleTime: number;
127
+ }
128
+ interface AsyncSliceConfig<TValue> {
129
+ dependsOn?: string[];
130
+ fetch: (ctx: never) => Promise<TValue>;
131
+ cacheKey?: (ctx: never) => string;
132
+ /** ms; 0 = always refetch on invalidation */
133
+ staleTime?: number;
134
+ }
135
+ declare function defineAsyncSlice<TName extends string, TValue>(name: TName, config: AsyncSliceConfig<TValue>): AsyncSliceDefinition<TName, TValue>;
136
+
137
+ interface SliceBuilder {
138
+ static: <TValue>(config: StaticSliceConfig<TValue>) => StaticSliceDefinition<string, TValue>;
139
+ fromAuth: <TValue>(config: FromAuthSliceConfig<TValue>) => FromAuthSliceDefinition<string, TValue>;
140
+ picker: <TValue = unknown>(config: PickerSliceConfig) => PickerSliceDefinition<string, TValue>;
141
+ async: <TValue>(config: AsyncSliceConfig<TValue>) => AsyncSliceDefinition<string, TValue>;
142
+ }
143
+ declare function defineContextSlices<T extends Record<string, SliceDefinition>>(factory: (s: SliceBuilder) => T): T;
144
+
145
+ interface Resolver<TSlices extends Record<string, SliceDefinition>> {
146
+ boot: () => Promise<void>;
147
+ snapshot: () => {
148
+ [K in keyof TSlices]: unknown;
149
+ };
150
+ setSlice: <K extends keyof TSlices>(key: K, value: unknown) => void;
151
+ subscribe: (fn: () => void, selector?: (s: never) => unknown) => () => void;
152
+ feedAuth: (rawAuthUser: unknown) => void;
153
+ flushAsync: () => Promise<void>;
154
+ }
155
+ declare function createResolver<TSlices extends Record<string, SliceDefinition>>(slices: TSlices): Resolver<TSlices>;
156
+
157
+ type Scope = Record<string, unknown> | null;
158
+ interface ScopeChainEntry {
159
+ name: string;
160
+ scope: (ctx: never) => Scope;
161
+ }
162
+ /**
163
+ * Shallow merge two scope objects. Patch wins on conflict.
164
+ * null on either side is treated as "no contribution".
165
+ */
166
+ declare function mergeScope(base: Scope, patch: Scope): Scope;
167
+ /**
168
+ * Cascade scope down a parent → child chain.
169
+ * Each entry's scope() is invoked with ctx; results are merged left-to-right.
170
+ * Later entries override earlier ones (child wins).
171
+ */
172
+ declare function cascadeScope(chain: ScopeChainEntry[], ctx: never): Scope;
173
+ interface RequireScopeOk {
174
+ ok: true;
175
+ }
176
+ interface RequireScopeFail {
177
+ ok: false;
178
+ missing: string[];
179
+ }
180
+ type RequireScopeResult = RequireScopeOk | RequireScopeFail;
181
+ /**
182
+ * Verify all required keys are present (and not undefined) in scope.
183
+ */
184
+ declare function checkRequireScope(scope: Scope, required: string[]): RequireScopeResult;
185
+
186
+ declare function createPendingPermissions(): PermissionMap;
187
+ declare function createErrorPermissions(error: unknown): PermissionMap;
188
+ interface ReadyPermissionsConfig {
189
+ can: (action: string, resource: string, record?: unknown) => boolean;
190
+ reason?: (action: string, resource: string, record?: unknown) => string | undefined;
191
+ }
192
+ declare function createReadyPermissions(config: ReadyPermissionsConfig): PermissionMap;
193
+
194
+ declare const PACKAGE_NAME: "@burojs/context";
195
+ declare const PACKAGE_VERSION: "0.0.0";
196
+
197
+ export { type AsyncSliceConfig, type AsyncSliceDefinition, type Atom, type AuthInvalidationEvent, type DagNode, type FromAuthSliceConfig, type FromAuthSliceDefinition, PACKAGE_NAME, PACKAGE_VERSION, type PermissionMap, type PermissionStatus, type PickerPersistMode, type PickerSliceConfig, type PickerSliceDefinition, type ReadyPermissionsConfig, type RequireScopeFail, type RequireScopeOk, type RequireScopeResult, type Resolver, type Scope, type ScopeChainEntry, type SliceBuilder, type SliceDefinition, type SliceKind, type StaticSliceConfig, type StaticSliceDefinition, type Store, type TrackedReadResult, cascadeScope, checkRequireScope, createAtom, createErrorPermissions, createPendingPermissions, createReadyPermissions, createResolver, createStore, defineAsyncSlice, defineContextSlices, defineFromAuthSlice, definePickerSlice, defineStaticSlice, mergeScope, topoSort, trackReads };