@stage5/lumine 0.1.1 → 0.1.3
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/package.json +1 -1
- package/sdk/BUILD_SDK_INDEX.md +102 -4
package/package.json
CHANGED
package/sdk/BUILD_SDK_INDEX.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Build SDK Index
|
|
2
2
|
|
|
3
|
-
Version: 1.
|
|
4
|
-
Updated: 2026-05-
|
|
5
|
-
Generated: 2026-05-
|
|
3
|
+
Version: 1.24.0
|
|
4
|
+
Updated: 2026-05-26
|
|
5
|
+
Generated: 2026-05-26T10:20:06.718Z
|
|
6
6
|
|
|
7
7
|
## Notes
|
|
8
8
|
- This SDK is injected into Build iframes via the Build preview/runtime.
|
|
@@ -21,217 +21,315 @@ Generated: 2026-05-25T00:43:05.370Z
|
|
|
21
21
|
- Twinkle.ai.chat history entries must use { role, content }; map local message.text fields to content before passing history.
|
|
22
22
|
|
|
23
23
|
## Token Scopes
|
|
24
|
-
files:read, user:read, users:read, dailyReflections:read, content:read, sharedDb:read, sharedDb:write, privateDb:read, privateDb:write, files:write, chat:read, chat:write, notifications:read, notifications:write, reminders:read, reminders:write
|
|
24
|
+
files:read, user:read, users:read, dailyReflections:read, content:read, sharedDb:read, sharedDb:write, privateDb:read, privateDb:write, files:write, chat:read, chat:write, notifications:read, notifications:write, notifications:emit, reminders:read, reminders:write
|
|
25
25
|
|
|
26
26
|
## Namespaces
|
|
27
27
|
|
|
28
28
|
### Twinkle.capabilities
|
|
29
29
|
- async get() | scopes: none
|
|
30
|
+
- Returns: Capability snapshot
|
|
30
31
|
- async can(actionName) | scopes: none
|
|
32
|
+
- Returns: boolean
|
|
31
33
|
- async listActions() | scopes: none
|
|
34
|
+
- Returns: { available, blocked, details }
|
|
32
35
|
- async refresh() | scopes: none
|
|
36
|
+
- Returns: Capability snapshot
|
|
33
37
|
|
|
34
38
|
### Twinkle.viewer
|
|
35
39
|
- async get() | scopes: none
|
|
40
|
+
- Returns: { id, username, profilePicUrl, isLoggedIn, isOwner, isGuest }
|
|
36
41
|
- async refresh() | scopes: none
|
|
42
|
+
- Returns: Viewer info
|
|
37
43
|
|
|
38
44
|
### Twinkle.preview
|
|
39
45
|
- getLayout() | scopes: none
|
|
46
|
+
- Returns: { mode, viewport, stage, safeInsets, playfield }
|
|
40
47
|
- Read the host preview layout and derive a fixed-world scale from layout.playfield before sizing a canvas, sprites, or mobile game UI.
|
|
41
48
|
- Example: const WORLD = { width: 360, height: 640 }; const layout = Twinkle.preview.getLayout(); const scale = Math.min(layout.playfield.width / WORLD.width, layout.playfield.height / WORLD.height);
|
|
42
49
|
- reserveInsets({ top, right, bottom, left }) | scopes: none
|
|
50
|
+
- Returns: { mode, viewport, stage, safeInsets, playfield }
|
|
43
51
|
- Reserve host-aware safe space for HUD bars, touch controls, or other overlays before clamping gameplay.
|
|
44
52
|
- Example: Twinkle.preview.reserveInsets({ top: 72, bottom: 120, left: 0, right: 0 });
|
|
45
53
|
- setPlayfield({ x, y, width, height } | null) | scopes: none
|
|
54
|
+
- Returns: { playfieldBounds, playerBounds, overflowTop, overflowRight, overflowBottom, overflowLeft, status, reportedAt } | null
|
|
46
55
|
- Declare the actual playable rectangle when the game area is smaller than the raw canvas.
|
|
47
56
|
- Example: Twinkle.preview.setPlayfield({ x: layout.playfield.x, y: layout.playfield.y, width: layout.playfield.width, height: layout.playfield.height });
|
|
48
57
|
- reportGameplayState({ playfieldBounds?, playerBounds? } | null) | scopes: none
|
|
58
|
+
- Returns: { playfieldBounds, playerBounds, overflowTop, overflowRight, overflowBottom, overflowLeft, status, reportedAt } | null
|
|
49
59
|
- Report live player or avatar bounds so the preview host can detect floor, wall, or out-of-bounds issues.
|
|
50
60
|
- Example: Twinkle.preview.reportGameplayState({ playerBounds: { x: player.x, y: player.y, width: player.width, height: player.height } });
|
|
51
61
|
- getGameplayTelemetry() | scopes: none
|
|
62
|
+
- Returns: { playfieldBounds, playerBounds, overflowTop, overflowRight, overflowBottom, overflowLeft, status, reportedAt } | null
|
|
52
63
|
- Read the latest preview-side gameplay telemetry snapshot.
|
|
53
64
|
- clearGameplayState() | scopes: none
|
|
65
|
+
- Returns: { playfieldBounds, playerBounds, overflowTop, overflowRight, overflowBottom, overflowLeft, status, reportedAt } | null
|
|
54
66
|
- clearReservedInsets() | scopes: none
|
|
67
|
+
- Returns: { mode, viewport, stage, safeInsets, playfield }
|
|
55
68
|
- subscribe(listener, { immediate } = {}) | scopes: none
|
|
69
|
+
- Returns: unsubscribe()
|
|
56
70
|
- Listen for host layout changes so a fixed-world game scale or canvas surface stays synced after resize, mobile viewport changes, or embedded runtime layout shifts.
|
|
57
71
|
- Example: const unsubscribe = Twinkle.preview.subscribe((layout) => syncGameLayout(layout), { immediate: true });
|
|
58
72
|
|
|
59
73
|
### Twinkle.mount
|
|
60
74
|
- async get() | scopes: none
|
|
75
|
+
- Returns: { type: 'subject', id: number } | null
|
|
61
76
|
- async refresh() | scopes: none
|
|
77
|
+
- Returns: { type: 'subject', id: number } | null
|
|
62
78
|
|
|
63
79
|
### Twinkle.notifications
|
|
64
80
|
- getLaunchTarget() | scopes: none
|
|
81
|
+
- Returns: { notificationId, buildId, eventKey, eventLabel, target, payload? } | null
|
|
65
82
|
- Read the current notification launch target, if the app was opened from a Build notification.
|
|
66
83
|
- onLaunchTarget(listener, { immediate } = {}) | scopes: none
|
|
84
|
+
- Returns: unsubscribe()
|
|
67
85
|
- Listen for notification launch targets while the Build app is already open.
|
|
68
86
|
- Example: const off = Twinkle.notifications.onLaunchTarget((launchTarget) => focusEntry(launchTarget?.target?.focus?.entryId));
|
|
87
|
+
- async getSubscription(channelKey, { targetKey }) | scopes: notifications:read
|
|
88
|
+
- Returns: { subscription }
|
|
89
|
+
- Read whether the current viewer is subscribed to an app-defined notification channel target.
|
|
90
|
+
- Example: const { subscription } = await Twinkle.notifications.getSubscription('room.message', { targetKey: 'room:lobby' });
|
|
91
|
+
- async subscribe(channelKey, { targetKey, launchTarget }) | scopes: notifications:write
|
|
92
|
+
- Returns: { subscription }
|
|
93
|
+
- Subscribe the current viewer to an app-defined notification channel target.
|
|
94
|
+
- Example: await Twinkle.notifications.subscribe('room.message', { targetKey: 'room:lobby', launchTarget: { view: 'room', roomId: 'lobby' } });
|
|
95
|
+
- async unsubscribe(channelKey, { targetKey }) | scopes: notifications:write
|
|
96
|
+
- Returns: { subscription: null }
|
|
97
|
+
- Unsubscribe the current viewer from an app-defined notification channel target.
|
|
98
|
+
- Example: await Twinkle.notifications.unsubscribe('room.message', { targetKey: 'room:lobby' });
|
|
99
|
+
- async notifySubscribers(channelKey, { targetKey, eventKey, label, summary, launchTarget, payload }) | scopes: notifications:emit
|
|
100
|
+
- Returns: { sent }
|
|
101
|
+
- Notify viewers who opted into an app-defined channel target, without requiring a sharedDb write.
|
|
102
|
+
- Example: await Twinkle.notifications.notifySubscribers('room.message', { targetKey: 'room:lobby', eventKey: 'room.message.created', label: 'Room messages', summary: 'posted in Lobby', launchTarget: { view: 'room', roomId: 'lobby', messageId } });
|
|
69
103
|
- async getSubjectUpdateSubscription(subjectId) | scopes: notifications:read
|
|
104
|
+
- Returns: { subscription }
|
|
70
105
|
- Read whether the current viewer is subscribed to Build notifications for updates to a subject.
|
|
71
106
|
- Example: const { subscription } = await Twinkle.notifications.getSubjectUpdateSubscription(subjectId);
|
|
72
107
|
- async subscribeToSubjectUpdates(subjectId, { target } = {}) | scopes: notifications:write
|
|
108
|
+
- Returns: { subscription }
|
|
73
109
|
- Subscribe the current viewer to notifications when the original subject author adds a new page or update.
|
|
74
110
|
- Example: await Twinkle.notifications.subscribeToSubjectUpdates(subjectId, { target: { view: 'book', subjectId } });
|
|
75
111
|
- async unsubscribeFromSubjectUpdates(subjectId) | scopes: notifications:write
|
|
112
|
+
- Returns: { subscription: null }
|
|
76
113
|
- Unsubscribe the current viewer from Build notifications for a subject's new pages or updates.
|
|
77
114
|
|
|
78
115
|
### Twinkle.chess
|
|
79
116
|
- async bestMove({ fen, depth?, skillLevel?, maxTimeMs?, timeoutMs? }) | scopes: none
|
|
117
|
+
- Returns: { success, move, bestMove, from, to, promotion, evaluation, depth, mate, error, engine }
|
|
80
118
|
- Ask the parent-hosted Stockfish engine for the best move from a FEN position.
|
|
81
119
|
- Example: const result = await Twinkle.chess.bestMove({ fen: game.fen(), skillLevel: 8, maxTimeMs: 1000 });
|
|
82
120
|
if (result.success) game.move({ from: result.from, to: result.to, promotion: result.promotion || undefined });
|
|
83
121
|
- async evaluate({ fen, depth?, skillLevel?, maxTimeMs?, timeoutMs? }) | scopes: none
|
|
122
|
+
- Returns: { success, move, bestMove, from, to, promotion, evaluation, depth, mate, error, engine }
|
|
84
123
|
- Analyze a FEN position and return Stockfish's current best move plus centipawn or mate evaluation.
|
|
85
124
|
- Example: const analysis = await Twinkle.chess.evaluate({ fen: game.fen(), depth: 12 });
|
|
86
125
|
console.log(analysis.bestMove, analysis.evaluation, analysis.mate);
|
|
87
126
|
|
|
88
127
|
### Twinkle.files
|
|
89
128
|
- async saveAs({ fileName, url, dataUrl, data, text, json, bytes, blob, file, mimeType } = {}) | scopes: none
|
|
129
|
+
- Returns: { success, fileName, size?, mimeType?, method }
|
|
90
130
|
- Download a generated or remote file to the viewer's local device through the parent frame without opening a popup.
|
|
91
131
|
- Example: await Twinkle.files.saveAs({ fileName: 'fashion-guide.png', dataUrl: imageUrl, mimeType: 'image/png' });
|
|
92
132
|
- async uploadGenerated({ fileName, url, dataUrl, data, text, json, bytes, blob, file, mimeType } = {}) | scopes: files:write
|
|
133
|
+
- Returns: { assets: [{ id, buildId, fileName, originalFileName, mimeType, sizeBytes, filePath, url, thumbUrl, fileType, uploadedByUserId, createdAt }], failed?: [{ fileName, message }], canceled }
|
|
93
134
|
- Upload an app-generated file to Twinkle-hosted cloud storage without opening a picker, then store the returned asset refs in sharedDb/privateDb/userDb.
|
|
94
135
|
- Example: const { assets } = await Twinkle.files.uploadGenerated({ fileName: 'fashion-guide.png', dataUrl: generatedImageUrl, mimeType: 'image/png' });
|
|
95
136
|
- async pickAndUpload({ accept, multiple } = {}) | scopes: files:write
|
|
137
|
+
- Returns: { assets: [{ id, buildId, fileName, originalFileName, mimeType, sizeBytes, filePath, url, thumbUrl, fileType, uploadedByUserId, createdAt }], failed?: [{ fileName, message }], canceled }
|
|
96
138
|
- Pick supported local files and upload them to Twinkle-hosted cloud storage, then store the returned asset refs in sharedDb/privateDb/userDb.
|
|
97
139
|
- Example: const { assets, canceled } = await Twinkle.files.pickAndUpload({ accept: 'image/*,.pdf', multiple: true });
|
|
98
140
|
- async list({ cursor, limit } = {}) | scopes: files:read
|
|
141
|
+
- Returns: { assets: [{ id, buildId, fileName, originalFileName, mimeType, sizeBytes, filePath, url, thumbUrl, fileType, uploadedByUserId, createdAt }], nextCursor, usage: { totalBytes, fileCount, maxRuntimeFileStorageBytes, remainingBytes } | null }
|
|
99
142
|
- List the current viewer's uploaded runtime files for this build.
|
|
100
143
|
- Example: const { assets, usage } = await Twinkle.files.list({ limit: 20 });
|
|
101
144
|
- async delete(assetId) | scopes: files:write
|
|
145
|
+
- Returns: { success, deletedAssetId, usage: { totalBytes, fileCount, maxRuntimeFileStorageBytes, remainingBytes } | null }
|
|
102
146
|
- Delete one of the current viewer's uploaded runtime files and free up Twinkle.files quota.
|
|
103
147
|
- Example: await Twinkle.files.delete(assetId);
|
|
104
148
|
|
|
105
149
|
### Twinkle.ai
|
|
106
150
|
- async listPrompts() | scopes: none
|
|
151
|
+
- Returns: Array<{ id, title, description }>
|
|
107
152
|
- async chat({ promptId, message, history, systemPrompt, requestId, onText, onStatus } = {}) | scopes: none
|
|
153
|
+
- Returns: { text, response, model, aiUsagePolicy }
|
|
108
154
|
- Generate text with the default Lumine text model, optionally streaming text updates through onText.
|
|
109
155
|
- Example: const chatHistory = conversation.slice(-12).map((entry) => ({ role: entry.role === 'assistant' ? 'assistant' : 'user', content: entry.text }));
|
|
110
156
|
const result = await Twinkle.ai.chat({ message, history: chatHistory, systemPrompt: 'You are a cheerful pirate helper who answers in one sentence.', onText: (text, meta) => renderReply(text), onStatus: (status) => setThinking(status === 'thinking') });
|
|
111
157
|
- async generateObject({ prompt, expectedStructure, thinkingMode, mode, instructions, systemPrompt } = {}) | scopes: none
|
|
158
|
+
- Returns: { object, result, model, provider, thinkingMode, requestedThinkingMode, aiUsagePolicy }
|
|
112
159
|
- Generate a validated structured JSON object for app decisions, routing, grading, and game-state logic.
|
|
113
160
|
- Example: const { object } = await Twinkle.ai.generateObject({ thinkingMode: 'medium', prompt: 'Classify the player intent from: ' + playerText, expectedStructure: { action: 'string', targetCharacter: 'string', confidence: 0, shouldAskFollowUp: false } });
|
|
114
161
|
- onChatStatus(listener) | scopes: none
|
|
162
|
+
- Returns: unsubscribe function
|
|
115
163
|
- Listen to shared runtime AI chat stream events.
|
|
116
164
|
- async generateImage({ prompt, referenceImageB64, previousResponseId, previousImageId, engine, quality, requestId, onStatus, timeoutMs } = {}) | scopes: none
|
|
165
|
+
- Returns: { success, imageUrl, responseId, imageId, engine, quality, aiUsagePolicy } or { success: false, error, reason, code, aiUsagePolicy }
|
|
117
166
|
- Generate or edit an image from a prompt and optional base64/data-URL reference image.
|
|
118
167
|
- Example: const result = await Twinkle.ai.generateImage({ prompt: 'Create a fashion guide portrait for this face with flattering colors and outfit ideas', referenceImageB64, quality: 'high', onStatus: (status) => console.log(status.stage) });
|
|
119
168
|
- onImageGenerationStatus(listener) | scopes: none
|
|
169
|
+
- Returns: unsubscribe function
|
|
120
170
|
- Subscribe to real-time image generation status events forwarded into the build iframe.
|
|
121
171
|
- Example: const unsubscribe = Twinkle.ai.onImageGenerationStatus((status) => console.log(status.stage));
|
|
122
172
|
|
|
123
173
|
### Twinkle.characters
|
|
124
174
|
- async chat({ character, thinkingMode, message, history, roomContext, scene, systemPrompt, instructions, includeWebsiteContext, requestId, onText, onStatus } = {}) | scopes: none
|
|
175
|
+
- Returns: { text, response, character, aiUsername, thinkingMode, requestedThinkingMode, includeWebsiteContext, model, provider, aiUsagePolicy }
|
|
125
176
|
- Talk to Zero or Ciel from a Build app, either as a final-response call or streaming RPG-style dialogue text with onText/onStatus.
|
|
126
177
|
- Example: const dialogueHistory = recentTurns.slice(-16).map((entry) => ({ role: entry.role === 'assistant' ? 'assistant' : 'user', content: entry.text, speaker: entry.speaker }));
|
|
127
178
|
const result = await Twinkle.characters.chat({ character: 'zero', thinkingMode: thinkHard ? 'high' : 'medium', message: playerText, history: dialogueHistory, roomContext, scene: { location: 'classroom', nearbyCharacters: ['zero', 'ciel'] }, includeWebsiteContext: false, onText: (text) => renderDialogue(text) });
|
|
128
179
|
- onChatStatus(listener) | scopes: none
|
|
180
|
+
- Returns: unsubscribe function
|
|
129
181
|
- Listen to shared Zero/Ciel runtime chat stream events.
|
|
130
182
|
|
|
131
183
|
### Twinkle.userDb
|
|
132
184
|
- async query(sql, params) | scopes: none
|
|
185
|
+
- Returns: { rows, rowCount, truncated }
|
|
133
186
|
- Run a SELECT against advanced private per-user SQLite. Use Twinkle.privateDb instead for simple preferences, drafts, settings, or small JSON state.
|
|
134
187
|
- async exec(sql, params) | scopes: none
|
|
188
|
+
- Returns: { changes, lastInsertRowid }
|
|
135
189
|
- Run a write or schema statement against advanced private per-user SQLite.
|
|
136
190
|
|
|
137
191
|
### Twinkle.subjects
|
|
138
192
|
- async getMySubjects({ limit, cursor } = {}) | scopes: content:read
|
|
193
|
+
- Returns: { subjects: [{ id, title, description, filePath, fileName, fileSize, thumbUrl, timeStamp, rootType, rootId, rewardLevel }], cursor? }
|
|
139
194
|
- async search({ query, limit, cursor } = {}) | scopes: content:read
|
|
195
|
+
- Returns: { subjects: [{ id, contentType, contentId, title, description, filePath, fileName, fileSize, thumbUrl, timeStamp, userId, username, profilePicUrl, rootType, rootId, rewardLevel, numComments }], cursor?, pagination: { limit, hasMore, nextCursor }, filters: { query } }
|
|
140
196
|
- Search Twinkle subjects by text for subject picker UIs, book apps, scrapbooks, and galleries.
|
|
141
197
|
- Example: const { subjects } = await Twinkle.subjects.search({ query: searchText, limit: 12 });
|
|
142
198
|
- async getSubject(subjectId) | scopes: content:read
|
|
199
|
+
- Returns: { subject: { id, title, description, filePath, fileName, fileSize, thumbUrl, secretAnswer, secretAttachment, timeStamp, userId, username, profilePicUrl, rootType, rootId, rewardLevel } }
|
|
143
200
|
- async getSubjectComments(subjectId, { limit, cursor } = {}) | scopes: content:read
|
|
201
|
+
- Returns: { comments: [{ id, content, filePath, fileName, fileSize, thumbUrl, timeStamp }], cursor? }
|
|
144
202
|
|
|
145
203
|
### Twinkle.aiStories
|
|
146
204
|
- async list({ limit, cursor, difficulty, type, isListening, userId, hasImage, hasQuestions } = {}) | scopes: content:read
|
|
205
|
+
- Returns: { stories: [{ id, contentType, contentId, topic, topicKey, type, story, explanation, difficulty, isListening, imagePath, imageUrl, audioPath, audioUrl, questions, questionsBy, hasImage, hasQuestions, userId, username, profilePicUrl, timeStamp }], cursor?, pagination: { limit, hasMore, nextCursor }, filters }
|
|
147
206
|
- List completed existing user-generated AI Stories newest first for galleries, quiz apps, readers, and image/story collections.
|
|
148
207
|
- Example: const { stories } = await Twinkle.aiStories.list({ hasImage: true, hasQuestions: true, limit: 12 });
|
|
149
208
|
- async search({ query, limit, cursor, difficulty, type, isListening, userId, hasImage, hasQuestions } = {}) | scopes: content:read
|
|
209
|
+
- Returns: { stories: [{ id, contentType, contentId, topic, topicKey, type, story, explanation, difficulty, isListening, imagePath, imageUrl, audioPath, audioUrl, questions, questionsBy, hasImage, hasQuestions, userId, username, profilePicUrl, timeStamp }], cursor?, pagination: { limit, hasMore, nextCursor }, filters }
|
|
150
210
|
- Search completed existing user-generated AI Stories by topic or story text for galleries, quizzes, and readers.
|
|
151
211
|
- Example: const { stories } = await Twinkle.aiStories.search({ query: searchText, hasQuestions: true, limit: 12 });
|
|
152
212
|
- async get(storyId) | scopes: content:read
|
|
213
|
+
- Returns: { story: { id, contentType, contentId, topic, topicKey, type, story, explanation, difficulty, isListening, imagePath, imageUrl, audioPath, audioUrl, questions, questionsBy, hasImage, hasQuestions, userId, username, profilePicUrl, timeStamp } }
|
|
153
214
|
- Fetch one completed existing AI Story by id, including story text, media URLs, and normalized questions when available.
|
|
154
215
|
- Example: const { story } = await Twinkle.aiStories.get(storyId);
|
|
155
216
|
|
|
156
217
|
### Twinkle.grammarbles
|
|
157
218
|
- async listQuestions({ level, limit, cursor } = {}) | scopes: content:read
|
|
219
|
+
- Returns: { questions: [{ id, level, rating, question, choices, answerIndex, correctChoice, correctChoiceKey, isChecked, explanation }], cursor?, pagination: { level, limit, hasMore, nextCursor } }
|
|
158
220
|
- Read public Grammarbles questions and answers by level with rating/id cursor pagination.
|
|
159
221
|
- Example: const page = await Twinkle.grammarbles.listQuestions({ level: 3, limit: 100 }); const question = page.questions[Math.floor(Math.random() * page.questions.length)];
|
|
160
222
|
- async getMyQuestionHistory({ level, limit, cursor } = {}) | scopes: content:read
|
|
223
|
+
- Returns: { attempts: [{ id, questionId, level, grade, gradeRank, isCorrect, attemptNumber, timeStamp }], cursor?, pagination: { level, limit, hasMore, nextCursor } }
|
|
161
224
|
- Read the signed-in viewer's real Grammarbles attempt rows for trainer filtering.
|
|
162
225
|
- Example: const history = await Twinkle.grammarbles.getMyQuestionHistory({ level: selectedLevel, limit: 500 }); const answeredIds = new Set(history.attempts.map((attempt) => attempt.questionId));
|
|
163
226
|
|
|
164
227
|
### Twinkle.subjectComments
|
|
165
228
|
- async list(subjectId, { limit, cursor, sortBy, includeReplies, author, authorUserId, replyScope } = {}) | scopes: content:read
|
|
229
|
+
- Returns: { comments: [{ id, content, filePath, fileName, fileSize, thumbUrl, timeStamp, userId, username, profilePicUrl, commentId, replyId }], cursor?, pagination: { limit, hasMore, nextCursor }, filters: { subjectId, sortBy, includeReplies, author, replyScope, authorUserId } }
|
|
166
230
|
- Read a subject's comment stream with stable keyset pagination, oldest/newest ordering, author filters, and optional same-author reply scoping.
|
|
167
231
|
- Example: const { subjects } = await Twinkle.subjects.search({ query: searchText, limit: 12 }); const subjectId = pickedSubject.id; const page = await Twinkle.subjectComments.list(subjectId, { sortBy: 'oldest', author: 'subjectPoster', includeReplies: true, replyScope: 'ownThread', limit: 50 });
|
|
168
232
|
|
|
169
233
|
### Twinkle.profileComments
|
|
170
234
|
- async getProfileComments({ profileUserId, limit, offset, sortBy, includeReplies, range, since, until } = {}) | scopes: content:read
|
|
235
|
+
- Returns: { comments: [{ id, content, filePath, fileName, fileSize, thumbUrl, timeStamp, userId, username, profilePicUrl, likes, replies, commentId, replyId }], pagination: { limit, offset, hasMore, nextOffset }, filters: { profileUserId, sortBy, includeReplies, since, until } }
|
|
171
236
|
- async getProfileCommentIds({ profileUserId, limit, offset, sortBy, includeReplies, range, since, until } = {}) | scopes: content:read
|
|
237
|
+
- Returns: { ids: number[], pagination: { limit, offset, hasMore, nextOffset }, filters: { profileUserId, sortBy, includeReplies, since, until } }
|
|
172
238
|
- async getCommentsByIds(idsOrOpts) | scopes: content:read
|
|
239
|
+
- Returns: { comments: [{ id, content, filePath, fileName, fileSize, thumbUrl, timeStamp, userId, username, profilePicUrl, commentId, replyId }] }
|
|
173
240
|
- async getProfileCommentCounts(idsOrOpts) | scopes: content:read
|
|
241
|
+
- Returns: { countsById: { [commentId]: { likes, replies } } }
|
|
174
242
|
|
|
175
243
|
### Twinkle.leaderboards
|
|
176
244
|
- async get({ boardKey = 'default', limit, cursor } = {}) | scopes: none
|
|
245
|
+
- Returns: { entries: [{ rank, id, buildId, boardKey, viewerKind, userId, displayName, score, meta, achievedAt, createdAt, updatedAt }], scores, cursor, hasMore, personalBest: { id, buildId, boardKey, viewerKind, userId, displayName, score, meta, achievedAt, createdAt, updatedAt } | null }
|
|
177
246
|
- Read score-sorted personal-best leaderboard rows for this Build app.
|
|
178
247
|
- async submit({ boardKey = 'default', score, displayName, meta } = {}) | scopes: none
|
|
248
|
+
- Returns: { entry: { id, buildId, boardKey, viewerKind, userId, displayName, score, meta, achievedAt, createdAt, updatedAt } | null, personalBest: { id, buildId, boardKey, viewerKind, userId, displayName, score, meta, achievedAt, createdAt, updatedAt } | null, improved, previousScore }
|
|
179
249
|
- Submit a score to a public Build leaderboard using server-owned viewer identity.
|
|
180
250
|
|
|
181
251
|
### Twinkle.sharedDb
|
|
182
252
|
- async getTopics() | scopes: sharedDb:read
|
|
253
|
+
- Returns: { topics: [{ id, name, createdBy, createdAt }] }
|
|
183
254
|
- async createTopic(name) | scopes: sharedDb:write
|
|
255
|
+
- Returns: { topic: { id, name, createdBy, createdAt } }
|
|
184
256
|
- async getEntries(topicName, { limit, pageSize, cursor, order, sort, direction } = {}) | scopes: sharedDb:read
|
|
257
|
+
- Returns: { entries: [{ id, topicId, userId, username, profilePicUrl, data, createdAt, updatedAt }], cursor?, hasMore }
|
|
185
258
|
- Read shared topic rows with cursor pagination.
|
|
186
259
|
- async loadMoreEntries(topicName, { limit, pageSize, cursor, order, sort, direction } = {}) | scopes: sharedDb:read
|
|
260
|
+
- Returns: { entries: [{ id, topicId, userId, username, profilePicUrl, data, createdAt, updatedAt }], cursor?, hasMore }
|
|
187
261
|
- Fetch the next sharedDb page.
|
|
188
262
|
- async addEntry(topicName, data, { notify } = {}) | scopes: sharedDb:write
|
|
263
|
+
- Returns: { entry: { id, topicId, userId, username, profilePicUrl, data, createdAt, updatedAt } }
|
|
189
264
|
- Append a shared JSON row, optionally creating a Twinkle notification from the canonical write.
|
|
190
265
|
- async updateEntry(entryId, data, { notify } = {}) | scopes: sharedDb:write
|
|
266
|
+
- Returns: { entry: { id, topicId, userId, username, profilePicUrl, data, createdAt, updatedAt } }
|
|
191
267
|
- Update a viewer-owned shared row, optionally notifying safe recipients from the canonical write.
|
|
192
268
|
- async deleteEntry(entryId) | scopes: sharedDb:write
|
|
269
|
+
- Returns: { success: true }
|
|
193
270
|
|
|
194
271
|
### Twinkle.chat
|
|
195
272
|
- async listRooms() | scopes: chat:read
|
|
273
|
+
- Returns: { rooms: [{ id, buildId, key, name, createdByUserId, createdAt, updatedAt }] }
|
|
196
274
|
- async createRoom({ roomKey, name }) | scopes: chat:write
|
|
275
|
+
- Returns: { room: { id, buildId, key, name, createdByUserId, createdAt, updatedAt } }
|
|
197
276
|
- async listMessages(roomKey, { cursor, limit } = {}) | scopes: chat:read
|
|
277
|
+
- Returns: { messages: [{ id, roomId, roomKey, userId, username, profilePicUrl, role, status, text, metadata, clientMessageId, createdAt, updatedAt }], cursor? }
|
|
198
278
|
- async sendMessage(roomKey, textOrOptions, options) | scopes: chat:write
|
|
279
|
+
- Returns: { message: { id, buildId, roomId, roomKey, userId, username, profilePicUrl, role, status, text, metadata, clientMessageId, createdAt, updatedAt }, room: { id, buildId, key, name, createdByUserId, createdAt, updatedAt }, created }
|
|
199
280
|
- async deleteMessage(messageId) | scopes: chat:write
|
|
281
|
+
- Returns: { success: true, messageId }
|
|
200
282
|
- subscribe(roomKey, listener) | scopes: chat:read
|
|
283
|
+
- Returns: unsubscribe function
|
|
201
284
|
|
|
202
285
|
### Twinkle.world
|
|
203
286
|
- async join({ worldKey = 'default', roomKey = 'main', instanceId = 'main', presence, player } = {}) | scopes: none
|
|
287
|
+
- Returns: { sessionId, session, room, players, snapshot, subscribe(listener), updatePresence(patch), send(actionOrType, data), leave() }
|
|
204
288
|
- Join a realtime Build world room and receive a snapshot plus a session handle for presence updates, actions, and room events.
|
|
205
289
|
- Example: const world = await Twinkle.world.join({ roomKey: 'town-square', presence: { x: 0, y: 0, z: 0, facing: 'south' }, player: { name: avatarName } });
|
|
206
290
|
world.subscribe((event) => updateRemotePlayers(event.players));
|
|
207
291
|
world.updatePresence({ x, y, z, facing });
|
|
208
292
|
- leaveAll() | scopes: none
|
|
293
|
+
- Returns: void
|
|
209
294
|
- Leave every active world session in the current iframe.
|
|
210
295
|
|
|
211
296
|
### Twinkle.users
|
|
212
297
|
- async getUser(userId) | scopes: user:read
|
|
298
|
+
- Returns: { id, username, profilePicUrl, realName } | null
|
|
213
299
|
- async getUsers({ search, userIds, cursor, limit } = {}) | scopes: users:read
|
|
300
|
+
- Returns: { users: [{ id, username, profilePicUrl, realName }], cursor? }
|
|
214
301
|
|
|
215
302
|
### Twinkle.reflections
|
|
216
303
|
- async getDailyReflections({ userIds, cursor, lastId, limit } = {}) | scopes: dailyReflections:read
|
|
304
|
+
- Returns: { reflections: [{ id, userId, response, questionId, submittedAt, sharedAt, username, profilePicUrl, question }], cursor? }
|
|
217
305
|
- async getDailyReflectionsByUser(userId, { cursor, lastId, limit } = {}) | scopes: dailyReflections:read
|
|
306
|
+
- Returns: { reflections: [{ id, userId, response, questionId, submittedAt, sharedAt, username, profilePicUrl, question }], cursor? }
|
|
218
307
|
|
|
219
308
|
### Twinkle.privateDb
|
|
220
309
|
- async get(key) | scopes: privateDb:read
|
|
310
|
+
- Returns: { item: { id, key, value, updatedAt } | null }
|
|
221
311
|
- Read one key from the default private per-user JSON store.
|
|
222
312
|
- async list({ prefix, limit, cursor } = {}) | scopes: privateDb:read
|
|
313
|
+
- Returns: { items: [{ id, key, value, updatedAt }], cursor? }
|
|
223
314
|
- List keys from the default private per-user JSON store.
|
|
224
315
|
- async set(key, value) | scopes: privateDb:write
|
|
316
|
+
- Returns: { item: { id, key, value, updatedAt } }
|
|
225
317
|
- Upsert one JSON-serializable value in the default private per-user store.
|
|
226
318
|
- async remove(key) | scopes: privateDb:write
|
|
319
|
+
- Returns: { success: true, deleted: boolean }
|
|
227
320
|
- Delete one key from the default private per-user JSON store.
|
|
228
321
|
|
|
229
322
|
### Twinkle.reminders
|
|
230
323
|
- async list({ includeDisabled, limit } = {}) | scopes: reminders:read
|
|
324
|
+
- Returns: { reminders: [{ id, buildId, userId, title, body, targetPath, payload, isEnabled, schedule, lastTriggeredAt, createdAt, updatedAt }] }
|
|
231
325
|
- async create({ title, body, targetPath, payload, schedule, isEnabled }) | scopes: reminders:write
|
|
326
|
+
- Returns: { reminder: { id, buildId, userId, title, body, targetPath, payload, isEnabled, schedule, lastTriggeredAt, createdAt, updatedAt } | null }
|
|
232
327
|
- async update(reminderId, patch) | scopes: reminders:write
|
|
328
|
+
- Returns: { reminder: { id, buildId, userId, title, body, targetPath, payload, isEnabled, schedule, lastTriggeredAt, createdAt, updatedAt } | null }
|
|
233
329
|
- async remove(reminderId) | scopes: reminders:write
|
|
330
|
+
- Returns: { success: true, deleted: boolean }
|
|
234
331
|
- async getDue({ now, autoAcknowledge, limit } = {}) | scopes: reminders:read
|
|
332
|
+
- Returns: { now, reminders: [{ id, buildId, userId, title, body, targetPath, payload, isEnabled, schedule, lastTriggeredAt, createdAt, updatedAt }] }
|
|
235
333
|
|
|
236
334
|
## Examples
|
|
237
335
|
|