polen 0.10.0-next.3 → 0.10.0-next.4
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 +49 -376
- package/build/api/api.d.ts +1 -0
- package/build/api/api.d.ts.map +1 -1
- package/build/api/api.js +1 -0
- package/build/api/api.js.map +1 -1
- package/build/api/static/index.d.ts +2 -0
- package/build/api/static/index.d.ts.map +1 -0
- package/build/api/static/index.js +2 -0
- package/build/api/static/index.js.map +1 -0
- package/build/api/static/manifest.d.ts +18 -0
- package/build/api/static/manifest.d.ts.map +1 -0
- package/build/api/static/manifest.js +13 -0
- package/build/api/static/manifest.js.map +1 -0
- package/build/api/static/rebase.d.ts +14 -0
- package/build/api/static/rebase.d.ts.map +1 -0
- package/build/api/static/rebase.js +110 -0
- package/build/api/static/rebase.js.map +1 -0
- package/build/api/static/static.d.ts +3 -0
- package/build/api/static/static.d.ts.map +1 -0
- package/build/api/static/static.js +3 -0
- package/build/api/static/static.js.map +1 -0
- package/build/api/vite/plugins/build.d.ts.map +1 -1
- package/build/api/vite/plugins/build.js +22 -1
- package/build/api/vite/plugins/build.js.map +1 -1
- package/build/cli/commands/static/$default.d.ts +3 -0
- package/build/cli/commands/static/$default.d.ts.map +1 -0
- package/build/cli/commands/static/$default.js +38 -0
- package/build/cli/commands/static/$default.js.map +1 -0
- package/build/cli/commands/static/rebase.d.ts +2 -0
- package/build/cli/commands/static/rebase.d.ts.map +1 -0
- package/build/cli/commands/static/rebase.js +26 -0
- package/build/cli/commands/static/rebase.js.map +1 -0
- package/build/cli/commands/static.d.ts +3 -0
- package/build/cli/commands/static.d.ts.map +1 -0
- package/build/cli/commands/static.js +5 -0
- package/build/cli/commands/static.js.map +1 -0
- package/build/lib/demos/builder.d.ts +83 -0
- package/build/lib/demos/builder.d.ts.map +1 -0
- package/build/lib/demos/builder.js +237 -0
- package/build/lib/demos/builder.js.map +1 -0
- package/build/lib/demos/config-schema.d.ts +243 -0
- package/build/lib/demos/config-schema.d.ts.map +1 -0
- package/build/lib/demos/config-schema.js +52 -0
- package/build/lib/demos/config-schema.js.map +1 -0
- package/build/lib/demos/config.d.ts +40 -0
- package/build/lib/demos/config.d.ts.map +1 -0
- package/build/lib/demos/config.js +180 -0
- package/build/lib/demos/config.js.map +1 -0
- package/build/lib/demos/index.d.ts +9 -0
- package/build/lib/demos/index.d.ts.map +1 -0
- package/build/lib/demos/index.js +8 -0
- package/build/lib/demos/index.js.map +1 -0
- package/build/lib/demos/ui/components.d.ts +33 -0
- package/build/lib/demos/ui/components.d.ts.map +1 -0
- package/build/lib/demos/ui/components.js +699 -0
- package/build/lib/demos/ui/components.js.map +1 -0
- package/build/lib/demos/ui/data-collector.d.ts +88 -0
- package/build/lib/demos/ui/data-collector.d.ts.map +1 -0
- package/build/lib/demos/ui/data-collector.js +174 -0
- package/build/lib/demos/ui/data-collector.js.map +1 -0
- package/build/lib/demos/ui/landing-page-cli.d.ts +3 -0
- package/build/lib/demos/ui/landing-page-cli.d.ts.map +1 -0
- package/build/lib/demos/ui/landing-page-cli.js +21 -0
- package/build/lib/demos/ui/landing-page-cli.js.map +1 -0
- package/build/lib/demos/ui/landing-page.d.ts +32 -0
- package/build/lib/demos/ui/landing-page.d.ts.map +1 -0
- package/build/lib/demos/ui/landing-page.js +83 -0
- package/build/lib/demos/ui/landing-page.js.map +1 -0
- package/build/lib/demos/ui/page-renderer.d.ts +26 -0
- package/build/lib/demos/ui/page-renderer.d.ts.map +1 -0
- package/build/lib/demos/ui/page-renderer.js +104 -0
- package/build/lib/demos/ui/page-renderer.js.map +1 -0
- package/build/lib/demos/utils.d.ts +14 -0
- package/build/lib/demos/utils.d.ts.map +1 -0
- package/build/lib/demos/utils.js +37 -0
- package/build/lib/demos/utils.js.map +1 -0
- package/build/lib/deployment/$$.d.ts +3 -0
- package/build/lib/deployment/$$.d.ts.map +1 -0
- package/build/lib/deployment/$$.js +3 -0
- package/build/lib/deployment/$$.js.map +1 -0
- package/build/lib/deployment/$.d.ts +2 -0
- package/build/lib/deployment/$.d.ts.map +1 -0
- package/build/lib/deployment/$.js +2 -0
- package/build/lib/deployment/$.js.map +1 -0
- package/build/lib/deployment/metadata.d.ts +32 -0
- package/build/lib/deployment/metadata.d.ts.map +1 -0
- package/build/lib/deployment/metadata.js +37 -0
- package/build/lib/deployment/metadata.js.map +1 -0
- package/build/lib/deployment/path-manager.d.ts +41 -0
- package/build/lib/deployment/path-manager.d.ts.map +1 -0
- package/build/lib/deployment/path-manager.js +157 -0
- package/build/lib/deployment/path-manager.js.map +1 -0
- package/build/lib/github-actions/git-controller.d.ts +50 -0
- package/build/lib/github-actions/git-controller.d.ts.map +1 -0
- package/build/lib/github-actions/git-controller.js +90 -0
- package/build/lib/github-actions/git-controller.js.map +1 -0
- package/build/lib/github-actions/github-actions.d.ts +7 -0
- package/build/lib/github-actions/github-actions.d.ts.map +1 -0
- package/build/lib/github-actions/github-actions.js +7 -0
- package/build/lib/github-actions/github-actions.js.map +1 -0
- package/build/lib/github-actions/index.d.ts +2 -0
- package/build/lib/github-actions/index.d.ts.map +1 -0
- package/build/lib/github-actions/index.js +2 -0
- package/build/lib/github-actions/index.js.map +1 -0
- package/build/lib/github-actions/lib/get-pr-deployments.d.ts +12 -0
- package/build/lib/github-actions/lib/get-pr-deployments.d.ts.map +1 -0
- package/build/lib/github-actions/lib/get-pr-deployments.js +51 -0
- package/build/lib/github-actions/lib/get-pr-deployments.js.map +1 -0
- package/build/lib/github-actions/pr-controller.d.ts +39 -0
- package/build/lib/github-actions/pr-controller.d.ts.map +1 -0
- package/build/lib/github-actions/pr-controller.js +122 -0
- package/build/lib/github-actions/pr-controller.js.map +1 -0
- package/build/lib/github-actions/run-step-cli.d.ts +9 -0
- package/build/lib/github-actions/run-step-cli.d.ts.map +1 -0
- package/build/lib/github-actions/run-step-cli.js +71 -0
- package/build/lib/github-actions/run-step-cli.js.map +1 -0
- package/build/lib/github-actions/runner.d.ts +17 -0
- package/build/lib/github-actions/runner.d.ts.map +1 -0
- package/build/lib/github-actions/runner.js +195 -0
- package/build/lib/github-actions/runner.js.map +1 -0
- package/build/lib/github-actions/schemas/context.d.ts +933 -0
- package/build/lib/github-actions/schemas/context.d.ts.map +1 -0
- package/build/lib/github-actions/schemas/context.js +407 -0
- package/build/lib/github-actions/schemas/context.js.map +1 -0
- package/build/lib/github-actions/schemas/index.d.ts +5 -0
- package/build/lib/github-actions/schemas/index.d.ts.map +1 -0
- package/build/lib/github-actions/schemas/index.js +5 -0
- package/build/lib/github-actions/schemas/index.js.map +1 -0
- package/build/lib/github-actions/search-module.d.ts +38 -0
- package/build/lib/github-actions/search-module.d.ts.map +1 -0
- package/build/lib/github-actions/search-module.js +40 -0
- package/build/lib/github-actions/search-module.js.map +1 -0
- package/build/lib/github-actions/step.d.ts +163 -0
- package/build/lib/github-actions/step.d.ts.map +1 -0
- package/build/lib/github-actions/step.js +121 -0
- package/build/lib/github-actions/step.js.map +1 -0
- package/build/lib/helpers.d.ts.map +1 -1
- package/build/lib/helpers.js +5 -3
- package/build/lib/helpers.js.map +1 -1
- package/build/lib/kit-temp.d.ts +54 -0
- package/build/lib/kit-temp.d.ts.map +1 -1
- package/build/lib/kit-temp.js +80 -14
- package/build/lib/kit-temp.js.map +1 -1
- package/build/lib/kit-temp.test-d.d.ts +2 -0
- package/build/lib/kit-temp.test-d.d.ts.map +1 -0
- package/build/lib/kit-temp.test-d.js +75 -0
- package/build/lib/kit-temp.test-d.js.map +1 -0
- package/build/lib/mask/$$.d.ts +3 -0
- package/build/lib/mask/$$.d.ts.map +1 -0
- package/build/lib/mask/$$.js +3 -0
- package/build/lib/mask/$$.js.map +1 -0
- package/build/lib/mask/$.d.ts +2 -0
- package/build/lib/mask/$.d.ts.map +1 -0
- package/build/lib/mask/$.js +2 -0
- package/build/lib/mask/$.js.map +1 -0
- package/build/lib/mask/apply.d.ts +86 -0
- package/build/lib/mask/apply.d.ts.map +1 -0
- package/build/lib/mask/apply.js +86 -0
- package/build/lib/mask/apply.js.map +1 -0
- package/build/lib/mask/mask.d.ts +124 -0
- package/build/lib/mask/mask.d.ts.map +1 -0
- package/build/lib/mask/mask.js +137 -0
- package/build/lib/mask/mask.js.map +1 -0
- package/build/lib/mask/mask.test-d.d.ts +2 -0
- package/build/lib/mask/mask.test-d.d.ts.map +1 -0
- package/build/lib/mask/mask.test-d.js +102 -0
- package/build/lib/mask/mask.test-d.js.map +1 -0
- package/build/lib/task/$$.d.ts +3 -0
- package/build/lib/task/$$.d.ts.map +1 -0
- package/build/lib/task/$$.js +3 -0
- package/build/lib/task/$$.js.map +1 -0
- package/build/lib/task/$.d.ts +2 -0
- package/build/lib/task/$.d.ts.map +1 -0
- package/build/lib/task/$.js +2 -0
- package/build/lib/task/$.js.map +1 -0
- package/build/lib/task/report.d.ts +28 -0
- package/build/lib/task/report.d.ts.map +1 -0
- package/build/lib/task/report.js +33 -0
- package/build/lib/task/report.js.map +1 -0
- package/build/lib/task/task.d.ts +44 -0
- package/build/lib/task/task.d.ts.map +1 -0
- package/build/lib/task/task.js +63 -0
- package/build/lib/task/task.js.map +1 -0
- package/build/lib/version-history/index.d.ts +3 -0
- package/build/lib/version-history/index.d.ts.map +1 -0
- package/build/lib/version-history/index.js +2 -0
- package/build/lib/version-history/index.js.map +1 -0
- package/build/lib/version-history/types.d.ts +64 -0
- package/build/lib/version-history/types.d.ts.map +1 -0
- package/build/lib/version-history/types.js +5 -0
- package/build/lib/version-history/types.js.map +1 -0
- package/build/lib/version-history/version-history.d.ts +85 -0
- package/build/lib/version-history/version-history.d.ts.map +1 -0
- package/build/lib/version-history/version-history.js +248 -0
- package/build/lib/version-history/version-history.js.map +1 -0
- package/build/sandbox.d.ts +2 -0
- package/build/sandbox.d.ts.map +1 -0
- package/build/sandbox.js +3 -0
- package/build/sandbox.js.map +1 -0
- package/build/template/components/Link.jsx +1 -1
- package/package.json +16 -9
- package/src/api/api.ts +1 -0
- package/src/api/singletons/markdown/markdown.test.ts +1 -1
- package/src/api/static/index.ts +1 -0
- package/src/api/static/manifest.test.ts +106 -0
- package/src/api/static/manifest.ts +16 -0
- package/src/api/static/rebase.test.ts +229 -0
- package/src/api/static/rebase.ts +140 -0
- package/src/api/static/static.ts +2 -0
- package/src/api/utils/asset-url/asset-url.test.ts +4 -4
- package/src/api/vite/plugins/build.ts +25 -1
- package/src/api/vite/plugins/core.ts +1 -1
- package/src/cli/commands/static/$default.ts +43 -0
- package/src/cli/commands/static/rebase.ts +37 -0
- package/src/cli/commands/static.ts +6 -0
- package/src/lib/demos/builder.ts +298 -0
- package/src/lib/demos/config-schema.ts +56 -0
- package/src/lib/demos/config.test.ts +193 -0
- package/src/lib/demos/config.ts +205 -0
- package/src/lib/demos/index.ts +9 -0
- package/src/lib/demos/ui/components.ts +739 -0
- package/src/lib/demos/ui/data-collector.ts +246 -0
- package/src/lib/demos/ui/landing-page-cli.ts +23 -0
- package/src/lib/demos/ui/landing-page.ts +126 -0
- package/src/lib/demos/ui/page-renderer.ts +124 -0
- package/src/lib/demos/utils.ts +43 -0
- package/src/lib/deployment/$$.ts +2 -0
- package/src/lib/deployment/$.test.ts +53 -0
- package/src/lib/deployment/$.ts +1 -0
- package/src/lib/deployment/metadata.ts +40 -0
- package/src/lib/deployment/path-manager.ts +186 -0
- package/src/lib/github-actions/git-controller.ts +151 -0
- package/src/lib/github-actions/github-actions.ts +6 -0
- package/src/lib/github-actions/index.ts +1 -0
- package/src/lib/github-actions/lib/get-pr-deployments.ts +76 -0
- package/src/lib/github-actions/pr-controller.test.ts +172 -0
- package/src/lib/github-actions/pr-controller.ts +183 -0
- package/src/lib/github-actions/run-step-cli.ts +84 -0
- package/src/lib/github-actions/runner.test.ts +192 -0
- package/src/lib/github-actions/runner.ts +226 -0
- package/src/lib/github-actions/schemas/context.ts +424 -0
- package/src/lib/github-actions/schemas/index.ts +5 -0
- package/src/lib/github-actions/search-module.test.ts +110 -0
- package/src/lib/github-actions/search-module.ts +76 -0
- package/src/lib/github-actions/step.test.ts +149 -0
- package/src/lib/github-actions/step.ts +232 -0
- package/src/lib/helpers.ts +4 -3
- package/src/lib/kit-temp.test-d.ts +115 -0
- package/src/lib/kit-temp.test.ts +127 -0
- package/src/lib/kit-temp.ts +126 -14
- package/src/lib/mask/$$.ts +2 -0
- package/src/lib/mask/$.test.ts +248 -0
- package/src/lib/mask/$.ts +1 -0
- package/src/lib/mask/apply.ts +134 -0
- package/src/lib/mask/mask.test-d.ts +144 -0
- package/src/lib/mask/mask.ts +244 -0
- package/src/lib/shiki/shiki.test.ts +1 -1
- package/src/lib/task/$$.ts +2 -0
- package/src/lib/task/$.test.ts +209 -0
- package/src/lib/task/$.ts +1 -0
- package/src/lib/task/report.ts +72 -0
- package/src/lib/task/task.ts +112 -0
- package/src/lib/version-history/index.test.ts +188 -0
- package/src/lib/version-history/index.ts +4 -0
- package/src/lib/version-history/types.ts +68 -0
- package/src/lib/version-history/version-history.ts +293 -0
- package/src/sandbox.ts +1 -0
- package/src/template/components/Link.tsx +1 -1
package/src/lib/kit-temp.ts
CHANGED
@@ -12,8 +12,10 @@
|
|
12
12
|
//
|
13
13
|
//
|
14
14
|
|
15
|
-
import { Fs, Http, Path, Undefined } from '@wollybeard/kit'
|
15
|
+
import { Arr, Err, Fs, Http, Path, type Ts, Undefined } from '@wollybeard/kit'
|
16
|
+
import { never } from '@wollybeard/kit/language'
|
16
17
|
import type { ResolveHookContext } from 'node:module'
|
18
|
+
import type { IsNever } from 'type-fest'
|
17
19
|
|
18
20
|
export const arrayEquals = (a: any[], b: any[]) => {
|
19
21
|
if (a.length !== b.length) return false
|
@@ -60,23 +62,101 @@ export interface ImportEvent {
|
|
60
62
|
context: ResolveHookContext
|
61
63
|
}
|
62
64
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
65
|
+
// dprint-ignore
|
66
|
+
export type ObjPolicyFilter<
|
67
|
+
$Object extends object,
|
68
|
+
$Key extends Keyof<$Object>,
|
69
|
+
Mode extends 'allow' | 'deny',
|
70
|
+
> = Mode extends 'allow'
|
71
|
+
? Pick<$Object, Extract<$Key, keyof $Object>>
|
72
|
+
: Omit<$Object, Extract<$Key, keyof $Object>>
|
73
|
+
|
74
|
+
/**
|
75
|
+
* Like keyof but returns PropertyKey for object
|
76
|
+
*/
|
77
|
+
type Keyof<$Object extends object> = object extends $Object ? PropertyKey : (keyof $Object)
|
78
|
+
|
79
|
+
/**
|
80
|
+
* Filter object properties based on a policy mode and set of keys
|
81
|
+
*
|
82
|
+
* @param mode - 'allow' to keep only specified keys, 'deny' to remove specified keys
|
83
|
+
* @param obj - The object to filter
|
84
|
+
* @param keys - The keys to process
|
85
|
+
* @returns A filtered object with proper type inference
|
86
|
+
*
|
87
|
+
* @example
|
88
|
+
* ```ts
|
89
|
+
* const obj = { a: 1, b: 2, c: 3 }
|
90
|
+
*
|
91
|
+
* // Allow mode: keep only 'a' and 'c'
|
92
|
+
* objPolicyFilter('allow', obj, ['a', 'c']) // { a: 1, c: 3 }
|
93
|
+
*
|
94
|
+
* // Deny mode: remove 'a' and 'c'
|
95
|
+
* objPolicyFilter('deny', obj, ['a', 'c']) // { b: 2 }
|
96
|
+
* ```
|
97
|
+
*/
|
98
|
+
export const objPolicyFilter = <
|
99
|
+
obj extends object,
|
100
|
+
keyUnion extends Keyof<obj>,
|
101
|
+
mode extends 'allow' | 'deny',
|
102
|
+
>(
|
103
|
+
mode: mode,
|
104
|
+
obj: obj,
|
105
|
+
keys: readonly keyUnion[],
|
106
|
+
): ObjPolicyFilter<obj, keyUnion, mode> => {
|
107
|
+
const result: any = mode === 'deny' ? { ...obj } : {}
|
108
|
+
|
109
|
+
if (mode === 'allow') {
|
110
|
+
// For allow mode, only add specified keys
|
111
|
+
for (const key of keys) {
|
112
|
+
if (key in obj) {
|
113
|
+
// @ts-expect-error
|
114
|
+
result[key] = obj[key]
|
115
|
+
}
|
67
116
|
}
|
68
|
-
|
69
|
-
|
117
|
+
} else {
|
118
|
+
// For deny mode, remove specified keys
|
119
|
+
for (const key of keys) {
|
120
|
+
delete result[key]
|
121
|
+
}
|
122
|
+
}
|
123
|
+
|
124
|
+
return result
|
70
125
|
}
|
71
126
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
127
|
+
/**
|
128
|
+
* Filter an object using a predicate function
|
129
|
+
*
|
130
|
+
* @param obj - The object to filter
|
131
|
+
* @param predicate - Function that returns true to keep a key/value pair
|
132
|
+
* @returns A new object with only the key/value pairs where predicate returned true
|
133
|
+
*
|
134
|
+
* @example
|
135
|
+
* ```ts
|
136
|
+
* const obj = { a: 1, b: 2, c: 3 }
|
137
|
+
* objFilter(obj, (k, v) => v > 1) // { b: 2, c: 3 }
|
138
|
+
* objFilter(obj, k => k !== 'b') // { a: 1, c: 3 }
|
139
|
+
* ```
|
140
|
+
*/
|
141
|
+
export const objFilter = <T extends object>(
|
142
|
+
obj: T,
|
143
|
+
predicate: (key: keyof T, value: T[keyof T], obj: T) => boolean,
|
144
|
+
): Partial<T> => {
|
145
|
+
const result = {} as Partial<T>
|
146
|
+
for (const key in obj) {
|
147
|
+
if (predicate(key, obj[key], obj)) {
|
148
|
+
result[key] = obj[key]
|
77
149
|
}
|
78
|
-
|
79
|
-
|
150
|
+
}
|
151
|
+
return result
|
152
|
+
}
|
153
|
+
|
154
|
+
export const ObjPick = <T extends object, K extends keyof T>(obj: T, keys: readonly K[]): Pick<T, K> => {
|
155
|
+
return objPolicyFilter('allow', obj, keys) as any
|
156
|
+
}
|
157
|
+
|
158
|
+
export const ObjOmit = <T extends object, K extends keyof T>(obj: T, keys: readonly K[]): Omit<T, K> => {
|
159
|
+
return objPolicyFilter('deny', obj, keys) as any
|
80
160
|
}
|
81
161
|
|
82
162
|
export const ObjPartition = <T extends object, K extends keyof T>(
|
@@ -106,3 +186,35 @@ export const ResponseInternalServerError = () =>
|
|
106
186
|
status: Http.Status.InternalServerError.code,
|
107
187
|
statusText: Http.Status.InternalServerError.description,
|
108
188
|
})
|
189
|
+
|
190
|
+
/**
|
191
|
+
* Execute an operation on multiple items, continuing even if some fail
|
192
|
+
*/
|
193
|
+
export async function tryCatchMany<item, result>(
|
194
|
+
items: item[],
|
195
|
+
operation: (item: item) => Promise<result>,
|
196
|
+
): Promise<[result[], (Error & { context: { item: item } })[]]> {
|
197
|
+
const partitionedResults = await Promise.all(items.map(async (item) => {
|
198
|
+
const result = await Err.tryCatch(() => operation(item))
|
199
|
+
if (Err.is(result)) {
|
200
|
+
const error = result as Error & { context: { item: item } }
|
201
|
+
error.context = { item }
|
202
|
+
return error
|
203
|
+
}
|
204
|
+
return result
|
205
|
+
})).then(Arr.partitionErrors)
|
206
|
+
return partitionedResults as any
|
207
|
+
}
|
208
|
+
|
209
|
+
/**
|
210
|
+
* Type-level helper to check if two types are exactly the same (invariant).
|
211
|
+
*/
|
212
|
+
export type IsExact<T, U> = T extends U ? U extends T ? true : false : false
|
213
|
+
|
214
|
+
// dprint-ignore
|
215
|
+
export type ExtendsExact<$Input, $Constraint> =
|
216
|
+
$Input extends $Constraint
|
217
|
+
? $Constraint extends $Input
|
218
|
+
? $Input
|
219
|
+
: never
|
220
|
+
: never
|
@@ -0,0 +1,248 @@
|
|
1
|
+
import * as fc from 'fast-check'
|
2
|
+
import { describe, expect, test } from 'vitest'
|
3
|
+
import { Mask } from './$.ts'
|
4
|
+
|
5
|
+
describe('Mask.create', () => {
|
6
|
+
test('boolean options create binary masks', () => {
|
7
|
+
const showMask = Mask.create(true)
|
8
|
+
const hideMask = Mask.create(false)
|
9
|
+
|
10
|
+
expect(showMask.type).toBe('binary')
|
11
|
+
expect(hideMask.type).toBe('binary')
|
12
|
+
|
13
|
+
if (showMask.type === 'binary' && hideMask.type === 'binary') {
|
14
|
+
expect(showMask.show).toBe(true)
|
15
|
+
expect(hideMask.show).toBe(false)
|
16
|
+
}
|
17
|
+
})
|
18
|
+
|
19
|
+
test('array options create allow mode properties mask', () => {
|
20
|
+
const mask = Mask.create(['name', 'age'])
|
21
|
+
|
22
|
+
expect(mask.type).toBe('properties')
|
23
|
+
if (mask.type === 'properties') {
|
24
|
+
expect(mask.mode).toBe('allow')
|
25
|
+
expect(mask.properties).toEqual(['name', 'age'])
|
26
|
+
}
|
27
|
+
})
|
28
|
+
|
29
|
+
test('object options create mode based on values', () => {
|
30
|
+
const allowMask = Mask.create({ name: true, age: true, password: false })
|
31
|
+
const denyMask = Mask.create({ password: false, secret: false })
|
32
|
+
|
33
|
+
expect(allowMask.type).toBe('properties')
|
34
|
+
expect(denyMask.type).toBe('properties')
|
35
|
+
|
36
|
+
if (allowMask.type === 'properties' && denyMask.type === 'properties') {
|
37
|
+
expect(allowMask.mode).toBe('allow')
|
38
|
+
expect(allowMask.properties).toEqual(['name', 'age'])
|
39
|
+
|
40
|
+
expect(denyMask.mode).toBe('deny')
|
41
|
+
expect(denyMask.properties).toEqual(['password', 'secret'])
|
42
|
+
}
|
43
|
+
})
|
44
|
+
})
|
45
|
+
|
46
|
+
describe('Mask.apply', () => {
|
47
|
+
test('binary masks show/hide data', () => {
|
48
|
+
const data = { a: 1 }
|
49
|
+
|
50
|
+
expect(Mask.apply(data, Mask.create(true))).toBe(data)
|
51
|
+
expect(Mask.apply(data, Mask.create(false))).toBe(undefined)
|
52
|
+
})
|
53
|
+
|
54
|
+
test('properties masks filter objects', () => {
|
55
|
+
const data = { name: 'John', age: 30, password: 'secret' }
|
56
|
+
|
57
|
+
// Allow mode
|
58
|
+
const allowMask = Mask.create(['name', 'age'])
|
59
|
+
expect(Mask.apply(data, allowMask)).toEqual({ name: 'John', age: 30 })
|
60
|
+
|
61
|
+
// Deny mode
|
62
|
+
const denyMask = Mask.create({ password: false })
|
63
|
+
expect(Mask.apply(data, denyMask)).toEqual({ name: 'John', age: 30 })
|
64
|
+
})
|
65
|
+
|
66
|
+
test('properties masks throw for non-objects', () => {
|
67
|
+
const mask = Mask.create(['name'])
|
68
|
+
|
69
|
+
expect(() => Mask.apply('string' as any, mask)).toThrow()
|
70
|
+
expect(() => Mask.apply(123 as any, mask)).toThrow()
|
71
|
+
expect(() => Mask.apply(null as any, mask)).toThrow()
|
72
|
+
})
|
73
|
+
})
|
74
|
+
|
75
|
+
describe('apply variants', () => {
|
76
|
+
test('applyPartial allows missing properties', () => {
|
77
|
+
const mask = Mask.create<{ name: string; age: number }>(['name', 'age'])
|
78
|
+
const partial = { name: 'John' }
|
79
|
+
|
80
|
+
expect(Mask.applyPartial(partial, mask)).toEqual({ name: 'John' })
|
81
|
+
expect(Mask.applyPartial({}, mask)).toEqual({})
|
82
|
+
})
|
83
|
+
|
84
|
+
test('applyExact works with any data for binary masks', () => {
|
85
|
+
const showMask = Mask.create(true)
|
86
|
+
const hideMask = Mask.create(false)
|
87
|
+
|
88
|
+
expect(Mask.applyExact('hello' as any, showMask)).toBe('hello')
|
89
|
+
expect(Mask.applyExact('hello' as any, hideMask)).toBe(undefined)
|
90
|
+
})
|
91
|
+
})
|
92
|
+
|
93
|
+
describe('property-based tests', () => {
|
94
|
+
test('binary masks - invariants', () => {
|
95
|
+
fc.assert(
|
96
|
+
fc.property(fc.anything(), (data) => {
|
97
|
+
expect(Mask.apply(data, Mask.create(true))).toBe(data)
|
98
|
+
expect(Mask.apply(data, Mask.create(false))).toBe(undefined)
|
99
|
+
}),
|
100
|
+
)
|
101
|
+
})
|
102
|
+
|
103
|
+
test('properties mask - allow mode filters correctly', () => {
|
104
|
+
fc.assert(
|
105
|
+
fc.property(
|
106
|
+
fc.dictionary(fc.string(), fc.anything()),
|
107
|
+
fc.array(fc.string(), { minLength: 1 }),
|
108
|
+
(obj, keys) => {
|
109
|
+
const mask = Mask.create(keys)
|
110
|
+
const result = Mask.apply(obj as any, mask)
|
111
|
+
const resultKeys = Object.keys(result as any)
|
112
|
+
|
113
|
+
// Result contains only keys that were in both mask and object
|
114
|
+
expect(resultKeys.every(key => keys.includes(key))).toBe(true)
|
115
|
+
|
116
|
+
// All requested keys that exist in obj are in result
|
117
|
+
keys.forEach(key => {
|
118
|
+
if (key in obj) {
|
119
|
+
expect(result).toHaveProperty(key, (obj as any)[key])
|
120
|
+
}
|
121
|
+
})
|
122
|
+
},
|
123
|
+
),
|
124
|
+
)
|
125
|
+
})
|
126
|
+
|
127
|
+
test('properties mask - deny mode filters correctly', () => {
|
128
|
+
fc.assert(
|
129
|
+
fc.property(
|
130
|
+
fc.dictionary(fc.string(), fc.anything()),
|
131
|
+
fc.uniqueArray(fc.string(), { minLength: 1 }),
|
132
|
+
(data, keysToRemove) => {
|
133
|
+
const maskSpec = Object.fromEntries(keysToRemove.map(k => [k, false]))
|
134
|
+
const mask = Mask.create(maskSpec)
|
135
|
+
|
136
|
+
expect(mask.type).toBe('properties')
|
137
|
+
if (mask.type !== 'properties') return
|
138
|
+
expect(mask.mode).toBe('deny')
|
139
|
+
|
140
|
+
const result = Mask.apply(data as any, mask)
|
141
|
+
|
142
|
+
// Result should not have any of the denied keys
|
143
|
+
keysToRemove.forEach(key => {
|
144
|
+
expect(Object.prototype.hasOwnProperty.call(result, key)).toBe(false)
|
145
|
+
})
|
146
|
+
|
147
|
+
// Result should have all other keys from data
|
148
|
+
Object.keys(data).forEach(key => {
|
149
|
+
if (!keysToRemove.includes(key)) {
|
150
|
+
expect(Object.prototype.hasOwnProperty.call(result, key)).toBe(true)
|
151
|
+
expect((result as any)[key]).toBe((data as any)[key])
|
152
|
+
}
|
153
|
+
})
|
154
|
+
},
|
155
|
+
),
|
156
|
+
)
|
157
|
+
})
|
158
|
+
|
159
|
+
test('undefined values are preserved', () => {
|
160
|
+
fc.assert(
|
161
|
+
fc.property(
|
162
|
+
fc.record({
|
163
|
+
a: fc.oneof(fc.anything(), fc.constant(undefined)),
|
164
|
+
b: fc.oneof(fc.anything(), fc.constant(undefined)),
|
165
|
+
c: fc.oneof(fc.anything(), fc.constant(undefined)),
|
166
|
+
}),
|
167
|
+
fc.shuffledSubarray(['a', 'b', 'c'], { minLength: 1 }),
|
168
|
+
(obj, keys) => {
|
169
|
+
const result = Mask.apply(obj as any, Mask.create(keys))
|
170
|
+
|
171
|
+
keys.forEach(key => {
|
172
|
+
if (key in obj) {
|
173
|
+
expect(result).toHaveProperty(key)
|
174
|
+
expect((result as any)[key]).toBe((obj as any)[key])
|
175
|
+
}
|
176
|
+
})
|
177
|
+
},
|
178
|
+
),
|
179
|
+
)
|
180
|
+
})
|
181
|
+
|
182
|
+
test('apply and applyPartial are consistent for complete data', () => {
|
183
|
+
fc.assert(
|
184
|
+
fc.property(
|
185
|
+
fc.dictionary(fc.string(), fc.anything()),
|
186
|
+
fc.array(fc.string(), { minLength: 1 }),
|
187
|
+
(data, keys) => {
|
188
|
+
const mask = Mask.create(keys)
|
189
|
+
expect(Mask.apply(data as any, mask)).toEqual(Mask.applyPartial(data as any, mask))
|
190
|
+
},
|
191
|
+
),
|
192
|
+
)
|
193
|
+
})
|
194
|
+
|
195
|
+
test('non-objects throw with properties masks', () => {
|
196
|
+
fc.assert(
|
197
|
+
fc.property(
|
198
|
+
fc.oneof(
|
199
|
+
fc.string(),
|
200
|
+
fc.integer(),
|
201
|
+
fc.boolean(),
|
202
|
+
fc.constant(null),
|
203
|
+
fc.constant(undefined),
|
204
|
+
),
|
205
|
+
fc.array(fc.string(), { minLength: 1 }),
|
206
|
+
(nonObject, keys) => {
|
207
|
+
const mask = Mask.create(keys)
|
208
|
+
expect(() => Mask.apply(nonObject as any, mask)).toThrow('Cannot apply properties mask to non-object data')
|
209
|
+
},
|
210
|
+
),
|
211
|
+
)
|
212
|
+
})
|
213
|
+
|
214
|
+
test('immutability invariants', () => {
|
215
|
+
fc.assert(
|
216
|
+
fc.property(
|
217
|
+
fc.dictionary(
|
218
|
+
fc.string(),
|
219
|
+
fc.oneof(
|
220
|
+
fc.string(),
|
221
|
+
fc.integer(),
|
222
|
+
fc.boolean(),
|
223
|
+
fc.constant(null),
|
224
|
+
),
|
225
|
+
),
|
226
|
+
fc.oneof(
|
227
|
+
fc.boolean(),
|
228
|
+
fc.array(fc.string()),
|
229
|
+
fc.dictionary(fc.string(), fc.boolean()),
|
230
|
+
),
|
231
|
+
(data, maskOptions) => {
|
232
|
+
const mask = Mask.create(maskOptions as any)
|
233
|
+
const originalData = { ...data }
|
234
|
+
const originalMask = JSON.parse(JSON.stringify(mask))
|
235
|
+
|
236
|
+
try {
|
237
|
+
Mask.apply(data as any, mask)
|
238
|
+
} catch {
|
239
|
+
// Ignore errors for invalid combinations
|
240
|
+
}
|
241
|
+
|
242
|
+
expect(data).toEqual(originalData)
|
243
|
+
expect(mask).toEqual(originalMask)
|
244
|
+
},
|
245
|
+
),
|
246
|
+
)
|
247
|
+
})
|
248
|
+
})
|
@@ -0,0 +1 @@
|
|
1
|
+
export * as Mask from './$$.ts'
|
@@ -0,0 +1,134 @@
|
|
1
|
+
import { type ExtendsExact, objPolicyFilter } from '#lib/kit-temp'
|
2
|
+
import { Obj } from '@wollybeard/kit'
|
3
|
+
import { never } from '@wollybeard/kit/language'
|
4
|
+
import type { GetDataType, Mask } from './mask.ts'
|
5
|
+
|
6
|
+
/**
|
7
|
+
* Type-level function that applies a mask to data.
|
8
|
+
*
|
9
|
+
* @template Data - The data type
|
10
|
+
* @template M - The mask type
|
11
|
+
*
|
12
|
+
* Binary masks:
|
13
|
+
* - show=true returns the data unchanged
|
14
|
+
* - show=false returns undefined
|
15
|
+
*
|
16
|
+
* Properties masks:
|
17
|
+
* - 'allow' mode returns Pick<Data, keys>
|
18
|
+
* - 'deny' mode returns Omit<Data, keys>
|
19
|
+
* - Non-objects throw an error at runtime
|
20
|
+
*/
|
21
|
+
// dprint-ignore
|
22
|
+
export type Apply<Data, M extends Mask<any>> =
|
23
|
+
M extends { type: 'binary', show: boolean }
|
24
|
+
? M['show'] extends true
|
25
|
+
? Data
|
26
|
+
: undefined
|
27
|
+
: M extends { type: 'properties', mode: string, properties: any[] }
|
28
|
+
? Data extends object
|
29
|
+
? M['mode'] extends 'allow'
|
30
|
+
? Pick<Data, Extract<M['properties'][number], keyof Data>>
|
31
|
+
: Omit<Data, Extract<M['properties'][number], keyof Data>>
|
32
|
+
: never // Non-objects not allowed with property masks
|
33
|
+
: never
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Apply mask to data with standard covariance.
|
37
|
+
*
|
38
|
+
* Data must be assignable to the mask's expected type (may have excess properties).
|
39
|
+
*
|
40
|
+
* @param data - The data to mask
|
41
|
+
* @param mask - The mask to apply
|
42
|
+
* @returns The masked data
|
43
|
+
*
|
44
|
+
* @example
|
45
|
+
* ```ts
|
46
|
+
* const user = { name: 'John', email: 'john@example.com', password: 'secret' }
|
47
|
+
* const mask = Mask.pick<User>(['name', 'email'])
|
48
|
+
* const safeUser = apply(user, mask) // { name: 'John', email: 'john@example.com' }
|
49
|
+
* ```
|
50
|
+
*/
|
51
|
+
export const apply = <
|
52
|
+
data extends GetDataType<mask>,
|
53
|
+
mask extends Mask<any>,
|
54
|
+
>(data: data, mask: mask): Apply<data, mask> => {
|
55
|
+
return applyInternal(data, mask) as Apply<data, mask>
|
56
|
+
}
|
57
|
+
|
58
|
+
/**
|
59
|
+
* Apply mask to partial data.
|
60
|
+
*
|
61
|
+
* Data may have only a subset of the mask's expected properties.
|
62
|
+
* Useful when working with incomplete data or optional fields.
|
63
|
+
*
|
64
|
+
* @param data - The partial data to mask
|
65
|
+
* @param mask - The mask to apply
|
66
|
+
* @returns The masked data
|
67
|
+
*
|
68
|
+
* @example
|
69
|
+
* ```ts
|
70
|
+
* const partialUser = { name: 'John' } // missing email
|
71
|
+
* const mask = Mask.pick<User>(['name', 'email'])
|
72
|
+
* const result = applyPartial(partialUser, mask) // { name: 'John' }
|
73
|
+
* ```
|
74
|
+
*/
|
75
|
+
export const applyPartial = <
|
76
|
+
data extends Partial<GetDataType<mask>>,
|
77
|
+
mask extends Mask<any>,
|
78
|
+
>(data: data, mask: mask): Apply<data, mask> => {
|
79
|
+
return applyInternal(data, mask) as Apply<data, mask>
|
80
|
+
}
|
81
|
+
|
82
|
+
/**
|
83
|
+
* Apply mask to data with exact type matching.
|
84
|
+
*
|
85
|
+
* Data must exactly match the mask's expected type - no missing or excess properties.
|
86
|
+
* Provides the strictest type checking.
|
87
|
+
*
|
88
|
+
* @param data - The data to mask (must exactly match expected type)
|
89
|
+
* @param mask - The mask to apply
|
90
|
+
* @returns The masked data
|
91
|
+
*
|
92
|
+
* @example
|
93
|
+
* ```ts
|
94
|
+
* type User = { name: string; email: string }
|
95
|
+
* const mask = Mask.pick<User>(['name'])
|
96
|
+
*
|
97
|
+
* // This works - exact match
|
98
|
+
* const user: User = { name: 'John', email: 'john@example.com' }
|
99
|
+
* const result = applyExact(user, mask)
|
100
|
+
*
|
101
|
+
* // This fails - has extra property
|
102
|
+
* const userWithExtra = { name: 'John', email: 'john@example.com', age: 30 }
|
103
|
+
* const result2 = applyExact(userWithExtra, mask) // Type error!
|
104
|
+
* ```
|
105
|
+
*/
|
106
|
+
export const applyExact = <
|
107
|
+
data,
|
108
|
+
mask extends Mask<any>,
|
109
|
+
>(
|
110
|
+
data: ExtendsExact<data, GetDataType<mask>>,
|
111
|
+
mask: mask,
|
112
|
+
): Apply<data, mask> => {
|
113
|
+
return applyInternal(data, mask) as Apply<data, mask>
|
114
|
+
}
|
115
|
+
|
116
|
+
// Internal implementation
|
117
|
+
const applyInternal = (data: any, mask: Mask<any>): any => {
|
118
|
+
// ━ Handle binary mask
|
119
|
+
if (mask.type === 'binary') {
|
120
|
+
return mask.show ? data : undefined
|
121
|
+
}
|
122
|
+
|
123
|
+
// ━ Handle properties mask
|
124
|
+
if (mask.type === 'properties') {
|
125
|
+
// Properties mask requires object data
|
126
|
+
if (!Obj.is(data)) {
|
127
|
+
throw new Error('Cannot apply properties mask to non-object data')
|
128
|
+
}
|
129
|
+
|
130
|
+
return objPolicyFilter(mask.mode, data, mask.properties)
|
131
|
+
}
|
132
|
+
|
133
|
+
never()
|
134
|
+
}
|
@@ -0,0 +1,144 @@
|
|
1
|
+
import { Ts } from '@wollybeard/kit'
|
2
|
+
import { Mask } from './$.ts'
|
3
|
+
import type { InferOptions, Mask as MaskType, PropertiesMask } from './mask.ts'
|
4
|
+
|
5
|
+
// Test 1: InferOptions with unknown should accept all option types
|
6
|
+
{
|
7
|
+
type Options = InferOptions<unknown>
|
8
|
+
|
9
|
+
// This is a type-level assertion - we need to use a different pattern
|
10
|
+
type _Test = Ts.AssertEqual<Options, boolean | string[] | Record<string, boolean>>
|
11
|
+
|
12
|
+
// All of these should be valid options
|
13
|
+
const option1: Options = true
|
14
|
+
const option2: Options = false
|
15
|
+
const option3: Options = ['name', 'age']
|
16
|
+
const option4: Options = []
|
17
|
+
const option5: Options = { name: true, age: false }
|
18
|
+
const option6: Options = {}
|
19
|
+
}
|
20
|
+
|
21
|
+
// Test 2: Mask.create with unknown data type should accept all option types
|
22
|
+
{
|
23
|
+
// Boolean options
|
24
|
+
const mask1 = Mask.create(true)
|
25
|
+
const mask2 = Mask.create(false)
|
26
|
+
// Without explicit type parameter, returns union type
|
27
|
+
Ts.assertEqual<MaskType<unknown>>()(mask1)
|
28
|
+
Ts.assertEqual<MaskType<unknown>>()(mask2)
|
29
|
+
|
30
|
+
// Array options (no type parameter needed)
|
31
|
+
const mask3 = Mask.create(['name', 'age'])
|
32
|
+
const mask4 = Mask.create([])
|
33
|
+
const mask5 = Mask.create(['a', 'b', 'c'])
|
34
|
+
// Returns union type with inferred properties
|
35
|
+
Ts.assertEqual<MaskType<{ name: any; age: any }>>()(mask3)
|
36
|
+
Ts.assertEqual<MaskType<unknown>>()(mask4)
|
37
|
+
Ts.assertEqual<MaskType<{ a: any; b: any; c: any }>>()(mask5)
|
38
|
+
|
39
|
+
// Object options
|
40
|
+
const mask6 = Mask.create({ name: true, age: false })
|
41
|
+
const mask7 = Mask.create({})
|
42
|
+
const mask8 = Mask.create({ foo: true, bar: true, baz: false })
|
43
|
+
// Returns union type with inferred properties
|
44
|
+
Ts.assertEqual<MaskType<unknown>>()(mask6)
|
45
|
+
Ts.assertEqual<MaskType<unknown>>()(mask7)
|
46
|
+
Ts.assertEqual<MaskType<unknown>>()(mask8)
|
47
|
+
}
|
48
|
+
|
49
|
+
// Test 3: With specific data type, options are constrained
|
50
|
+
{
|
51
|
+
type User = { name: string; age: number; email: string }
|
52
|
+
|
53
|
+
// Valid options
|
54
|
+
const mask1 = Mask.create<User>(true)
|
55
|
+
const mask2 = Mask.create<User>(['name', 'age'])
|
56
|
+
const mask3 = Mask.create<User>({ name: true, age: false, email: true })
|
57
|
+
|
58
|
+
// With explicit type parameter, still returns union type
|
59
|
+
Ts.assertEqual<MaskType<User>>()(mask1)
|
60
|
+
Ts.assertEqual<MaskType<User>>()(mask2)
|
61
|
+
Ts.assertEqual<MaskType<User>>()(mask3)
|
62
|
+
|
63
|
+
// Invalid cases would be compile errors
|
64
|
+
// const mask4 = Mask.create<User>(['invalid']) // Error: 'invalid' is not a key of User
|
65
|
+
// const mask5 = Mask.create<User>({ invalid: true }) // Error: 'invalid' is not a property of User
|
66
|
+
}
|
67
|
+
|
68
|
+
// Test 4: Non-object types only accept boolean
|
69
|
+
{
|
70
|
+
const mask1 = Mask.create<string>(true)
|
71
|
+
const mask2 = Mask.create<string>(false)
|
72
|
+
|
73
|
+
Ts.assertEqual<MaskType<string>>()(mask1)
|
74
|
+
Ts.assertEqual<MaskType<string>>()(mask2)
|
75
|
+
|
76
|
+
// Invalid cases would be compile errors
|
77
|
+
// const mask3 = Mask.create<string>(['prop']) // Error: string is not an object
|
78
|
+
// const mask4 = Mask.create<string>({ prop: true }) // Error: string is not an object
|
79
|
+
}
|
80
|
+
|
81
|
+
// Test 5: Test inference in practical scenarios
|
82
|
+
{
|
83
|
+
// Should infer PropertiesMask with object data type
|
84
|
+
const userMask = Mask.create(['name', 'email', 'password'])
|
85
|
+
Ts.assertEqual<MaskType<{ name: any; email: any; password: any }>>()(userMask)
|
86
|
+
|
87
|
+
// When mask is a union type, apply returns a union of possible results
|
88
|
+
const user1 = { name: 'John', email: 'john@example.com', password: 'secret', extra: 'data' }
|
89
|
+
const maskedUser1 = Mask.apply(user1, userMask)
|
90
|
+
// Result is a union type that includes the properties mask result
|
91
|
+
type MaskedUser1 = typeof maskedUser1
|
92
|
+
// Can't use assertSub with union types
|
93
|
+
|
94
|
+
// For partial data, use applyPartial
|
95
|
+
const user2 = { name: 'Jane', email: 'jane@example.com' }
|
96
|
+
const maskedUser2 = Mask.applyPartial(user2, userMask)
|
97
|
+
// Result is a union type
|
98
|
+
type MaskedUser2 = typeof maskedUser2
|
99
|
+
}
|
100
|
+
|
101
|
+
// Test 6: Pick and omit helpers
|
102
|
+
{
|
103
|
+
const pick1 = Mask.pick(['name', 'email'])
|
104
|
+
const omit1 = Mask.omit(['password', 'secret'])
|
105
|
+
|
106
|
+
// Infers specific property types
|
107
|
+
Ts.assertEqual<PropertiesMask<{ name: any; email: any }>>()(pick1)
|
108
|
+
Ts.assertEqual<PropertiesMask<{ password: any; secret: any }>>()(omit1)
|
109
|
+
|
110
|
+
type User = { name: string; email: string; password: string }
|
111
|
+
const pick2 = Mask.pick<User>(['name', 'email'])
|
112
|
+
const omit2 = Mask.omit<User>(['password'])
|
113
|
+
|
114
|
+
Ts.assertEqual<PropertiesMask<User>>()(pick2)
|
115
|
+
Ts.assertEqual<PropertiesMask<User>>()(omit2)
|
116
|
+
}
|
117
|
+
|
118
|
+
// Test 7: Apply type transformations
|
119
|
+
{
|
120
|
+
type User = { name: string; email: string; password: string }
|
121
|
+
const user: User = { name: 'John', email: 'john@example.com', password: 'secret' }
|
122
|
+
|
123
|
+
// Binary mask
|
124
|
+
const showMask = Mask.create<User>(true)
|
125
|
+
const hideMask = Mask.create<User>(false)
|
126
|
+
const shown = Mask.apply(user, showMask)
|
127
|
+
const hidden = Mask.apply(user, hideMask)
|
128
|
+
|
129
|
+
// showMask and hideMask are MaskType<User> (union types), so results are unions
|
130
|
+
type ShownType = typeof shown // Omit<User, keyof User> | undefined
|
131
|
+
type HiddenType = typeof hidden // Omit<User, keyof User> | undefined
|
132
|
+
|
133
|
+
// Properties mask - allow mode
|
134
|
+
const allowMask = Mask.create<User>(['name', 'email'])
|
135
|
+
const allowed = Mask.apply(user, allowMask)
|
136
|
+
// Result is a union due to mask being a union type
|
137
|
+
type AllowedType = typeof allowed
|
138
|
+
|
139
|
+
// Properties mask - deny mode
|
140
|
+
const denyMask = Mask.create<User>({ password: false })
|
141
|
+
const denied = Mask.apply(user, denyMask)
|
142
|
+
// Result is a union due to mask being a union type
|
143
|
+
type DeniedType = typeof denied
|
144
|
+
}
|