@ordinatio/entities 1.0.3 → 1.1.0

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/dist/index.mjs CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  evaluateConstraint,
10
10
  fireObservers,
11
11
  knowledgeError
12
- } from "./chunk-QXBSEBJW.mjs";
12
+ } from "./chunk-WWGAALKG.mjs";
13
13
 
14
14
  // src/knowledge/field-definitions.ts
15
15
  import { Prisma } from "@prisma/client";
@@ -199,7 +199,7 @@ async function setEntityFields(db, entityType, entityId, fields, source, sourceI
199
199
  } catch {
200
200
  }
201
201
  try {
202
- const { fireObservers: fireObservers2 } = await import("./observer-YUTEGG4S.mjs");
202
+ const { fireObservers: fireObservers2 } = await import("./observer-2GIY55TX.mjs");
203
203
  await fireObservers2(db, entityType, entityId, results.map((r) => r.key), callbacks);
204
204
  } catch {
205
205
  }
@@ -1668,6 +1668,7 @@ async function createNote(db, input, callbacks) {
1668
1668
  isDraft: input.isDraft ?? false,
1669
1669
  authorId: input.authorId ?? null,
1670
1670
  source: input.source ?? "MANUAL",
1671
+ ...input.metadata ? { metadata: input.metadata } : {},
1671
1672
  ...input.attachments && input.attachments.length > 0 ? {
1672
1673
  attachments: {
1673
1674
  create: input.attachments.map((a) => ({
@@ -1726,6 +1727,21 @@ async function updateNote(db, noteId, entityId, input, callbacks) {
1726
1727
  });
1727
1728
  if (!note) return null;
1728
1729
  const isPublishing = note.isDraft && input.isDraft === false;
1730
+ const isContentChange = input.content !== void 0 && input.content !== note.content || input.contentHtml !== void 0 && input.contentHtml !== note.contentHtml;
1731
+ if (isContentChange && !note.isDraft) {
1732
+ try {
1733
+ await db.clientNoteRevision.create({
1734
+ data: {
1735
+ noteId: note.id,
1736
+ content: note.content,
1737
+ contentHtml: note.contentHtml,
1738
+ editedBy: input.editedBy ?? note.authorId,
1739
+ editedAt: /* @__PURE__ */ new Date()
1740
+ }
1741
+ });
1742
+ } catch {
1743
+ }
1744
+ }
1729
1745
  const updated = await db.clientNote.update({
1730
1746
  where: { id: noteId },
1731
1747
  data: {
@@ -1735,7 +1751,11 @@ async function updateNote(db, noteId, entityId, input, callbacks) {
1735
1751
  },
1736
1752
  include: {
1737
1753
  author: { select: { id: true, name: true } },
1738
- attachments: true
1754
+ attachments: true,
1755
+ revisions: {
1756
+ orderBy: { editedAt: "desc" },
1757
+ select: { id: true, content: true, editedBy: true, editedAt: true }
1758
+ }
1739
1759
  }
1740
1760
  });
1741
1761
  if (input.attachments !== void 0) {
@@ -1752,6 +1772,26 @@ async function updateNote(db, noteId, entityId, input, callbacks) {
1752
1772
  });
1753
1773
  }
1754
1774
  }
1775
+ if (isContentChange && !note.isDraft && !isPublishing) {
1776
+ try {
1777
+ const noteContent = input.content ?? note.content;
1778
+ const truncated = truncateText(noteContent, 200);
1779
+ await callbacks?.logActivity?.(
1780
+ "CLIENT_NOTE_EDITED",
1781
+ `Note edited: ${truncated}`,
1782
+ {
1783
+ userId: input.editedBy ?? note.authorId ?? null,
1784
+ clientId: entityId,
1785
+ metadata: {
1786
+ noteId,
1787
+ contentPreview: truncateText(noteContent, 500),
1788
+ revisionCount: updated.revisions?.length ?? 0
1789
+ }
1790
+ }
1791
+ );
1792
+ } catch {
1793
+ }
1794
+ }
1755
1795
  if (isPublishing) {
1756
1796
  try {
1757
1797
  const noteContent = input.content ?? note.content;
@@ -1776,7 +1816,11 @@ async function updateNote(db, noteId, entityId, input, callbacks) {
1776
1816
  where: { id: noteId },
1777
1817
  include: {
1778
1818
  author: { select: { id: true, name: true } },
1779
- attachments: true
1819
+ attachments: true,
1820
+ revisions: {
1821
+ orderBy: { editedAt: "desc" },
1822
+ select: { id: true, content: true, editedBy: true, editedAt: true }
1823
+ }
1780
1824
  }
1781
1825
  });
1782
1826
  }
@@ -1960,6 +2004,7 @@ async function findOrCreateContact(db, email, name, source = "EMAIL_SYNC") {
1960
2004
  }
1961
2005
 
1962
2006
  // src/knowledge/contact-fields-seed.ts
2007
+ import { Prisma as Prisma4 } from "@prisma/client";
1963
2008
  var CONTACT_FIELD_DEFINITIONS = [
1964
2009
  // --- Communication ---
1965
2010
  { entityType: "contact", key: "preferred_contact_method", label: "Preferred Contact Method", dataType: "enum", category: "communication", enumOptions: ["email", "phone", "text", "in-person"], sortOrder: 0 },
@@ -1982,7 +2027,7 @@ async function seedContactFieldDefinitions(db) {
1982
2027
  label: d.label,
1983
2028
  dataType: d.dataType,
1984
2029
  category: d.category,
1985
- enumOptions: "enumOptions" in d ? d.enumOptions : null,
2030
+ enumOptions: "enumOptions" in d ? [...d.enumOptions] : Prisma4.JsonNull,
1986
2031
  extractionHint: "extractionHint" in d ? d.extractionHint : null,
1987
2032
  sortOrder: d.sortOrder,
1988
2033
  status: "approved",
@@ -2119,7 +2164,7 @@ var CreateNoteSchema = z3.object({
2119
2164
  content: z3.string().min(1, "Note content is required").max(5e3, "Note content must be 5000 characters or fewer"),
2120
2165
  contentHtml: z3.string().max(5e4).optional(),
2121
2166
  isDraft: z3.boolean().default(false),
2122
- source: z3.enum(["MANUAL", "AGENT", "SYSTEM"]).default("MANUAL"),
2167
+ source: z3.enum(["MANUAL", "AGENT", "SYSTEM", "QUOTE"]).default("MANUAL"),
2123
2168
  attachments: z3.array(AttachmentSchema).max(5, "Maximum 5 attachments").default([]),
2124
2169
  fields: z3.record(z3.string(), z3.unknown()).optional()
2125
2170
  });
@@ -2,7 +2,7 @@ import {
2
2
  checkConstraints,
3
3
  evaluateConstraint,
4
4
  fireObservers
5
- } from "./chunk-QXBSEBJW.mjs";
5
+ } from "./chunk-WWGAALKG.mjs";
6
6
  export {
7
7
  checkConstraints,
8
8
  evaluateConstraint,
@@ -0,0 +1,78 @@
1
+ declare const TAG_COLORS: readonly ["neutral", "red", "amber", "green", "blue", "purple", "pink"];
2
+ type TagColor = (typeof TAG_COLORS)[number];
3
+ interface TagLimits {
4
+ /** Longest tag name, in characters, after cleaning. */
5
+ maxNameLength: number;
6
+ /** Upper bound on how many tags one save may add or remove: stops a crafted request sending thousands of ids into an IN query. */
7
+ maxChangesPerSave: number;
8
+ /** How many tags may be pinned (shown as bookmarks): pinning more is refused rather than silently ignored. */
9
+ maxPinned: number;
10
+ /** Per-workspace ceiling: forms load every tag, so an unbounded vocabulary would slow every form. */
11
+ maxPerWorkspace: number;
12
+ }
13
+ declare const DEFAULT_TAG_LIMITS: Readonly<TagLimits>;
14
+
15
+ /** Trims and collapses every run of whitespace (including tabs and no-break spaces) to one plain space. */
16
+ declare function cleanTagName(name: string): string;
17
+ /** The identity of a name inside one workspace: the cleaned name, lower-cased. Two names with the same key are the same tag. */
18
+ declare function tagNameKey(name: string): string;
19
+ type TagNameCheck = {
20
+ ok: true;
21
+ name: string;
22
+ key: string;
23
+ } | {
24
+ ok: false;
25
+ reason: 'empty' | 'too-long';
26
+ length: number;
27
+ };
28
+ /** Cleans the name and checks its length (1 to `maxLength`, default 40, counted in UTF-16 code units like the stored value). */
29
+ declare function checkTagName(name: string, maxLength?: number): TagNameCheck;
30
+ declare function isTagColor(value: unknown): value is TagColor;
31
+ /** No colour means the neutral one; a name that is not in the list is invalid. */
32
+ declare function resolveTagColor(color: string | undefined): {
33
+ ok: true;
34
+ color: TagColor;
35
+ } | {
36
+ ok: false;
37
+ };
38
+
39
+ interface TagSnapshot {
40
+ name: string;
41
+ color: string;
42
+ pinned: boolean;
43
+ }
44
+ /** One phrase per field that actually changed; empty when nothing did (so nothing is logged). */
45
+ declare function describeTagChanges(before: TagSnapshot, after: TagSnapshot): string[];
46
+ /** Order-independent identity of a set of tag ids. */
47
+ declare function tagSetKey(ids: readonly string[]): string;
48
+ /**
49
+ * The server's tag set for an item changed while the user had the picker open (their own save came back, or a teammate tagged the item).
50
+ * Keep the user's unsaved edits (what they added and removed relative to the OLD baseline), apply them on top of the NEW server set, and
51
+ * make the new server set the baseline. So nothing the user chose is lost, nothing a teammate added is stripped, and the next save's diff
52
+ * stays correct.
53
+ */
54
+ interface RebasedSelection {
55
+ /** The new baseline: the server's current set, de-duplicated. */
56
+ initial: string[];
57
+ /** The user's unsaved choices applied on top of it. */
58
+ selected: string[];
59
+ }
60
+ declare function rebaseSelection(args: {
61
+ initial: readonly string[];
62
+ selected: readonly string[];
63
+ server: readonly string[];
64
+ }): RebasedSelection;
65
+ interface TagChoice {
66
+ id: string;
67
+ name: string;
68
+ color: string;
69
+ }
70
+ /**
71
+ * The tag list a picker shows: the server's current list (so renames, new tags and deletions by teammates show up), plus tags created in
72
+ * this very picker that the server list has not caught up with yet. Sorted by name with `localeCompare`, which follows the runtime's
73
+ * locale and ICU data, so a server and a browser can order accented or mixed-case names differently: do not rely on the order matching
74
+ * across runtimes.
75
+ */
76
+ declare function mergeTagOptions<T extends TagChoice>(fresh: readonly T[], createdHere: readonly T[]): T[];
77
+
78
+ export { DEFAULT_TAG_LIMITS, type RebasedSelection, TAG_COLORS, type TagChoice, type TagColor, type TagLimits, type TagNameCheck, type TagSnapshot, checkTagName, cleanTagName, describeTagChanges, isTagColor, mergeTagOptions, rebaseSelection, resolveTagColor, tagNameKey, tagSetKey };
@@ -0,0 +1,66 @@
1
+ // src/tags/constants.ts
2
+ var TAG_COLORS = ["neutral", "red", "amber", "green", "blue", "purple", "pink"];
3
+ var DEFAULT_TAG_LIMITS = Object.freeze({
4
+ maxNameLength: 40,
5
+ maxChangesPerSave: 50,
6
+ maxPinned: 15,
7
+ maxPerWorkspace: 500
8
+ });
9
+
10
+ // src/tags/name.ts
11
+ function cleanTagName(name) {
12
+ return name.trim().replace(/\s+/g, " ");
13
+ }
14
+ function tagNameKey(name) {
15
+ return cleanTagName(name).toLowerCase();
16
+ }
17
+ function checkTagName(name, maxLength = DEFAULT_TAG_LIMITS.maxNameLength) {
18
+ const cleaned = cleanTagName(name);
19
+ if (cleaned.length < 1) return { ok: false, reason: "empty", length: cleaned.length };
20
+ if (cleaned.length > maxLength) return { ok: false, reason: "too-long", length: cleaned.length };
21
+ return { ok: true, name: cleaned, key: tagNameKey(cleaned) };
22
+ }
23
+ function isTagColor(value) {
24
+ return typeof value === "string" && TAG_COLORS.includes(value);
25
+ }
26
+ function resolveTagColor(color) {
27
+ if (color === void 0) return { ok: true, color: "neutral" };
28
+ return isTagColor(color) ? { ok: true, color } : { ok: false };
29
+ }
30
+
31
+ // src/tags/changes.ts
32
+ function describeTagChanges(before, after) {
33
+ return [
34
+ before.name !== after.name ? `renamed "${before.name}" to "${after.name}"` : "",
35
+ before.color !== after.color ? `changed its color to ${after.color}` : "",
36
+ before.pinned !== after.pinned ? after.pinned ? "pinned it to Bookmarks" : "unpinned it from Bookmarks" : ""
37
+ ].filter(Boolean);
38
+ }
39
+ function tagSetKey(ids) {
40
+ return [...new Set(ids)].sort().join(",");
41
+ }
42
+ function rebaseSelection(args) {
43
+ const initial = new Set(args.initial);
44
+ const selected = new Set(args.selected);
45
+ const userAdded = [...selected].filter((t) => !initial.has(t));
46
+ const userRemoved = new Set([...initial].filter((t) => !selected.has(t)));
47
+ const next = new Set([...args.server, ...userAdded].filter((t) => !userRemoved.has(t)));
48
+ return { initial: [...new Set(args.server)], selected: [...next] };
49
+ }
50
+ function mergeTagOptions(fresh, createdHere) {
51
+ const freshIds = new Set(fresh.map((t) => t.id));
52
+ return [...fresh, ...createdHere.filter((t) => !freshIds.has(t.id))].sort((a, b) => a.name.localeCompare(b.name));
53
+ }
54
+ export {
55
+ DEFAULT_TAG_LIMITS,
56
+ TAG_COLORS,
57
+ checkTagName,
58
+ cleanTagName,
59
+ describeTagChanges,
60
+ isTagColor,
61
+ mergeTagOptions,
62
+ rebaseSelection,
63
+ resolveTagColor,
64
+ tagNameKey,
65
+ tagSetKey
66
+ };
package/package.json CHANGED
@@ -1,44 +1,37 @@
1
1
  {
2
2
  "name": "@ordinatio/entities",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "description": "Entity knowledge, agent intelligence, notes, and contacts",
7
7
  "engines": {
8
8
  "node": ">=22.0.0"
9
9
  },
10
- "main": "./src/index.ts",
11
- "types": "./src/index.ts",
10
+ "main": "./dist/index.mjs",
11
+ "types": "./dist/index.d.mts",
12
12
  "exports": {
13
13
  ".": {
14
- "import": "./src/index.ts",
15
- "types": "./src/index.ts"
14
+ "types": "./dist/index.d.mts",
15
+ "import": "./dist/index.mjs",
16
+ "default": "./dist/index.mjs"
16
17
  },
17
- "./schemas": "./src/schemas.ts",
18
- "./errors": "./src/errors.ts"
18
+ "./tags": {
19
+ "types": "./dist/tags/index.d.mts",
20
+ "import": "./dist/tags/index.mjs",
21
+ "default": "./dist/tags/index.mjs"
22
+ }
19
23
  },
20
24
  "publishConfig": {
21
- "access": "public",
22
- "main": "./dist/index.js",
23
- "types": "./dist/index.d.ts",
24
- "exports": {
25
- ".": {
26
- "import": "./dist/index.js",
27
- "types": "./dist/index.d.ts"
28
- }
29
- }
25
+ "access": "public"
30
26
  },
31
27
  "files": [
32
28
  "dist",
33
29
  "README.md",
34
30
  "LICENSE"
35
31
  ],
36
- "scripts": {
37
- "build": "tsup src/index.ts --format esm --clean",
38
- "test": "vitest",
39
- "test:run": "vitest run"
32
+ "dependencies": {
33
+ "@ordinatio/errors": "1.2.0"
40
34
  },
41
- "dependencies": {},
42
35
  "devDependencies": {
43
36
  "@ordinatio/core": "^1.0.0",
44
37
  "fast-check": "^4.5.3",
@@ -54,5 +47,10 @@
54
47
  "@prisma/client": {
55
48
  "optional": true
56
49
  }
50
+ },
51
+ "scripts": {
52
+ "build": "tsup src/index.ts src/tags/index.ts --format esm --dts --clean",
53
+ "test": "vitest",
54
+ "test:run": "vitest run"
57
55
  }
58
56
  }