@memberjunction/server 5.23.0 → 5.25.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 (63) hide show
  1. package/dist/agents/skip-sdk.d.ts +12 -0
  2. package/dist/agents/skip-sdk.d.ts.map +1 -1
  3. package/dist/agents/skip-sdk.js +70 -1
  4. package/dist/agents/skip-sdk.js.map +1 -1
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/config.js +11 -0
  7. package/dist/config.js.map +1 -1
  8. package/dist/generated/generated.d.ts +954 -0
  9. package/dist/generated/generated.d.ts.map +1 -1
  10. package/dist/generated/generated.js +26108 -20749
  11. package/dist/generated/generated.js.map +1 -1
  12. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  13. package/dist/generic/RunViewResolver.js.map +1 -1
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/resolvers/ArtifactFileResolver.d.ts +15 -0
  19. package/dist/resolvers/ArtifactFileResolver.d.ts.map +1 -0
  20. package/dist/resolvers/ArtifactFileResolver.js +74 -0
  21. package/dist/resolvers/ArtifactFileResolver.js.map +1 -0
  22. package/dist/resolvers/AutotagPipelineResolver.d.ts +23 -1
  23. package/dist/resolvers/AutotagPipelineResolver.d.ts.map +1 -1
  24. package/dist/resolvers/AutotagPipelineResolver.js +197 -13
  25. package/dist/resolvers/AutotagPipelineResolver.js.map +1 -1
  26. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  27. package/dist/resolvers/FetchEntityVectorsResolver.js +6 -2
  28. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  29. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  30. package/dist/resolvers/FileResolver.js +12 -32
  31. package/dist/resolvers/FileResolver.js.map +1 -1
  32. package/dist/resolvers/GeoResolver.d.ts +58 -0
  33. package/dist/resolvers/GeoResolver.d.ts.map +1 -0
  34. package/dist/resolvers/GeoResolver.js +302 -0
  35. package/dist/resolvers/GeoResolver.js.map +1 -0
  36. package/dist/resolvers/RunAIAgentResolver.d.ts +34 -1
  37. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  38. package/dist/resolvers/RunAIAgentResolver.js +183 -48
  39. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  40. package/dist/resolvers/SearchKnowledgeResolver.d.ts +23 -41
  41. package/dist/resolvers/SearchKnowledgeResolver.d.ts.map +1 -1
  42. package/dist/resolvers/SearchKnowledgeResolver.js +133 -382
  43. package/dist/resolvers/SearchKnowledgeResolver.js.map +1 -1
  44. package/dist/resolvers/SearchKnowledgeSystemUserResolver.d.ts +19 -0
  45. package/dist/resolvers/SearchKnowledgeSystemUserResolver.d.ts.map +1 -0
  46. package/dist/resolvers/SearchKnowledgeSystemUserResolver.js +149 -0
  47. package/dist/resolvers/SearchKnowledgeSystemUserResolver.js.map +1 -0
  48. package/package.json +63 -63
  49. package/src/__tests__/search-knowledge-tags.test.ts +255 -0
  50. package/src/__tests__/skip-sdk-organic-keys.test.ts +274 -0
  51. package/src/agents/skip-sdk.ts +83 -2
  52. package/src/config.ts +11 -0
  53. package/src/generated/generated.ts +3690 -1
  54. package/src/generic/RunViewResolver.ts +1 -0
  55. package/src/index.ts +2 -0
  56. package/src/resolvers/ArtifactFileResolver.ts +71 -0
  57. package/src/resolvers/AutotagPipelineResolver.ts +213 -10
  58. package/src/resolvers/FetchEntityVectorsResolver.ts +6 -2
  59. package/src/resolvers/FileResolver.ts +12 -41
  60. package/src/resolvers/GeoResolver.ts +258 -0
  61. package/src/resolvers/RunAIAgentResolver.ts +229 -76
  62. package/src/resolvers/SearchKnowledgeResolver.ts +118 -462
  63. package/src/resolvers/SearchKnowledgeSystemUserResolver.ts +138 -0
@@ -765,6 +765,7 @@ export class RunViewResolver extends ResolverBase {
765
765
  for (const [index, data] of rawData.entries()) {
766
766
  // EntityName is backfilled by RunViewsGeneric when ViewID/ViewName was used
767
767
  const entity = input[index].EntityName ? provider.Entities.find((e) => e.Name === input[index].EntityName) : null;
768
+
768
769
  const returnData: any[] = this.processRawData(data.Results, entity ? entity.ID : null, entity);
769
770
 
770
771
  results.push({
package/src/index.ts CHANGED
@@ -108,6 +108,7 @@ export * from './generic/DeleteOptionsInput.js';
108
108
  export * from './agents/skip-agent.js';
109
109
  export * from './agents/skip-sdk.js';
110
110
 
111
+ export * from './resolvers/GeoResolver.js';
111
112
  export * from './resolvers/ColorResolver.js';
112
113
  export * from './resolvers/ComponentRegistryResolver.js';
113
114
  export * from './resolvers/DatasetResolver.js';
@@ -130,6 +131,7 @@ export * from './resolvers/ActionResolver.js';
130
131
  export * from './resolvers/EntityCommunicationsResolver.js';
131
132
  export * from './resolvers/EntityResolver.js';
132
133
  export * from './resolvers/ISAEntityResolver.js';
134
+ export * from './resolvers/ArtifactFileResolver.js';
133
135
  export * from './resolvers/FileCategoryResolver.js';
134
136
  export * from './resolvers/FileResolver.js';
135
137
  export * from './resolvers/InfoResolver.js';
@@ -0,0 +1,71 @@
1
+ import { Resolver, Query, Arg, Ctx } from 'type-graphql';
2
+ import { Metadata, IMetadataProvider } from '@memberjunction/core';
3
+ import { MJArtifactVersionEntity, MJFileEntity } from '@memberjunction/core-entities';
4
+ import { FileStorageEngine } from '@memberjunction/storage';
5
+ import { ResolverBase } from '../generic/ResolverBase.js';
6
+ import { AppContext } from '../types.js';
7
+ import { GetReadWriteProvider } from '../util.js';
8
+
9
+ /**
10
+ * GraphQL resolver that produces a short-lived download URL for an artifact version
11
+ * stored as a binary file (ContentMode = 'File').
12
+ *
13
+ * Separating this from the generated FileResolver keeps the artifact-specific
14
+ * logic in one place and avoids mixing concerns in the large FileResolver class.
15
+ */
16
+ @Resolver()
17
+ export class ArtifactFileResolver extends ResolverBase {
18
+
19
+ @Query(() => String, { description: 'Returns a pre-authenticated download URL for an artifact version whose ContentMode is "File".' })
20
+ async ArtifactFileDownloadUrl(
21
+ @Arg('artifactVersionId', () => String) artifactVersionId: string,
22
+ @Ctx() context: AppContext,
23
+ ): Promise<string> {
24
+ const user = this.GetUserFromPayload(context.userPayload);
25
+ if (!user) {
26
+ throw new Error('Unauthorized');
27
+ }
28
+
29
+ const p = GetReadWriteProvider(context.providers);
30
+
31
+ // Load the artifact version
32
+ const artifactVersion = await p.GetEntityObject<MJArtifactVersionEntity>('MJ: Artifact Versions', user);
33
+ const loaded = await artifactVersion.Load(artifactVersionId);
34
+ if (!loaded) {
35
+ throw new Error(`Artifact version ${artifactVersionId} not found`);
36
+ }
37
+
38
+ if (artifactVersion.ContentMode !== 'File') {
39
+ throw new Error(`Artifact version ${artifactVersionId} is not a file artifact (ContentMode=${artifactVersion.ContentMode})`);
40
+ }
41
+
42
+ if (!artifactVersion.FileID) {
43
+ throw new Error(`Artifact version ${artifactVersionId} has no associated file`);
44
+ }
45
+
46
+ return this.buildDownloadUrl(artifactVersion.FileID, user, p);
47
+ }
48
+
49
+ /** Load the File + its storage account/provider and generate a signed URL. */
50
+ private async buildDownloadUrl(
51
+ fileId: string,
52
+ user: ReturnType<ResolverBase['GetUserFromPayload']>,
53
+ provider: IMetadataProvider,
54
+ ): Promise<string> {
55
+ const fileEntity = await provider.GetEntityObject<MJFileEntity>('MJ: Files', user);
56
+ const fileLoaded = await fileEntity.Load(fileId);
57
+ if (!fileLoaded) {
58
+ throw new Error(`File record ${fileId} not found`);
59
+ }
60
+
61
+ // Find the storage account for this file's provider using cached metadata
62
+ await FileStorageEngine.Instance.Config(false, user!);
63
+ const matchingAccounts = FileStorageEngine.Instance.GetAccountsByProviderID(fileEntity.ProviderID);
64
+ if (matchingAccounts.length === 0) {
65
+ throw new Error(`No FileStorageAccount found for ProviderID ${fileEntity.ProviderID}. Cannot generate download URL.`);
66
+ }
67
+
68
+ const driver = await FileStorageEngine.Instance.GetDriver(matchingAccounts[0].ID, user!);
69
+ return driver.CreatePreAuthDownloadUrl(fileEntity.ProviderKey ?? fileEntity.Name);
70
+ }
71
+ }
@@ -1,6 +1,7 @@
1
- import { Resolver, Mutation, Ctx, ObjectType, Field } from 'type-graphql';
1
+ import { Resolver, Mutation, Ctx, Arg, ObjectType, Field } from 'type-graphql';
2
2
  import { AppContext } from '../types.js';
3
- import { LogError, LogStatus } from '@memberjunction/core';
3
+ import { LogError, LogStatus, Metadata, RunView, UserInfo } from '@memberjunction/core';
4
+ import { MJContentProcessRunEntity } from '@memberjunction/core-entities';
4
5
  import { ResolverBase } from '../generic/ResolverBase.js';
5
6
  import { ActionEngineServer } from '@memberjunction/actions';
6
7
  import { PubSubManager } from '../generic/PubSubManager.js';
@@ -28,6 +29,8 @@ export class AutotagPipelineResult {
28
29
  export class AutotagPipelineResolver extends ResolverBase {
29
30
  @Mutation(() => AutotagPipelineResult)
30
31
  async RunAutotagPipeline(
32
+ @Arg('contentSourceIDs', () => [String], { nullable: true }) contentSourceIDs: string[] | undefined,
33
+ @Arg('forceReprocess', { nullable: true }) forceReprocess: boolean | undefined,
31
34
  @Ctx() { userPayload }: AppContext = {} as AppContext
32
35
  ): Promise<AutotagPipelineResult> {
33
36
  try {
@@ -40,7 +43,7 @@ export class AutotagPipelineResolver extends ResolverBase {
40
43
  LogStatus(`RunAutotagPipeline: starting pipeline ${pipelineRunID}`);
41
44
 
42
45
  // Fire-and-forget: start the pipeline in the background and return immediately
43
- this.runPipelineInBackground(pipelineRunID, currentUser);
46
+ this.runPipelineInBackground(pipelineRunID, currentUser, contentSourceIDs, forceReprocess);
44
47
 
45
48
  return {
46
49
  Success: true,
@@ -64,9 +67,13 @@ export class AutotagPipelineResolver extends ResolverBase {
64
67
  */
65
68
  private async runPipelineInBackground(
66
69
  pipelineRunID: string,
67
- currentUser: import('@memberjunction/core').UserInfo
70
+ currentUser: UserInfo,
71
+ contentSourceIDs?: string[],
72
+ forceReprocess?: boolean
68
73
  ): Promise<void> {
69
74
  const startTime = Date.now();
75
+ const processRun = await this.createProcessRun(pipelineRunID, currentUser, contentSourceIDs);
76
+
70
77
  try {
71
78
  this.publishProgress(pipelineRunID, 'autotag', 0, 0, startTime, 'Initializing pipeline...');
72
79
 
@@ -78,27 +85,39 @@ export class AutotagPipelineResolver extends ResolverBase {
78
85
  if (!action) {
79
86
  LogError(`RunAutotagPipeline: Action 'Autotag and Vectorize Content' not found`);
80
87
  this.publishProgress(pipelineRunID, 'error', 0, 0, startTime, 'Autotag action not found');
88
+ await this.completeProcessRun(processRun, 'Failed', 'Autotag action not found', currentUser);
81
89
  return;
82
90
  }
83
91
 
84
92
  // Stage: autotagging — provide a progress callback that publishes per-item updates
93
+ // and keeps the process run record in sync
85
94
  this.publishProgress(pipelineRunID, 'autotag', 0, 0, startTime, 'Running autotaggers...');
86
95
 
87
96
  const progressCallback = (processed: number, total: number, currentItem?: string) => {
88
97
  const pct = total > 0 ? Math.round((processed / total) * 80) : 0; // 0-80% for tagging
89
98
  this.publishProgress(pipelineRunID, 'autotag', total, pct, startTime, currentItem || `${processed}/${total} items`);
99
+ this.updateProcessRunProgress(processRun, processed, total);
90
100
  };
91
101
 
92
- // Run with both Autotag=1 and Vectorize=1: the action will tag and embed in parallel
102
+ // Build action params include the process run ID so the action can create detail records
103
+ const actionParams: Array<{ Name: string; Value: unknown; Type: 'Input' | 'Output' | 'Both' }> = [
104
+ { Name: 'Autotag', Value: 1, Type: 'Input' },
105
+ { Name: 'Vectorize', Value: 1, Type: 'Input' },
106
+ { Name: '__progressCallback', Value: progressCallback, Type: 'Input' },
107
+ { Name: 'ContentProcessRunID', Value: pipelineRunID, Type: 'Input' }
108
+ ];
109
+ if (contentSourceIDs && contentSourceIDs.length > 0) {
110
+ actionParams.push({ Name: 'ContentSourceIDs', Value: contentSourceIDs, Type: 'Input' });
111
+ }
112
+ if (forceReprocess) {
113
+ actionParams.push({ Name: 'ForceReprocess', Value: 1, Type: 'Input' });
114
+ }
115
+
93
116
  const result = await ActionEngineServer.Instance.RunAction({
94
117
  Action: action,
95
118
  ContextUser: currentUser,
96
119
  Filters: [],
97
- Params: [
98
- { Name: 'Autotag', Value: 1, Type: 'Input' },
99
- { Name: 'Vectorize', Value: 1, Type: 'Input' },
100
- { Name: '__progressCallback', Value: progressCallback, Type: 'Input' }
101
- ]
120
+ Params: actionParams
102
121
  });
103
122
 
104
123
  // Stage: vectorize complete
@@ -107,14 +126,198 @@ export class AutotagPipelineResolver extends ResolverBase {
107
126
  if (result.Success) {
108
127
  LogStatus(`RunAutotagPipeline: pipeline ${pipelineRunID} completed successfully`);
109
128
  this.publishProgress(pipelineRunID, 'complete', 100, 100, startTime);
129
+ await this.completeProcessRun(processRun, 'Completed', undefined, currentUser);
110
130
  } else {
111
131
  LogError(`RunAutotagPipeline: pipeline ${pipelineRunID} failed: ${result.Message}`);
112
132
  this.publishProgress(pipelineRunID, 'error', 0, 0, startTime, String(result.Message));
133
+ await this.completeProcessRun(processRun, 'Failed', String(result.Message), currentUser);
113
134
  }
114
135
  } catch (error) {
115
136
  const msg = error instanceof Error ? error.message : String(error);
116
137
  LogError(`RunAutotagPipeline pipeline ${pipelineRunID} failed: ${msg}`);
117
138
  this.publishProgress(pipelineRunID, 'error', 0, 0, startTime, msg);
139
+ await this.completeProcessRun(processRun, 'Failed', msg, currentUser);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Create a ContentProcessRun record to track this pipeline execution.
145
+ * Returns the entity so it can be updated during and after the run.
146
+ */
147
+ private async createProcessRun(
148
+ pipelineRunID: string,
149
+ currentUser: UserInfo,
150
+ contentSourceIDs?: string[]
151
+ ): Promise<MJContentProcessRunEntity | null> {
152
+ try {
153
+ // Resolve the source ID — use the specified source or the first available
154
+ let sourceID: string | undefined;
155
+ if (contentSourceIDs && contentSourceIDs.length > 0) {
156
+ sourceID = contentSourceIDs[0];
157
+ } else {
158
+ // Load content sources to get any available source ID (SourceID is NOT NULL)
159
+ const rv = new RunView();
160
+ const result = await rv.RunView<{ ID: string }>({
161
+ EntityName: 'MJ: Content Sources',
162
+ Fields: ['ID'],
163
+ ResultType: 'simple',
164
+ MaxRows: 1
165
+ }, currentUser);
166
+ if (result.Success && result.Results.length > 0) {
167
+ sourceID = result.Results[0].ID;
168
+ }
169
+ }
170
+
171
+ if (!sourceID) {
172
+ LogError('RunAutotagPipeline: no content sources available, cannot create process run');
173
+ return null;
174
+ }
175
+
176
+ const md = new Metadata();
177
+ const run = await md.GetEntityObject<MJContentProcessRunEntity>('MJ: Content Process Runs', currentUser);
178
+ run.NewRecord();
179
+ run.ID = pipelineRunID;
180
+ run.SourceID = sourceID;
181
+ run.StartTime = new Date();
182
+ run.Status = 'Running';
183
+ run.StartedByUserID = currentUser.ID;
184
+ run.ProcessedItems = 0;
185
+ run.TotalItemCount = 0;
186
+ run.LastProcessedOffset = 0;
187
+ run.ErrorCount = 0;
188
+ run.BatchSize = 20;
189
+ run.CancellationRequested = false;
190
+ const saved = await run.Save();
191
+ if (!saved) {
192
+ LogError(`RunAutotagPipeline: failed to create ContentProcessRun: ${run.LatestResult?.CompleteMessage}`);
193
+ return null;
194
+ }
195
+ LogStatus(`RunAutotagPipeline: created ContentProcessRun ${pipelineRunID}`);
196
+ return run;
197
+ } catch (error) {
198
+ const msg = error instanceof Error ? error.message : String(error);
199
+ LogError(`RunAutotagPipeline: error creating ContentProcessRun: ${msg}`);
200
+ return null;
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Update the process run record with current progress (fire-and-forget, non-blocking).
206
+ */
207
+ private updateProcessRunProgress(
208
+ processRun: MJContentProcessRunEntity | null,
209
+ processedItems: number,
210
+ totalItems: number
211
+ ): void {
212
+ if (!processRun) return;
213
+ processRun.ProcessedItems = processedItems;
214
+ processRun.TotalItemCount = totalItems;
215
+ processRun.LastProcessedOffset = processedItems;
216
+ // Fire-and-forget save — don't await to avoid slowing down the pipeline
217
+ processRun.Save().catch(e => {
218
+ LogError(`RunAutotagPipeline: error updating process run progress: ${e instanceof Error ? e.message : String(e)}`);
219
+ });
220
+ }
221
+
222
+ /**
223
+ * Mark the process run as completed, failed, or cancelled.
224
+ */
225
+ private async completeProcessRun(
226
+ processRun: MJContentProcessRunEntity | null,
227
+ status: string,
228
+ errorMessage: string | undefined,
229
+ currentUser: import('@memberjunction/core').UserInfo
230
+ ): Promise<void> {
231
+ if (!processRun) return;
232
+ try {
233
+ processRun.Status = status;
234
+ processRun.EndTime = new Date();
235
+ if (errorMessage) {
236
+ processRun.ErrorMessage = errorMessage;
237
+ processRun.ErrorCount = (processRun.ErrorCount ?? 0) + 1;
238
+ }
239
+ const saved = await processRun.Save();
240
+ if (!saved) {
241
+ LogError(`RunAutotagPipeline: failed to complete ContentProcessRun: ${processRun.LatestResult?.CompleteMessage}`);
242
+ }
243
+ } catch (error) {
244
+ LogError(`RunAutotagPipeline: error completing ContentProcessRun: ${error instanceof Error ? error.message : String(error)}`);
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Pause a running classification pipeline by setting CancellationRequested on the process run.
250
+ * The engine checks this flag between batches and pauses gracefully.
251
+ */
252
+ @Mutation(() => AutotagPipelineResult)
253
+ async PauseClassificationPipeline(
254
+ @Arg('processRunID') processRunID: string,
255
+ @Ctx() { userPayload }: AppContext = {} as AppContext
256
+ ): Promise<AutotagPipelineResult> {
257
+ try {
258
+ const currentUser = this.GetUserFromPayload(userPayload);
259
+ if (!currentUser) {
260
+ return { Success: false, Status: 'Error', ErrorMessage: 'Unable to determine current user' };
261
+ }
262
+
263
+ const md = new Metadata();
264
+ const run = await md.GetEntityObject<MJContentProcessRunEntity>('MJ: Content Process Runs', currentUser);
265
+ const loaded = await run.Load(processRunID);
266
+ if (!loaded) {
267
+ return { Success: false, Status: 'Error', ErrorMessage: `Process run ${processRunID} not found` };
268
+ }
269
+
270
+ run.CancellationRequested = true;
271
+ await run.Save();
272
+
273
+ LogStatus(`PauseClassificationPipeline: Pause requested for run ${processRunID}`);
274
+ return { Success: true, Status: 'PauseRequested' };
275
+ } catch (error) {
276
+ const msg = error instanceof Error ? error.message : String(error);
277
+ return { Success: false, Status: 'Error', ErrorMessage: msg };
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Resume a paused classification pipeline from its last completed offset.
283
+ */
284
+ @Mutation(() => AutotagPipelineResult)
285
+ async ResumeClassificationPipeline(
286
+ @Arg('processRunID') processRunID: string,
287
+ @Ctx() { userPayload }: AppContext = {} as AppContext
288
+ ): Promise<AutotagPipelineResult> {
289
+ try {
290
+ const currentUser = this.GetUserFromPayload(userPayload);
291
+ if (!currentUser) {
292
+ return { Success: false, Status: 'Error', ErrorMessage: 'Unable to determine current user' };
293
+ }
294
+
295
+ const md = new Metadata();
296
+ const run = await md.GetEntityObject<MJContentProcessRunEntity>('MJ: Content Process Runs', currentUser);
297
+ const loaded = await run.Load(processRunID);
298
+ if (!loaded) {
299
+ return { Success: false, Status: 'Error', ErrorMessage: `Process run ${processRunID} not found` };
300
+ }
301
+
302
+ if (run.Status !== 'Paused') {
303
+ return { Success: false, Status: 'Error', ErrorMessage: `Run is not paused (Status: ${run.Status})` };
304
+ }
305
+
306
+ // Reset cancellation flag and set status back to Running
307
+ run.CancellationRequested = false;
308
+ run.Status = 'Running';
309
+ await run.Save();
310
+
311
+ // Fire-and-forget: resume pipeline in background from the last offset
312
+ const pipelineRunID = uuidv4();
313
+ LogStatus(`ResumeClassificationPipeline: Resuming run ${processRunID} from offset ${run.LastProcessedOffset}`);
314
+
315
+ this.runPipelineInBackground(pipelineRunID, currentUser, undefined, undefined);
316
+
317
+ return { Success: true, Status: 'Resumed', PipelineRunID: pipelineRunID };
318
+ } catch (error) {
319
+ const msg = error instanceof Error ? error.message : String(error);
320
+ return { Success: false, Status: 'Error', ErrorMessage: msg };
118
321
  }
119
322
  }
120
323
 
@@ -90,13 +90,17 @@ export class FetchEntityVectorsResolver extends ResolverBase {
90
90
  // but the metadata filter ensures we only get vectors for this entity.
91
91
  const entityName = entityDoc.Entity;
92
92
  const dimensions = vectorIndex.Dimensions || 1536; // fall back to common embedding size
93
- const zeroVector = new Array(dimensions).fill(0);
93
+ // Use a tiny uniform vector instead of zero — cosine similarity is undefined
94
+ // for a zero vector (division by zero), causing Pinecone to return 0 matches.
95
+ // A uniform vector has equal similarity to all vectors, giving us an unbiased
96
+ // listing that respects the metadata filter.
97
+ const uniformVector = new Array(dimensions).fill(1.0 / Math.sqrt(dimensions));
94
98
 
95
99
  const metadataFilter: Record<string, unknown> = { Entity: { $eq: entityName } };
96
100
 
97
101
  const queryResponse = await vectorDBInstance.QueryIndex({
98
102
  id: vectorIndex.Name, // index name (stripped before Pinecone query)
99
- vector: zeroVector,
103
+ vector: uniformVector,
100
104
  topK: limit,
101
105
  includeMetadata: true,
102
106
  includeValues: true,
@@ -1,4 +1,4 @@
1
- import { EntityPermissionType, Metadata, FieldValueCollection, EntitySaveOptions, RunView } from '@memberjunction/core';
1
+ import { EntityPermissionType, Metadata, FieldValueCollection, EntitySaveOptions } from '@memberjunction/core';
2
2
  import { NormalizeUUID } from '@memberjunction/global';
3
3
  import { MJFileEntity, MJFileStorageProviderEntity, MJFileStorageAccountEntity } from '@memberjunction/core-entities';
4
4
  import {
@@ -32,7 +32,7 @@ import {
32
32
  FileSearchResult,
33
33
  UserContextOptions,
34
34
  ExtendedUserContextOptions,
35
- initializeDriverWithAccountCredentials,
35
+ FileStorageEngine,
36
36
  } from '@memberjunction/storage';
37
37
  import { CreateMJFileInput, MJFileResolver as FileResolverBase, MJFile_, UpdateMJFileInput } from '../generated/generated.js';
38
38
  import { FieldMapper } from '@memberjunction/graphql-dataprovider';
@@ -648,12 +648,9 @@ export class FileResolver extends FileResolverBase {
648
648
  const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
649
649
  fileEntity.CheckPermissions(EntityPermissionType.Create, true);
650
650
 
651
- // Initialize driver with account-based credentials from Credential Engine
652
- const driver = await initializeDriverWithAccountCredentials({
653
- accountEntity,
654
- providerEntity,
655
- contextUser: user,
656
- });
651
+ // Initialize driver via FileStorageEngine (handles credential decryption + token refresh)
652
+ await FileStorageEngine.Instance.Config(false, user);
653
+ const driver = await FileStorageEngine.Instance.GetDriver(accountEntity.ID, user);
657
654
 
658
655
  const success = await driver.CreateDirectory(input.Path);
659
656
  return success;
@@ -726,23 +723,12 @@ export class FileResolver extends FileResolverBase {
726
723
  const fileEntity = await md.GetEntityObject<MJFileEntity>('MJ: Files', user);
727
724
  fileEntity.CheckPermissions(EntityPermissionType.Read, true);
728
725
 
729
- // Load all requested account entities in a single query
730
- const rv = new RunView();
731
- const quotedIDs = input.AccountIDs.map((id) => `'${id}'`).join(', ');
732
- const accountResult = await rv.RunView<MJFileStorageAccountEntity>(
733
- {
734
- EntityName: 'MJ: File Storage Accounts',
735
- ExtraFilter: `ID IN (${quotedIDs})`,
736
- ResultType: 'entity_object',
737
- },
738
- user,
739
- );
726
+ // Use cached accounts from the engine no RunView needed
727
+ await FileStorageEngine.Instance.Config(false, user);
728
+ const normalizedIDs = new Set(input.AccountIDs.map((id: string) => NormalizeUUID(id)));
729
+ const accountEntities = FileStorageEngine.Instance.Accounts
730
+ .filter(a => normalizedIDs.has(NormalizeUUID(a.ID)));
740
731
 
741
- if (!accountResult.Success) {
742
- throw new Error(`Failed to load storage accounts: ${accountResult.ErrorMessage}`);
743
- }
744
-
745
- const accountEntities = accountResult.Results;
746
732
  if (accountEntities.length === 0) {
747
733
  throw new Error('No valid storage accounts found for the provided IDs');
748
734
  }
@@ -754,24 +740,9 @@ export class FileResolver extends FileResolverBase {
754
740
  console.warn(`[FileResolver] Accounts not found: ${missingIDs.join(', ')}`);
755
741
  }
756
742
 
757
- // Load providers for all accounts
758
- const providerIDs = [...new Set(accountEntities.map((a) => a.ProviderID))];
759
- const quotedProviderIDs = providerIDs.map((id) => `'${id}'`).join(', ');
760
- const providerResult = await rv.RunView<MJFileStorageProviderEntity>(
761
- {
762
- EntityName: 'MJ: File Storage Providers',
763
- ExtraFilter: `ID IN (${quotedProviderIDs})`,
764
- ResultType: 'entity_object',
765
- },
766
- user,
767
- );
768
-
769
- if (!providerResult.Success) {
770
- throw new Error(`Failed to load storage providers: ${providerResult.ErrorMessage}`);
771
- }
772
-
743
+ // Load providers from cached metadata
773
744
  const providerMap = new Map<string, MJFileStorageProviderEntity>();
774
- for (const provider of providerResult.Results) {
745
+ for (const provider of FileStorageEngine.Instance.Providers) {
775
746
  providerMap.set(provider.ID, provider);
776
747
  }
777
748