@notis_ai/cli 0.2.0-beta.102.1
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 +334 -0
- package/bin/notis.js +2 -0
- package/config/notis_app_boundary_rules.json +50 -0
- package/dist/scaffolds/notis-database/CHANGELOG.md +5 -0
- package/dist/scaffolds/notis-database/app/globals.css +44 -0
- package/dist/scaffolds/notis-database/app/layout.tsx +6 -0
- package/dist/scaffolds/notis-database/app/page.tsx +1091 -0
- package/dist/scaffolds/notis-database/components/ui/badge.tsx +28 -0
- package/dist/scaffolds/notis-database/components/ui/button.tsx +53 -0
- package/dist/scaffolds/notis-database/components/ui/card.tsx +56 -0
- package/dist/scaffolds/notis-database/components/ui/table.tsx +120 -0
- package/dist/scaffolds/notis-database/components.json +20 -0
- package/dist/scaffolds/notis-database/index.html +12 -0
- package/dist/scaffolds/notis-database/lib/types.ts +134 -0
- package/dist/scaffolds/notis-database/lib/utils.ts +6 -0
- package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-database/notis.config.ts +26 -0
- package/dist/scaffolds/notis-database/package.json +31 -0
- package/dist/scaffolds/notis-database/postcss.config.mjs +8 -0
- package/dist/scaffolds/notis-database/src/dev-main.tsx +23 -0
- package/dist/scaffolds/notis-database/src/mock-runtime.ts +558 -0
- package/dist/scaffolds/notis-database/tailwind.config.ts +59 -0
- package/dist/scaffolds/notis-database/tsconfig.json +23 -0
- package/dist/scaffolds/notis-database/vite.config.ts +22 -0
- package/dist/scaffolds/notis-journal/CHANGELOG.md +25 -0
- package/dist/scaffolds/notis-journal/app/globals.css +37 -0
- package/dist/scaffolds/notis-journal/app/insights/page.tsx +513 -0
- package/dist/scaffolds/notis-journal/app/journal-core.tsx +362 -0
- package/dist/scaffolds/notis-journal/app/journal-ui.tsx +337 -0
- package/dist/scaffolds/notis-journal/app/layout.tsx +6 -0
- package/dist/scaffolds/notis-journal/app/page.tsx +485 -0
- package/dist/scaffolds/notis-journal/components/ui/badge.tsx +28 -0
- package/dist/scaffolds/notis-journal/components/ui/button.tsx +53 -0
- package/dist/scaffolds/notis-journal/components/ui/card.tsx +56 -0
- package/dist/scaffolds/notis-journal/components.json +20 -0
- package/dist/scaffolds/notis-journal/index.html +12 -0
- package/dist/scaffolds/notis-journal/lib/utils.ts +6 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-3.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-4.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-5.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-6.png +0 -0
- package/dist/scaffolds/notis-journal/metadata/screenshot-fixtures.json +132 -0
- package/dist/scaffolds/notis-journal/notis.config.ts +93 -0
- package/dist/scaffolds/notis-journal/package.json +34 -0
- package/dist/scaffolds/notis-journal/packages/sdk/package.json +36 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/DocumentEditor.tsx +93 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/Markdown.tsx +60 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +145 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/documents.ts +229 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useBackend.ts +41 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocument.ts +78 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useDocuments.ts +121 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotis.ts +34 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTool.ts +64 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTools.ts +56 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/index.ts +83 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/provider.tsx +43 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/runtime.ts +220 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/styles.css +186 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/ui.ts +15 -0
- package/dist/scaffolds/notis-journal/packages/sdk/src/vite.ts +56 -0
- package/dist/scaffolds/notis-journal/packages/sdk/tsconfig.json +15 -0
- package/dist/scaffolds/notis-journal/postcss.config.mjs +8 -0
- package/dist/scaffolds/notis-journal/skills/journal-onboarding/SKILL.md +120 -0
- package/dist/scaffolds/notis-journal/src/dev-main.tsx +58 -0
- package/dist/scaffolds/notis-journal/src/mock-runtime.ts +197 -0
- package/dist/scaffolds/notis-journal/tailwind.config.ts +58 -0
- package/dist/scaffolds/notis-journal/tsconfig.json +23 -0
- package/dist/scaffolds/notis-journal/vite.config.ts +10 -0
- package/dist/scaffolds/notis-notes/CHANGELOG.md +5 -0
- package/dist/scaffolds/notis-notes/app/globals.css +3 -0
- package/dist/scaffolds/notis-notes/app/layout.tsx +6 -0
- package/dist/scaffolds/notis-notes/app/page.tsx +1950 -0
- package/dist/scaffolds/notis-notes/components/ui/badge.tsx +28 -0
- package/dist/scaffolds/notis-notes/components/ui/button.tsx +53 -0
- package/dist/scaffolds/notis-notes/components/ui/card.tsx +56 -0
- package/dist/scaffolds/notis-notes/components.json +20 -0
- package/dist/scaffolds/notis-notes/lib/utils.ts +6 -0
- package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-notes/notis.config.ts +35 -0
- package/dist/scaffolds/notis-notes/package.json +31 -0
- package/dist/scaffolds/notis-notes/postcss.config.mjs +8 -0
- package/dist/scaffolds/notis-notes/tailwind.config.ts +58 -0
- package/dist/scaffolds/notis-notes/tsconfig.json +23 -0
- package/dist/scaffolds/notis-notes/vite.config.ts +10 -0
- package/dist/scaffolds/notis-random/CHANGELOG.md +5 -0
- package/dist/scaffolds/notis-random/README.md +33 -0
- package/dist/scaffolds/notis-random/app/globals.css +11 -0
- package/dist/scaffolds/notis-random/app/history/page.tsx +66 -0
- package/dist/scaffolds/notis-random/app/layout.tsx +7 -0
- package/dist/scaffolds/notis-random/app/page.tsx +222 -0
- package/dist/scaffolds/notis-random/index.html +12 -0
- package/dist/scaffolds/notis-random/lib/notis-tools.ts +109 -0
- package/dist/scaffolds/notis-random/lib/rng.ts +42 -0
- package/dist/scaffolds/notis-random/lib/roll-record.ts +102 -0
- package/dist/scaffolds/notis-random/lib/utils.ts +25 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
- package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
- package/dist/scaffolds/notis-random/notis.config.ts +42 -0
- package/dist/scaffolds/notis-random/package.json +32 -0
- package/dist/scaffolds/notis-random/postcss.config.mjs +6 -0
- package/dist/scaffolds/notis-random/src/dev-main.tsx +70 -0
- package/dist/scaffolds/notis-random/src/mock-runtime.ts +129 -0
- package/dist/scaffolds/notis-random/tailwind.config.ts +43 -0
- package/dist/scaffolds/notis-random/tsconfig.json +23 -0
- package/dist/scaffolds/notis-random/vite.config.ts +11 -0
- package/dist/scaffolds.json +47 -0
- package/package.json +45 -0
- package/skills/notis-apps/SKILL.md +219 -0
- package/skills/notis-apps/cli.md +244 -0
- package/skills/notis-cli/SKILL.md +269 -0
- package/skills/notis-query/cli.md +39 -0
- package/src/cli.js +175 -0
- package/src/command-specs/apps.js +1939 -0
- package/src/command-specs/helpers.js +186 -0
- package/src/command-specs/index.js +14 -0
- package/src/command-specs/meta.js +161 -0
- package/src/command-specs/tools.js +499 -0
- package/src/runtime/agent-browser.js +464 -0
- package/src/runtime/app-boundary-validator.js +183 -0
- package/src/runtime/app-changelog.js +79 -0
- package/src/runtime/app-dev-server.js +712 -0
- package/src/runtime/app-dev-sessions.js +251 -0
- package/src/runtime/app-platform.js +1500 -0
- package/src/runtime/assets/store-screenshot-dark.png +0 -0
- package/src/runtime/cli-mode.generated.js +4 -0
- package/src/runtime/cli-mode.js +29 -0
- package/src/runtime/desktop-auth.js +93 -0
- package/src/runtime/errors.js +55 -0
- package/src/runtime/help.js +60 -0
- package/src/runtime/output.js +180 -0
- package/src/runtime/ports.js +15 -0
- package/src/runtime/profiles.js +261 -0
- package/src/runtime/store-screenshot.js +138 -0
- package/src/runtime/transport.js +347 -0
- package/template/.harness/index.html.tmpl +333 -0
- package/template/CHANGELOG.md +5 -0
- package/template/app/globals.css +3 -0
- package/template/app/layout.tsx +6 -0
- package/template/app/page.tsx +60 -0
- package/template/components/ui/badge.tsx +28 -0
- package/template/components/ui/button.tsx +53 -0
- package/template/components/ui/card.tsx +56 -0
- package/template/components.json +20 -0
- package/template/lib/utils.ts +6 -0
- package/template/metadata/screenshot-1.png +0 -0
- package/template/metadata/screenshot-2.png +0 -0
- package/template/metadata/screenshot-3.png +0 -0
- package/template/notis.config.ts +36 -0
- package/template/package-lock.json +4137 -0
- package/template/package.json +32 -0
- package/template/packages/sdk/package.json +36 -0
- package/template/packages/sdk/src/components/DocumentEditor.tsx +93 -0
- package/template/packages/sdk/src/components/Markdown.tsx +60 -0
- package/template/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
- package/template/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
- package/template/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
- package/template/packages/sdk/src/config.ts +145 -0
- package/template/packages/sdk/src/documents.ts +229 -0
- package/template/packages/sdk/src/hooks/useBackend.ts +41 -0
- package/template/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
- package/template/packages/sdk/src/hooks/useDocument.ts +78 -0
- package/template/packages/sdk/src/hooks/useDocuments.ts +121 -0
- package/template/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
- package/template/packages/sdk/src/hooks/useNotis.ts +34 -0
- package/template/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
- package/template/packages/sdk/src/hooks/useTool.ts +64 -0
- package/template/packages/sdk/src/hooks/useTools.ts +56 -0
- package/template/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
- package/template/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
- package/template/packages/sdk/src/index.ts +83 -0
- package/template/packages/sdk/src/provider.tsx +43 -0
- package/template/packages/sdk/src/runtime.ts +220 -0
- package/template/packages/sdk/src/styles.css +186 -0
- package/template/packages/sdk/src/ui.ts +15 -0
- package/template/packages/sdk/src/vite.ts +56 -0
- package/template/packages/sdk/tsconfig.json +15 -0
- package/template/postcss.config.mjs +8 -0
- package/template/tailwind.config.ts +58 -0
- package/template/tsconfig.json +22 -0
- package/template/vite.config.ts +10 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration utilities for notis.config.ts.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* ```ts
|
|
6
|
+
* // notis.config.ts
|
|
7
|
+
* import { defineNotisApp } from '@notis/sdk/config';
|
|
8
|
+
*
|
|
9
|
+
* export default defineNotisApp({
|
|
10
|
+
* name: 'My App',
|
|
11
|
+
* description: 'Does things',
|
|
12
|
+
* icon: 'phosphor:squares-four',
|
|
13
|
+
* databases: ['tasks'],
|
|
14
|
+
* routes: [
|
|
15
|
+
* {
|
|
16
|
+
* path: '/',
|
|
17
|
+
* slug: 'notes',
|
|
18
|
+
* name: 'Notes',
|
|
19
|
+
* default: true,
|
|
20
|
+
* collection: {
|
|
21
|
+
* database: 'notes',
|
|
22
|
+
* titleProperty: 'Name',
|
|
23
|
+
* parentProperty: 'Parent',
|
|
24
|
+
* sidebar: {
|
|
25
|
+
* mode: 'tree',
|
|
26
|
+
* allowCreate: true,
|
|
27
|
+
* },
|
|
28
|
+
* },
|
|
29
|
+
* },
|
|
30
|
+
* ],
|
|
31
|
+
* tools: [...],
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
export interface NotisRouteConfig {
|
|
37
|
+
path: string;
|
|
38
|
+
slug: string;
|
|
39
|
+
name: string;
|
|
40
|
+
icon?: string;
|
|
41
|
+
parentSlug?: string | null;
|
|
42
|
+
default?: boolean;
|
|
43
|
+
exportName?: string;
|
|
44
|
+
collection?: {
|
|
45
|
+
database: string;
|
|
46
|
+
titleProperty: string;
|
|
47
|
+
parentProperty?: string | null;
|
|
48
|
+
sidebar?: {
|
|
49
|
+
mode: 'flat-list' | 'tree';
|
|
50
|
+
allowCreate: boolean;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const NOTIS_APP_CATEGORIES = [
|
|
56
|
+
'Productivity',
|
|
57
|
+
'Sales & Marketing',
|
|
58
|
+
'Operations',
|
|
59
|
+
'Product & Engineering',
|
|
60
|
+
'Personal',
|
|
61
|
+
] as const;
|
|
62
|
+
|
|
63
|
+
export type NotisAppCategory = typeof NOTIS_APP_CATEGORIES[number];
|
|
64
|
+
|
|
65
|
+
export interface NotisAppAuthor {
|
|
66
|
+
name: string;
|
|
67
|
+
handle?: string;
|
|
68
|
+
url?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface NotisAppSkillConfig {
|
|
72
|
+
key: string;
|
|
73
|
+
path: string;
|
|
74
|
+
name: string;
|
|
75
|
+
description?: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface NotisAppOnboardingConfig {
|
|
79
|
+
skill: string;
|
|
80
|
+
prompt: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface NotisAppScreenshotConfig {
|
|
84
|
+
path: string;
|
|
85
|
+
alt: string;
|
|
86
|
+
route?: string;
|
|
87
|
+
scenario?: string;
|
|
88
|
+
focus?: string;
|
|
89
|
+
theme?: 'light' | 'dark';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Named accent tokens for an app's avatar. Keep in sync with the portal
|
|
94
|
+
* `ACCENT_NAMES` and the server `ACCENT_TOKENS`.
|
|
95
|
+
*/
|
|
96
|
+
export const NOTIS_APP_ACCENTS = [
|
|
97
|
+
'blue',
|
|
98
|
+
'violet',
|
|
99
|
+
'emerald',
|
|
100
|
+
'amber',
|
|
101
|
+
'rose',
|
|
102
|
+
'sky',
|
|
103
|
+
'fuchsia',
|
|
104
|
+
'teal',
|
|
105
|
+
] as const;
|
|
106
|
+
|
|
107
|
+
export type NotisAppAccent = typeof NOTIS_APP_ACCENTS[number];
|
|
108
|
+
|
|
109
|
+
export interface NotisAppConfig {
|
|
110
|
+
/** URL-safe app slug. Existing apps may still use a display name here. */
|
|
111
|
+
name: string;
|
|
112
|
+
/** Human display title, Raycast-style. Falls back to `name`. */
|
|
113
|
+
title?: string;
|
|
114
|
+
description?: string;
|
|
115
|
+
/**
|
|
116
|
+
* App icon. A `phosphor:<name>` value (e.g. `phosphor:dice-five`) or
|
|
117
|
+
* `metadata/icon.png`. When unset, the app shows its two-letter initials.
|
|
118
|
+
*/
|
|
119
|
+
icon?: string;
|
|
120
|
+
/**
|
|
121
|
+
* Optional accent color for the app avatar. One of {@link NOTIS_APP_ACCENTS}.
|
|
122
|
+
* When unset, a stable accent is derived automatically from the app id.
|
|
123
|
+
*/
|
|
124
|
+
accent?: NotisAppAccent;
|
|
125
|
+
author?: NotisAppAuthor;
|
|
126
|
+
categories?: NotisAppCategory[];
|
|
127
|
+
tagline?: string;
|
|
128
|
+
/** @deprecated Add release entries to the root CHANGELOG.md instead. */
|
|
129
|
+
versionNotes?: string;
|
|
130
|
+
screenshots?: NotisAppScreenshotConfig[];
|
|
131
|
+
databases?: string[];
|
|
132
|
+
routes?: NotisRouteConfig[];
|
|
133
|
+
tools?: string[];
|
|
134
|
+
skills?: NotisAppSkillConfig[];
|
|
135
|
+
onboarding?: NotisAppOnboardingConfig;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Identity function that provides type checking and autocomplete for the
|
|
140
|
+
* Notis app configuration. The returned object is read at build time by
|
|
141
|
+
* `notis apps build` to generate the manifest.
|
|
142
|
+
*/
|
|
143
|
+
export function defineNotisApp(config: NotisAppConfig): NotisAppConfig {
|
|
144
|
+
return config;
|
|
145
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document data helpers: normalize raw tool results (Notion-shaped property
|
|
3
|
+
* values, snake_case fields) into the SDK's `DocumentRecord`/`DatabaseProperty`
|
|
4
|
+
* shapes, and derive plain-text projections from typed content.
|
|
5
|
+
*
|
|
6
|
+
* These are pure functions — safe in any context (portal, dev harness, tests).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
DatabaseProperty,
|
|
11
|
+
DatabasePropertyOption,
|
|
12
|
+
DatabasePropertyType,
|
|
13
|
+
DocumentContentType,
|
|
14
|
+
DocumentRecord,
|
|
15
|
+
} from './runtime';
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Value primitives
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
export function isPresentString(value: unknown): value is string {
|
|
22
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function asRecord(value: unknown): Record<string, unknown> | null {
|
|
26
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
27
|
+
? (value as Record<string, unknown>)
|
|
28
|
+
: null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function optionalString(value: unknown): string | null {
|
|
32
|
+
return isPresentString(value) ? value.trim() : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Flattens a Notion-shaped rich_text array (or plain string) to text. */
|
|
36
|
+
export function extractRichText(value: unknown): string {
|
|
37
|
+
if (typeof value === 'string') return value;
|
|
38
|
+
if (!Array.isArray(value)) return '';
|
|
39
|
+
return value
|
|
40
|
+
.map((item) => {
|
|
41
|
+
const record = asRecord(item);
|
|
42
|
+
const text = asRecord(record?.text);
|
|
43
|
+
return optionalString(text?.content) ?? optionalString(record?.plain_text) ?? '';
|
|
44
|
+
})
|
|
45
|
+
.join('');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Extracts the ids of a normalized relation property value. */
|
|
49
|
+
export function getRelationIds(value: unknown): string[] {
|
|
50
|
+
if (!Array.isArray(value)) return [];
|
|
51
|
+
return value.filter((item): item is string => isPresentString(item));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// Property + document normalization
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Collapses a Notion-shaped property value ({type: 'select', select: {...}})
|
|
60
|
+
* into a plain JS value (string, string[], number, boolean, ...). Values that
|
|
61
|
+
* are already plain pass through unchanged.
|
|
62
|
+
*/
|
|
63
|
+
export function normalizePropertyValue(value: unknown): unknown {
|
|
64
|
+
const record = asRecord(value);
|
|
65
|
+
if (!record) return value;
|
|
66
|
+
|
|
67
|
+
const type = optionalString(record.type);
|
|
68
|
+
if (!type) return value;
|
|
69
|
+
|
|
70
|
+
if (type === 'title') return extractRichText(record.title);
|
|
71
|
+
if (type === 'rich_text') return extractRichText(record.rich_text);
|
|
72
|
+
if (type === 'select' || type === 'status') {
|
|
73
|
+
return optionalString(asRecord(record[type])?.name) ?? record[type] ?? null;
|
|
74
|
+
}
|
|
75
|
+
if (type === 'multi_select') {
|
|
76
|
+
const items = Array.isArray(record.multi_select) ? record.multi_select : [];
|
|
77
|
+
return items.map((item) => optionalString(asRecord(item)?.name) ?? item).filter(Boolean);
|
|
78
|
+
}
|
|
79
|
+
if (type === 'relation') {
|
|
80
|
+
const items = Array.isArray(record.relation) ? record.relation : [];
|
|
81
|
+
return items.map((item) => optionalString(asRecord(item)?.id) ?? item).filter(Boolean);
|
|
82
|
+
}
|
|
83
|
+
if (type === 'date') return optionalString(asRecord(record.date)?.start) ?? record.date ?? null;
|
|
84
|
+
if (type in record) return record[type];
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function normalizeContentType(value: unknown): DocumentContentType | null {
|
|
89
|
+
return value === 'markdown' || value === 'file' ? value : null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Normalizes a raw document from a Notis database tool result
|
|
94
|
+
* (LOCAL_NOTIS_DATABASE_QUERY / GET_DOCUMENT / UPSERT_*) into a
|
|
95
|
+
* `DocumentRecord`: camelCases fields and collapses property values.
|
|
96
|
+
*/
|
|
97
|
+
export function normalizeDocumentRecord(value: unknown): DocumentRecord {
|
|
98
|
+
const record = asRecord(value) ?? {};
|
|
99
|
+
const rawProperties = asRecord(record.properties) ?? {};
|
|
100
|
+
const properties: Record<string, unknown> = {};
|
|
101
|
+
for (const [key, propertyValue] of Object.entries(rawProperties)) {
|
|
102
|
+
properties[key] = normalizePropertyValue(propertyValue);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
id: optionalString(record.id) ?? '',
|
|
107
|
+
title: optionalString(record.title) ?? 'Untitled',
|
|
108
|
+
url: optionalString(record.url),
|
|
109
|
+
properties,
|
|
110
|
+
icon: optionalString(record.icon),
|
|
111
|
+
cover: optionalString(record.cover),
|
|
112
|
+
databaseSlug: optionalString(record.databaseSlug) ?? optionalString(record.database_slug) ?? undefined,
|
|
113
|
+
contentType: normalizeContentType(record.contentType ?? record.content_type),
|
|
114
|
+
fileType: optionalString(record.fileType) ?? optionalString(record.file_type),
|
|
115
|
+
contentBlocknote: Array.isArray(record.contentBlocknote)
|
|
116
|
+
? (record.contentBlocknote as Array<Record<string, unknown>>)
|
|
117
|
+
: Array.isArray(record.content_blocknote)
|
|
118
|
+
? (record.content_blocknote as Array<Record<string, unknown>>)
|
|
119
|
+
: null,
|
|
120
|
+
contentMarkdown: optionalString(record.contentMarkdown) ?? optionalString(record.content_markdown),
|
|
121
|
+
plainText: optionalString(record.plainText) ?? optionalString(record.plain_text),
|
|
122
|
+
createdAt:
|
|
123
|
+
optionalString(record.createdAt)
|
|
124
|
+
?? optionalString(record.created_at)
|
|
125
|
+
?? optionalString(record.created_time),
|
|
126
|
+
lastEditedTime:
|
|
127
|
+
optionalString(record.lastEditedTime)
|
|
128
|
+
?? optionalString(record.last_edited_time)
|
|
129
|
+
?? optionalString(record.updated_at),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Normalizes a raw schema property from LOCAL_NOTIS_DATABASE_GET_DATABASE. */
|
|
134
|
+
export function normalizeDatabaseProperty(value: unknown): DatabaseProperty | null {
|
|
135
|
+
const record = asRecord(value);
|
|
136
|
+
const name = optionalString(record?.name);
|
|
137
|
+
if (!record || !name) return null;
|
|
138
|
+
|
|
139
|
+
const rawOptions = Array.isArray(record.options) ? record.options : [];
|
|
140
|
+
const options = rawOptions.flatMap((option): DatabasePropertyOption[] => {
|
|
141
|
+
const optionRecord = asRecord(option);
|
|
142
|
+
const optionName = optionalString(optionRecord?.name);
|
|
143
|
+
if (!optionRecord || !optionName) return [];
|
|
144
|
+
return [{
|
|
145
|
+
id: optionalString(optionRecord.id),
|
|
146
|
+
name: optionName,
|
|
147
|
+
color: optionalString(optionRecord.color),
|
|
148
|
+
order: typeof optionRecord.order === 'number' ? optionRecord.order : undefined,
|
|
149
|
+
}];
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
id: optionalString(record.id),
|
|
154
|
+
name,
|
|
155
|
+
type: (optionalString(record.type) ?? 'rich_text') as DatabasePropertyType,
|
|
156
|
+
description: optionalString(record.description),
|
|
157
|
+
options,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Plain-text projections
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
/** Strips markdown syntax to readable plain text (previews, search). */
|
|
166
|
+
export function markdownToPlainText(markdown: string): string {
|
|
167
|
+
return markdown
|
|
168
|
+
// fenced code blocks: keep the code, drop the fences
|
|
169
|
+
.replace(/```[^\n]*\n?/g, '')
|
|
170
|
+
// images: keep alt text
|
|
171
|
+
.replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
172
|
+
// links: keep link text
|
|
173
|
+
.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
174
|
+
// html tags
|
|
175
|
+
.replace(/<[^>]+>/g, ' ')
|
|
176
|
+
// headings, blockquotes, list markers, task boxes
|
|
177
|
+
.replace(/^\s{0,3}(#{1,6}\s+|>\s?|[-*+]\s+(\[[ xX]\]\s+)?|\d+\.\s+)/gm, '')
|
|
178
|
+
// emphasis / strikethrough / inline code
|
|
179
|
+
.replace(/(\*\*|__|[*_~`])/g, '')
|
|
180
|
+
// table separators and pipes
|
|
181
|
+
.replace(/^\s*\|?[-:| ]+\|?\s*$/gm, '')
|
|
182
|
+
.replace(/\|/g, ' ')
|
|
183
|
+
// horizontal rules
|
|
184
|
+
.replace(/^\s*([-*_]\s*){3,}$/gm, '')
|
|
185
|
+
.replace(/\s+/g, ' ')
|
|
186
|
+
.trim();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Flattens BlockNote block JSON to plain text. */
|
|
190
|
+
export function blockNoteToPlainText(blocks: unknown): string {
|
|
191
|
+
const segments: string[] = [];
|
|
192
|
+
|
|
193
|
+
function visit(node: unknown): void {
|
|
194
|
+
if (typeof node === 'string') {
|
|
195
|
+
if (node.trim()) segments.push(node.trim());
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (Array.isArray(node)) {
|
|
199
|
+
node.forEach(visit);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const record = asRecord(node);
|
|
203
|
+
if (!record) return;
|
|
204
|
+
if (typeof record.text === 'string' && record.text.trim()) {
|
|
205
|
+
segments.push(record.text.trim());
|
|
206
|
+
}
|
|
207
|
+
visit(record.content);
|
|
208
|
+
visit(record.children);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
visit(blocks);
|
|
212
|
+
return segments.join(' ').replace(/\s+/g, ' ').trim();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Best-available plain-text preview for a document:
|
|
217
|
+
* plainText -> contentMarkdown -> contentBlocknote -> title.
|
|
218
|
+
*/
|
|
219
|
+
export function getDocumentPreview(document: DocumentRecord): string {
|
|
220
|
+
if (isPresentString(document.plainText)) {
|
|
221
|
+
return document.plainText.replace(/\s+/g, ' ').trim();
|
|
222
|
+
}
|
|
223
|
+
if (isPresentString(document.contentMarkdown)) {
|
|
224
|
+
return markdownToPlainText(document.contentMarkdown);
|
|
225
|
+
}
|
|
226
|
+
const fromBlocks = blockNoteToPlainText(document.contentBlocknote);
|
|
227
|
+
if (fromBlocks) return fromBlocks;
|
|
228
|
+
return isPresentString(document.title) ? document.title : 'Untitled';
|
|
229
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
|
|
6
|
+
interface UseBackendResult {
|
|
7
|
+
/**
|
|
8
|
+
* Make an authenticated request to the Notis backend.
|
|
9
|
+
* The path should be an absolute backend path (e.g. `/portal_composio/actions`).
|
|
10
|
+
*/
|
|
11
|
+
request: (path: string, options?: {
|
|
12
|
+
method?: string;
|
|
13
|
+
headers?: Record<string, string>;
|
|
14
|
+
body?: unknown;
|
|
15
|
+
}) => Promise<unknown>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Raw backend request proxy. Use this for custom server endpoints or
|
|
20
|
+
* integration APIs not covered by the typed hooks.
|
|
21
|
+
*
|
|
22
|
+
* ```tsx
|
|
23
|
+
* const { request } = useBackend();
|
|
24
|
+
* const data = await request('/portal_composio/actions', { method: 'POST', body: { ... } });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function useBackend(): UseBackendResult {
|
|
28
|
+
const runtime = useNotisRuntime();
|
|
29
|
+
|
|
30
|
+
const request = useCallback(
|
|
31
|
+
async (path: string, options?: { method?: string; headers?: Record<string, string>; body?: unknown }) => {
|
|
32
|
+
if (!runtime) {
|
|
33
|
+
throw new Error('Notis runtime not available. Ensure NotisProvider is mounted.');
|
|
34
|
+
}
|
|
35
|
+
return runtime.request(path, options);
|
|
36
|
+
},
|
|
37
|
+
[runtime],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return { request };
|
|
41
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
import { normalizeDatabaseProperty, optionalString } from '../documents';
|
|
6
|
+
import type { DatabaseProperty } from '../runtime';
|
|
7
|
+
|
|
8
|
+
interface GetDatabaseResult {
|
|
9
|
+
database?: {
|
|
10
|
+
name?: string;
|
|
11
|
+
description?: string | null;
|
|
12
|
+
schema?: {
|
|
13
|
+
properties?: unknown[];
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
message?: string;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface UseDatabaseSchemaResult {
|
|
21
|
+
name: string | null;
|
|
22
|
+
description: string | null;
|
|
23
|
+
properties: DatabaseProperty[];
|
|
24
|
+
loading: boolean;
|
|
25
|
+
error: Error | null;
|
|
26
|
+
refetch: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Fetch a database's schema (normalized properties + metadata). */
|
|
30
|
+
export function useDatabaseSchema(databaseSlug: string): UseDatabaseSchemaResult {
|
|
31
|
+
const runtime = useNotisRuntime();
|
|
32
|
+
const [name, setName] = useState<string | null>(null);
|
|
33
|
+
const [description, setDescription] = useState<string | null>(null);
|
|
34
|
+
const [properties, setProperties] = useState<DatabaseProperty[]>([]);
|
|
35
|
+
const [loading, setLoading] = useState(true);
|
|
36
|
+
const [error, setError] = useState<Error | null>(null);
|
|
37
|
+
const [fetchKey, setFetchKey] = useState(0);
|
|
38
|
+
|
|
39
|
+
const refetch = useCallback(() => {
|
|
40
|
+
setFetchKey((key) => key + 1);
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!runtime) {
|
|
45
|
+
setLoading(false);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let cancelled = false;
|
|
50
|
+
setLoading(true);
|
|
51
|
+
setError(null);
|
|
52
|
+
|
|
53
|
+
runtime
|
|
54
|
+
.callTool<GetDatabaseResult>('LOCAL_NOTIS_DATABASE_GET_DATABASE', {
|
|
55
|
+
database_slug: databaseSlug,
|
|
56
|
+
})
|
|
57
|
+
.then((result) => {
|
|
58
|
+
if (cancelled) return;
|
|
59
|
+
const message = result.error ?? result.message;
|
|
60
|
+
if (!result.database && message) {
|
|
61
|
+
throw new Error(message);
|
|
62
|
+
}
|
|
63
|
+
setName(optionalString(result.database?.name));
|
|
64
|
+
setDescription(optionalString(result.database?.description));
|
|
65
|
+
const rawProperties = result.database?.schema?.properties ?? [];
|
|
66
|
+
setProperties(
|
|
67
|
+
rawProperties
|
|
68
|
+
.map(normalizeDatabaseProperty)
|
|
69
|
+
.filter((property): property is DatabaseProperty => Boolean(property)),
|
|
70
|
+
);
|
|
71
|
+
setLoading(false);
|
|
72
|
+
})
|
|
73
|
+
.catch((err) => {
|
|
74
|
+
if (cancelled) return;
|
|
75
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
76
|
+
setLoading(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
cancelled = true;
|
|
81
|
+
};
|
|
82
|
+
}, [runtime, databaseSlug, fetchKey]);
|
|
83
|
+
|
|
84
|
+
return { name, description, properties, loading, error, refetch };
|
|
85
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
import { normalizeDocumentRecord } from '../documents';
|
|
6
|
+
import type { DocumentRecord } from '../runtime';
|
|
7
|
+
|
|
8
|
+
interface GetDocumentResult {
|
|
9
|
+
status?: string;
|
|
10
|
+
document?: unknown;
|
|
11
|
+
message?: string;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UseDocumentOptions {
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UseDocumentResult {
|
|
20
|
+
document: DocumentRecord | null;
|
|
21
|
+
loading: boolean;
|
|
22
|
+
error: Error | null;
|
|
23
|
+
refetch: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Fetch a single document (with content) by id, normalized. */
|
|
27
|
+
export function useDocument(
|
|
28
|
+
documentId: string | null | undefined,
|
|
29
|
+
options: UseDocumentOptions = {},
|
|
30
|
+
): UseDocumentResult {
|
|
31
|
+
const runtime = useNotisRuntime();
|
|
32
|
+
const [document, setDocument] = useState<DocumentRecord | null>(null);
|
|
33
|
+
const [loading, setLoading] = useState(Boolean(documentId));
|
|
34
|
+
const [error, setError] = useState<Error | null>(null);
|
|
35
|
+
const [fetchKey, setFetchKey] = useState(0);
|
|
36
|
+
|
|
37
|
+
const enabled = options.enabled !== false && Boolean(documentId);
|
|
38
|
+
|
|
39
|
+
const refetch = useCallback(() => {
|
|
40
|
+
setFetchKey((key) => key + 1);
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!runtime || !enabled || !documentId) {
|
|
45
|
+
setDocument(null);
|
|
46
|
+
setLoading(false);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let cancelled = false;
|
|
51
|
+
setLoading(true);
|
|
52
|
+
setError(null);
|
|
53
|
+
|
|
54
|
+
runtime
|
|
55
|
+
.callTool<GetDocumentResult>('LOCAL_NOTIS_DATABASE_GET_DOCUMENT', {
|
|
56
|
+
document_id: documentId,
|
|
57
|
+
})
|
|
58
|
+
.then((result) => {
|
|
59
|
+
if (cancelled) return;
|
|
60
|
+
if (!result.document) {
|
|
61
|
+
throw new Error(result.error ?? result.message ?? 'Document not found');
|
|
62
|
+
}
|
|
63
|
+
setDocument(normalizeDocumentRecord(result.document));
|
|
64
|
+
setLoading(false);
|
|
65
|
+
})
|
|
66
|
+
.catch((err) => {
|
|
67
|
+
if (cancelled) return;
|
|
68
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
69
|
+
setLoading(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return () => {
|
|
73
|
+
cancelled = true;
|
|
74
|
+
};
|
|
75
|
+
}, [runtime, documentId, enabled, fetchKey]);
|
|
76
|
+
|
|
77
|
+
return { document, loading, error, refetch };
|
|
78
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { useNotisRuntime } from '../provider';
|
|
5
|
+
import { normalizeDocumentRecord } from '../documents';
|
|
6
|
+
import type { DocumentRecord, QueryFilter } from '../runtime';
|
|
7
|
+
|
|
8
|
+
interface QueryDatabaseResult {
|
|
9
|
+
documents?: unknown[];
|
|
10
|
+
has_more?: boolean;
|
|
11
|
+
next_offset?: number | null;
|
|
12
|
+
message?: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface UseDocumentsOptions {
|
|
17
|
+
filter?: QueryFilter;
|
|
18
|
+
pageSize?: number;
|
|
19
|
+
offset?: number;
|
|
20
|
+
/** Fetch every page, starting at offset, instead of returning only one page. */
|
|
21
|
+
fetchAll?: boolean;
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface UseDocumentsResult {
|
|
26
|
+
documents: DocumentRecord[];
|
|
27
|
+
loading: boolean;
|
|
28
|
+
error: Error | null;
|
|
29
|
+
refetch: () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Query a Notis database and get normalized `DocumentRecord`s.
|
|
34
|
+
*
|
|
35
|
+
* ```tsx
|
|
36
|
+
* const { documents, loading, refetch } = useDocuments('notes', {
|
|
37
|
+
* filter: { filters: [{ property: 'Folder', operator: 'contains', type: 'relation', value: folderId }] },
|
|
38
|
+
* pageSize: 250,
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function useDocuments(
|
|
43
|
+
databaseSlug: string,
|
|
44
|
+
options: UseDocumentsOptions = {},
|
|
45
|
+
): UseDocumentsResult {
|
|
46
|
+
const runtime = useNotisRuntime();
|
|
47
|
+
const [documents, setDocuments] = useState<DocumentRecord[]>([]);
|
|
48
|
+
const [loading, setLoading] = useState(true);
|
|
49
|
+
const [error, setError] = useState<Error | null>(null);
|
|
50
|
+
const [fetchKey, setFetchKey] = useState(0);
|
|
51
|
+
|
|
52
|
+
const enabled = options.enabled !== false;
|
|
53
|
+
const filterKey = JSON.stringify(options.filter ?? null);
|
|
54
|
+
|
|
55
|
+
const refetch = useCallback(() => {
|
|
56
|
+
setFetchKey((key) => key + 1);
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (!runtime || !enabled) {
|
|
61
|
+
setLoading(false);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let cancelled = false;
|
|
66
|
+
setLoading(true);
|
|
67
|
+
setError(null);
|
|
68
|
+
|
|
69
|
+
const filter = filterKey === 'null' ? null : (JSON.parse(filterKey) as QueryFilter);
|
|
70
|
+
|
|
71
|
+
const fetchDocuments = async (): Promise<unknown[]> => {
|
|
72
|
+
const allDocuments: unknown[] = [];
|
|
73
|
+
let offset = options.offset ?? 0;
|
|
74
|
+
|
|
75
|
+
while (true) {
|
|
76
|
+
const result = await runtime.callTool<QueryDatabaseResult>('LOCAL_NOTIS_DATABASE_QUERY', {
|
|
77
|
+
database_slug: databaseSlug,
|
|
78
|
+
query: {
|
|
79
|
+
...(filter ?? {}),
|
|
80
|
+
...(options.pageSize !== undefined ? { page_size: options.pageSize } : {}),
|
|
81
|
+
},
|
|
82
|
+
...(offset > 0 ? { offset } : {}),
|
|
83
|
+
});
|
|
84
|
+
const message = result.error ?? result.message;
|
|
85
|
+
if (!result.documents && message) {
|
|
86
|
+
throw new Error(message);
|
|
87
|
+
}
|
|
88
|
+
allDocuments.push(...(result.documents ?? []));
|
|
89
|
+
if (!options.fetchAll || !result.has_more) return allDocuments;
|
|
90
|
+
|
|
91
|
+
const nextOffset = result.next_offset;
|
|
92
|
+
if (typeof nextOffset !== 'number' || nextOffset <= offset) {
|
|
93
|
+
throw new Error('Database query returned an invalid pagination offset');
|
|
94
|
+
}
|
|
95
|
+
offset = nextOffset;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
fetchDocuments()
|
|
100
|
+
.then((result) => {
|
|
101
|
+
if (cancelled) return;
|
|
102
|
+
setDocuments(
|
|
103
|
+
result
|
|
104
|
+
.map(normalizeDocumentRecord)
|
|
105
|
+
.filter((document) => document.id),
|
|
106
|
+
);
|
|
107
|
+
setLoading(false);
|
|
108
|
+
})
|
|
109
|
+
.catch((err) => {
|
|
110
|
+
if (cancelled) return;
|
|
111
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
112
|
+
setLoading(false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return () => {
|
|
116
|
+
cancelled = true;
|
|
117
|
+
};
|
|
118
|
+
}, [runtime, databaseSlug, enabled, fetchKey, filterKey, options.fetchAll, options.offset, options.pageSize]);
|
|
119
|
+
|
|
120
|
+
return { documents, loading, error, refetch };
|
|
121
|
+
}
|