@frigear-nu/preferences 0.0.0-stage → 0.0.2
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 +17 -2
- package/dist/module.d.mts +27 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +81 -0
- package/dist/runtime/app/composables/index.d.ts +1 -0
- package/dist/runtime/app/composables/index.js +1 -0
- package/dist/runtime/app/composables/useUserPreferences.d.ts +13 -0
- package/dist/runtime/app/composables/useUserPreferences.js +18 -0
- package/dist/runtime/define.d.ts +36 -0
- package/dist/runtime/define.js +1 -0
- package/dist/runtime/server/db/schema.sqlite.d.ts +26 -0
- package/dist/runtime/server/db/schema.sqlite.js +4 -0
- package/dist/runtime/server/plugins/oauth-claims.d.ts +2 -0
- package/dist/runtime/server/plugins/oauth-claims.js +10 -0
- package/dist/runtime/server/routes/preferences.patch.d.ts +2 -0
- package/dist/runtime/server/routes/preferences.patch.js +17 -0
- package/dist/runtime/server/utils/preferences.d.ts +13 -0
- package/dist/runtime/server/utils/preferences.js +9 -0
- package/dist/runtime/utils/build.d.ts +17 -0
- package/dist/runtime/utils/build.js +5 -0
- package/dist/runtime/utils/registry.d.ts +32 -0
- package/dist/runtime/utils/registry.js +45 -0
- package/dist/types.d.mts +5 -0
- package/package.json +80 -3
package/README.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @frigear-nu/preferences
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Typed per-user preferences that modules and the host app register with `definePreferences`, stored in one JSON column
|
|
4
|
+
on `users`, and optionally forwarded to OAuth applications as claims.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
// shared/preferences.ts
|
|
8
|
+
import { z } from 'zod'
|
|
9
|
+
import { definePreferences } from '@frigear-nu/preferences/define'
|
|
10
|
+
|
|
11
|
+
export default definePreferences({
|
|
12
|
+
compactTables: z.boolean(),
|
|
13
|
+
colorMode: { schema: z.enum(['light', 'dark', 'system']), claim: 'color_mode' },
|
|
14
|
+
})
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`useUserPreferences()` on the client and `getUserPreference(user, key)` on the server are typed from those schemas.
|
|
18
|
+
See the [documentation](https://docs.frigear.dev/modules/preferences).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
export { PreferenceDefinition, PreferenceDefinitions, PreferencesOf } from '../dist/runtime/define.js';
|
|
3
|
+
|
|
4
|
+
type ModuleOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* Files that default-export `definePreferences({...})`, resolved from the project root. A `shared/preferences.ts`
|
|
7
|
+
* is picked up on its own. Other modules register theirs with `addUserPreferences`.
|
|
8
|
+
*/
|
|
9
|
+
definitions?: string | string[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* The preferences of a user, keyed by name. This is filled in by the generated types from every registered
|
|
13
|
+
* definer file, so `preferences.colorMode` is `'light' | 'dark' | 'system' | undefined` and a misspelled key
|
|
14
|
+
* does not compile.
|
|
15
|
+
*/
|
|
16
|
+
interface UserPreferences {
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
declare module 'nuxt/schema' {
|
|
20
|
+
interface NuxtConfig {
|
|
21
|
+
preferences?: ModuleOptions;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
25
|
+
|
|
26
|
+
export { _default as default };
|
|
27
|
+
export type { ModuleOptions, UserPreferences };
|
package/dist/module.json
ADDED
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { defineNuxtModule, createResolver, addServerTemplate, addTypeTemplate, addServerImportsDir, addImports, addServerHandler, hasNuxtModule, addServerPlugin } from '@nuxt/kit';
|
|
4
|
+
import { registerModule, extendDatabaseTable } from '@frigear-nu/module-kit/utils';
|
|
5
|
+
|
|
6
|
+
const module$1 = defineNuxtModule({
|
|
7
|
+
meta: { name: "@frigear-nu/preferences", configKey: "preferences" },
|
|
8
|
+
defaults: {},
|
|
9
|
+
moduleDependencies: {
|
|
10
|
+
"@nuxthub/core": { version: ">=0.10" },
|
|
11
|
+
"@frigear-nu/module-kit": { version: ">=0.0.1" },
|
|
12
|
+
"@frigear-nu/auth": { version: ">=0.0.1" },
|
|
13
|
+
"@frigear-nu/users": { version: ">=0.0.1" },
|
|
14
|
+
// Only needed to forward preferences as claims; listed so it is installed before this module is set up.
|
|
15
|
+
"@frigear-nu/oauth-server": { version: ">=0.0.1", optional: true }
|
|
16
|
+
},
|
|
17
|
+
async setup(options, nuxt) {
|
|
18
|
+
registerModule("@frigear-nu/preferences", nuxt);
|
|
19
|
+
const resolver = createResolver(import.meta.url);
|
|
20
|
+
extendDatabaseTable(nuxt, {
|
|
21
|
+
name: "users",
|
|
22
|
+
resolver,
|
|
23
|
+
file: (dialect) => `./runtime/server/db/schema.${dialect}`
|
|
24
|
+
});
|
|
25
|
+
const collectSources = async () => {
|
|
26
|
+
const configured = [options.definitions ?? []].flat();
|
|
27
|
+
const conventional = join(nuxt.options.rootDir, "shared/preferences.ts");
|
|
28
|
+
const paths = [
|
|
29
|
+
...await Promise.all(configured.map((path) => resolver.resolvePath(path, { cwd: nuxt.options.rootDir }))),
|
|
30
|
+
...!configured.length && existsSync(conventional) ? [conventional] : []
|
|
31
|
+
];
|
|
32
|
+
const sources = paths.map((path) => ({ path }));
|
|
33
|
+
await nuxt.callHook("preferences:definitions", sources);
|
|
34
|
+
return [...new Map(sources.map((source) => [source.path, source])).values()];
|
|
35
|
+
};
|
|
36
|
+
addServerTemplate({
|
|
37
|
+
filename: "#preferences/definitions",
|
|
38
|
+
getContents: async () => {
|
|
39
|
+
const sources = await collectSources();
|
|
40
|
+
return [
|
|
41
|
+
...sources.map((source, index) => `import definitions${index} from ${JSON.stringify(source.path)}`),
|
|
42
|
+
`export const definitions = [${sources.map((_, index) => `definitions${index}`).join(", ")}]`
|
|
43
|
+
].join("\n");
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
addTypeTemplate({
|
|
47
|
+
filename: "types/preferences.d.ts",
|
|
48
|
+
getContents: async () => {
|
|
49
|
+
const sources = await collectSources();
|
|
50
|
+
const extend = sources.length ? ` extends ${sources.map((source) => `PreferencesOf<typeof import(${JSON.stringify(source.path)}).default>`).join(", ")}` : "";
|
|
51
|
+
return `import type { PreferencesOf } from '${resolver.resolve("./runtime/define")}'
|
|
52
|
+
import type { UserPreferences as RegisteredPreferences } from '@frigear-nu/preferences'
|
|
53
|
+
|
|
54
|
+
declare module '@frigear-nu/preferences' {
|
|
55
|
+
interface UserPreferences${extend} {}
|
|
56
|
+
}
|
|
57
|
+
declare module '#auth-utils' {
|
|
58
|
+
interface User {
|
|
59
|
+
preferences?: RegisteredPreferences
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
declare module '#preferences/definitions' {
|
|
63
|
+
export const definitions: Array<import('${resolver.resolve("./runtime/define")}').PreferenceDefinitions>
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
}, { nitro: true, nuxt: true });
|
|
68
|
+
addServerImportsDir(resolver.resolve("./runtime/server/utils"));
|
|
69
|
+
addImports({ name: "useUserPreferences", from: resolver.resolve("./runtime/app/composables/useUserPreferences") });
|
|
70
|
+
addServerHandler({
|
|
71
|
+
route: "/api/preferences",
|
|
72
|
+
method: "patch",
|
|
73
|
+
handler: resolver.resolve("./runtime/server/routes/preferences.patch")
|
|
74
|
+
});
|
|
75
|
+
if (hasNuxtModule("@frigear-nu/oauth-server")) {
|
|
76
|
+
addServerPlugin(resolver.resolve("./runtime/server/plugins/oauth-claims"));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
export { module$1 as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useUserPreferences.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./useUserPreferences.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { UserPreferences } from '../../../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The signed-in user's preferences, typed from every registered definer file.
|
|
4
|
+
*
|
|
5
|
+
* const { preferences, set } = useUserPreferences()
|
|
6
|
+
* await set('colorMode', 'dark')
|
|
7
|
+
*/
|
|
8
|
+
export declare const useUserPreferences: () => {
|
|
9
|
+
preferences: any;
|
|
10
|
+
get: <K extends keyof UserPreferences>(key: K) => UserPreferences[K] | undefined;
|
|
11
|
+
set: <K extends keyof UserPreferences>(key: K, value: UserPreferences[K] | null) => Promise<void>;
|
|
12
|
+
pending: any;
|
|
13
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const useUserPreferences = () => {
|
|
2
|
+
const { user, fetch: refreshSession } = useUserSession();
|
|
3
|
+
const pending = ref(false);
|
|
4
|
+
const preferences = computed(() => user.value?.preferences ?? {});
|
|
5
|
+
function get(key) {
|
|
6
|
+
return preferences.value[key];
|
|
7
|
+
}
|
|
8
|
+
async function set(key, value) {
|
|
9
|
+
pending.value = true;
|
|
10
|
+
try {
|
|
11
|
+
await $fetch("/api/preferences", { method: "PATCH", body: { [key]: value } });
|
|
12
|
+
await refreshSession();
|
|
13
|
+
} finally {
|
|
14
|
+
pending.value = false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return { preferences, get, set, pending };
|
|
18
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* One preference: a zod schema, or a schema plus how it is exposed.
|
|
4
|
+
*
|
|
5
|
+
* - `claim` forwards the value to OAuth applications under that claim name (ID token and `userinfo`).
|
|
6
|
+
* Nothing is forwarded without it, so a preference stays private to this app unless it opts in.
|
|
7
|
+
* - `scope` is the OAuth scope an application must have been granted to receive the claim (default `profile`).
|
|
8
|
+
*/
|
|
9
|
+
export type PreferenceDefinition<S extends z.ZodType = z.ZodType> = S | {
|
|
10
|
+
schema: S;
|
|
11
|
+
claim?: string;
|
|
12
|
+
scope?: string;
|
|
13
|
+
};
|
|
14
|
+
export type PreferenceDefinitions = Record<string, PreferenceDefinition>;
|
|
15
|
+
/** The schema of a definition, whichever of the two shapes it was written in. */
|
|
16
|
+
export type PreferenceSchema<D> = D extends z.ZodType ? D : D extends {
|
|
17
|
+
schema: infer S extends z.ZodType;
|
|
18
|
+
} ? S : never;
|
|
19
|
+
/** `{ key: value }` as the preferences of a user: every key is optional because a user may not have chosen yet. */
|
|
20
|
+
export type PreferencesOf<T extends PreferenceDefinitions> = {
|
|
21
|
+
[K in keyof T]?: z.output<PreferenceSchema<T[K]>>;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Declares preferences. A definer file default-exports the result, and is registered with
|
|
25
|
+
* `addUserPreferences(nuxt, { path })` or listed in the module's `definitions` option. Its keys and value types
|
|
26
|
+
* become `UserPreferences`, so the schema is the single source of truth for validation and typing.
|
|
27
|
+
*
|
|
28
|
+
* // shared/preferences.ts
|
|
29
|
+
* export default definePreferences({
|
|
30
|
+
* // plain schema: stored and readable by this app only
|
|
31
|
+
* compactTables: z.boolean(),
|
|
32
|
+
* // forwarded to OAuth applications as `color_mode`
|
|
33
|
+
* colorMode: { schema: z.enum(['light', 'dark', 'system']), claim: 'color_mode' },
|
|
34
|
+
* })
|
|
35
|
+
*/
|
|
36
|
+
export declare const definePreferences: <const T extends PreferenceDefinitions>(definitions: T) => T;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const definePreferences = (definitions) => definitions;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const users: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
2
|
+
name: "users";
|
|
3
|
+
schema: undefined;
|
|
4
|
+
columns: {
|
|
5
|
+
preferences: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
6
|
+
name: "preferences";
|
|
7
|
+
tableName: "users";
|
|
8
|
+
dataType: "json";
|
|
9
|
+
columnType: "SQLiteTextJson";
|
|
10
|
+
data: Record<string, unknown>;
|
|
11
|
+
driverParam: string;
|
|
12
|
+
notNull: false;
|
|
13
|
+
hasDefault: false;
|
|
14
|
+
isPrimaryKey: false;
|
|
15
|
+
isAutoincrement: false;
|
|
16
|
+
hasRuntimeDefault: false;
|
|
17
|
+
enumValues: undefined;
|
|
18
|
+
baseColumn: never;
|
|
19
|
+
identity: undefined;
|
|
20
|
+
generated: undefined;
|
|
21
|
+
}, {}, {
|
|
22
|
+
$type: Record<string, unknown>;
|
|
23
|
+
}>;
|
|
24
|
+
};
|
|
25
|
+
dialect: "sqlite";
|
|
26
|
+
}>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { preferenceClaims } from "../../utils/registry.js";
|
|
2
|
+
export default defineNitroPlugin((nitro) => {
|
|
3
|
+
const hooks = nitro.hooks;
|
|
4
|
+
hooks.hook("oauth:claims", (ctx) => {
|
|
5
|
+
const claims = preferenceClaims(getPreferenceRegistry(), ctx.user.preferences, ctx);
|
|
6
|
+
for (const [name, value] of Object.entries(claims)) {
|
|
7
|
+
if (!(name in ctx.claims)) ctx.claims[name] = value;
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { db, schema } from "@nuxthub/db";
|
|
2
|
+
import { eq } from "drizzle-orm";
|
|
3
|
+
import { parsePreferencePatch } from "../../utils/registry.js";
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const user = await requireUser(event, { sessionOnly: true });
|
|
6
|
+
const patch = parsePreferencePatch(getPreferenceRegistry(), await readBody(event));
|
|
7
|
+
if (!patch.ok) throw createError({ statusCode: 400, statusMessage: patch.message });
|
|
8
|
+
const [current] = await db.select({ preferences: schema.users.preferences }).from(schema.users).where(eq(schema.users.id, user.id)).limit(1);
|
|
9
|
+
if (!current) throw createError({ statusCode: 404, statusMessage: "User not found." });
|
|
10
|
+
const preferences = Object.fromEntries(
|
|
11
|
+
Object.entries({ ...current.preferences, ...patch.set }).filter(([key]) => !patch.remove.includes(key))
|
|
12
|
+
);
|
|
13
|
+
const [updated] = await db.update(schema.users).set({ preferences }).where(eq(schema.users.id, user.id)).returning();
|
|
14
|
+
if (!updated) throw createError({ statusCode: 404, statusMessage: "User not found." });
|
|
15
|
+
await setUserSession(event, { user: mapUserToSession(updated) });
|
|
16
|
+
return preferences;
|
|
17
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { UserPreferences } from '../../../types.js';
|
|
2
|
+
import type { ResolvedPreference } from '../../utils/registry.js';
|
|
3
|
+
/** Every registered preference, keyed by name. */
|
|
4
|
+
export declare function getPreferenceRegistry(): Map<string, ResolvedPreference>;
|
|
5
|
+
/**
|
|
6
|
+
* A typed preference of a user (the session user or a database row), or `undefined` when it was never set or no
|
|
7
|
+
* longer satisfies its schema.
|
|
8
|
+
*
|
|
9
|
+
* getUserPreference(user, 'colorMode') // 'light' | 'dark' | 'system' | undefined
|
|
10
|
+
*/
|
|
11
|
+
export declare function getUserPreference<K extends keyof UserPreferences & string>(user: {
|
|
12
|
+
preferences?: unknown;
|
|
13
|
+
} | null | undefined, key: K): UserPreferences[K] | undefined;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { definitions } from "#preferences/definitions";
|
|
2
|
+
import { readPreference, resolvePreferences } from "../../utils/registry.js";
|
|
3
|
+
let registry;
|
|
4
|
+
export function getPreferenceRegistry() {
|
|
5
|
+
return registry ??= resolvePreferences(definitions);
|
|
6
|
+
}
|
|
7
|
+
export function getUserPreference(user, key) {
|
|
8
|
+
return readPreference(getPreferenceRegistry(), user?.preferences, key);
|
|
9
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Nuxt } from '@nuxt/schema';
|
|
2
|
+
export interface PreferenceDefinitionSource {
|
|
3
|
+
/** Absolute path to a file that default-exports `definePreferences({...})`. */
|
|
4
|
+
path: string;
|
|
5
|
+
}
|
|
6
|
+
declare module '@nuxt/schema' {
|
|
7
|
+
interface NuxtHooks {
|
|
8
|
+
'preferences:definitions': (sources: PreferenceDefinitionSource[]) => void | Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Register a definer file. Its keys become part of `UserPreferences`, are validated by the preferences API, and
|
|
13
|
+
* forwarded as OAuth claims where the definition says so.
|
|
14
|
+
*
|
|
15
|
+
* addUserPreferences(nuxt, { path: resolver.resolve('./runtime/preferences') })
|
|
16
|
+
*/
|
|
17
|
+
export declare function addUserPreferences(nuxt: Nuxt, source: PreferenceDefinitionSource): void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
import type { PreferenceDefinitions } from '../define.js';
|
|
3
|
+
export interface ResolvedPreference {
|
|
4
|
+
key: string;
|
|
5
|
+
schema: z.ZodType;
|
|
6
|
+
/** Name of the OAuth claim the value is forwarded as, if any. */
|
|
7
|
+
claim?: string;
|
|
8
|
+
/** Scope an application needs to receive the claim. */
|
|
9
|
+
scope: string;
|
|
10
|
+
}
|
|
11
|
+
/** Flattens the registered definer files into one lookup. The same key in two files is a mistake worth failing on. */
|
|
12
|
+
export declare function resolvePreferences(sources: PreferenceDefinitions[]): Map<string, ResolvedPreference>;
|
|
13
|
+
export type PreferencePatchResult = {
|
|
14
|
+
ok: true;
|
|
15
|
+
set: Record<string, unknown>;
|
|
16
|
+
remove: string[];
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
message: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Validates a partial update: every key must be registered, a value must satisfy its schema, and `null` clears
|
|
23
|
+
* the preference.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parsePreferencePatch(registry: Map<string, ResolvedPreference>, body: unknown): PreferencePatchResult;
|
|
26
|
+
/** The stored value when it still satisfies the schema, so a changed schema never leaks stale data. */
|
|
27
|
+
export declare function readPreference(registry: Map<string, ResolvedPreference>, stored: unknown, key: string): unknown;
|
|
28
|
+
/** The claims to add for a token: only preferences that opted in, only when the scope was granted. */
|
|
29
|
+
export declare function preferenceClaims(registry: Map<string, ResolvedPreference>, stored: unknown, ctx: {
|
|
30
|
+
tokenType: string;
|
|
31
|
+
scope: string[];
|
|
32
|
+
}): Record<string, unknown>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const isSchema = (value) => typeof value?.safeParse === "function";
|
|
2
|
+
export function resolvePreferences(sources) {
|
|
3
|
+
const registry = /* @__PURE__ */ new Map();
|
|
4
|
+
for (const source of sources) {
|
|
5
|
+
for (const [key, definition] of Object.entries(source)) {
|
|
6
|
+
if (registry.has(key)) throw new Error(`[preferences] "${key}" is defined more than once.`);
|
|
7
|
+
const resolved = isSchema(definition) ? { key, schema: definition, scope: "profile" } : { key, schema: definition.schema, claim: definition.claim, scope: definition.scope ?? "profile" };
|
|
8
|
+
registry.set(key, resolved);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return registry;
|
|
12
|
+
}
|
|
13
|
+
export function parsePreferencePatch(registry, body) {
|
|
14
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return { ok: false, message: "Expected an object of preferences." };
|
|
15
|
+
const set = {};
|
|
16
|
+
const remove = [];
|
|
17
|
+
for (const [key, value] of Object.entries(body)) {
|
|
18
|
+
const definition = registry.get(key);
|
|
19
|
+
if (!definition) return { ok: false, message: `Unknown preference "${key}".` };
|
|
20
|
+
if (value === null) {
|
|
21
|
+
remove.push(key);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const parsed = definition.schema.safeParse(value);
|
|
25
|
+
if (!parsed.success) return { ok: false, message: `Invalid value for "${key}".` };
|
|
26
|
+
set[key] = parsed.data;
|
|
27
|
+
}
|
|
28
|
+
return { ok: true, set, remove };
|
|
29
|
+
}
|
|
30
|
+
export function readPreference(registry, stored, key) {
|
|
31
|
+
const definition = registry.get(key);
|
|
32
|
+
if (!definition || !stored || typeof stored !== "object" || !(key in stored)) return void 0;
|
|
33
|
+
const parsed = definition.schema.safeParse(stored[key]);
|
|
34
|
+
return parsed.success ? parsed.data : void 0;
|
|
35
|
+
}
|
|
36
|
+
export function preferenceClaims(registry, stored, ctx) {
|
|
37
|
+
if (ctx.tokenType === "access_token") return {};
|
|
38
|
+
const claims = {};
|
|
39
|
+
for (const definition of registry.values()) {
|
|
40
|
+
if (!definition.claim || !ctx.scope.includes(definition.scope)) continue;
|
|
41
|
+
const value = readPreference(registry, stored, definition.key);
|
|
42
|
+
if (value !== void 0) claims[definition.claim] = value;
|
|
43
|
+
}
|
|
44
|
+
return claims;
|
|
45
|
+
}
|
package/dist/types.d.mts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,83 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frigear-nu/preferences",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "Typed per-user preferences that modules and the host app register, stored on the user and optionally forwarded to OAuth applications as claims.",
|
|
5
|
+
"homepage": "https://docs.frigear.dev/modules/preferences",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/frigear-nu/frigear-nu.git",
|
|
9
|
+
"directory": "packages/preferences"
|
|
10
|
+
},
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/module.mjs",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/types.d.mts",
|
|
17
|
+
"import": "./dist/module.mjs"
|
|
18
|
+
},
|
|
19
|
+
"./utils": {
|
|
20
|
+
"types": "./dist/runtime/utils/build.d.ts",
|
|
21
|
+
"import": "./dist/runtime/utils/build.js"
|
|
22
|
+
},
|
|
23
|
+
"./define": {
|
|
24
|
+
"types": "./dist/runtime/define.d.ts",
|
|
25
|
+
"import": "./dist/runtime/define.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist"
|
|
30
|
+
],
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@frigear-nu/auth": "0.0.2",
|
|
33
|
+
"@frigear-nu/module-kit": "0.1.6",
|
|
34
|
+
"@frigear-nu/users": "1.0.8",
|
|
35
|
+
"@libsql/client": "^0.18.0",
|
|
36
|
+
"@nuxt/kit": "^4.6.0",
|
|
37
|
+
"drizzle-kit": "^0.31.11",
|
|
38
|
+
"drizzle-orm": "^0.45.3",
|
|
39
|
+
"zod": "^4.6.5"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@frigear-nu/oauth-server": ">=0.0.1",
|
|
43
|
+
"@nuxthub/core": "^0.10.8"
|
|
44
|
+
},
|
|
45
|
+
"peerDependenciesMeta": {
|
|
46
|
+
"@frigear-nu/oauth-server": {
|
|
47
|
+
"optional": true
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@frigear-nu/oauth-server": "0.0.7",
|
|
52
|
+
"@nuxt/devtools": "^3.4.2",
|
|
53
|
+
"@nuxt/eslint-config": "^1.17.0",
|
|
54
|
+
"@nuxt/module-builder": "^1.0.3",
|
|
55
|
+
"@nuxt/schema": "^4.6.0",
|
|
56
|
+
"@nuxt/test-utils": "^4.3.2",
|
|
57
|
+
"@nuxthub/core": "^0.10.8",
|
|
58
|
+
"@types/node": "latest",
|
|
59
|
+
"changelogen": "^0.6.2",
|
|
60
|
+
"eslint": "^10.10.0",
|
|
61
|
+
"nuxt": "^4.6.1",
|
|
62
|
+
"typescript": "^6.0.3",
|
|
63
|
+
"vitest": "^5.0.1",
|
|
64
|
+
"vue-tsc": "^3.3.11"
|
|
65
|
+
},
|
|
66
|
+
"scripts": {
|
|
67
|
+
"postinstall": "nuxt-module-build build --stub && nuxt prepare",
|
|
68
|
+
"build": "nuxt-module-build build",
|
|
69
|
+
"release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
|
|
70
|
+
"lint": "eslint .",
|
|
71
|
+
"lint:fix": "eslint . --fix",
|
|
72
|
+
"test": "vitest run",
|
|
73
|
+
"test:watch": "vitest watch",
|
|
74
|
+
"test:types": "vue-tsc --noEmit && cd test/fixtures/basic && vue-tsc --noEmit"
|
|
75
|
+
},
|
|
76
|
+
"typesVersions": {
|
|
77
|
+
"*": {
|
|
78
|
+
".": [
|
|
79
|
+
"./dist/types.d.mts"
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
6
83
|
}
|