@astronox/core 0.2.1 → 0.2.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.
Files changed (64) hide show
  1. package/package.json +7 -1
  2. package/src/admin/contracts.ts +28 -0
  3. package/src/admin/index.ts +2 -0
  4. package/src/admin/module.ts +28 -0
  5. package/src/auth/contracts.ts +53 -0
  6. package/src/auth/firebase/client.ts +53 -0
  7. package/src/auth/firebase/contracts.ts +8 -0
  8. package/src/auth/firebase/index.ts +1 -0
  9. package/src/auth/index.ts +2 -0
  10. package/src/auth/module.ts +99 -0
  11. package/src/client/index.ts +3 -0
  12. package/src/client/preferences.ts +400 -0
  13. package/src/components/actions/Button/Button.astro +42 -6
  14. package/src/components/actions/Button/Button.css +19 -0
  15. package/src/components/actions/IdentityProviderAction/IdentityProviderAction.astro +35 -0
  16. package/src/components/content/CardBody/CardBody.astro +3 -0
  17. package/src/components/content/CardBody/CardBody.css +10 -0
  18. package/src/components/content/MarkdownContent/markdownContentRenderer.ts +549 -0
  19. package/src/components/feedback/PreferenceBootstrap/PreferenceBootstrap.astro +403 -0
  20. package/src/components/forms/Checkbox/Checkbox.astro +6 -0
  21. package/src/components/forms/ChoiceGroup/ChoiceGroup.astro +57 -0
  22. package/src/components/forms/ChoiceGroup/ChoiceGroup.css +108 -0
  23. package/src/components/forms/ChoiceGroup/contracts.ts +9 -0
  24. package/src/components/forms/DateTimeInput/DateTimeInput.astro +14 -0
  25. package/src/components/forms/TextInput/TextInput.astro +7 -1
  26. package/src/components/forms/Textarea/Textarea.astro +32 -0
  27. package/src/components/forms/Textarea/Textarea.css +54 -0
  28. package/src/components/icons/BackIcon.astro +26 -0
  29. package/src/components/icons/DeleteIcon.astro +22 -0
  30. package/src/components/icons/StatusIndicatorIcon.astro +16 -0
  31. package/src/components/index.ts +10 -0
  32. package/src/components/media/CanvasFrame/CanvasFrame.astro +1 -17
  33. package/src/components/media/EmbedFrame/EmbedFrame.astro +1 -19
  34. package/src/components/media/EmbedFrame/EmbedFrame.css +0 -2
  35. package/src/components/navigation/SubNavigation/SubNavigation.css +0 -5
  36. package/src/composition/SurfaceGroup/SurfaceGroup.css +21 -7
  37. package/src/composition/SurfaceGroup/stickySurfaceGroupController.ts +3 -0
  38. package/src/data/firestore/client/contracts.ts +55 -0
  39. package/src/data/firestore/client/firebase.ts +12 -0
  40. package/src/data/firestore/client/index.ts +3 -0
  41. package/src/data/firestore/client/repository.ts +111 -0
  42. package/src/display/contracts.ts +40 -0
  43. package/src/display/index.ts +1 -0
  44. package/src/display/tableProgressive.ts +62 -0
  45. package/src/patterns/Combobox/Combobox.astro +23 -0
  46. package/src/patterns/Fullscreen/FullscreenControl.astro +4 -14
  47. package/src/patterns/Fullscreen/FullscreenControl.css +0 -26
  48. package/src/patterns/Modal/Modal.astro +1 -1
  49. package/src/patterns/Modal/Modal.css +38 -1
  50. package/src/patterns/Table/Table.astro +122 -7
  51. package/src/patterns/Table/TableBody.astro +8 -0
  52. package/src/patterns/Table/TableCell.css +6 -0
  53. package/src/patterns/Table/TableCellValue.astro +31 -0
  54. package/src/patterns/Table/TableFooter.astro +3 -1
  55. package/src/patterns/Table/TableHeader.astro +1 -0
  56. package/src/patterns/Table/TablePagination.astro +63 -47
  57. package/src/patterns/Table/TableSkeleton.astro +18 -22
  58. package/src/patterns/Table/TableSkeleton.css +30 -5
  59. package/src/patterns/Table/tableClientController.ts +253 -0
  60. package/src/patterns/Table/tableFilterController.ts +23 -31
  61. package/src/patterns/Table/tableProgressiveController.ts +615 -227
  62. package/src/patterns/Table/tableSortController.ts +25 -7
  63. package/src/styles/interaction.css +16 -0
  64. package/src/styles/style.css +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astronox/core",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Reusable Astro components, patterns, styles, and infrastructure for AstroNox sites.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -30,10 +30,15 @@
30
30
  "exports": {
31
31
  ".": "./src/index.ts",
32
32
  "./client": "./src/client/index.ts",
33
+ "./admin": "./src/admin/index.ts",
34
+ "./auth": "./src/auth/index.ts",
35
+ "./auth/firebase": "./src/auth/firebase/index.ts",
36
+ "./auth/firebase/client": "./src/auth/firebase/client.ts",
33
37
  "./components": "./src/components/index.ts",
34
38
  "./composition": "./src/composition/index.ts",
35
39
  "./config": "./src/config/index.ts",
36
40
  "./data": "./src/data/index.ts",
41
+ "./data/firestore/client": "./src/data/firestore/client/index.ts",
37
42
  "./display": "./src/display/index.ts",
38
43
  "./i18n": "./src/i18n/index.ts",
39
44
  "./layouts": "./src/layouts/index.ts",
@@ -62,6 +67,7 @@
62
67
  "astro": "^7.0.0"
63
68
  },
64
69
  "dependencies": {
70
+ "firebase": "^12.0.0",
65
71
  "zod": "4.4.3"
66
72
  },
67
73
  "devDependencies": {
@@ -0,0 +1,28 @@
1
+ import type { AuthActor, AuthSession } from '../auth';
2
+
3
+ export type AdminPage<TItem, TCursor = string> = {
4
+ items: readonly TItem[];
5
+ nextCursor?: TCursor;
6
+ };
7
+
8
+ export interface AdminResource<
9
+ TItem,
10
+ TCreateInput,
11
+ TUpdateInput = TCreateInput,
12
+ TIndexInput = void,
13
+ > {
14
+ getIndex(input: TIndexInput): Promise<AdminPage<TItem>>;
15
+ getDetail(input: { id: string }): Promise<TItem | null>;
16
+ create(input: TCreateInput, actor: AuthActor): Promise<TItem>;
17
+ update(
18
+ input: { id: string; value: TUpdateInput },
19
+ actor: AuthActor,
20
+ ): Promise<TItem>;
21
+ delete(input: { id: string }, actor: AuthActor): Promise<void>;
22
+ }
23
+
24
+ export type AdminAccess<TResources extends Record<string, unknown>> = {
25
+ actor: AuthActor;
26
+ resources: TResources;
27
+ session: AuthSession;
28
+ };
@@ -0,0 +1,2 @@
1
+ export * from './contracts';
2
+ export * from './module';
@@ -0,0 +1,28 @@
1
+ import type { AuthModule } from '../auth';
2
+ import type { AdminAccess } from './contracts';
3
+
4
+ export function createAdminModule<
5
+ TCredential,
6
+ TCapability extends string,
7
+ TResources extends Record<string, unknown>,
8
+ >({
9
+ auth,
10
+ capability,
11
+ resources,
12
+ }: {
13
+ auth: AuthModule<TCredential, TCapability>;
14
+ capability: TCapability;
15
+ resources: TResources;
16
+ }) {
17
+ return {
18
+ async getActor(token?: string | null) {
19
+ return (await auth.getSession(token))?.actor ?? null;
20
+ },
21
+ async requireAccess(
22
+ token?: string | null,
23
+ ): Promise<AdminAccess<TResources>> {
24
+ const session = await auth.requireCapability(token, capability);
25
+ return { actor: session.actor, resources, session };
26
+ },
27
+ };
28
+ }
@@ -0,0 +1,53 @@
1
+ export type AuthClaims = Readonly<Record<string, unknown>>;
2
+
3
+ export type AuthActor = {
4
+ id: string;
5
+ claims: AuthClaims;
6
+ displayName?: string;
7
+ email?: string;
8
+ imageUrl?: string;
9
+ };
10
+
11
+ export type AuthSession = {
12
+ actor: AuthActor;
13
+ expiresAt: Date;
14
+ };
15
+
16
+ export type AuthSessionGrant = AuthSession & {
17
+ token: string;
18
+ };
19
+
20
+ export interface IdentityProvider<TCredential = string> {
21
+ authenticate(credential: TCredential): Promise<AuthActor>;
22
+ }
23
+
24
+ export interface SessionProvider<TCredential = string> {
25
+ create(
26
+ credential: TCredential,
27
+ actor: AuthActor,
28
+ ): Promise<AuthSessionGrant>;
29
+ get(token: string): Promise<AuthSession | null>;
30
+ revoke(token: string): Promise<void>;
31
+ }
32
+
33
+ export interface AuthorizationPolicy<TCapability extends string = string> {
34
+ allows(actor: AuthActor, capability: TCapability): boolean | Promise<boolean>;
35
+ }
36
+
37
+ export class AuthenticationError extends Error {
38
+ readonly code = 'AUTHENTICATION_REQUIRED';
39
+
40
+ constructor(message = 'Authentication is required') {
41
+ super(message);
42
+ this.name = 'AuthenticationError';
43
+ }
44
+ }
45
+
46
+ export class AuthorizationError extends Error {
47
+ readonly code = 'AUTHORIZATION_DENIED';
48
+
49
+ constructor(message = 'Access is denied') {
50
+ super(message);
51
+ this.name = 'AuthorizationError';
52
+ }
53
+ }
@@ -0,0 +1,53 @@
1
+ import { getApps, initializeApp } from 'firebase/app';
2
+ import {
3
+ GoogleAuthProvider,
4
+ getAuth,
5
+ onAuthStateChanged,
6
+ signInWithPopup,
7
+ signOut,
8
+ type User,
9
+ } from 'firebase/auth';
10
+ import type { AuthActor } from '../contracts';
11
+ import type { FirebaseClientConfig } from './contracts';
12
+
13
+ export type FirebaseGoogleAuthClient = {
14
+ observe(listener: (actor: AuthActor | null) => void): () => void;
15
+ signIn(): Promise<AuthActor>;
16
+ signOut(): Promise<void>;
17
+ };
18
+
19
+ function toAuthActor(user: User): AuthActor {
20
+ return {
21
+ id: user.uid,
22
+ claims: {},
23
+ displayName: user.displayName ?? undefined,
24
+ email: user.email ?? undefined,
25
+ imageUrl: user.photoURL ?? undefined,
26
+ };
27
+ }
28
+
29
+ export function createFirebaseGoogleAuthClient(
30
+ config: FirebaseClientConfig,
31
+ ): FirebaseGoogleAuthClient {
32
+ const app =
33
+ getApps().find((candidate) => candidate.name === config.projectId) ??
34
+ initializeApp(config, config.projectId);
35
+ const auth = getAuth(app);
36
+ const provider = new GoogleAuthProvider();
37
+ provider.setCustomParameters({ prompt: 'select_account' });
38
+
39
+ return {
40
+ observe(listener) {
41
+ return onAuthStateChanged(auth, (user) => {
42
+ listener(user ? toAuthActor(user) : null);
43
+ });
44
+ },
45
+ async signIn() {
46
+ const result = await signInWithPopup(auth, provider);
47
+ return toAuthActor(result.user);
48
+ },
49
+ async signOut() {
50
+ await signOut(auth);
51
+ },
52
+ };
53
+ }
@@ -0,0 +1,8 @@
1
+ export type FirebaseClientConfig = {
2
+ apiKey: string;
3
+ appId: string;
4
+ authDomain: string;
5
+ messagingSenderId?: string;
6
+ projectId: string;
7
+ storageBucket?: string;
8
+ };
@@ -0,0 +1 @@
1
+ export * from './contracts';
@@ -0,0 +1,2 @@
1
+ export * from './contracts';
2
+ export * from './module';
@@ -0,0 +1,99 @@
1
+ import {
2
+ AuthenticationError,
3
+ AuthorizationError,
4
+ type AuthActor,
5
+ type AuthSession,
6
+ type AuthSessionGrant,
7
+ type AuthorizationPolicy,
8
+ type IdentityProvider,
9
+ type SessionProvider,
10
+ } from './contracts';
11
+
12
+ export type AuthModule<TCredential, TCapability extends string> = {
13
+ createSession(credential: TCredential): Promise<AuthSessionGrant>;
14
+ createAuthorizedSession(
15
+ credential: TCredential,
16
+ capability: TCapability,
17
+ ): Promise<AuthSessionGrant>;
18
+ getSession(token?: string | null): Promise<AuthSession | null>;
19
+ requireCapability(
20
+ token: string | null | undefined,
21
+ capability: TCapability,
22
+ ): Promise<AuthSession>;
23
+ revokeSession(token?: string | null): Promise<void>;
24
+ };
25
+
26
+ export function createAuthModule<TCredential, TCapability extends string>({
27
+ authorizationPolicy,
28
+ identityProvider,
29
+ sessionProvider,
30
+ }: {
31
+ authorizationPolicy: AuthorizationPolicy<TCapability>;
32
+ identityProvider: IdentityProvider<TCredential>;
33
+ sessionProvider: SessionProvider<TCredential>;
34
+ }): AuthModule<TCredential, TCapability> {
35
+ async function authenticate(credential: TCredential): Promise<AuthActor> {
36
+ return identityProvider.authenticate(credential);
37
+ }
38
+
39
+ async function authorize(
40
+ actor: AuthActor,
41
+ capability: TCapability,
42
+ ): Promise<void> {
43
+ if (!(await authorizationPolicy.allows(actor, capability))) {
44
+ throw new AuthorizationError();
45
+ }
46
+ }
47
+
48
+ return {
49
+ async createSession(credential) {
50
+ const actor = await authenticate(credential);
51
+ return sessionProvider.create(credential, actor);
52
+ },
53
+
54
+ async createAuthorizedSession(credential, capability) {
55
+ const actor = await authenticate(credential);
56
+ await authorize(actor, capability);
57
+ return sessionProvider.create(credential, actor);
58
+ },
59
+
60
+ async getSession(token) {
61
+ if (!token) return null;
62
+
63
+ try {
64
+ return await sessionProvider.get(token);
65
+ } catch {
66
+ return null;
67
+ }
68
+ },
69
+
70
+ async requireCapability(token, capability) {
71
+ if (!token) throw new AuthenticationError();
72
+
73
+ const session = await sessionProvider.get(token);
74
+ if (!session) throw new AuthenticationError();
75
+ await authorize(session.actor, capability);
76
+ return session;
77
+ },
78
+
79
+ async revokeSession(token) {
80
+ if (!token) return;
81
+ await sessionProvider.revoke(token);
82
+ },
83
+ };
84
+ }
85
+
86
+ export function requireClaim<TCapability extends string>(
87
+ claim: string,
88
+ expected: unknown,
89
+ capability?: TCapability,
90
+ ): AuthorizationPolicy<TCapability> {
91
+ return {
92
+ allows(actor: AuthActor, requestedCapability: TCapability) {
93
+ if (capability !== undefined && requestedCapability !== capability) {
94
+ return false;
95
+ }
96
+ return actor.claims[claim] === expected;
97
+ },
98
+ };
99
+ }
@@ -1,3 +1,6 @@
1
1
  export * from './clipboard';
2
2
  export * from './focus';
3
+ export * from './preferences';
3
4
  export * from './scroll';
5
+ export * from '../components/content/MarkdownContent/markdownContentRenderer';
6
+ export * from '../patterns/Table/tableClientController';