@rimori/client 2.5.17 → 2.5.18-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,14 @@
1
1
  /**
2
2
  * Supported database column data types for table schema definitions.
3
3
  */
4
- type DbColumnType = 'decimal' | 'integer' | 'text' | 'boolean' | 'json' | 'timestamp' | 'uuid';
4
+ /**
5
+ * 'markdown' is stored as `text` in the database.
6
+ * Marking a column as 'markdown' causes the migration system to:
7
+ * 1. Add an `updated_at` timestamp + trigger to the table.
8
+ * 2. Add an `updated_at` trigger so the image-sync cron can detect recently
9
+ * modified entries. The cron derives which columns to scan from release.db_schema.
10
+ */
11
+ type DbColumnType = 'decimal' | 'integer' | 'text' | 'boolean' | 'json' | 'timestamp' | 'uuid' | 'markdown';
5
12
  /**
6
13
  * Foreign key relationship configuration with cascade delete support.
7
14
  * Defines a relationship where the source record is deleted when the destination record is deleted.
package/dist/index.d.ts CHANGED
@@ -18,3 +18,4 @@ export type { Theme, ApplicationMode } from './plugin/module/PluginModule';
18
18
  export type { UserInfo, Language, UserRole, ExplicitUndefined } from './plugin/module/PluginModule';
19
19
  export type { SharedContent, BasicSharedContent, ContentStatus } from './plugin/module/SharedContentController';
20
20
  export type { MacroAccomplishmentPayload, MicroAccomplishmentPayload } from './controller/AccomplishmentController';
21
+ export { StorageModule } from './plugin/module/StorageModule';
package/dist/index.js CHANGED
@@ -10,3 +10,4 @@ export * from './plugin/CommunicationHandler';
10
10
  export { setupWorker } from './worker/WorkerSetup';
11
11
  export { AudioController } from './controller/AudioController';
12
12
  export { Translator } from './controller/TranslationController';
13
+ export { StorageModule } from './plugin/module/StorageModule';
@@ -4,6 +4,7 @@ import { DbModule } from './module/DbModule';
4
4
  import { EventModule } from './module/EventModule';
5
5
  import { AIModule } from './module/AIModule';
6
6
  import { ExerciseModule } from './module/ExerciseModule';
7
+ import { StorageModule } from './module/StorageModule';
7
8
  export declare class RimoriClient {
8
9
  private static instance;
9
10
  sharedContent: SharedContentController;
@@ -12,6 +13,8 @@ export declare class RimoriClient {
12
13
  plugin: PluginModule;
13
14
  ai: AIModule;
14
15
  exercise: ExerciseModule;
16
+ /** Upload and manage images stored in Supabase via the backend. */
17
+ storage: StorageModule;
15
18
  private rimoriInfo;
16
19
  private constructor();
17
20
  static getInstance(pluginId?: string): Promise<RimoriClient>;
@@ -15,6 +15,7 @@ import { DbModule } from './module/DbModule';
15
15
  import { EventModule } from './module/EventModule';
16
16
  import { AIModule } from './module/AIModule';
17
17
  import { ExerciseModule } from './module/ExerciseModule';
18
+ import { StorageModule } from './module/StorageModule';
18
19
  import { EventBus } from '../fromRimori/EventBus';
19
20
  export class RimoriClient {
20
21
  constructor(controller, supabase, info) {
@@ -38,6 +39,7 @@ export class RimoriClient {
38
39
  this.db = new DbModule(supabase, controller, info);
39
40
  this.plugin = new PluginModule(supabase, controller, info, this.ai);
40
41
  this.exercise = new ExerciseModule(supabase, controller, info, this.event);
42
+ this.storage = new StorageModule(info.backendUrl, () => this.rimoriInfo.token);
41
43
  controller.onUpdate((updatedInfo) => {
42
44
  this.rimoriInfo = updatedInfo;
43
45
  });
@@ -67,6 +67,11 @@ export declare class AIModule {
67
67
  setSessionToken(id: string): void;
68
68
  /** Clears the stored session token (called after macro accomplishment). */
69
69
  clearSessionToken(): void;
70
+ /**
71
+ * Ensures a session token exists, requesting one from the backend if needed.
72
+ * Mirrors the lazy-issuance pattern used by the AI/LLM endpoint.
73
+ */
74
+ private ensureSessionToken;
70
75
  /** Registers a callback invoked whenever a 429 rate-limit response is received. */
71
76
  setOnRateLimited(cb: (exercisesRemaining: number) => void): void;
72
77
  /**
@@ -86,7 +91,7 @@ export declare class AIModule {
86
91
  * @param cache Whether to cache the result (default: false).
87
92
  * @param model The model to use for generation.
88
93
  */
89
- getSteamedText(messages: Message[], onMessage: OnLLMResponse, tools?: Tool[], cache?: boolean, model?: string, knowledgeId?: string): Promise<void>;
94
+ getSteamedText(messages: Message[], onMessage: OnLLMResponse, tools?: Tool[], cache?: boolean, model?: string, knowledgeId?: string): Promise<string>;
90
95
  /**
91
96
  * Generate voice audio from text using AI.
92
97
  * @param text The text to convert to voice.
@@ -146,7 +151,7 @@ export declare class AIModule {
146
151
  tools?: Tool[];
147
152
  model?: string;
148
153
  knowledgeId?: string;
149
- }): Promise<void>;
154
+ }): Promise<T>;
150
155
  private streamObject;
151
156
  private sendToolResult;
152
157
  }
@@ -29,6 +29,32 @@ export class AIModule {
29
29
  clearSessionToken() {
30
30
  this.sessionTokenId = null;
31
31
  }
32
+ /**
33
+ * Ensures a session token exists, requesting one from the backend if needed.
34
+ * Mirrors the lazy-issuance pattern used by the AI/LLM endpoint.
35
+ */
36
+ ensureSessionToken() {
37
+ return __awaiter(this, void 0, void 0, function* () {
38
+ var _a, _b, _c;
39
+ if (this.sessionTokenId)
40
+ return;
41
+ const response = yield fetch(`${this.backendUrl}/ai/session`, {
42
+ method: 'POST',
43
+ headers: { Authorization: `Bearer ${this.getToken()}` },
44
+ });
45
+ if (!response.ok) {
46
+ if (response.status === 429) {
47
+ const body = yield response.json().catch(() => ({}));
48
+ const remaining = (_a = body.exercises_remaining) !== null && _a !== void 0 ? _a : 0;
49
+ (_b = this.onRateLimitedCb) === null || _b === void 0 ? void 0 : _b.call(this, remaining);
50
+ throw new Error(`Rate limit exceeded: ${(_c = body.error) !== null && _c !== void 0 ? _c : 'Daily exercise limit reached'}. exercises_remaining: ${remaining}`);
51
+ }
52
+ throw new Error(`Failed to create session: ${response.status} ${response.statusText}`);
53
+ }
54
+ const { session_token_id } = yield response.json();
55
+ this.sessionTokenId = session_token_id;
56
+ });
57
+ }
32
58
  /** Registers a callback invoked whenever a 429 rate-limit response is received. */
33
59
  setOnRateLimited(cb) {
34
60
  this.onRateLimitedCb = cb;
@@ -82,6 +108,7 @@ export class AIModule {
82
108
  onResult: ({ result }) => onMessage(messageId, result, false),
83
109
  });
84
110
  onMessage(messageId, result, true);
111
+ return result;
85
112
  });
86
113
  }
87
114
  /**
@@ -96,6 +123,7 @@ export class AIModule {
96
123
  getVoice(text_1) {
97
124
  return __awaiter(this, arguments, void 0, function* (text, voice = 'alloy', speed = 1, language, cache = false) {
98
125
  var _a;
126
+ yield this.ensureSessionToken();
99
127
  return yield fetch(`${this.backendUrl}/voice/tts`, {
100
128
  method: 'POST',
101
129
  headers: {
@@ -114,6 +142,7 @@ export class AIModule {
114
142
  */
115
143
  getTextFromVoice(file, language) {
116
144
  return __awaiter(this, void 0, void 0, function* () {
145
+ yield this.ensureSessionToken();
117
146
  const formData = new FormData();
118
147
  formData.append('file', file);
119
148
  if (language) {
@@ -180,7 +209,7 @@ export class AIModule {
180
209
  getStreamedObject(params) {
181
210
  return __awaiter(this, void 0, void 0, function* () {
182
211
  const { systemPrompt, responseSchema, userPrompt, onResult, cache = false, tools = [], model = undefined, knowledgeId, } = params;
183
- yield this.streamObject({
212
+ return yield this.streamObject({
184
213
  responseSchema,
185
214
  messages: this.getChatMessage(systemPrompt, userPrompt),
186
215
  onResult,
@@ -45,8 +45,9 @@ export declare class ExerciseModule {
45
45
  */
46
46
  view(): Promise<Exercise[]>;
47
47
  /**
48
- * Creates a new exercise or multiple exercises via the backend API.
49
- * When creating multiple exercises, all requests are made in parallel but only one event is emitted.
48
+ * Creates one or more exercises via the backend API.
49
+ * Multiple exercises are sent in a single bulk request to ensure atomicity —
50
+ * either all succeed or none are inserted.
50
51
  * @param params Exercise creation parameters (single or array).
51
52
  * @returns Created exercise objects.
52
53
  */
@@ -37,31 +37,30 @@ export class ExerciseModule {
37
37
  });
38
38
  }
39
39
  /**
40
- * Creates a new exercise or multiple exercises via the backend API.
41
- * When creating multiple exercises, all requests are made in parallel but only one event is emitted.
40
+ * Creates one or more exercises via the backend API.
41
+ * Multiple exercises are sent in a single bulk request to ensure atomicity —
42
+ * either all succeed or none are inserted.
42
43
  * @param params Exercise creation parameters (single or array).
43
44
  * @returns Created exercise objects.
44
45
  */
45
46
  add(params) {
46
47
  return __awaiter(this, void 0, void 0, function* () {
47
48
  const exercises = Array.isArray(params) ? params : [params];
48
- const responses = yield Promise.all(exercises.map((exercise) => __awaiter(this, void 0, void 0, function* () {
49
- const response = yield fetch(`${this.backendUrl}/exercises`, {
50
- method: 'POST',
51
- headers: {
52
- 'Content-Type': 'application/json',
53
- Authorization: `Bearer ${this.token}`,
54
- },
55
- body: JSON.stringify(exercise),
56
- });
57
- if (!response.ok) {
58
- const errorText = yield response.text();
59
- throw new Error(`Failed to create exercise: ${errorText}`);
60
- }
61
- return yield response.json();
62
- })));
49
+ const response = yield fetch(`${this.backendUrl}/exercises`, {
50
+ method: 'POST',
51
+ headers: {
52
+ 'Content-Type': 'application/json',
53
+ Authorization: `Bearer ${this.token}`,
54
+ },
55
+ body: JSON.stringify({ exercises }),
56
+ });
57
+ if (!response.ok) {
58
+ const errorText = yield response.text();
59
+ throw new Error(`Failed to create exercises: ${errorText}`);
60
+ }
61
+ const data = yield response.json();
63
62
  this.eventModule.emit('global.exercises.triggerChange');
64
- return responses;
63
+ return data;
65
64
  });
66
65
  }
67
66
  /**
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Storage module for plugin image operations.
3
+ *
4
+ * Handles uploading images to Supabase storage via the backend.
5
+ *
6
+ * Images are tracked automatically: the backend cron scans markdown columns
7
+ * (declared via `type: 'markdown'` in db.config.ts) every 30 minutes,
8
+ * confirms images found in content, and deletes orphaned ones. No plugin-side
9
+ * confirm or delete calls are needed.
10
+ */
11
+ export declare class StorageModule {
12
+ private readonly backendUrl;
13
+ private readonly getToken;
14
+ constructor(backendUrl: string, getToken: () => string);
15
+ /**
16
+ * Upload a PNG image blob to Supabase storage via the backend.
17
+ *
18
+ * The image is initially "unconfirmed". The background cron will link it to
19
+ * the entry automatically when it scans the markdown column after the entry
20
+ * is saved (within ~30 minutes).
21
+ *
22
+ * @returns `{ data: { url, path } }` on success, `{ error }` on failure.
23
+ */
24
+ uploadImage(pngBlob: Blob): Promise<{
25
+ data: {
26
+ url: string;
27
+ path: string;
28
+ };
29
+ error?: undefined;
30
+ } | {
31
+ data?: undefined;
32
+ error: Error;
33
+ }>;
34
+ }
@@ -0,0 +1,57 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ /**
11
+ * Storage module for plugin image operations.
12
+ *
13
+ * Handles uploading images to Supabase storage via the backend.
14
+ *
15
+ * Images are tracked automatically: the backend cron scans markdown columns
16
+ * (declared via `type: 'markdown'` in db.config.ts) every 30 minutes,
17
+ * confirms images found in content, and deletes orphaned ones. No plugin-side
18
+ * confirm or delete calls are needed.
19
+ */
20
+ export class StorageModule {
21
+ constructor(backendUrl, getToken) {
22
+ this.backendUrl = backendUrl;
23
+ this.getToken = getToken;
24
+ }
25
+ /**
26
+ * Upload a PNG image blob to Supabase storage via the backend.
27
+ *
28
+ * The image is initially "unconfirmed". The background cron will link it to
29
+ * the entry automatically when it scans the markdown column after the entry
30
+ * is saved (within ~30 minutes).
31
+ *
32
+ * @returns `{ data: { url, path } }` on success, `{ error }` on failure.
33
+ */
34
+ uploadImage(pngBlob) {
35
+ return __awaiter(this, void 0, void 0, function* () {
36
+ var _a;
37
+ const formData = new FormData();
38
+ formData.append('file', pngBlob, 'image.png');
39
+ try {
40
+ const response = yield fetch(`${this.backendUrl}/plugin-images/upload`, {
41
+ method: 'POST',
42
+ headers: { Authorization: `Bearer ${this.getToken()}` },
43
+ body: formData,
44
+ });
45
+ if (!response.ok) {
46
+ const body = (yield response.json().catch(() => ({})));
47
+ return { error: new Error((_a = body.message) !== null && _a !== void 0 ? _a : `Upload failed (${response.status})`) };
48
+ }
49
+ const result = (yield response.json());
50
+ return { data: result };
51
+ }
52
+ catch (err) {
53
+ return { error: err instanceof Error ? err : new Error(String(err)) };
54
+ }
55
+ });
56
+ }
57
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimori/client",
3
- "version": "2.5.17",
3
+ "version": "2.5.18-next.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "repository": {