ai-database 2.1.3 → 2.3.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.
Files changed (260) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/README.md +880 -669
  3. package/dist/actions.d.ts +2 -2
  4. package/dist/actions.d.ts.map +1 -1
  5. package/dist/actions.js +1 -1
  6. package/dist/actions.js.map +1 -1
  7. package/dist/ai-promise-db.d.ts +49 -23
  8. package/dist/ai-promise-db.d.ts.map +1 -1
  9. package/dist/ai-promise-db.js +91 -63
  10. package/dist/ai-promise-db.js.map +1 -1
  11. package/dist/authorization.d.ts.map +1 -1
  12. package/dist/authorization.js +38 -30
  13. package/dist/authorization.js.map +1 -1
  14. package/dist/cascade-orchestrator.d.ts +404 -0
  15. package/dist/cascade-orchestrator.d.ts.map +1 -0
  16. package/dist/cascade-orchestrator.js +828 -0
  17. package/dist/cascade-orchestrator.js.map +1 -0
  18. package/dist/cascade-write-strategy.d.ts +584 -0
  19. package/dist/cascade-write-strategy.d.ts.map +1 -0
  20. package/dist/cascade-write-strategy.js +590 -0
  21. package/dist/cascade-write-strategy.js.map +1 -0
  22. package/dist/ch-adapter.d.ts +358 -0
  23. package/dist/ch-adapter.d.ts.map +1 -0
  24. package/dist/ch-adapter.js +929 -0
  25. package/dist/ch-adapter.js.map +1 -0
  26. package/dist/client/index.d.ts +42 -0
  27. package/dist/client/index.d.ts.map +1 -0
  28. package/dist/client/index.js +43 -0
  29. package/dist/client/index.js.map +1 -0
  30. package/dist/client.d.ts +266 -0
  31. package/dist/client.d.ts.map +1 -0
  32. package/dist/client.js +81 -0
  33. package/dist/client.js.map +1 -0
  34. package/dist/constants.d.ts +64 -1
  35. package/dist/constants.d.ts.map +1 -1
  36. package/dist/constants.js +52 -2
  37. package/dist/constants.js.map +1 -1
  38. package/dist/dataloader.d.ts +99 -0
  39. package/dist/dataloader.d.ts.map +1 -0
  40. package/dist/dataloader.js +225 -0
  41. package/dist/dataloader.js.map +1 -0
  42. package/dist/db-provider-port.d.ts +501 -0
  43. package/dist/db-provider-port.d.ts.map +1 -0
  44. package/dist/db-provider-port.js +113 -0
  45. package/dist/db-provider-port.js.map +1 -0
  46. package/dist/digital-objects-provider.d.ts +49 -0
  47. package/dist/digital-objects-provider.d.ts.map +1 -0
  48. package/dist/digital-objects-provider.js +55 -0
  49. package/dist/digital-objects-provider.js.map +1 -0
  50. package/dist/do-sqlite-adapter.d.ts +402 -0
  51. package/dist/do-sqlite-adapter.d.ts.map +1 -0
  52. package/dist/do-sqlite-adapter.js +745 -0
  53. package/dist/do-sqlite-adapter.js.map +1 -0
  54. package/dist/docs-rels/custom-types.d.ts +134 -0
  55. package/dist/docs-rels/custom-types.d.ts.map +1 -0
  56. package/dist/docs-rels/custom-types.js +70 -0
  57. package/dist/docs-rels/custom-types.js.map +1 -0
  58. package/dist/docs-rels/index.d.ts +16 -0
  59. package/dist/docs-rels/index.d.ts.map +1 -0
  60. package/dist/docs-rels/index.js +16 -0
  61. package/dist/docs-rels/index.js.map +1 -0
  62. package/dist/docs-rels/migrations/index.d.ts +30 -0
  63. package/dist/docs-rels/migrations/index.d.ts.map +1 -0
  64. package/dist/docs-rels/migrations/index.js +128 -0
  65. package/dist/docs-rels/migrations/index.js.map +1 -0
  66. package/dist/docs-rels/schema.d.ts +2961 -0
  67. package/dist/docs-rels/schema.d.ts.map +1 -0
  68. package/dist/docs-rels/schema.js +244 -0
  69. package/dist/docs-rels/schema.js.map +1 -0
  70. package/dist/durable-clickhouse.d.ts.map +1 -1
  71. package/dist/durable-clickhouse.js +16 -13
  72. package/dist/durable-clickhouse.js.map +1 -1
  73. package/dist/durable-promise.d.ts.map +1 -1
  74. package/dist/durable-promise.js +34 -15
  75. package/dist/durable-promise.js.map +1 -1
  76. package/dist/errors.d.ts +127 -0
  77. package/dist/errors.d.ts.map +1 -0
  78. package/dist/errors.js +210 -0
  79. package/dist/errors.js.map +1 -0
  80. package/dist/eventbridge.d.ts +117 -0
  81. package/dist/eventbridge.d.ts.map +1 -0
  82. package/dist/eventbridge.js +238 -0
  83. package/dist/eventbridge.js.map +1 -0
  84. package/dist/events.d.ts +2 -2
  85. package/dist/events.d.ts.map +1 -1
  86. package/dist/events.js +1 -1
  87. package/dist/events.js.map +1 -1
  88. package/dist/execution-queue.d.ts.map +1 -1
  89. package/dist/execution-queue.js +4 -5
  90. package/dist/execution-queue.js.map +1 -1
  91. package/dist/index.d.ts +35 -8
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +106 -6
  94. package/dist/index.js.map +1 -1
  95. package/dist/linguistic.d.ts +3 -108
  96. package/dist/linguistic.d.ts.map +1 -1
  97. package/dist/linguistic.js +3 -372
  98. package/dist/linguistic.js.map +1 -1
  99. package/dist/logger.d.ts +132 -0
  100. package/dist/logger.d.ts.map +1 -0
  101. package/dist/logger.js +137 -0
  102. package/dist/logger.js.map +1 -0
  103. package/dist/memory-provider.d.ts +128 -0
  104. package/dist/memory-provider.d.ts.map +1 -1
  105. package/dist/memory-provider.js +592 -257
  106. package/dist/memory-provider.js.map +1 -1
  107. package/dist/pg-adapter.d.ts +424 -0
  108. package/dist/pg-adapter.d.ts.map +1 -0
  109. package/dist/pg-adapter.js +921 -0
  110. package/dist/pg-adapter.js.map +1 -0
  111. package/dist/pipelines-iceberg-emitter.d.ts +327 -0
  112. package/dist/pipelines-iceberg-emitter.d.ts.map +1 -0
  113. package/dist/pipelines-iceberg-emitter.js +351 -0
  114. package/dist/pipelines-iceberg-emitter.js.map +1 -0
  115. package/dist/provider-capabilities.d.ts +146 -0
  116. package/dist/provider-capabilities.d.ts.map +1 -0
  117. package/dist/provider-capabilities.js +214 -0
  118. package/dist/provider-capabilities.js.map +1 -0
  119. package/dist/rdb-provider-adapter.d.ts +195 -0
  120. package/dist/rdb-provider-adapter.d.ts.map +1 -0
  121. package/dist/rdb-provider-adapter.js +291 -0
  122. package/dist/rdb-provider-adapter.js.map +1 -0
  123. package/dist/schema/cascade.d.ts +48 -17
  124. package/dist/schema/cascade.d.ts.map +1 -1
  125. package/dist/schema/cascade.js +477 -278
  126. package/dist/schema/cascade.js.map +1 -1
  127. package/dist/schema/definition-caches.d.ts +24 -0
  128. package/dist/schema/definition-caches.d.ts.map +1 -0
  129. package/dist/schema/definition-caches.js +26 -0
  130. package/dist/schema/definition-caches.js.map +1 -0
  131. package/dist/schema/dependency-graph.d.ts +21 -109
  132. package/dist/schema/dependency-graph.d.ts.map +1 -1
  133. package/dist/schema/dependency-graph.js +25 -333
  134. package/dist/schema/dependency-graph.js.map +1 -1
  135. package/dist/schema/diff.d.ts +103 -0
  136. package/dist/schema/diff.d.ts.map +1 -0
  137. package/dist/schema/diff.js +329 -0
  138. package/dist/schema/diff.js.map +1 -0
  139. package/dist/schema/entity-operations.d.ts +99 -0
  140. package/dist/schema/entity-operations.d.ts.map +1 -0
  141. package/dist/schema/entity-operations.js +818 -0
  142. package/dist/schema/entity-operations.js.map +1 -0
  143. package/dist/schema/index.d.ts +28 -34
  144. package/dist/schema/index.d.ts.map +1 -1
  145. package/dist/schema/index.js +454 -521
  146. package/dist/schema/index.js.map +1 -1
  147. package/dist/schema/migration.d.ts +205 -0
  148. package/dist/schema/migration.d.ts.map +1 -0
  149. package/dist/schema/migration.js +327 -0
  150. package/dist/schema/migration.js.map +1 -0
  151. package/dist/schema/nl-query-generator.d.ts +68 -0
  152. package/dist/schema/nl-query-generator.d.ts.map +1 -0
  153. package/dist/schema/nl-query-generator.js +362 -0
  154. package/dist/schema/nl-query-generator.js.map +1 -0
  155. package/dist/schema/nl-query.d.ts +65 -0
  156. package/dist/schema/nl-query.d.ts.map +1 -0
  157. package/dist/schema/nl-query.js +178 -0
  158. package/dist/schema/nl-query.js.map +1 -0
  159. package/dist/schema/parse.d.ts.map +1 -1
  160. package/dist/schema/parse.js +144 -89
  161. package/dist/schema/parse.js.map +1 -1
  162. package/dist/schema/provider.d.ts +37 -0
  163. package/dist/schema/provider.d.ts.map +1 -1
  164. package/dist/schema/provider.js +15 -7
  165. package/dist/schema/provider.js.map +1 -1
  166. package/dist/schema/resolve.d.ts +46 -5
  167. package/dist/schema/resolve.d.ts.map +1 -1
  168. package/dist/schema/resolve.js +237 -95
  169. package/dist/schema/resolve.js.map +1 -1
  170. package/dist/schema/search-utils.d.ts +76 -0
  171. package/dist/schema/search-utils.d.ts.map +1 -0
  172. package/dist/schema/search-utils.js +86 -0
  173. package/dist/schema/search-utils.js.map +1 -0
  174. package/dist/schema/seed.d.ts +53 -0
  175. package/dist/schema/seed.d.ts.map +1 -0
  176. package/dist/schema/seed.js +94 -0
  177. package/dist/schema/seed.js.map +1 -0
  178. package/dist/schema/semantic.d.ts +10 -0
  179. package/dist/schema/semantic.d.ts.map +1 -1
  180. package/dist/schema/semantic.js +192 -86
  181. package/dist/schema/semantic.js.map +1 -1
  182. package/dist/schema/sub-apis.d.ts +52 -0
  183. package/dist/schema/sub-apis.d.ts.map +1 -0
  184. package/dist/schema/sub-apis.js +216 -0
  185. package/dist/schema/sub-apis.js.map +1 -0
  186. package/dist/schema/system-entities.d.ts +42 -0
  187. package/dist/schema/system-entities.d.ts.map +1 -0
  188. package/dist/schema/system-entities.js +101 -0
  189. package/dist/schema/system-entities.js.map +1 -0
  190. package/dist/schema/types.d.ts +91 -9
  191. package/dist/schema/types.d.ts.map +1 -1
  192. package/dist/schema/union-fallback.d.ts.map +1 -1
  193. package/dist/schema/union-fallback.js +21 -15
  194. package/dist/schema/union-fallback.js.map +1 -1
  195. package/dist/schema/value-generators/ai.d.ts +54 -0
  196. package/dist/schema/value-generators/ai.d.ts.map +1 -0
  197. package/dist/schema/value-generators/ai.js +136 -0
  198. package/dist/schema/value-generators/ai.js.map +1 -0
  199. package/dist/schema/value-generators/index.d.ts +126 -0
  200. package/dist/schema/value-generators/index.d.ts.map +1 -0
  201. package/dist/schema/value-generators/index.js +219 -0
  202. package/dist/schema/value-generators/index.js.map +1 -0
  203. package/dist/schema/value-generators/placeholder.d.ts +52 -0
  204. package/dist/schema/value-generators/placeholder.d.ts.map +1 -0
  205. package/dist/schema/value-generators/placeholder.js +328 -0
  206. package/dist/schema/value-generators/placeholder.js.map +1 -0
  207. package/dist/schema/value-generators/types.d.ts +116 -0
  208. package/dist/schema/value-generators/types.d.ts.map +1 -0
  209. package/dist/schema/value-generators/types.js +11 -0
  210. package/dist/schema/value-generators/types.js.map +1 -0
  211. package/dist/schema/version.d.ts +111 -0
  212. package/dist/schema/version.d.ts.map +1 -0
  213. package/dist/schema/version.js +190 -0
  214. package/dist/schema/version.js.map +1 -0
  215. package/dist/schema.d.ts +1095 -24
  216. package/dist/schema.d.ts.map +1 -1
  217. package/dist/schema.js +2852 -40
  218. package/dist/schema.js.map +1 -1
  219. package/dist/semantic-vectors.d.ts +39 -0
  220. package/dist/semantic-vectors.d.ts.map +1 -0
  221. package/dist/semantic-vectors.js +334 -0
  222. package/dist/semantic-vectors.js.map +1 -0
  223. package/dist/semantic.d.ts +29 -1
  224. package/dist/semantic.d.ts.map +1 -1
  225. package/dist/semantic.js +26 -16
  226. package/dist/semantic.js.map +1 -1
  227. package/dist/telemetry.d.ts +128 -0
  228. package/dist/telemetry.d.ts.map +1 -0
  229. package/dist/telemetry.js +305 -0
  230. package/dist/telemetry.js.map +1 -0
  231. package/dist/tests.d.ts.map +1 -1
  232. package/dist/tests.js +30 -22
  233. package/dist/tests.js.map +1 -1
  234. package/dist/type-guards.d.ts +50 -5
  235. package/dist/type-guards.d.ts.map +1 -1
  236. package/dist/type-guards.js +87 -16
  237. package/dist/type-guards.js.map +1 -1
  238. package/dist/types.d.ts +33 -245
  239. package/dist/types.d.ts.map +1 -1
  240. package/dist/types.js +62 -72
  241. package/dist/types.js.map +1 -1
  242. package/dist/validation.d.ts +2 -5
  243. package/dist/validation.d.ts.map +1 -1
  244. package/dist/validation.js +65 -93
  245. package/dist/validation.js.map +1 -1
  246. package/dist/worker/db-provider.d.ts +168 -0
  247. package/dist/worker/db-provider.d.ts.map +1 -0
  248. package/dist/worker/db-provider.js +277 -0
  249. package/dist/worker/db-provider.js.map +1 -0
  250. package/dist/worker/index.d.ts +35 -0
  251. package/dist/worker/index.d.ts.map +1 -0
  252. package/dist/worker/index.js +37 -0
  253. package/dist/worker/index.js.map +1 -0
  254. package/dist/worker.d.ts +779 -0
  255. package/dist/worker.d.ts.map +1 -0
  256. package/dist/worker.js +2786 -0
  257. package/dist/worker.js.map +1 -0
  258. package/package.json +46 -16
  259. package/src/docs-rels/migrations/0001-init.sql +125 -0
  260. package/LICENSE +0 -21
@@ -37,6 +37,25 @@ export declare function resolveContextPath(path: string, entity: Record<string,
37
37
  * @returns The instructions with all template variables resolved
38
38
  */
39
39
  export declare function resolveInstructions(instructions: string, entity: Record<string, unknown>, typeName: string, schema: ParsedSchema, provider: DBProvider): Promise<string>;
40
+ /**
41
+ * Pre-fetch context dependencies declared in $context using dotted paths
42
+ *
43
+ * The $context field declares explicit context dependencies that should
44
+ * be pre-fetched before generating AI fields. Supports dotted paths like
45
+ * 'Startup.icp.industry' for deeply nested context fetching.
46
+ *
47
+ * Context paths can be:
48
+ * - Simple type names: 'Startup' - finds field that references Startup type
49
+ * - Dotted paths: 'Startup.icp.industry' - traverses relationships
50
+ *
51
+ * @param contextDeps - Array of context dependency paths (e.g., ['Startup', 'Startup.icp.industry'])
52
+ * @param entity - The current entity data
53
+ * @param typeName - The current entity type name
54
+ * @param schema - The parsed schema
55
+ * @param provider - The database provider for fetching related entities
56
+ * @returns Map of context path to fetched entity data
57
+ */
58
+ export declare function prefetchContextPaths(contextDeps: string[], entity: Record<string, unknown>, typeName: string, schema: ParsedSchema, provider: DBProvider): Promise<Map<string, Record<string, unknown>>>;
40
59
  /**
41
60
  * Pre-fetch context dependencies declared in $context
42
61
  *
@@ -53,7 +72,8 @@ export declare function resolveInstructions(instructions: string, entity: Record
53
72
  */
54
73
  export declare function prefetchContext(contextDeps: string[], entity: Record<string, unknown>, typeName: string, schema: ParsedSchema, provider: DBProvider): Promise<Map<string, Record<string, unknown>>>;
55
74
  /**
56
- * Check if a field type is a prompt (contains spaces, indicating AI generation)
75
+ * Check if a field type is a prompt (contains spaces, slashes, or question marks indicating AI generation).
76
+ * Examples: 'Describe the product', 'low/medium/high', 'What is the severity?'
57
77
  */
58
78
  export declare function isPromptField(field: ParsedField): boolean;
59
79
  /**
@@ -70,7 +90,11 @@ export declare function resolveNestedPending(data: Record<string, unknown>, enti
70
90
  * For exact matches (-> and <-), creates new entities.
71
91
  * For fuzzy matches (~> and <~), searches for existing entities first.
72
92
  */
73
- export declare function resolveReferenceSpec(spec: ReferenceSpec, contextData: Record<string, unknown>, schema: ParsedSchema, provider: DBProvider, generateContextAwareValue: (fieldName: string, type: string, fullContext: string, hint: string | undefined, parentData: Record<string, unknown>) => string): Promise<string | null>;
93
+ export declare function resolveReferenceSpec(spec: ReferenceSpec, contextData: Record<string, unknown>, schema: ParsedSchema, provider: DBProvider, generateContextAwareValue: (fieldName: string, type: string, fullContext: string, hint: string | undefined, parentData: Record<string, unknown>) => string, generateEntityWithAI?: (type: string, prompt: string | undefined, context: {
94
+ parent: string;
95
+ parentData: Record<string, unknown>;
96
+ parentId?: string;
97
+ }, schema: ParsedSchema) => Promise<Record<string, unknown>>): Promise<string | null>;
74
98
  /**
75
99
  * Hydrate an entity with lazy-loaded relations
76
100
  *
@@ -78,10 +102,27 @@ export declare function resolveReferenceSpec(spec: ReferenceSpec, contextData: R
78
102
  * of the related type that have a reference pointing TO this entity.
79
103
  * This enables reverse lookups like "get all comments for a post".
80
104
  *
81
- * Backward reference resolution:
82
- * - Single backward ref with stored ID: resolve directly (e.g., member.team = teamId -> get Team by ID)
105
+ * ## Backward Exact (<-) vs Backward Fuzzy (<~) Resolution
106
+ *
107
+ * This function handles both backward exact and backward fuzzy resolution,
108
+ * but the resolution strategies differ:
109
+ *
110
+ * **Backward Exact (`<-`)**: Uses foreign key lookup (exact ID match)
111
+ * - Queries `provider.list(type, { where: { backrefField: id } })`
112
+ * - Returns all entities that explicitly reference the current entity
113
+ * - Example: `Blog.posts: ['<-Post']` finds Posts where `post.blog === blog.$id`
114
+ *
115
+ * **Backward Fuzzy (`<~`)**: Uses pre-resolved IDs from semantic search
116
+ * - During creation, `resolveBackwardFuzzy()` stores matched IDs
117
+ * - Hydration retrieves entities using those stored IDs
118
+ * - Example: `ICP.as: '<~Occupation'` was resolved via semantic search at creation time
119
+ *
120
+ * ## Resolution Cases
121
+ *
122
+ * - Single backward ref with stored ID: resolve directly (e.g., member.team = teamId)
83
123
  * - Single backward ref without stored ID: find related entity that points to us via relations
84
- * - Array backward ref: find all entities of related type where their forward ref points to us
124
+ * - Array backward ref with stored IDs: resolve each stored ID (backward fuzzy case)
125
+ * - Array backward ref without stored IDs: query via backref lookup (backward exact case)
85
126
  */
86
127
  export declare function hydrateEntity(data: Record<string, unknown>, entity: ParsedEntity, schema: ParsedSchema, resolveProvider: () => Promise<DBProvider>): Record<string, unknown>;
87
128
  //# sourceMappingURL=resolve.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/schema/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,YAAY,EAEb,MAAM,aAAa,CAAA;AAEpB,OAAO,KAAK,EAAE,UAAU,EAAwB,MAAM,eAAe,CAAA;AAErE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAO/C;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOjD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,YAAY,GAAG,MAAM,GAAG,SAAS,CAcpH;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,UAAU,GACnB,OAAO,CAAC,OAAO,CAAC,CAqClB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CACvC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,UAAU,GACnB,OAAO,CAAC,MAAM,CAAC,CAcjB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EAAE,EACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,UAAU,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAsC/C;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAIzD;AAMD;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,UAAU,GACnB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAoBlC;AAMD;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,aAAa,EACnB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,UAAU,EACpB,yBAAyB,EAAE,CACzB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAChC,MAAM,GACV,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyExB;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,YAAY,EACpB,eAAe,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,GACzC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA+RzB"}
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/schema/resolve.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { hasSemanticSearch } from './provider.js';
10
10
  import { isPrimitiveType } from './parse.js';
11
+ import { findBestMatchAcrossTypes } from './search-utils.js';
11
12
  // =============================================================================
12
13
  // Context Resolution - Template Variables and $instructions
13
14
  // =============================================================================
@@ -104,63 +105,132 @@ export async function resolveInstructions(instructions, entity, typeName, schema
104
105
  return resolved;
105
106
  }
106
107
  /**
107
- * Pre-fetch context dependencies declared in $context
108
+ * Find the field in the entity that references a given type
109
+ */
110
+ function findFieldByRelatedType(entityDef, relatedType) {
111
+ for (const [fieldName, field] of entityDef.fields) {
112
+ if (field.isRelation && field.relatedType === relatedType) {
113
+ return { fieldName, field };
114
+ }
115
+ }
116
+ return undefined;
117
+ }
118
+ /**
119
+ * Pre-fetch context dependencies declared in $context using dotted paths
108
120
  *
109
121
  * The $context field declares explicit context dependencies that should
110
- * be pre-fetched before generating AI fields. This ensures all referenced
111
- * entities are available for template variable resolution.
122
+ * be pre-fetched before generating AI fields. Supports dotted paths like
123
+ * 'Startup.icp.industry' for deeply nested context fetching.
112
124
  *
113
- * @param contextDeps - Array of context dependency types (e.g., ['Startup', 'ICP'])
125
+ * Context paths can be:
126
+ * - Simple type names: 'Startup' - finds field that references Startup type
127
+ * - Dotted paths: 'Startup.icp.industry' - traverses relationships
128
+ *
129
+ * @param contextDeps - Array of context dependency paths (e.g., ['Startup', 'Startup.icp.industry'])
114
130
  * @param entity - The current entity data
115
131
  * @param typeName - The current entity type name
116
132
  * @param schema - The parsed schema
117
133
  * @param provider - The database provider for fetching related entities
118
- * @returns Map of context name to fetched entity data
134
+ * @returns Map of context path to fetched entity data
119
135
  */
120
- export async function prefetchContext(contextDeps, entity, typeName, schema, provider) {
136
+ export async function prefetchContextPaths(contextDeps, entity, typeName, schema, provider) {
121
137
  const contextData = new Map();
122
138
  const currentEntity = schema.entities.get(typeName);
123
139
  if (!currentEntity)
124
140
  return contextData;
125
- for (const dep of contextDeps) {
126
- // Convert to camelCase for field lookup (e.g., "Startup" -> "startup")
127
- const fieldName = dep.charAt(0).toLowerCase() + dep.slice(1);
128
- // Check if we have a field that references this type
129
- const field = currentEntity.fields.get(fieldName);
130
- if (field?.isRelation && field.relatedType) {
131
- const entityId = entity[fieldName];
132
- if (typeof entityId === 'string') {
133
- const fetched = await provider.get(field.relatedType, entityId);
134
- if (fetched) {
135
- contextData.set(fieldName, fetched);
136
- // Also store nested relationships
137
- const relatedEntity = schema.entities.get(field.relatedType);
138
- if (relatedEntity) {
139
- for (const [nestedFieldName, nestedField] of relatedEntity.fields) {
140
- if (nestedField.isRelation && nestedField.relatedType) {
141
- const nestedId = fetched[nestedFieldName];
142
- if (typeof nestedId === 'string') {
143
- const nestedFetched = await provider.get(nestedField.relatedType, nestedId);
144
- if (nestedFetched) {
145
- contextData.set(`${fieldName}.${nestedFieldName}`, nestedFetched);
146
- }
147
- }
148
- }
149
- }
150
- }
141
+ // Track already fetched entities to avoid duplicate fetches
142
+ // Key: "TypeName:entityId", Value: fetched entity data
143
+ const fetchCache = new Map();
144
+ // Deduplicate context deps
145
+ const uniqueDeps = [...new Set(contextDeps)];
146
+ for (const dep of uniqueDeps) {
147
+ // Parse the path - could be "Startup" or "Startup.icp.industry"
148
+ const pathParts = dep.split('.');
149
+ // Start from the current entity and traverse the path
150
+ let currentData = entity;
151
+ let currentEntityDef = currentEntity;
152
+ let currentPath = '';
153
+ for (let i = 0; i < pathParts.length; i++) {
154
+ const part = pathParts[i];
155
+ // Try two approaches:
156
+ // 1. Convert PascalCase type name to camelCase field name
157
+ // 2. Find field by related type
158
+ const camelCaseFieldName = part.charAt(0).toLowerCase() + part.slice(1);
159
+ let fieldInfo;
160
+ // First try exact field name match
161
+ const directField = currentEntityDef.fields.get(camelCaseFieldName);
162
+ if (directField?.isRelation && directField.relatedType) {
163
+ fieldInfo = { fieldName: camelCaseFieldName, field: directField };
164
+ }
165
+ // If no direct match, try to find field by related type (e.g., 'Related' -> find field that points to Related)
166
+ if (!fieldInfo) {
167
+ fieldInfo = findFieldByRelatedType(currentEntityDef, part);
168
+ }
169
+ if (!fieldInfo) {
170
+ // Path invalid - no field found
171
+ break;
172
+ }
173
+ const { fieldName, field } = fieldInfo;
174
+ currentPath = currentPath ? `${currentPath}.${fieldName}` : fieldName;
175
+ // Get the entity ID from current data
176
+ const entityId = currentData[fieldName];
177
+ if (typeof entityId !== 'string') {
178
+ // Path invalid - no entity ID
179
+ break;
180
+ }
181
+ // Check cache first
182
+ const cacheKey = `${field.relatedType}:${entityId}`;
183
+ let fetched = fetchCache.get(cacheKey);
184
+ if (!fetched) {
185
+ // Fetch the related entity
186
+ const fetchedResult = await provider.get(field.relatedType, entityId);
187
+ if (fetchedResult) {
188
+ fetched = fetchedResult;
189
+ fetchCache.set(cacheKey, fetched);
151
190
  }
152
191
  }
192
+ if (!fetched) {
193
+ // Path invalid - entity not found
194
+ break;
195
+ }
196
+ // Store the result with the actual field name path
197
+ contextData.set(currentPath, fetched);
198
+ // Update for next iteration
199
+ currentData = fetched;
200
+ const nextEntityDef = schema.entities.get(field.relatedType);
201
+ if (!nextEntityDef) {
202
+ // Path invalid - entity type not in schema
203
+ break;
204
+ }
205
+ currentEntityDef = nextEntityDef;
153
206
  }
154
207
  }
155
208
  return contextData;
156
209
  }
157
210
  /**
158
- * Check if a field type is a prompt (contains spaces, indicating AI generation)
211
+ * Pre-fetch context dependencies declared in $context
212
+ *
213
+ * The $context field declares explicit context dependencies that should
214
+ * be pre-fetched before generating AI fields. This ensures all referenced
215
+ * entities are available for template variable resolution.
216
+ *
217
+ * @param contextDeps - Array of context dependency types (e.g., ['Startup', 'ICP'])
218
+ * @param entity - The current entity data
219
+ * @param typeName - The current entity type name
220
+ * @param schema - The parsed schema
221
+ * @param provider - The database provider for fetching related entities
222
+ * @returns Map of context name to fetched entity data
223
+ */
224
+ export async function prefetchContext(contextDeps, entity, typeName, schema, provider) {
225
+ // Use the new path-based implementation which handles both simple and dotted paths
226
+ return prefetchContextPaths(contextDeps, entity, typeName, schema, provider);
227
+ }
228
+ /**
229
+ * Check if a field type is a prompt (contains spaces, slashes, or question marks indicating AI generation).
230
+ * Examples: 'Describe the product', 'low/medium/high', 'What is the severity?'
159
231
  */
160
232
  export function isPromptField(field) {
161
- // Fields with spaces in their type are prompts for AI generation
162
- return field.type.includes(' ') ||
163
- (field.type === 'string' && !field.isRelation && !isPrimitiveType(field.type));
233
+ return field.type.includes(' ') || field.type.includes('/') || field.type.includes('?');
164
234
  }
165
235
  // =============================================================================
166
236
  // Nested Pending Resolution
@@ -184,7 +254,7 @@ export async function resolveNestedPending(data, entity, schema, provider) {
184
254
  if (relatedEntity) {
185
255
  const resolvedNested = await resolveNestedPending(pending.data, relatedEntity, schema, provider);
186
256
  const created = await provider.create(pending.type, undefined, resolvedNested);
187
- resolved[fieldName] = created.$id;
257
+ resolved[fieldName] = created['$id'];
188
258
  }
189
259
  }
190
260
  }
@@ -199,7 +269,7 @@ export async function resolveNestedPending(data, entity, schema, provider) {
199
269
  * For exact matches (-> and <-), creates new entities.
200
270
  * For fuzzy matches (~> and <~), searches for existing entities first.
201
271
  */
202
- export async function resolveReferenceSpec(spec, contextData, schema, provider, generateContextAwareValue) {
272
+ export async function resolveReferenceSpec(spec, contextData, schema, provider, generateContextAwareValue, generateEntityWithAI) {
203
273
  const targetEntity = schema.entities.get(spec.type);
204
274
  if (!targetEntity) {
205
275
  throw new Error(`Unknown target type: ${spec.type}`);
@@ -208,66 +278,123 @@ export async function resolveReferenceSpec(spec, contextData, schema, provider,
208
278
  // For fuzzy references, try to find an existing entity first
209
279
  if (hasSemanticSearch(provider)) {
210
280
  const searchQuery = spec.generatedText || spec.prompt || spec.field;
211
- const matches = await provider.semanticSearch(spec.type, searchQuery, {
212
- minScore: 0.5,
213
- limit: 1,
281
+ // Use threshold from spec, or default to 0.75
282
+ const threshold = spec.threshold ?? 0.75;
283
+ // Determine which types to search - all union types or just the primary type
284
+ const typesToSearch = spec.unionTypes && spec.unionTypes.length > 0 ? spec.unionTypes : [spec.type];
285
+ // Search all types and find the best match using shared utility
286
+ const bestMatchResult = await findBestMatchAcrossTypes(typesToSearch, searchQuery, {
287
+ threshold,
288
+ limit: 5,
289
+ schema,
290
+ provider,
214
291
  });
215
- const firstMatch = matches[0];
216
- if (firstMatch) {
217
- return firstMatch.$id;
292
+ if (bestMatchResult) {
293
+ // Update the matched entity with metadata
294
+ await provider.update(bestMatchResult.type, bestMatchResult.match.$id, {
295
+ $matchedType: bestMatchResult.type,
296
+ $similarity: bestMatchResult.match.$score,
297
+ });
298
+ // Also set metadata on contextData for edge creation later
299
+ contextData[`${spec.field}$matchedType`] = bestMatchResult.type;
300
+ contextData[`${spec.field}$score`] = bestMatchResult.match.$score;
301
+ return bestMatchResult.match.$id;
218
302
  }
219
303
  }
220
304
  // If no match found for fuzzy, fall through to create
221
305
  }
222
306
  // Create a new entity
223
- const generatedData = {};
224
- // Build context for generation
307
+ let generatedData = {};
225
308
  const hint = spec.generatedText || spec.prompt || spec.field;
226
- const parentContextFields = [];
227
- for (const [key, value] of Object.entries(contextData)) {
228
- if (!key.startsWith('$') && !key.startsWith('_') && typeof value === 'string' && value) {
229
- parentContextFields.push(`${key}: ${value}`);
309
+ const parentType = contextData['$type'];
310
+ const parentId = contextData['$id'];
311
+ // Try AI generation first if available
312
+ if (generateEntityWithAI && parentType) {
313
+ try {
314
+ const aiData = await generateEntityWithAI(spec.type, hint, {
315
+ parent: parentType,
316
+ parentData: contextData,
317
+ ...(parentId !== undefined ? { parentId } : {}),
318
+ }, schema);
319
+ if (aiData && Object.keys(aiData).length > 0) {
320
+ // Resolve any nested pending relations (from generateEntity)
321
+ generatedData = await resolveNestedPending(aiData, targetEntity, schema, provider);
322
+ }
323
+ }
324
+ catch {
325
+ // AI generation failed - fall through to placeholder generation
230
326
  }
231
327
  }
232
- const fullContext = [hint, ...parentContextFields].filter(Boolean).join(' | ');
233
- // Generate default values for the target entity's fields
234
- for (const [fieldName, field] of targetEntity.fields) {
235
- if (!field.isRelation && !field.isOptional) {
236
- if (field.type === 'string') {
237
- generatedData[fieldName] = generateContextAwareValue(fieldName, spec.type, fullContext, hint, contextData);
238
- }
239
- else if (field.type === 'number') {
240
- generatedData[fieldName] = 0;
241
- }
242
- else if (field.type === 'boolean') {
243
- generatedData[fieldName] = false;
328
+ // If AI generation didn't produce data, fall back to placeholder generation
329
+ if (Object.keys(generatedData).length === 0) {
330
+ // Build context for generation
331
+ const parentContextFields = [];
332
+ for (const [key, value] of Object.entries(contextData)) {
333
+ if (!key.startsWith('$') && !key.startsWith('_') && typeof value === 'string' && value) {
334
+ parentContextFields.push(`${key}: ${value}`);
244
335
  }
245
336
  }
246
- else if (field.isRelation && field.operator === '->' && !field.isArray && !field.isOptional) {
247
- // Recursively resolve nested forward exact relations
248
- const nestedSpec = {
249
- field: fieldName,
250
- operator: '->',
251
- type: field.relatedType,
252
- matchMode: 'exact',
253
- resolved: false,
254
- prompt: field.prompt,
255
- };
256
- const nestedId = await resolveReferenceSpec(nestedSpec, generatedData, schema, provider, generateContextAwareValue);
257
- if (nestedId) {
258
- generatedData[fieldName] = nestedId;
337
+ // Include source entity's $instructions (parent context) and target entity's $instructions
338
+ const sourceInstructions = spec.sourceInstructions;
339
+ const targetInstructions = targetEntity.schema?.['$instructions'];
340
+ const contextParts = [];
341
+ // Source instructions first (parent context propagation)
342
+ if (sourceInstructions) {
343
+ contextParts.push(sourceInstructions);
344
+ }
345
+ // Then target instructions (child entity's own context)
346
+ if (targetInstructions) {
347
+ contextParts.push(targetInstructions);
348
+ }
349
+ if (hint) {
350
+ contextParts.push(hint);
351
+ }
352
+ contextParts.push(...parentContextFields);
353
+ const fullContext = contextParts.filter(Boolean).join(' | ');
354
+ // Generate default values for the target entity's fields
355
+ for (const [fieldName, field] of targetEntity.fields) {
356
+ if (!field.isRelation && !field.isOptional) {
357
+ const isPrompt = isPromptField(field);
358
+ if (field.type === 'string' || isPrompt) {
359
+ // For prompt fields, use the type as additional context for generation
360
+ const fieldHint = isPrompt ? field.type : hint;
361
+ generatedData[fieldName] = generateContextAwareValue(fieldName, spec.type, fullContext, fieldHint, contextData);
362
+ }
363
+ else if (field.type === 'number') {
364
+ generatedData[fieldName] = 0;
365
+ }
366
+ else if (field.type === 'boolean') {
367
+ generatedData[fieldName] = false;
368
+ }
369
+ }
370
+ else if (field.isRelation &&
371
+ field.operator === '->' &&
372
+ !field.isArray &&
373
+ !field.isOptional) {
374
+ // Recursively resolve nested forward exact relations
375
+ const nestedSpec = {
376
+ field: fieldName,
377
+ operator: '->',
378
+ type: field.relatedType,
379
+ matchMode: 'exact',
380
+ resolved: false,
381
+ ...(field.prompt !== undefined ? { prompt: field.prompt } : {}),
382
+ };
383
+ const nestedId = await resolveReferenceSpec(nestedSpec, generatedData, schema, provider, generateContextAwareValue, generateEntityWithAI);
384
+ if (nestedId) {
385
+ generatedData[fieldName] = nestedId;
386
+ }
259
387
  }
260
388
  }
261
389
  }
262
390
  // Use parent ID if available, otherwise use a descriptive string
263
- const parentId = contextData.$id;
264
391
  const created = await provider.create(spec.type, undefined, {
265
392
  ...generatedData,
266
393
  $generated: true,
267
394
  $generatedBy: parentId || (spec.matchMode === 'fuzzy' ? 'fuzzy-resolution' : 'reference-resolution'),
268
395
  $sourceField: spec.field,
269
396
  });
270
- return created.$id;
397
+ return created['$id'];
271
398
  }
272
399
  // =============================================================================
273
400
  // Entity Hydration
@@ -279,14 +406,31 @@ export async function resolveReferenceSpec(spec, contextData, schema, provider,
279
406
  * of the related type that have a reference pointing TO this entity.
280
407
  * This enables reverse lookups like "get all comments for a post".
281
408
  *
282
- * Backward reference resolution:
283
- * - Single backward ref with stored ID: resolve directly (e.g., member.team = teamId -> get Team by ID)
409
+ * ## Backward Exact (<-) vs Backward Fuzzy (<~) Resolution
410
+ *
411
+ * This function handles both backward exact and backward fuzzy resolution,
412
+ * but the resolution strategies differ:
413
+ *
414
+ * **Backward Exact (`<-`)**: Uses foreign key lookup (exact ID match)
415
+ * - Queries `provider.list(type, { where: { backrefField: id } })`
416
+ * - Returns all entities that explicitly reference the current entity
417
+ * - Example: `Blog.posts: ['<-Post']` finds Posts where `post.blog === blog.$id`
418
+ *
419
+ * **Backward Fuzzy (`<~`)**: Uses pre-resolved IDs from semantic search
420
+ * - During creation, `resolveBackwardFuzzy()` stores matched IDs
421
+ * - Hydration retrieves entities using those stored IDs
422
+ * - Example: `ICP.as: '<~Occupation'` was resolved via semantic search at creation time
423
+ *
424
+ * ## Resolution Cases
425
+ *
426
+ * - Single backward ref with stored ID: resolve directly (e.g., member.team = teamId)
284
427
  * - Single backward ref without stored ID: find related entity that points to us via relations
285
- * - Array backward ref: find all entities of related type where their forward ref points to us
428
+ * - Array backward ref with stored IDs: resolve each stored ID (backward fuzzy case)
429
+ * - Array backward ref without stored IDs: query via backref lookup (backward exact case)
286
430
  */
287
431
  export function hydrateEntity(data, entity, schema, resolveProvider) {
288
432
  const hydrated = { ...data };
289
- const id = (data.$id || data.id);
433
+ const id = (data['$id'] || data['id']);
290
434
  const typeName = entity.name;
291
435
  // Add lazy getters for relations
292
436
  for (const [fieldName, field] of entity.fields) {
@@ -302,9 +446,7 @@ export function hydrateEntity(data, entity, schema, resolveProvider) {
302
446
  if (!isBackward && !field.isArray && data[fieldName]) {
303
447
  const storedId = data[fieldName];
304
448
  // For union types, we need to check all possible types
305
- const typesToSearch = field.unionTypes && field.unionTypes.length > 0
306
- ? field.unionTypes
307
- : [field.relatedType];
449
+ const typesToSearch = field.unionTypes && field.unionTypes.length > 0 ? field.unionTypes : [field.relatedType];
308
450
  // Check if we have a stored matchedType from the creation
309
451
  const storedMatchedType = data[`${fieldName}$matchedType`];
310
452
  const thenableProxy = new Proxy({}, {
@@ -352,6 +494,12 @@ export function hydrateEntity(data, entity, schema, resolveProvider) {
352
494
  if (prop === '$type') {
353
495
  return storedMatchedType || field.relatedType;
354
496
  }
497
+ // Return the stored ID for $id property access (useful for equality checks)
498
+ if (prop === '$id') {
499
+ return storedId;
500
+ }
501
+ // For any other property access, return undefined
502
+ // Note: Use problem.task.$id or String(problem.task) for equality checks
355
503
  return undefined;
356
504
  },
357
505
  });
@@ -365,9 +513,7 @@ export function hydrateEntity(data, entity, schema, resolveProvider) {
365
513
  if (!isBackward && field.isArray && Array.isArray(data[fieldName])) {
366
514
  const storedIds = data[fieldName];
367
515
  // For union types, we need to check all possible types
368
- const typesToSearch = field.unionTypes && field.unionTypes.length > 0
369
- ? field.unionTypes
370
- : [field.relatedType];
516
+ const typesToSearch = field.unionTypes && field.unionTypes.length > 0 ? field.unionTypes : [field.relatedType];
371
517
  // Create a proxy that wraps the array but adds thenable behavior
372
518
  const thenableArray = new Proxy(storedIds, {
373
519
  get(target, prop) {
@@ -428,9 +574,7 @@ export function hydrateEntity(data, entity, schema, resolveProvider) {
428
574
  if (storedId) {
429
575
  // Has stored ID - directly fetch the related entity
430
576
  const result = await provider.get(field.relatedType, storedId);
431
- return result
432
- ? hydrateEntity(result, relatedEntity, schema, resolveProvider)
433
- : null;
577
+ return result ? hydrateEntity(result, relatedEntity, schema, resolveProvider) : null;
434
578
  }
435
579
  // No stored ID - find via inverse relation lookup
436
580
  // Find entities of relatedType that have this entity in their relations
@@ -443,9 +587,9 @@ export function hydrateEntity(data, entity, schema, resolveProvider) {
443
587
  // Check if any entity of relatedType has this entity in that relation
444
588
  const allRelated = await provider.list(field.relatedType);
445
589
  for (const candidate of allRelated) {
446
- const candidateId = (candidate.$id || candidate.id);
590
+ const candidateId = (candidate['$id'] || candidate['id']);
447
591
  const related = await provider.related(field.relatedType, candidateId, relFieldName);
448
- if (related.some(r => (r.$id || r.id) === id)) {
592
+ if (related.some((r) => (r['$id'] || r['id']) === id)) {
449
593
  return hydrateEntity(candidate, relatedEntity, schema, resolveProvider);
450
594
  }
451
595
  }
@@ -533,9 +677,7 @@ export function hydrateEntity(data, entity, schema, resolveProvider) {
533
677
  if (!relatedId)
534
678
  return null;
535
679
  const result = await provider.get(field.relatedType, relatedId);
536
- return result
537
- ? hydrateEntity(result, relatedEntity, schema, resolveProvider)
538
- : null;
680
+ return result ? hydrateEntity(result, relatedEntity, schema, resolveProvider) : null;
539
681
  }
540
682
  })();
541
683
  },