@intelligo-dev/core 1.0.0-beta.1 → 1.0.0-beta.14
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/NOTICE +6 -0
- package/README.md +102 -0
- package/dist/attachments/errors.d.ts +7 -0
- package/dist/attachments/errors.d.ts.map +1 -0
- package/dist/attachments/errors.js +11 -0
- package/dist/attachments/errors.js.map +1 -0
- package/dist/attachments/index.d.ts +8 -0
- package/dist/attachments/index.d.ts.map +1 -0
- package/dist/attachments/index.js +7 -0
- package/dist/attachments/index.js.map +1 -0
- package/dist/attachments/service.d.ts +45 -0
- package/dist/attachments/service.d.ts.map +1 -0
- package/dist/attachments/service.js +100 -0
- package/dist/attachments/service.js.map +1 -0
- package/dist/attachments/types.d.ts +6 -0
- package/dist/attachments/types.d.ts.map +1 -0
- package/dist/attachments/types.js +2 -0
- package/dist/attachments/types.js.map +1 -0
- package/dist/conversations/errors.d.ts +2 -8
- package/dist/conversations/errors.d.ts.map +1 -1
- package/dist/conversations/errors.js +2 -8
- package/dist/conversations/errors.js.map +1 -1
- package/dist/conversations/index.d.ts +4 -12
- package/dist/conversations/index.d.ts.map +1 -1
- package/dist/conversations/index.js +2 -11
- package/dist/conversations/index.js.map +1 -1
- package/dist/conversations/service.d.ts +49 -61
- package/dist/conversations/service.d.ts.map +1 -1
- package/dist/conversations/service.js +110 -83
- package/dist/conversations/service.js.map +1 -1
- package/dist/conversations/types.d.ts +1 -4
- package/dist/conversations/types.d.ts.map +1 -1
- package/dist/conversations/types.js +0 -3
- package/dist/conversations/types.js.map +1 -1
- package/dist/db/client.d.ts +21 -10
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +43 -18
- package/dist/db/client.js.map +1 -1
- package/dist/db/index.d.ts +3 -3
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +3 -7
- package/dist/db/index.js.map +1 -1
- package/dist/db/schema/agents.d.ts +14 -51
- package/dist/db/schema/agents.d.ts.map +1 -1
- package/dist/db/schema/agents.js +9 -21
- package/dist/db/schema/agents.js.map +1 -1
- package/dist/db/schema/ai.d.ts +86 -1556
- package/dist/db/schema/ai.d.ts.map +1 -1
- package/dist/db/schema/ai.js +32 -236
- package/dist/db/schema/ai.js.map +1 -1
- package/dist/db/schema/auth.d.ts +5 -34
- package/dist/db/schema/auth.d.ts.map +1 -1
- package/dist/db/schema/auth.js +25 -56
- package/dist/db/schema/auth.js.map +1 -1
- package/dist/db/schema/billing.d.ts +339 -105
- package/dist/db/schema/billing.d.ts.map +1 -1
- package/dist/db/schema/billing.js +99 -58
- package/dist/db/schema/billing.js.map +1 -1
- package/dist/db/schema/feature-flags.d.ts +3 -12
- package/dist/db/schema/feature-flags.d.ts.map +1 -1
- package/dist/db/schema/feature-flags.js +3 -12
- package/dist/db/schema/feature-flags.js.map +1 -1
- package/dist/db/schema/identity.d.ts +12 -274
- package/dist/db/schema/identity.d.ts.map +1 -1
- package/dist/db/schema/identity.js +19 -104
- package/dist/db/schema/identity.js.map +1 -1
- package/dist/db/schema/index.d.ts +8 -15
- package/dist/db/schema/index.d.ts.map +1 -1
- package/dist/db/schema/index.js +12 -42
- package/dist/db/schema/index.js.map +1 -1
- package/dist/db/schema/notifications.d.ts +3 -17
- package/dist/db/schema/notifications.d.ts.map +1 -1
- package/dist/db/schema/notifications.js +3 -17
- package/dist/db/schema/notifications.js.map +1 -1
- package/dist/db/schema/usage.d.ts +94 -77
- package/dist/db/schema/usage.d.ts.map +1 -1
- package/dist/db/schema/usage.js +59 -89
- package/dist/db/schema/usage.js.map +1 -1
- package/dist/db/validate.d.ts +2 -11
- package/dist/db/validate.d.ts.map +1 -1
- package/dist/db/validate.js +6 -17
- package/dist/db/validate.js.map +1 -1
- package/dist/db/workspace-queries.d.ts +5 -29
- package/dist/db/workspace-queries.d.ts.map +1 -1
- package/dist/db/workspace-queries.js +5 -29
- package/dist/db/workspace-queries.js.map +1 -1
- package/dist/documents/classifier.d.ts +5 -12
- package/dist/documents/classifier.d.ts.map +1 -1
- package/dist/documents/classifier.js +16 -16
- package/dist/documents/classifier.js.map +1 -1
- package/dist/documents/errors.d.ts +2 -10
- package/dist/documents/errors.d.ts.map +1 -1
- package/dist/documents/errors.js +2 -10
- package/dist/documents/errors.js.map +1 -1
- package/dist/documents/index.d.ts +4 -13
- package/dist/documents/index.d.ts.map +1 -1
- package/dist/documents/index.js +3 -12
- package/dist/documents/index.js.map +1 -1
- package/dist/documents/service.d.ts +19 -26
- package/dist/documents/service.d.ts.map +1 -1
- package/dist/documents/service.js +44 -38
- package/dist/documents/service.js.map +1 -1
- package/dist/documents/types.d.ts +6 -10
- package/dist/documents/types.d.ts.map +1 -1
- package/dist/documents/types.js +0 -3
- package/dist/documents/types.js.map +1 -1
- package/dist/email/brand.d.ts +17 -0
- package/dist/email/brand.d.ts.map +1 -0
- package/dist/email/brand.js +9 -0
- package/dist/email/brand.js.map +1 -0
- package/dist/email/index.d.ts +4 -4
- package/dist/email/index.js +4 -4
- package/dist/email/provider.d.ts +1 -7
- package/dist/email/provider.d.ts.map +1 -1
- package/dist/email/provider.js +10 -19
- package/dist/email/provider.js.map +1 -1
- package/dist/email/send.d.ts +12 -12
- package/dist/email/send.d.ts.map +1 -1
- package/dist/email/send.js +5 -19
- package/dist/email/send.js.map +1 -1
- package/dist/email/senders.d.ts +17 -30
- package/dist/email/senders.d.ts.map +1 -1
- package/dist/email/senders.js +33 -47
- package/dist/email/senders.js.map +1 -1
- package/dist/email/templates/base-layout.d.ts +1 -1
- package/dist/email/templates/base-layout.d.ts.map +1 -1
- package/dist/email/templates/base-layout.js +4 -6
- package/dist/email/templates/base-layout.js.map +1 -1
- package/dist/email/templates/index.d.ts +10 -10
- package/dist/email/templates/index.d.ts.map +1 -1
- package/dist/email/templates/index.js +10 -16
- package/dist/email/templates/index.js.map +1 -1
- package/dist/email/templates/password-reset.d.ts +2 -2
- package/dist/email/templates/password-reset.d.ts.map +1 -1
- package/dist/email/templates/password-reset.js +10 -22
- package/dist/email/templates/password-reset.js.map +1 -1
- package/dist/email/templates/payment-failed.d.ts +2 -2
- package/dist/email/templates/payment-failed.d.ts.map +1 -1
- package/dist/email/templates/payment-failed.js +11 -24
- package/dist/email/templates/payment-failed.js.map +1 -1
- package/dist/email/templates/quota-warning.d.ts +4 -4
- package/dist/email/templates/quota-warning.d.ts.map +1 -1
- package/dist/email/templates/quota-warning.js +19 -39
- package/dist/email/templates/quota-warning.js.map +1 -1
- package/dist/email/templates/subscription-confirmed.d.ts +2 -2
- package/dist/email/templates/subscription-confirmed.d.ts.map +1 -1
- package/dist/email/templates/subscription-confirmed.js +13 -28
- package/dist/email/templates/subscription-confirmed.js.map +1 -1
- package/dist/email/templates/trial-expiry.d.ts +2 -2
- package/dist/email/templates/trial-expiry.d.ts.map +1 -1
- package/dist/email/templates/trial-expiry.js +12 -36
- package/dist/email/templates/trial-expiry.js.map +1 -1
- package/dist/email/templates/trial-warning.d.ts +2 -2
- package/dist/email/templates/trial-warning.d.ts.map +1 -1
- package/dist/email/templates/trial-warning.js +15 -32
- package/dist/email/templates/trial-warning.js.map +1 -1
- package/dist/email/templates/verify-email.d.ts +2 -2
- package/dist/email/templates/verify-email.d.ts.map +1 -1
- package/dist/email/templates/verify-email.js +10 -22
- package/dist/email/templates/verify-email.js.map +1 -1
- package/dist/email/templates/welcome.d.ts +2 -2
- package/dist/email/templates/welcome.d.ts.map +1 -1
- package/dist/email/templates/welcome.js +13 -20
- package/dist/email/templates/welcome.js.map +1 -1
- package/dist/email/templates/workspace-invitation.d.ts +2 -2
- package/dist/email/templates/workspace-invitation.d.ts.map +1 -1
- package/dist/email/templates/workspace-invitation.js +12 -26
- package/dist/email/templates/workspace-invitation.js.map +1 -1
- package/dist/env.d.ts +5 -10
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +41 -31
- package/dist/env.js.map +1 -1
- package/dist/identity/audit.d.ts +11 -24
- package/dist/identity/audit.d.ts.map +1 -1
- package/dist/identity/audit.js +13 -27
- package/dist/identity/audit.js.map +1 -1
- package/dist/identity/errors.d.ts +4 -12
- package/dist/identity/errors.d.ts.map +1 -1
- package/dist/identity/errors.js +2 -8
- package/dist/identity/errors.js.map +1 -1
- package/dist/identity/index.d.ts +6 -13
- package/dist/identity/index.d.ts.map +1 -1
- package/dist/identity/index.js +5 -12
- package/dist/identity/index.js.map +1 -1
- package/dist/identity/service.d.ts +29 -47
- package/dist/identity/service.d.ts.map +1 -1
- package/dist/identity/service.js +107 -79
- package/dist/identity/service.js.map +1 -1
- package/dist/identity/types.d.ts +28 -4
- package/dist/identity/types.d.ts.map +1 -1
- package/dist/identity/types.js +0 -3
- package/dist/identity/types.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -10
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +7 -18
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +45 -49
- package/dist/logger.js.map +1 -1
- package/dist/money.d.ts +95 -0
- package/dist/money.d.ts.map +1 -0
- package/dist/money.js +208 -0
- package/dist/money.js.map +1 -0
- package/dist/notifications/index.d.ts +9 -43
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +9 -49
- package/dist/notifications/index.js.map +1 -1
- package/dist/notifications/triggers.d.ts +9 -32
- package/dist/notifications/triggers.d.ts.map +1 -1
- package/dist/notifications/triggers.js +21 -58
- package/dist/notifications/triggers.js.map +1 -1
- package/dist/notifications/types.d.ts +6 -8
- package/dist/notifications/types.d.ts.map +1 -1
- package/dist/notifications/types.js +0 -9
- package/dist/notifications/types.js.map +1 -1
- package/dist/prompt.d.ts +38 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +118 -0
- package/dist/prompt.js.map +1 -0
- package/dist/registry.d.ts +32 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +60 -0
- package/dist/registry.js.map +1 -0
- package/dist/request-context.d.ts +49 -0
- package/dist/request-context.d.ts.map +1 -0
- package/dist/request-context.js +100 -0
- package/dist/request-context.js.map +1 -0
- package/dist/storage/index.d.ts +65 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +109 -0
- package/dist/storage/index.js.map +1 -0
- package/package.json +74 -24
- package/src/attachments/errors.ts +18 -0
- package/src/attachments/index.ts +22 -0
- package/src/attachments/service.ts +178 -0
- package/src/attachments/types.ts +6 -0
- package/src/conversations/errors.ts +45 -0
- package/src/conversations/index.ts +39 -0
- package/src/conversations/service.ts +594 -0
- package/src/conversations/types.ts +15 -0
- package/src/db/client.ts +91 -0
- package/src/db/index.ts +3 -0
- package/src/db/migrations/0000_baseline.sql +652 -0
- package/src/db/migrations/0001_reconcile_chain_built_databases.sql +53 -0
- package/src/db/migrations/0002_webhook_claim_lease.sql +1 -0
- package/src/db/migrations/0003_local_payments.sql +18 -0
- package/src/db/migrations/README.md +103 -36
- package/src/db/migrations/legacy-chain.json +206 -0
- package/src/db/migrations/meta/0000_snapshot.json +4182 -157
- package/src/db/migrations/meta/0002_snapshot.json +2478 -368
- package/src/db/migrations/meta/0003_snapshot.json +2567 -554
- package/src/db/migrations/meta/_journal.json +8 -274
- package/src/db/schema/agents.ts +57 -0
- package/src/db/schema/ai.ts +174 -0
- package/src/db/schema/auth.ts +183 -0
- package/src/db/schema/billing.ts +245 -0
- package/src/db/schema/feature-flags.ts +20 -0
- package/src/db/schema/identity.ts +250 -0
- package/src/db/schema/index.ts +12 -0
- package/src/db/schema/notifications.ts +31 -0
- package/src/db/schema/usage.ts +269 -0
- package/src/db/validate.ts +63 -0
- package/src/db/workspace-queries.ts +64 -0
- package/src/documents/classifier.ts +61 -0
- package/src/documents/errors.ts +44 -0
- package/src/documents/index.ts +24 -0
- package/src/documents/service.ts +256 -0
- package/src/documents/types.ts +24 -0
- package/src/email/brand.ts +24 -0
- package/src/email/index.ts +19 -0
- package/src/email/provider.ts +278 -0
- package/src/email/send.ts +169 -0
- package/src/email/senders.ts +292 -0
- package/src/email/templates/base-layout.tsx +145 -0
- package/src/email/templates/index.ts +28 -0
- package/src/email/templates/password-reset.tsx +72 -0
- package/src/email/templates/payment-failed.tsx +77 -0
- package/src/email/templates/quota-warning.tsx +118 -0
- package/src/email/templates/subscription-confirmed.tsx +91 -0
- package/src/email/templates/trial-expiry.tsx +124 -0
- package/src/email/templates/trial-warning.tsx +104 -0
- package/src/email/templates/verify-email.tsx +72 -0
- package/src/email/templates/welcome.tsx +64 -0
- package/src/email/templates/workspace-invitation.tsx +105 -0
- package/src/env.ts +180 -0
- package/src/identity/audit.ts +65 -0
- package/src/identity/errors.ts +44 -0
- package/src/identity/index.ts +29 -0
- package/src/identity/service.ts +303 -0
- package/src/identity/types.ts +58 -0
- package/src/index.ts +4 -0
- package/src/logger.ts +198 -0
- package/src/money.ts +270 -0
- package/src/notifications/index.ts +112 -0
- package/src/notifications/triggers.ts +158 -0
- package/src/notifications/types.ts +26 -0
- package/src/prompt.ts +142 -0
- package/src/registry.ts +75 -0
- package/src/request-context.ts +137 -0
- package/src/storage/index.ts +153 -0
- package/dist/db/schema/rag.d.ts +0 -171
- package/dist/db/schema/rag.d.ts.map +0 -1
- package/dist/db/schema/rag.js +0 -42
- package/dist/db/schema/rag.js.map +0 -1
- package/src/db/migrations/0000_sharp_night_nurse.sql +0 -52
- package/src/db/migrations/0001_hesitant_vapor.sql +0 -40
- package/src/db/migrations/0002_numerous_lady_vermin.sql +0 -235
- package/src/db/migrations/0003_mighty_jean_grey.sql +0 -37
- package/src/db/migrations/0004_cynical_grandmaster.sql +0 -2
- package/src/db/migrations/0005_careful_switch.sql +0 -8
- package/src/db/migrations/0006_tense_blue_marvel.sql +0 -16
- package/src/db/migrations/0007_next_squadron_sinister.sql +0 -1
- package/src/db/migrations/0008_powerful_scream.sql +0 -39
- package/src/db/migrations/0009_sad_ink.sql +0 -1
- package/src/db/migrations/0010_shocking_omega_flight.sql +0 -2
- package/src/db/migrations/0011_ambiguous_otto_octavius.sql +0 -151
- package/src/db/migrations/0011_hnsw_vector_index.sql +0 -17
- package/src/db/migrations/0012_user_profiles.sql +0 -30
- package/src/db/migrations/0013_trending_seasonal_columns.sql +0 -9
- package/src/db/migrations/0014_add_product_type_to_agents.sql +0 -6
- package/src/db/migrations/0015_new_v1_tables.sql +0 -149
- package/src/db/migrations/0016_document_types.sql +0 -30
- package/src/db/migrations/0017_document_metadata.sql +0 -2
- package/src/db/migrations/0018_identity_graph.sql +0 -200
- package/src/db/migrations/0019_db_driven_prompts.sql +0 -20
- package/src/db/migrations/0020_pending_extractions.sql +0 -35
- package/src/db/migrations/0021_rag_documents.sql +0 -39
- package/src/db/migrations/0022_user_quotas_usage_jsonb.sql +0 -70
- package/src/db/migrations/0023_user_facts_user_scoped_unique.sql +0 -17
- package/src/db/migrations/0024_user_memories_hnsw.sql +0 -23
- package/src/db/migrations/0025_audit_trigger_errcode.sql +0 -21
- package/src/db/migrations/0026_user_profile_snapshots_composite_pk.sql +0 -17
- package/src/db/migrations/0027_billing_settings_mnt_columns.sql +0 -54
- package/src/db/migrations/0028_trial_credits_normalized_email.sql +0 -37
- package/src/db/migrations/0029_usage_records_conversation_idx.sql +0 -13
- package/src/db/migrations/0030_pending_extractions_composite_idx.sql +0 -11
- package/src/db/migrations/0031_rag_documents_hnsw.sql +0 -16
- package/src/db/migrations/0032_rate_limit_unique_idx.sql +0 -16
- package/src/db/migrations/0033_pending_extractions_partial_idx.sql +0 -18
- package/src/db/migrations/0034_rate_limit_count.sql +0 -9
- package/src/db/migrations/0035_credit_reservations.sql +0 -24
- package/src/db/migrations/0036_executions_audit_jobs.sql +0 -96
- package/src/db/migrations/0037_rate_limit_minute_bucket.sql +0 -46
- package/src/db/migrations/0038_user_quotas_generic_usage.sql +0 -25
- package/src/db/migrations/0039_platform_admin_impersonation.sql +0 -27
- package/src/db/migrations/0040_ee_audit_chain.sql +0 -23
- package/src/db/migrations/meta/0001_snapshot.json +0 -631
- package/src/db/migrations/meta/0004_snapshot.json +0 -2500
- package/src/db/migrations/meta/0005_snapshot.json +0 -2548
- package/src/db/migrations/meta/0006_snapshot.json +0 -2650
- package/src/db/migrations/meta/0007_snapshot.json +0 -2657
- package/src/db/migrations/meta/0008_snapshot.json +0 -2719
- package/src/db/migrations/meta/0009_snapshot.json +0 -2726
- package/src/db/migrations/meta/0010_snapshot.json +0 -2739
- package/src/db/migrations/meta/0011_snapshot.json +0 -3886
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document title classifier. Products register their title patterns here;
|
|
3
|
+
* the documents service uses them to derive agent labels and the "report"
|
|
4
|
+
* filter. Kept free of the db import so client code and tests can load it.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createRegistry } from "../registry";
|
|
8
|
+
|
|
9
|
+
export type DocumentPatternEntry = {
|
|
10
|
+
/** Stable product slug — same one used across product configs. */
|
|
11
|
+
productSlug: string;
|
|
12
|
+
/** Human-readable agent label rendered in the documents UI. */
|
|
13
|
+
agentLabel: string;
|
|
14
|
+
/** Case-insensitive substring patterns; a document matches when its
|
|
15
|
+
* title contains ANY of these, whatever the case of either. */
|
|
16
|
+
patterns: string[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Keyed by product slug rather than appended, so a composition root that
|
|
21
|
+
* runs twice (or two copies of it) replaces the entry instead of matching
|
|
22
|
+
* the same patterns twice. Insertion order decides first-match
|
|
23
|
+
* classification.
|
|
24
|
+
*/
|
|
25
|
+
const registry = createRegistry<DocumentPatternEntry>("core/document-patterns");
|
|
26
|
+
|
|
27
|
+
export function registerDocumentPatterns(entry: DocumentPatternEntry): void {
|
|
28
|
+
registry.set(entry.productSlug, entry);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function classifyDocumentTitle(title: string): {
|
|
32
|
+
productSlug: string | null;
|
|
33
|
+
agentLabel: string;
|
|
34
|
+
} {
|
|
35
|
+
const lower = title.toLowerCase();
|
|
36
|
+
for (const entry of registry.values()) {
|
|
37
|
+
if (entry.patterns.some((p) => lower.includes(p.toLowerCase()))) {
|
|
38
|
+
return { productSlug: entry.productSlug, agentLabel: entry.agentLabel };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return { productSlug: null, agentLabel: "AI Assistant" };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Returns true when the document looks like it belongs to any registered product. */
|
|
45
|
+
export function isProductDocument(
|
|
46
|
+
title: string,
|
|
47
|
+
productSlug?: string
|
|
48
|
+
): boolean {
|
|
49
|
+
const { productSlug: matched } = classifyDocumentTitle(title);
|
|
50
|
+
if (!matched) return false;
|
|
51
|
+
if (productSlug) return matched === productSlug;
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function listRegisteredDocumentPatterns(): readonly DocumentPatternEntry[] {
|
|
56
|
+
return [...registry.values()];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function clearDocumentPatternRegistry(): void {
|
|
60
|
+
registry.clear();
|
|
61
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown by the documents service for every failure it recognizes. Shaping
|
|
3
|
+
* it into a transport envelope is the caller's job.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* - `not_found` — no document (or no version of it) exists at the
|
|
8
|
+
* given id within the actor's workspace/user scope.
|
|
9
|
+
* - `forbidden` — a document with this id exists but is owned by a
|
|
10
|
+
* different user within the same workspace.
|
|
11
|
+
* - `invalid_input` — a caller-supplied argument failed a basic
|
|
12
|
+
* invariant (e.g. an unparsable or out-of-window delete timestamp).
|
|
13
|
+
* - `database_error` — the underlying insert/update did not return the
|
|
14
|
+
* row it was expected to.
|
|
15
|
+
*/
|
|
16
|
+
export type DocumentServiceErrorCode =
|
|
17
|
+
"not_found" | "forbidden" | "invalid_input" | "database_error";
|
|
18
|
+
|
|
19
|
+
export class DocumentServiceError extends Error {
|
|
20
|
+
readonly code: DocumentServiceErrorCode;
|
|
21
|
+
// The ES2020 target predates `Error.cause`; declared so `err.cause`
|
|
22
|
+
// type-checks for callers.
|
|
23
|
+
readonly cause?: unknown;
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
code: DocumentServiceErrorCode,
|
|
27
|
+
message: string,
|
|
28
|
+
options?: { cause?: unknown }
|
|
29
|
+
) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = "DocumentServiceError";
|
|
32
|
+
this.code = code;
|
|
33
|
+
if (options?.cause !== undefined) {
|
|
34
|
+
(this as { cause?: unknown }).cause = options.cause;
|
|
35
|
+
}
|
|
36
|
+
Object.setPrototypeOf(this, DocumentServiceError.prototype);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function isDocumentServiceError(
|
|
41
|
+
error: unknown
|
|
42
|
+
): error is DocumentServiceError {
|
|
43
|
+
return error instanceof DocumentServiceError;
|
|
44
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export {
|
|
2
|
+
getUserDocuments,
|
|
3
|
+
getDocument,
|
|
4
|
+
getDocumentVersions,
|
|
5
|
+
saveDocument,
|
|
6
|
+
deleteDocumentVersions,
|
|
7
|
+
} from "./service";
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
DocumentServiceError,
|
|
11
|
+
isDocumentServiceError,
|
|
12
|
+
type DocumentServiceErrorCode,
|
|
13
|
+
} from "./errors";
|
|
14
|
+
|
|
15
|
+
export type { DocumentActor, DocumentFilter, DocumentListItem } from "./types";
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
registerDocumentPatterns,
|
|
19
|
+
classifyDocumentTitle,
|
|
20
|
+
isProductDocument,
|
|
21
|
+
listRegisteredDocumentPatterns,
|
|
22
|
+
clearDocumentPatternRegistry,
|
|
23
|
+
type DocumentPatternEntry,
|
|
24
|
+
} from "./classifier";
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-only persistence for AI-generated documents.
|
|
3
|
+
*
|
|
4
|
+
* Callers pass a resolved actor (workspaceId, userId); core cannot depend on
|
|
5
|
+
* `@intelligo-dev/auth`. Every query filters by both ids — the caller is
|
|
6
|
+
* never trusted to have done it. Failures throw `DocumentServiceError`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { and, desc, eq, gt } from "drizzle-orm";
|
|
10
|
+
import { db } from "../db";
|
|
11
|
+
import { documents } from "../db/schema";
|
|
12
|
+
import { classifyDocumentTitle, isProductDocument } from "./classifier";
|
|
13
|
+
import { DocumentServiceError } from "./errors";
|
|
14
|
+
import type { DocumentActor, DocumentFilter, DocumentListItem } from "./types";
|
|
15
|
+
|
|
16
|
+
type DocumentRow = {
|
|
17
|
+
id: string;
|
|
18
|
+
title: string;
|
|
19
|
+
kind: string;
|
|
20
|
+
createdAt: Date;
|
|
21
|
+
content: string | null;
|
|
22
|
+
metadata?: unknown;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* `metadata` is untyped `jsonb` written by whoever saved the document,
|
|
27
|
+
* so it is read defensively: anything that is not a non-empty string
|
|
28
|
+
* is no link at all.
|
|
29
|
+
*/
|
|
30
|
+
function conversationIdOf(metadata: unknown): string | null {
|
|
31
|
+
if (!metadata || typeof metadata !== "object") return null;
|
|
32
|
+
const value = (metadata as { conversationId?: unknown }).conversationId;
|
|
33
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function toListItem(doc: DocumentRow): DocumentListItem {
|
|
37
|
+
return {
|
|
38
|
+
id: doc.id,
|
|
39
|
+
title: doc.title,
|
|
40
|
+
kind: doc.kind,
|
|
41
|
+
createdAt: doc.createdAt.toISOString(),
|
|
42
|
+
agentLabel: classifyDocumentTitle(doc.title).agentLabel,
|
|
43
|
+
content: doc.content,
|
|
44
|
+
conversationId: conversationIdOf(doc.metadata),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The latest version of each of the actor's documents, newest first.
|
|
50
|
+
*/
|
|
51
|
+
export async function getUserDocuments(
|
|
52
|
+
actor: DocumentActor,
|
|
53
|
+
filter?: DocumentFilter
|
|
54
|
+
): Promise<DocumentListItem[]> {
|
|
55
|
+
const uniqueDocs = await db
|
|
56
|
+
.selectDistinctOn([documents.id])
|
|
57
|
+
.from(documents)
|
|
58
|
+
.where(
|
|
59
|
+
and(
|
|
60
|
+
eq(documents.workspaceId, actor.workspaceId),
|
|
61
|
+
eq(documents.userId, actor.userId)
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
.orderBy(documents.id, desc(documents.createdAt));
|
|
65
|
+
|
|
66
|
+
let result: DocumentListItem[] = uniqueDocs.map(toListItem);
|
|
67
|
+
|
|
68
|
+
const filterType = filter?.type ?? "all";
|
|
69
|
+
if (filterType !== "all") {
|
|
70
|
+
result =
|
|
71
|
+
filterType === "report"
|
|
72
|
+
? result.filter((doc) => isProductDocument(doc.title))
|
|
73
|
+
: result.filter((doc) => doc.kind === filterType);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The latest version of one document, scoped to the actor.
|
|
81
|
+
* Throws DocumentServiceError("not_found") if no such document exists.
|
|
82
|
+
*/
|
|
83
|
+
export async function getDocument(
|
|
84
|
+
actor: DocumentActor,
|
|
85
|
+
id: string
|
|
86
|
+
): Promise<DocumentListItem> {
|
|
87
|
+
const [doc] = await db
|
|
88
|
+
.selectDistinctOn([documents.id])
|
|
89
|
+
.from(documents)
|
|
90
|
+
.where(
|
|
91
|
+
and(
|
|
92
|
+
eq(documents.id, id),
|
|
93
|
+
eq(documents.workspaceId, actor.workspaceId),
|
|
94
|
+
eq(documents.userId, actor.userId)
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
.orderBy(documents.id, desc(documents.createdAt));
|
|
98
|
+
|
|
99
|
+
if (!doc) {
|
|
100
|
+
throw new DocumentServiceError("not_found", "Document not found");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return toListItem(doc);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Every version of a document, newest first — what a canvas version
|
|
108
|
+
* picker walks. Throws ("not_found") when the actor has no such id.
|
|
109
|
+
*/
|
|
110
|
+
export async function getDocumentVersions(
|
|
111
|
+
actor: DocumentActor,
|
|
112
|
+
id: string
|
|
113
|
+
): Promise<DocumentListItem[]> {
|
|
114
|
+
const rows = await db
|
|
115
|
+
.select()
|
|
116
|
+
.from(documents)
|
|
117
|
+
.where(
|
|
118
|
+
and(
|
|
119
|
+
eq(documents.id, id),
|
|
120
|
+
eq(documents.workspaceId, actor.workspaceId),
|
|
121
|
+
eq(documents.userId, actor.userId)
|
|
122
|
+
)
|
|
123
|
+
)
|
|
124
|
+
.orderBy(desc(documents.createdAt));
|
|
125
|
+
|
|
126
|
+
if (rows.length === 0) {
|
|
127
|
+
throw new DocumentServiceError("not_found", "Document not found");
|
|
128
|
+
}
|
|
129
|
+
return rows.map(toListItem);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Saves a document as a new version.
|
|
134
|
+
* Throws DocumentServiceError("forbidden") if a document with this id
|
|
135
|
+
* already exists in the workspace under a different user.
|
|
136
|
+
*/
|
|
137
|
+
export async function saveDocument(
|
|
138
|
+
actor: DocumentActor,
|
|
139
|
+
params: {
|
|
140
|
+
id: string;
|
|
141
|
+
title: string;
|
|
142
|
+
content: string;
|
|
143
|
+
kind: string;
|
|
144
|
+
/**
|
|
145
|
+
* The conversation this was written in, stored on `metadata` so
|
|
146
|
+
* the artifacts page can link back to it. Optional: a document
|
|
147
|
+
* saved by a job or an import belongs to no conversation.
|
|
148
|
+
*/
|
|
149
|
+
conversationId?: string;
|
|
150
|
+
}
|
|
151
|
+
): Promise<DocumentListItem> {
|
|
152
|
+
const existingDocs = await db
|
|
153
|
+
.select()
|
|
154
|
+
.from(documents)
|
|
155
|
+
.where(
|
|
156
|
+
and(
|
|
157
|
+
eq(documents.id, params.id),
|
|
158
|
+
eq(documents.workspaceId, actor.workspaceId)
|
|
159
|
+
)
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (existingDocs.length > 0) {
|
|
163
|
+
const firstDoc = existingDocs[0];
|
|
164
|
+
if (firstDoc && firstDoc.userId !== actor.userId) {
|
|
165
|
+
throw new DocumentServiceError(
|
|
166
|
+
"forbidden",
|
|
167
|
+
"Document belongs to a different user"
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Every save writes a new version row, so an edit that does not resend
|
|
173
|
+
// the conversation link would drop it. Carry the known one forward.
|
|
174
|
+
const conversationId =
|
|
175
|
+
params.conversationId ??
|
|
176
|
+
existingDocs.map((doc) => conversationIdOf(doc.metadata)).find(Boolean) ??
|
|
177
|
+
null;
|
|
178
|
+
|
|
179
|
+
const [newDocument] = await db
|
|
180
|
+
.insert(documents)
|
|
181
|
+
.values({
|
|
182
|
+
id: params.id,
|
|
183
|
+
userId: actor.userId,
|
|
184
|
+
workspaceId: actor.workspaceId,
|
|
185
|
+
title: params.title,
|
|
186
|
+
content: params.content,
|
|
187
|
+
kind: params.kind,
|
|
188
|
+
createdAt: new Date(),
|
|
189
|
+
metadata: conversationId ? { conversationId } : null,
|
|
190
|
+
})
|
|
191
|
+
.returning();
|
|
192
|
+
|
|
193
|
+
if (!newDocument) {
|
|
194
|
+
throw new DocumentServiceError("database_error", "Failed to save document");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return toListItem(newDocument);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const MAX_DELETE_WINDOW_DAYS = 30;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Delete all versions of a document created after `timestamp`.
|
|
204
|
+
* Throws DocumentServiceError("invalid_input") for an unparsable
|
|
205
|
+
* timestamp or one older than the delete window, and ("not_found") if
|
|
206
|
+
* no document exists at that id within the actor's scope.
|
|
207
|
+
*/
|
|
208
|
+
export async function deleteDocumentVersions(
|
|
209
|
+
actor: DocumentActor,
|
|
210
|
+
id: string,
|
|
211
|
+
timestamp: string
|
|
212
|
+
): Promise<DocumentListItem[]> {
|
|
213
|
+
const parsedTimestamp = new Date(timestamp);
|
|
214
|
+
if (Number.isNaN(parsedTimestamp.getTime())) {
|
|
215
|
+
throw new DocumentServiceError("invalid_input", "Invalid timestamp");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const oldestAllowed = new Date(
|
|
219
|
+
Date.now() - MAX_DELETE_WINDOW_DAYS * 24 * 60 * 60 * 1000
|
|
220
|
+
);
|
|
221
|
+
if (parsedTimestamp < oldestAllowed) {
|
|
222
|
+
throw new DocumentServiceError(
|
|
223
|
+
"invalid_input",
|
|
224
|
+
`timestamp must be within the last ${MAX_DELETE_WINDOW_DAYS} days`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const existingDocs = await db
|
|
229
|
+
.select()
|
|
230
|
+
.from(documents)
|
|
231
|
+
.where(
|
|
232
|
+
and(
|
|
233
|
+
eq(documents.id, id),
|
|
234
|
+
eq(documents.workspaceId, actor.workspaceId),
|
|
235
|
+
eq(documents.userId, actor.userId)
|
|
236
|
+
)
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
if (existingDocs.length === 0) {
|
|
240
|
+
throw new DocumentServiceError("not_found", "Document not found");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const deletedDocs = await db
|
|
244
|
+
.delete(documents)
|
|
245
|
+
.where(
|
|
246
|
+
and(
|
|
247
|
+
eq(documents.id, id),
|
|
248
|
+
eq(documents.workspaceId, actor.workspaceId),
|
|
249
|
+
eq(documents.userId, actor.userId),
|
|
250
|
+
gt(documents.createdAt, parsedTimestamp)
|
|
251
|
+
)
|
|
252
|
+
)
|
|
253
|
+
.returning();
|
|
254
|
+
|
|
255
|
+
return deletedDocs.map(toListItem);
|
|
256
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Resolved actor identity — every query is scoped to this pair. */
|
|
2
|
+
export type DocumentActor = {
|
|
3
|
+
workspaceId: string;
|
|
4
|
+
userId: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type DocumentFilter = {
|
|
8
|
+
type?: "all" | "report" | "text" | "code" | "sheet" | "image";
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type DocumentListItem = {
|
|
12
|
+
id: string;
|
|
13
|
+
title: string;
|
|
14
|
+
kind: string;
|
|
15
|
+
createdAt: string; // ISO string, so it serializes to the client
|
|
16
|
+
agentLabel: string;
|
|
17
|
+
content: string | null;
|
|
18
|
+
/**
|
|
19
|
+
* The conversation this document was written in, when known. Read from
|
|
20
|
+
* `metadata.conversationId` rather than a column: a document may also be
|
|
21
|
+
* written from a job or an import.
|
|
22
|
+
*/
|
|
23
|
+
conversationId: string | null;
|
|
24
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whose emails these are. Every deployment sends as itself, so the name
|
|
3
|
+
* and the support address come from its environment, never from the
|
|
4
|
+
* framework:
|
|
5
|
+
*
|
|
6
|
+
* - `APP_NAME` — the product's name. Falls back to the display name in
|
|
7
|
+
* `EMAIL_FROM` ("Acme <noreply@acme.com>" → "Acme").
|
|
8
|
+
* - `SUPPORT_EMAIL` — where a reader can write back. Without it the
|
|
9
|
+
* footer offers no address rather than someone else's.
|
|
10
|
+
*/
|
|
11
|
+
export type EmailBrand = {
|
|
12
|
+
/** Empty when neither variable names one. */
|
|
13
|
+
name: string;
|
|
14
|
+
supportEmail: string | null;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function emailBrand(): EmailBrand {
|
|
18
|
+
const from = process.env.EMAIL_FROM?.trim() ?? "";
|
|
19
|
+
const display = /^"?([^"<]+?)"?\s*</.exec(from)?.[1]?.trim() ?? "";
|
|
20
|
+
return {
|
|
21
|
+
name: process.env.APP_NAME?.trim() || display,
|
|
22
|
+
supportEmail: process.env.SUPPORT_EMAIL?.trim() || null,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export {
|
|
2
|
+
sendEmail,
|
|
3
|
+
type SendEmailParams,
|
|
4
|
+
type SendEmailWithComponentParams,
|
|
5
|
+
type SendEmailResult,
|
|
6
|
+
} from "./send";
|
|
7
|
+
export {
|
|
8
|
+
getEmailProvider,
|
|
9
|
+
resetEmailProviderCache,
|
|
10
|
+
type EmailProvider,
|
|
11
|
+
type EmailProviderName,
|
|
12
|
+
type EmailSendParams,
|
|
13
|
+
type EmailTemplateRef,
|
|
14
|
+
ResendProvider,
|
|
15
|
+
LoopsProvider,
|
|
16
|
+
ConsoleProvider,
|
|
17
|
+
} from "./provider";
|
|
18
|
+
export * from "./templates";
|
|
19
|
+
export * from "./senders";
|