@starkeep/sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +130 -0
- package/dist/index.cjs +290 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +135 -0
- package/dist/index.d.ts +135 -0
- package/dist/index.js +273 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aaron Koller, Pedro Pinto, Craig Schroeder
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# @starkeep/sdk
|
|
2
|
+
|
|
3
|
+
High-level facade over all Starkeep packages. Provides a single entry point for data operations, metadata generation, search, aggregations, sync, access control, and API handling.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @starkeep/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { createStarkeepSdk } from "@starkeep/sdk";
|
|
15
|
+
|
|
16
|
+
const starkeepSdk = createStarkeepSdk({
|
|
17
|
+
databaseAdapter: localDatabaseAdapter,
|
|
18
|
+
objectStorageAdapter: localObjectStorage,
|
|
19
|
+
ownerId: "user-123",
|
|
20
|
+
nodeId: "device-abc",
|
|
21
|
+
// Optional: enable sync by providing remote adapters
|
|
22
|
+
remoteDatabaseAdapter: remoteDatabaseAdapter,
|
|
23
|
+
remoteObjectStorageAdapter: remoteObjectStorage,
|
|
24
|
+
// Optional: register metadata generators
|
|
25
|
+
generators: [imageDimensionsGenerator, textPreviewGenerator],
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Data operations
|
|
29
|
+
const record = await starkeepSdk.data.put({
|
|
30
|
+
type: "photo",
|
|
31
|
+
ownerId: "user-123",
|
|
32
|
+
payload: { title: "Sunset", tags: ["nature"] },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const recordWithFile = await starkeepSdk.data.putWithFile(
|
|
36
|
+
{ type: "photo", ownerId: "user-123", payload: { title: "Beach" } },
|
|
37
|
+
fileBuffer,
|
|
38
|
+
"image/jpeg",
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const fetched = await starkeepSdk.data.get(record.id);
|
|
42
|
+
await starkeepSdk.data.delete(record.id);
|
|
43
|
+
|
|
44
|
+
// Metadata
|
|
45
|
+
const generationResults = await starkeepSdk.metadata.generateAll(record.id, "photo");
|
|
46
|
+
const metadataRecords = await starkeepSdk.metadata.getForRecord(record.id);
|
|
47
|
+
|
|
48
|
+
// Search
|
|
49
|
+
const searchResult = await starkeepSdk.index.search({
|
|
50
|
+
types: ["photo"],
|
|
51
|
+
fullTextSearch: "sunset",
|
|
52
|
+
limit: 20,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Aggregations
|
|
56
|
+
const aggregationResult = await starkeepSdk.aggregations.compute({
|
|
57
|
+
dateGranularity: "month",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Sync (available when remote adapters are provided)
|
|
61
|
+
if (starkeepSdk.sync) {
|
|
62
|
+
const syncResult = await starkeepSdk.sync.fullSync();
|
|
63
|
+
const unsubscribe = starkeepSdk.sync.onUpdate((changeEvent) => {
|
|
64
|
+
console.log(changeEvent.eventType, changeEvent.recordIds);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Access control
|
|
69
|
+
const policy = await starkeepSdk.accessControl.createPolicy({
|
|
70
|
+
subjectType: "user",
|
|
71
|
+
subjectId: "user-456",
|
|
72
|
+
resourceType: "type",
|
|
73
|
+
resourceId: "photo",
|
|
74
|
+
permissions: ["read"],
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// API
|
|
78
|
+
const apiResponse = await starkeepSdk.api.handleRequest({
|
|
79
|
+
path: "/photos/v1/albums",
|
|
80
|
+
method: "GET",
|
|
81
|
+
subject: { subjectType: "user", subjectId: "user-456" },
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Cleanup
|
|
85
|
+
await starkeepSdk.close();
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## API
|
|
89
|
+
|
|
90
|
+
### Factory Function
|
|
91
|
+
|
|
92
|
+
| Function | Description |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `createStarkeepSdk(options)` | Creates a `StarkeepSdk` instance wiring together all subsystems |
|
|
95
|
+
|
|
96
|
+
### `StarkeepSdk`
|
|
97
|
+
|
|
98
|
+
| Member | Description |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `data` | `DataOperations` -- put, get, delete records and files |
|
|
101
|
+
| `metadata` | `MetadataOperations` -- generate and retrieve metadata for records |
|
|
102
|
+
| `index` | `IndexOperations` -- search across data and metadata |
|
|
103
|
+
| `aggregations` | `AggregationOperations` -- compute counts, sizes, and histograms |
|
|
104
|
+
| `sync` | `SyncOperations \| null` -- push, pull, full sync, and change subscriptions (null when no remote adapters) |
|
|
105
|
+
| `accessControl` | `AccessControlOperations` -- create/revoke policies and check access |
|
|
106
|
+
| `api` | `ApiOperations` -- handle shared space API requests |
|
|
107
|
+
| `close()` | Clean up resources |
|
|
108
|
+
|
|
109
|
+
### `StarkeepSdkOptions`
|
|
110
|
+
|
|
111
|
+
| Option | Required | Description |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `databaseAdapter` | Yes | Local database adapter |
|
|
114
|
+
| `objectStorageAdapter` | Yes | Local object storage adapter |
|
|
115
|
+
| `ownerId` | Yes | Owner identifier for records |
|
|
116
|
+
| `nodeId` | Yes | Unique node ID for HLC clock |
|
|
117
|
+
| `clock` | No | Custom HLC clock instance |
|
|
118
|
+
| `remoteDatabaseAdapter` | No | Remote database adapter (enables sync) |
|
|
119
|
+
| `remoteObjectStorageAdapter` | No | Remote object storage adapter (enables sync) |
|
|
120
|
+
| `generators` | No | Metadata generator definitions to register |
|
|
121
|
+
|
|
122
|
+
### Re-exported Types
|
|
123
|
+
|
|
124
|
+
The SDK re-exports commonly used types from `@starkeep/protocol-primitives` for convenience: `StarkeepId`, `DataRecord`, `MetadataRecord`, `HLCTimestamp`, `CreateDataRecordInput`, `CreateMetadataRecordInput`.
|
|
125
|
+
|
|
126
|
+
## Testing
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
pnpm --filter @starkeep/sdk test
|
|
130
|
+
```
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
createStarkeepSdk: () => createStarkeepSdk
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/sdk.ts
|
|
28
|
+
var import_protocol_primitives = require("@starkeep/protocol-primitives");
|
|
29
|
+
var import_promises = require("fs/promises");
|
|
30
|
+
var import_node_path = require("path");
|
|
31
|
+
var import_query_orchestrator = require("@starkeep/query-orchestrator");
|
|
32
|
+
var import_sync_engine = require("@starkeep/sync-engine");
|
|
33
|
+
var import_access_control = require("@starkeep/access-control");
|
|
34
|
+
var import_shared_space_api = require("@starkeep/shared-space-api");
|
|
35
|
+
async function sha256Hex(data) {
|
|
36
|
+
const copy = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
37
|
+
const buf = await crypto.subtle.digest("SHA-256", copy);
|
|
38
|
+
return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
39
|
+
}
|
|
40
|
+
function metadataCategory(typeOrCategory) {
|
|
41
|
+
return (0, import_protocol_primitives.isCategoryId)(typeOrCategory) ? typeOrCategory : (0, import_protocol_primitives.categoryOf)(typeOrCategory);
|
|
42
|
+
}
|
|
43
|
+
async function createStarkeepSdk(options) {
|
|
44
|
+
const {
|
|
45
|
+
databaseAdapter: rawDatabaseAdapter,
|
|
46
|
+
objectStorageAdapter,
|
|
47
|
+
accessPolicyStore,
|
|
48
|
+
sharingTokenStore,
|
|
49
|
+
ownerId,
|
|
50
|
+
nodeId,
|
|
51
|
+
syncStateStore,
|
|
52
|
+
subject
|
|
53
|
+
} = options;
|
|
54
|
+
await rawDatabaseAdapter.init();
|
|
55
|
+
await objectStorageAdapter.init();
|
|
56
|
+
const initialHlcState = await syncStateStore?.getHlcClockState() ?? void 0;
|
|
57
|
+
let pendingClockState = null;
|
|
58
|
+
let clockFlushTimer = null;
|
|
59
|
+
const clock = options.clock ?? (0, import_protocol_primitives.createHLCClock)({
|
|
60
|
+
nodeId,
|
|
61
|
+
wallClockFunction: Date.now,
|
|
62
|
+
initialState: initialHlcState,
|
|
63
|
+
onTick: syncStateStore ? (state) => {
|
|
64
|
+
pendingClockState = state;
|
|
65
|
+
if (clockFlushTimer) return;
|
|
66
|
+
clockFlushTimer = setTimeout(() => {
|
|
67
|
+
clockFlushTimer = null;
|
|
68
|
+
if (pendingClockState) {
|
|
69
|
+
void syncStateStore.setHlcClockState(pendingClockState);
|
|
70
|
+
}
|
|
71
|
+
}, 5e3);
|
|
72
|
+
} : void 0
|
|
73
|
+
});
|
|
74
|
+
const changeNotifier = options.changeNotifier ?? (0, import_sync_engine.createChangeNotifier)();
|
|
75
|
+
const accessControlEngine = (0, import_access_control.createAccessControlEngine)({
|
|
76
|
+
policyStore: accessPolicyStore,
|
|
77
|
+
tokenStore: sharingTokenStore,
|
|
78
|
+
clock,
|
|
79
|
+
ownerId
|
|
80
|
+
});
|
|
81
|
+
await accessControlEngine.loadPolicies();
|
|
82
|
+
const databaseAdapter = subject ? (0, import_access_control.createEnforcedDatabaseAdapter)({
|
|
83
|
+
databaseAdapter: rawDatabaseAdapter,
|
|
84
|
+
accessControlEngine,
|
|
85
|
+
subjectType: subject.subjectType,
|
|
86
|
+
subjectId: subject.subjectId
|
|
87
|
+
}) : rawDatabaseAdapter;
|
|
88
|
+
const unifiedIndex = (0, import_query_orchestrator.createUnifiedIndex)({ databaseAdapter });
|
|
89
|
+
function logChange(record) {
|
|
90
|
+
changeNotifier.emit({
|
|
91
|
+
eventType: "local-change-recorded",
|
|
92
|
+
recordIds: [record.id],
|
|
93
|
+
timestamp: clock.now()
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const sharedSpaceApi = (0, import_shared_space_api.createSharedSpaceApi)({
|
|
97
|
+
databaseAdapter,
|
|
98
|
+
objectStorageAdapter,
|
|
99
|
+
clock,
|
|
100
|
+
ownerId,
|
|
101
|
+
changeNotifier,
|
|
102
|
+
getAppSpecific: options.getAppSpecific
|
|
103
|
+
});
|
|
104
|
+
async function writeRecordAndMetadata(input, fileBytes, contentType, originalFilename) {
|
|
105
|
+
const contentHash = await sha256Hex(fileBytes);
|
|
106
|
+
const objectStorageKey = (0, import_protocol_primitives.dataRecordObjectKey)(input.type, contentHash);
|
|
107
|
+
const record = (0, import_protocol_primitives.createDataRecord)(
|
|
108
|
+
{
|
|
109
|
+
...input,
|
|
110
|
+
contentHash,
|
|
111
|
+
objectStorageKey,
|
|
112
|
+
mimeType: contentType,
|
|
113
|
+
sizeBytes: fileBytes.length,
|
|
114
|
+
originalFilename
|
|
115
|
+
},
|
|
116
|
+
clock
|
|
117
|
+
);
|
|
118
|
+
await databaseAdapter.put(record);
|
|
119
|
+
if (input.metadata && metadataCategory(input.type) !== "other") {
|
|
120
|
+
await databaseAdapter.putMetadata(input.type, {
|
|
121
|
+
...input.metadata,
|
|
122
|
+
recordId: record.id
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
logChange(record);
|
|
126
|
+
return record;
|
|
127
|
+
}
|
|
128
|
+
return {
|
|
129
|
+
data: {
|
|
130
|
+
async putWithFile(input, file, contentType) {
|
|
131
|
+
const contentHash = await sha256Hex(file);
|
|
132
|
+
const objectStorageKey = (0, import_protocol_primitives.dataRecordObjectKey)(input.type, contentHash);
|
|
133
|
+
await objectStorageAdapter.put(objectStorageKey, file, { contentType });
|
|
134
|
+
return writeRecordAndMetadata(
|
|
135
|
+
{ ...input, contentHash, objectStorageKey },
|
|
136
|
+
file,
|
|
137
|
+
contentType,
|
|
138
|
+
input.originalFilename ?? null
|
|
139
|
+
);
|
|
140
|
+
},
|
|
141
|
+
async putWithLocalFile(input, filePath, contentType) {
|
|
142
|
+
const fileBytes = await (0, import_promises.readFile)(filePath);
|
|
143
|
+
const contentHash = await sha256Hex(fileBytes);
|
|
144
|
+
const objectStorageKey = (0, import_protocol_primitives.dataRecordObjectKey)(input.type, contentHash);
|
|
145
|
+
if (objectStorageAdapter.putSymlink) {
|
|
146
|
+
await objectStorageAdapter.putSymlink(objectStorageKey, filePath, { contentType });
|
|
147
|
+
} else {
|
|
148
|
+
await objectStorageAdapter.put(objectStorageKey, fileBytes, { contentType });
|
|
149
|
+
}
|
|
150
|
+
return writeRecordAndMetadata(
|
|
151
|
+
input,
|
|
152
|
+
fileBytes,
|
|
153
|
+
contentType,
|
|
154
|
+
input.originalFilename ?? (0, import_node_path.basename)(filePath)
|
|
155
|
+
);
|
|
156
|
+
},
|
|
157
|
+
async putWithExistingBlob(input, blob) {
|
|
158
|
+
const record = (0, import_protocol_primitives.createDataRecord)(
|
|
159
|
+
{
|
|
160
|
+
...input,
|
|
161
|
+
contentHash: blob.contentHash,
|
|
162
|
+
objectStorageKey: blob.objectStorageKey,
|
|
163
|
+
mimeType: blob.mimeType,
|
|
164
|
+
sizeBytes: blob.sizeBytes,
|
|
165
|
+
originalFilename: input.originalFilename ?? null
|
|
166
|
+
},
|
|
167
|
+
clock
|
|
168
|
+
);
|
|
169
|
+
await databaseAdapter.put(record);
|
|
170
|
+
if (input.metadata) {
|
|
171
|
+
await databaseAdapter.putMetadata(input.type, {
|
|
172
|
+
...input.metadata,
|
|
173
|
+
recordId: record.id
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
logChange(record);
|
|
177
|
+
return record;
|
|
178
|
+
},
|
|
179
|
+
async get(recordId) {
|
|
180
|
+
const record = await databaseAdapter.get(recordId);
|
|
181
|
+
if (!record) return null;
|
|
182
|
+
if (record.deletedAt) return null;
|
|
183
|
+
return record;
|
|
184
|
+
},
|
|
185
|
+
async update(recordId, patch) {
|
|
186
|
+
const existing = await databaseAdapter.get(recordId);
|
|
187
|
+
if (!existing) {
|
|
188
|
+
throw new Error(`No data record found with id ${recordId}`);
|
|
189
|
+
}
|
|
190
|
+
const updated = {
|
|
191
|
+
...existing,
|
|
192
|
+
originalFilename: patch.originalFilename ?? existing.originalFilename,
|
|
193
|
+
parentId: patch.parentId ?? existing.parentId,
|
|
194
|
+
version: existing.version + 1,
|
|
195
|
+
updatedAt: clock.now()
|
|
196
|
+
};
|
|
197
|
+
await databaseAdapter.put(updated);
|
|
198
|
+
logChange(updated);
|
|
199
|
+
return updated;
|
|
200
|
+
},
|
|
201
|
+
async delete(recordId) {
|
|
202
|
+
const existing = await databaseAdapter.get(recordId);
|
|
203
|
+
if (!existing) return;
|
|
204
|
+
const ts = clock.now();
|
|
205
|
+
await databaseAdapter.delete(recordId, ts);
|
|
206
|
+
await databaseAdapter.deleteMetadata(existing.type, recordId);
|
|
207
|
+
const tombstone = {
|
|
208
|
+
...existing,
|
|
209
|
+
updatedAt: ts,
|
|
210
|
+
deletedAt: ts,
|
|
211
|
+
version: existing.version + 1
|
|
212
|
+
};
|
|
213
|
+
logChange(tombstone);
|
|
214
|
+
},
|
|
215
|
+
async query(params) {
|
|
216
|
+
const result = await databaseAdapter.query(params);
|
|
217
|
+
return result.records;
|
|
218
|
+
},
|
|
219
|
+
// `typeId` may be a record's extension or a category id; the adapter
|
|
220
|
+
// derives the per-category metadata table. The `other` category has no
|
|
221
|
+
// metadata table, so these are no-ops for it.
|
|
222
|
+
async putMetadata(typeId, row) {
|
|
223
|
+
if (metadataCategory(typeId) === "other") return;
|
|
224
|
+
await databaseAdapter.putMetadata(typeId, row);
|
|
225
|
+
},
|
|
226
|
+
async getMetadata(typeId, recordId) {
|
|
227
|
+
if (metadataCategory(typeId) === "other") return null;
|
|
228
|
+
return databaseAdapter.getMetadata(typeId, recordId);
|
|
229
|
+
},
|
|
230
|
+
async getMetadataByIds(typeId, recordIds) {
|
|
231
|
+
if (metadataCategory(typeId) === "other") return /* @__PURE__ */ new Map();
|
|
232
|
+
return databaseAdapter.getMetadataByIds(typeId, recordIds);
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
index: {
|
|
236
|
+
async search(query) {
|
|
237
|
+
return unifiedIndex.search(query);
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
accessControl: {
|
|
241
|
+
async createPolicy(input) {
|
|
242
|
+
if (subject) {
|
|
243
|
+
throw new Error(
|
|
244
|
+
"createPolicy is not available on an app-scoped SDK. Policies are managed by the admin layer."
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
return accessControlEngine.createPolicy(input);
|
|
248
|
+
},
|
|
249
|
+
async revokePolicy(policyId) {
|
|
250
|
+
if (subject) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
"revokePolicy is not available on an app-scoped SDK. Policies are managed by the admin layer."
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
return accessControlEngine.revokePolicy(policyId);
|
|
256
|
+
},
|
|
257
|
+
async listPolicies(listOptions) {
|
|
258
|
+
return accessControlEngine.listPolicies(listOptions);
|
|
259
|
+
},
|
|
260
|
+
async checkAccess(request) {
|
|
261
|
+
return accessControlEngine.checkAccess(request);
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
api: {
|
|
265
|
+
get router() {
|
|
266
|
+
return sharedSpaceApi.router;
|
|
267
|
+
},
|
|
268
|
+
async handleRequest(request) {
|
|
269
|
+
return sharedSpaceApi.handleRequest(request);
|
|
270
|
+
},
|
|
271
|
+
handleWebSocketConnect(connection) {
|
|
272
|
+
return sharedSpaceApi.handleWebSocketConnect(connection);
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
changeNotifier,
|
|
276
|
+
clock,
|
|
277
|
+
async close() {
|
|
278
|
+
if (clockFlushTimer) {
|
|
279
|
+
clearTimeout(clockFlushTimer);
|
|
280
|
+
clockFlushTimer = null;
|
|
281
|
+
}
|
|
282
|
+
if (pendingClockState && syncStateStore) {
|
|
283
|
+
await syncStateStore.setHlcClockState(pendingClockState);
|
|
284
|
+
}
|
|
285
|
+
await databaseAdapter.close();
|
|
286
|
+
await objectStorageAdapter.close();
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/sdk.ts"],"sourcesContent":["export type {\n StarkeepSdk,\n StarkeepSdkOptions,\n DataOperations,\n IndexOperations,\n AccessControlOperations,\n ApiOperations,\n} from \"./types.js\";\n\nexport { createStarkeepSdk } from \"./sdk.js\";\n\n// Re-export commonly used types from core for convenience\nexport type {\n StarkeepId,\n DataRecord,\n HLCTimestamp,\n CreateDataRecordInput,\n} from \"@starkeep/protocol-primitives\";\n","import {\n createHLCClock,\n createDataRecord,\n dataRecordObjectKey,\n categoryOf,\n isCategoryId,\n type DataRecord,\n type MetadataRow,\n} from \"@starkeep/protocol-primitives\";\nimport { readFile } from \"node:fs/promises\";\nimport { basename } from \"node:path\";\nasync function sha256Hex(data: Uint8Array | Buffer): Promise<string> {\n const copy = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer;\n const buf = await crypto.subtle.digest(\"SHA-256\", copy);\n return Array.from(new Uint8Array(buf))\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\nimport { createUnifiedIndex } from \"@starkeep/query-orchestrator\";\nimport { createChangeNotifier } from \"@starkeep/sync-engine\";\nimport { createAccessControlEngine, createEnforcedDatabaseAdapter } from \"@starkeep/access-control\";\nimport { createSharedSpaceApi } from \"@starkeep/shared-space-api\";\nimport type {\n StarkeepSdk,\n StarkeepSdkOptions,\n DataPutInput,\n} from \"./types.js\";\n\n// Resolve a record's type/extension (or a category id) to its metadata\n// category. `other` has no metadata table, so callers skip persistence for it.\nfunction metadataCategory(typeOrCategory: string): string {\n return isCategoryId(typeOrCategory) ? typeOrCategory : categoryOf(typeOrCategory);\n}\n\nexport async function createStarkeepSdk(\n options: StarkeepSdkOptions,\n): Promise<StarkeepSdk> {\n const {\n databaseAdapter: rawDatabaseAdapter,\n objectStorageAdapter,\n accessPolicyStore,\n sharingTokenStore,\n ownerId,\n nodeId,\n syncStateStore,\n subject,\n } = options;\n\n await rawDatabaseAdapter.init();\n await objectStorageAdapter.init();\n\n // Seed the clock from persisted state and debounce write-back on tick,\n // so a restart resumes with an HLC causally after anything we emitted.\n // The clock state is global (one clock per node) — the supervisor's\n // per-app cursors live alongside it in the same store but are owned\n // elsewhere.\n const initialHlcState =\n (await syncStateStore?.getHlcClockState()) ?? undefined;\n let pendingClockState: { wallTime: number; counter: number } | null = null;\n let clockFlushTimer: NodeJS.Timeout | null = null;\n const clock =\n options.clock ??\n createHLCClock({\n nodeId,\n wallClockFunction: Date.now,\n initialState: initialHlcState,\n onTick: syncStateStore\n ? (state) => {\n pendingClockState = state;\n if (clockFlushTimer) return;\n clockFlushTimer = setTimeout(() => {\n clockFlushTimer = null;\n if (pendingClockState) {\n void syncStateStore.setHlcClockState(pendingClockState);\n }\n }, 5000);\n }\n : undefined,\n });\n\n // One shared change notifier. Writes emit `local-change-recorded`; the\n // supervisor's per-app sync engines forward their own pull/conflict events\n // onto this same notifier so consumers (sharedSpaceApi, SSE clients) see\n // one unified stream. Callers may inject a notifier (e.g. the local-data-\n // server hoists it to share with the app-specific factory, which emits\n // its own local-change events tagged with the writing app's id).\n const changeNotifier = options.changeNotifier ?? createChangeNotifier();\n\n // When a subject is provided, wrap the adapter so every operation is\n // gated by access control and the private-storage structural rule.\n const accessControlEngine = createAccessControlEngine({\n policyStore: accessPolicyStore,\n tokenStore: sharingTokenStore,\n clock,\n ownerId,\n });\n await accessControlEngine.loadPolicies();\n\n const databaseAdapter = subject\n ? createEnforcedDatabaseAdapter({\n databaseAdapter: rawDatabaseAdapter,\n accessControlEngine,\n subjectType: subject.subjectType,\n subjectId: subject.subjectId,\n })\n : rawDatabaseAdapter;\n\n const unifiedIndex = createUnifiedIndex({ databaseAdapter });\n\n /**\n * Emit a `local-change-recorded` event for a write. The supervisor wakes its\n * exchange loop in response; the records-table row itself is the durable\n * source of truth for what to ship (no separate change log).\n */\n function logChange(record: DataRecord): void {\n changeNotifier.emit({\n eventType: \"local-change-recorded\",\n recordIds: [record.id],\n timestamp: clock.now(),\n });\n }\n\n const sharedSpaceApi = createSharedSpaceApi({\n databaseAdapter,\n objectStorageAdapter,\n clock,\n ownerId,\n changeNotifier,\n getAppSpecific: options.getAppSpecific,\n });\n\n async function writeRecordAndMetadata(\n input: DataPutInput,\n fileBytes: Uint8Array,\n contentType: string,\n originalFilename: string | null,\n ): Promise<DataRecord> {\n const contentHash = await sha256Hex(fileBytes);\n const objectStorageKey = dataRecordObjectKey(input.type, contentHash);\n\n const record = createDataRecord(\n {\n ...input,\n contentHash,\n objectStorageKey,\n mimeType: contentType,\n sizeBytes: fileBytes.length,\n originalFilename,\n },\n clock,\n );\n await databaseAdapter.put(record);\n if (input.metadata && metadataCategory(input.type) !== \"other\") {\n await databaseAdapter.putMetadata(input.type, {\n ...input.metadata,\n recordId: record.id,\n });\n }\n logChange(record);\n return record;\n }\n\n return {\n data: {\n async putWithFile(input, file, contentType) {\n const contentHash = await sha256Hex(file);\n const objectStorageKey = dataRecordObjectKey(input.type, contentHash);\n\n await objectStorageAdapter.put(objectStorageKey, file, { contentType });\n return writeRecordAndMetadata(\n { ...input, contentHash, objectStorageKey } as DataPutInput,\n file,\n contentType,\n input.originalFilename ?? null,\n );\n },\n\n async putWithLocalFile(input, filePath, contentType) {\n const fileBytes = await readFile(filePath);\n const contentHash = await sha256Hex(fileBytes);\n const objectStorageKey = dataRecordObjectKey(input.type, contentHash);\n\n if (objectStorageAdapter.putSymlink) {\n await objectStorageAdapter.putSymlink(objectStorageKey, filePath, { contentType });\n } else {\n await objectStorageAdapter.put(objectStorageKey, fileBytes, { contentType });\n }\n\n return writeRecordAndMetadata(\n input,\n fileBytes,\n contentType,\n input.originalFilename ?? basename(filePath),\n );\n },\n\n async putWithExistingBlob(input, blob) {\n // Bytes are already in object storage (uploaded out-of-band, e.g. via\n // a presigned PUT). Skip the upload + re-hash; trust the caller's\n // contentHash and sizeBytes. The records-table row is otherwise\n // identical to what putWithFile would produce.\n const record = createDataRecord(\n {\n ...input,\n contentHash: blob.contentHash,\n objectStorageKey: blob.objectStorageKey,\n mimeType: blob.mimeType,\n sizeBytes: blob.sizeBytes,\n originalFilename: input.originalFilename ?? null,\n } as DataPutInput as never,\n clock,\n );\n await databaseAdapter.put(record);\n if (input.metadata) {\n await databaseAdapter.putMetadata(input.type, {\n ...input.metadata,\n recordId: record.id,\n });\n }\n logChange(record);\n return record;\n },\n\n async get(recordId) {\n const record = await databaseAdapter.get(recordId);\n if (!record) return null;\n if (record.deletedAt) return null;\n return record;\n },\n\n async update(recordId, patch) {\n const existing = await databaseAdapter.get(recordId);\n if (!existing) {\n throw new Error(`No data record found with id ${recordId}`);\n }\n const updated: DataRecord = {\n ...existing,\n originalFilename: patch.originalFilename ?? existing.originalFilename,\n parentId: patch.parentId ?? existing.parentId,\n version: existing.version + 1,\n updatedAt: clock.now(),\n };\n await databaseAdapter.put(updated);\n logChange(updated);\n return updated;\n },\n\n async delete(recordId) {\n const existing = await databaseAdapter.get(recordId);\n if (!existing) return;\n const ts = clock.now();\n await databaseAdapter.delete(recordId, ts);\n await databaseAdapter.deleteMetadata(existing.type, recordId);\n const tombstone: DataRecord = {\n ...existing,\n updatedAt: ts,\n deletedAt: ts,\n version: existing.version + 1,\n };\n logChange(tombstone);\n },\n\n async query(params) {\n const result = await databaseAdapter.query(params);\n return result.records;\n },\n\n // `typeId` may be a record's extension or a category id; the adapter\n // derives the per-category metadata table. The `other` category has no\n // metadata table, so these are no-ops for it.\n async putMetadata(typeId: string, row: MetadataRow) {\n if (metadataCategory(typeId) === \"other\") return;\n await databaseAdapter.putMetadata(typeId, row);\n },\n\n async getMetadata(typeId, recordId) {\n if (metadataCategory(typeId) === \"other\") return null;\n return databaseAdapter.getMetadata(typeId, recordId);\n },\n\n async getMetadataByIds(typeId, recordIds) {\n if (metadataCategory(typeId) === \"other\") return new Map();\n return databaseAdapter.getMetadataByIds(typeId, recordIds);\n },\n },\n\n index: {\n async search(query) {\n return unifiedIndex.search(query);\n },\n },\n\n accessControl: {\n async createPolicy(input) {\n if (subject) {\n throw new Error(\n \"createPolicy is not available on an app-scoped SDK. Policies are managed by the admin layer.\",\n );\n }\n return accessControlEngine.createPolicy(input);\n },\n\n async revokePolicy(policyId) {\n if (subject) {\n throw new Error(\n \"revokePolicy is not available on an app-scoped SDK. Policies are managed by the admin layer.\",\n );\n }\n return accessControlEngine.revokePolicy(policyId);\n },\n\n async listPolicies(listOptions) {\n return accessControlEngine.listPolicies(listOptions);\n },\n\n async checkAccess(request) {\n return accessControlEngine.checkAccess(request);\n },\n },\n\n api: {\n get router() {\n return sharedSpaceApi.router;\n },\n async handleRequest(request) {\n return sharedSpaceApi.handleRequest(request);\n },\n handleWebSocketConnect(connection) {\n return sharedSpaceApi.handleWebSocketConnect(connection);\n },\n },\n\n changeNotifier,\n clock,\n\n async close() {\n if (clockFlushTimer) {\n clearTimeout(clockFlushTimer);\n clockFlushTimer = null;\n }\n if (pendingClockState && syncStateStore) {\n await syncStateStore.setHlcClockState(pendingClockState);\n }\n await databaseAdapter.close();\n await objectStorageAdapter.close();\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import * as _starkeep_storage_adapter from '@starkeep/storage-adapter';
|
|
2
|
+
import { DatabaseAdapter, ObjectStorageAdapter } from '@starkeep/storage-adapter';
|
|
3
|
+
import { StarkeepId, CreateDataRecordInput, MetadataRow, DataRecord, HLCClock } from '@starkeep/protocol-primitives';
|
|
4
|
+
export { CreateDataRecordInput, DataRecord, HLCTimestamp, StarkeepId } from '@starkeep/protocol-primitives';
|
|
5
|
+
import { IndexQuery, IndexResult } from '@starkeep/query-orchestrator';
|
|
6
|
+
import { ChangeNotifier, SyncStateStore } from '@starkeep/sync-engine';
|
|
7
|
+
import { CreatePolicyInput, AccessPolicy, AccessCheckRequest, AccessCheckResult, AccessPolicyStore, SharingTokenStore, SubjectType } from '@starkeep/access-control';
|
|
8
|
+
import { ApiRouter, ApiRequest, ApiResponse, WebSocketConnection, ApiSubject, AppSpecificOperations } from '@starkeep/shared-space-api';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Input to `data.putWithFile` / `data.putWithLocalFile` — the file-bytes /
|
|
12
|
+
* content-hash / object-storage-key / size / mimeType are filled in by the
|
|
13
|
+
* SDK from the supplied bytes, so callers only specify the metadata they
|
|
14
|
+
* choose explicitly.
|
|
15
|
+
*/
|
|
16
|
+
type DataPutInput = Omit<CreateDataRecordInput, "contentHash" | "objectStorageKey" | "mimeType" | "sizeBytes"> & {
|
|
17
|
+
/**
|
|
18
|
+
* Optional per-category metadata row to write atomically with the
|
|
19
|
+
* records-table row. Columns are defined by the record's category entry in
|
|
20
|
+
* `CATEGORIES` (category = `categoryOf(type)`); `other` records have no
|
|
21
|
+
* metadata table. The SDK supplies the `recordId` itself — callers omit it.
|
|
22
|
+
*/
|
|
23
|
+
metadata?: Omit<MetadataRow, "recordId">;
|
|
24
|
+
};
|
|
25
|
+
interface DataOperations {
|
|
26
|
+
putWithFile(input: DataPutInput, file: Uint8Array, contentType: string): Promise<DataRecord>;
|
|
27
|
+
putWithLocalFile(input: DataPutInput, filePath: string, contentType: string): Promise<DataRecord>;
|
|
28
|
+
/**
|
|
29
|
+
* Write a record for a blob that has already been placed in object storage
|
|
30
|
+
* (e.g. via a presigned PUT upload). The caller supplies the content hash,
|
|
31
|
+
* the resulting object-storage key, the byte length, and the mime type;
|
|
32
|
+
* the SDK does not re-read the bytes. Use this when the upload path is
|
|
33
|
+
* external to the SDK (e.g. browser → S3 → cloud-data-server confirm).
|
|
34
|
+
*/
|
|
35
|
+
putWithExistingBlob(input: DataPutInput, blob: {
|
|
36
|
+
contentHash: string;
|
|
37
|
+
objectStorageKey: string;
|
|
38
|
+
sizeBytes: number;
|
|
39
|
+
mimeType: string;
|
|
40
|
+
}): Promise<DataRecord>;
|
|
41
|
+
get(recordId: StarkeepId): Promise<DataRecord | null>;
|
|
42
|
+
/**
|
|
43
|
+
* Update tracked record metadata (parentId, originalFilename, mimeType). All
|
|
44
|
+
* data-bearing fields are derived from the underlying file; to change them,
|
|
45
|
+
* upload a new file via `putWithFile`. The metadata row, if any, is updated
|
|
46
|
+
* by `putMetadata`.
|
|
47
|
+
*/
|
|
48
|
+
update(recordId: StarkeepId, patch: Partial<Pick<DataRecord, "originalFilename" | "parentId">>): Promise<DataRecord>;
|
|
49
|
+
delete(recordId: StarkeepId): Promise<void>;
|
|
50
|
+
query(params: {
|
|
51
|
+
type?: string;
|
|
52
|
+
filters?: _starkeep_storage_adapter.Filter[];
|
|
53
|
+
}): Promise<DataRecord[]>;
|
|
54
|
+
/** Write (insert-or-replace) a per-type metadata row. */
|
|
55
|
+
putMetadata(typeId: string, row: MetadataRow): Promise<void>;
|
|
56
|
+
/** Read a per-type metadata row by recordId. */
|
|
57
|
+
getMetadata(typeId: string, recordId: StarkeepId): Promise<MetadataRow | null>;
|
|
58
|
+
/** Batch-read per-type metadata rows. */
|
|
59
|
+
getMetadataByIds(typeId: string, recordIds: StarkeepId[]): Promise<Map<StarkeepId, MetadataRow>>;
|
|
60
|
+
}
|
|
61
|
+
interface IndexOperations {
|
|
62
|
+
search(query: IndexQuery): Promise<IndexResult>;
|
|
63
|
+
}
|
|
64
|
+
interface AccessControlOperations {
|
|
65
|
+
createPolicy(input: CreatePolicyInput): Promise<AccessPolicy>;
|
|
66
|
+
revokePolicy(policyId: StarkeepId): Promise<void>;
|
|
67
|
+
listPolicies(options?: {
|
|
68
|
+
subjectId?: string;
|
|
69
|
+
resourceId?: string;
|
|
70
|
+
}): Promise<AccessPolicy[]>;
|
|
71
|
+
checkAccess(request: AccessCheckRequest): Promise<AccessCheckResult>;
|
|
72
|
+
}
|
|
73
|
+
interface ApiOperations {
|
|
74
|
+
readonly router: ApiRouter;
|
|
75
|
+
handleRequest(request: ApiRequest): Promise<ApiResponse>;
|
|
76
|
+
handleWebSocketConnect(connection: WebSocketConnection): () => void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface StarkeepSdk {
|
|
80
|
+
readonly data: DataOperations;
|
|
81
|
+
readonly index: IndexOperations;
|
|
82
|
+
readonly accessControl: AccessControlOperations;
|
|
83
|
+
readonly api: ApiOperations;
|
|
84
|
+
/**
|
|
85
|
+
* Broadcast channel for record-level events. The SDK emits
|
|
86
|
+
* `local-change-recorded` on every write; the sync supervisor forwards
|
|
87
|
+
* `local-data-synced` from its per-app engines onto this same notifier so
|
|
88
|
+
* subscribers (sharedSpaceApi, SSE clients) see one unified stream.
|
|
89
|
+
*/
|
|
90
|
+
readonly changeNotifier: ChangeNotifier;
|
|
91
|
+
/** The clock backing this SDK — exposed so the supervisor can share it. */
|
|
92
|
+
readonly clock: HLCClock;
|
|
93
|
+
close(): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
interface StarkeepSdkOptions {
|
|
96
|
+
readonly databaseAdapter: DatabaseAdapter;
|
|
97
|
+
readonly objectStorageAdapter: ObjectStorageAdapter;
|
|
98
|
+
/** Backing store for AccessPolicy rows (instance-local). */
|
|
99
|
+
readonly accessPolicyStore: AccessPolicyStore;
|
|
100
|
+
/**
|
|
101
|
+
* Backing store for sharing tokens. Pass `disabledSharingTokenStore()` on
|
|
102
|
+
* the local-data-server — tokens are issued and validated cloud-side.
|
|
103
|
+
*/
|
|
104
|
+
readonly sharingTokenStore: SharingTokenStore;
|
|
105
|
+
readonly ownerId: string;
|
|
106
|
+
readonly nodeId: string;
|
|
107
|
+
readonly clock?: HLCClock;
|
|
108
|
+
/**
|
|
109
|
+
* Optional state store. The SDK uses it only to seed and persist HLC clock
|
|
110
|
+
* state (one clock per node). Per-app watermarks are owned by the
|
|
111
|
+
* supervisor and never touched here.
|
|
112
|
+
*/
|
|
113
|
+
readonly syncStateStore?: SyncStateStore;
|
|
114
|
+
/**
|
|
115
|
+
* Optional change-notifier to inject. When omitted the SDK creates its own.
|
|
116
|
+
* Callers inject when they want a sibling component (e.g. the local-data-
|
|
117
|
+
* server's app-specific factory) to emit `local-change-recorded` events
|
|
118
|
+
* onto the same channel the supervisor subscribes to.
|
|
119
|
+
*/
|
|
120
|
+
readonly changeNotifier?: ChangeNotifier;
|
|
121
|
+
readonly subject?: {
|
|
122
|
+
readonly subjectType: SubjectType;
|
|
123
|
+
readonly subjectId: string;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Factory for the app-scoped app-specific operations exposed on the
|
|
127
|
+
* ApiContext. Provided by the harness (local-data-server) since it owns
|
|
128
|
+
* the syncable-namespace registry and storage layout.
|
|
129
|
+
*/
|
|
130
|
+
readonly getAppSpecific?: (subject: ApiSubject) => AppSpecificOperations | null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
declare function createStarkeepSdk(options: StarkeepSdkOptions): Promise<StarkeepSdk>;
|
|
134
|
+
|
|
135
|
+
export { type AccessControlOperations, type ApiOperations, type DataOperations, type IndexOperations, type StarkeepSdk, type StarkeepSdkOptions, createStarkeepSdk };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import * as _starkeep_storage_adapter from '@starkeep/storage-adapter';
|
|
2
|
+
import { DatabaseAdapter, ObjectStorageAdapter } from '@starkeep/storage-adapter';
|
|
3
|
+
import { StarkeepId, CreateDataRecordInput, MetadataRow, DataRecord, HLCClock } from '@starkeep/protocol-primitives';
|
|
4
|
+
export { CreateDataRecordInput, DataRecord, HLCTimestamp, StarkeepId } from '@starkeep/protocol-primitives';
|
|
5
|
+
import { IndexQuery, IndexResult } from '@starkeep/query-orchestrator';
|
|
6
|
+
import { ChangeNotifier, SyncStateStore } from '@starkeep/sync-engine';
|
|
7
|
+
import { CreatePolicyInput, AccessPolicy, AccessCheckRequest, AccessCheckResult, AccessPolicyStore, SharingTokenStore, SubjectType } from '@starkeep/access-control';
|
|
8
|
+
import { ApiRouter, ApiRequest, ApiResponse, WebSocketConnection, ApiSubject, AppSpecificOperations } from '@starkeep/shared-space-api';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Input to `data.putWithFile` / `data.putWithLocalFile` — the file-bytes /
|
|
12
|
+
* content-hash / object-storage-key / size / mimeType are filled in by the
|
|
13
|
+
* SDK from the supplied bytes, so callers only specify the metadata they
|
|
14
|
+
* choose explicitly.
|
|
15
|
+
*/
|
|
16
|
+
type DataPutInput = Omit<CreateDataRecordInput, "contentHash" | "objectStorageKey" | "mimeType" | "sizeBytes"> & {
|
|
17
|
+
/**
|
|
18
|
+
* Optional per-category metadata row to write atomically with the
|
|
19
|
+
* records-table row. Columns are defined by the record's category entry in
|
|
20
|
+
* `CATEGORIES` (category = `categoryOf(type)`); `other` records have no
|
|
21
|
+
* metadata table. The SDK supplies the `recordId` itself — callers omit it.
|
|
22
|
+
*/
|
|
23
|
+
metadata?: Omit<MetadataRow, "recordId">;
|
|
24
|
+
};
|
|
25
|
+
interface DataOperations {
|
|
26
|
+
putWithFile(input: DataPutInput, file: Uint8Array, contentType: string): Promise<DataRecord>;
|
|
27
|
+
putWithLocalFile(input: DataPutInput, filePath: string, contentType: string): Promise<DataRecord>;
|
|
28
|
+
/**
|
|
29
|
+
* Write a record for a blob that has already been placed in object storage
|
|
30
|
+
* (e.g. via a presigned PUT upload). The caller supplies the content hash,
|
|
31
|
+
* the resulting object-storage key, the byte length, and the mime type;
|
|
32
|
+
* the SDK does not re-read the bytes. Use this when the upload path is
|
|
33
|
+
* external to the SDK (e.g. browser → S3 → cloud-data-server confirm).
|
|
34
|
+
*/
|
|
35
|
+
putWithExistingBlob(input: DataPutInput, blob: {
|
|
36
|
+
contentHash: string;
|
|
37
|
+
objectStorageKey: string;
|
|
38
|
+
sizeBytes: number;
|
|
39
|
+
mimeType: string;
|
|
40
|
+
}): Promise<DataRecord>;
|
|
41
|
+
get(recordId: StarkeepId): Promise<DataRecord | null>;
|
|
42
|
+
/**
|
|
43
|
+
* Update tracked record metadata (parentId, originalFilename, mimeType). All
|
|
44
|
+
* data-bearing fields are derived from the underlying file; to change them,
|
|
45
|
+
* upload a new file via `putWithFile`. The metadata row, if any, is updated
|
|
46
|
+
* by `putMetadata`.
|
|
47
|
+
*/
|
|
48
|
+
update(recordId: StarkeepId, patch: Partial<Pick<DataRecord, "originalFilename" | "parentId">>): Promise<DataRecord>;
|
|
49
|
+
delete(recordId: StarkeepId): Promise<void>;
|
|
50
|
+
query(params: {
|
|
51
|
+
type?: string;
|
|
52
|
+
filters?: _starkeep_storage_adapter.Filter[];
|
|
53
|
+
}): Promise<DataRecord[]>;
|
|
54
|
+
/** Write (insert-or-replace) a per-type metadata row. */
|
|
55
|
+
putMetadata(typeId: string, row: MetadataRow): Promise<void>;
|
|
56
|
+
/** Read a per-type metadata row by recordId. */
|
|
57
|
+
getMetadata(typeId: string, recordId: StarkeepId): Promise<MetadataRow | null>;
|
|
58
|
+
/** Batch-read per-type metadata rows. */
|
|
59
|
+
getMetadataByIds(typeId: string, recordIds: StarkeepId[]): Promise<Map<StarkeepId, MetadataRow>>;
|
|
60
|
+
}
|
|
61
|
+
interface IndexOperations {
|
|
62
|
+
search(query: IndexQuery): Promise<IndexResult>;
|
|
63
|
+
}
|
|
64
|
+
interface AccessControlOperations {
|
|
65
|
+
createPolicy(input: CreatePolicyInput): Promise<AccessPolicy>;
|
|
66
|
+
revokePolicy(policyId: StarkeepId): Promise<void>;
|
|
67
|
+
listPolicies(options?: {
|
|
68
|
+
subjectId?: string;
|
|
69
|
+
resourceId?: string;
|
|
70
|
+
}): Promise<AccessPolicy[]>;
|
|
71
|
+
checkAccess(request: AccessCheckRequest): Promise<AccessCheckResult>;
|
|
72
|
+
}
|
|
73
|
+
interface ApiOperations {
|
|
74
|
+
readonly router: ApiRouter;
|
|
75
|
+
handleRequest(request: ApiRequest): Promise<ApiResponse>;
|
|
76
|
+
handleWebSocketConnect(connection: WebSocketConnection): () => void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface StarkeepSdk {
|
|
80
|
+
readonly data: DataOperations;
|
|
81
|
+
readonly index: IndexOperations;
|
|
82
|
+
readonly accessControl: AccessControlOperations;
|
|
83
|
+
readonly api: ApiOperations;
|
|
84
|
+
/**
|
|
85
|
+
* Broadcast channel for record-level events. The SDK emits
|
|
86
|
+
* `local-change-recorded` on every write; the sync supervisor forwards
|
|
87
|
+
* `local-data-synced` from its per-app engines onto this same notifier so
|
|
88
|
+
* subscribers (sharedSpaceApi, SSE clients) see one unified stream.
|
|
89
|
+
*/
|
|
90
|
+
readonly changeNotifier: ChangeNotifier;
|
|
91
|
+
/** The clock backing this SDK — exposed so the supervisor can share it. */
|
|
92
|
+
readonly clock: HLCClock;
|
|
93
|
+
close(): Promise<void>;
|
|
94
|
+
}
|
|
95
|
+
interface StarkeepSdkOptions {
|
|
96
|
+
readonly databaseAdapter: DatabaseAdapter;
|
|
97
|
+
readonly objectStorageAdapter: ObjectStorageAdapter;
|
|
98
|
+
/** Backing store for AccessPolicy rows (instance-local). */
|
|
99
|
+
readonly accessPolicyStore: AccessPolicyStore;
|
|
100
|
+
/**
|
|
101
|
+
* Backing store for sharing tokens. Pass `disabledSharingTokenStore()` on
|
|
102
|
+
* the local-data-server — tokens are issued and validated cloud-side.
|
|
103
|
+
*/
|
|
104
|
+
readonly sharingTokenStore: SharingTokenStore;
|
|
105
|
+
readonly ownerId: string;
|
|
106
|
+
readonly nodeId: string;
|
|
107
|
+
readonly clock?: HLCClock;
|
|
108
|
+
/**
|
|
109
|
+
* Optional state store. The SDK uses it only to seed and persist HLC clock
|
|
110
|
+
* state (one clock per node). Per-app watermarks are owned by the
|
|
111
|
+
* supervisor and never touched here.
|
|
112
|
+
*/
|
|
113
|
+
readonly syncStateStore?: SyncStateStore;
|
|
114
|
+
/**
|
|
115
|
+
* Optional change-notifier to inject. When omitted the SDK creates its own.
|
|
116
|
+
* Callers inject when they want a sibling component (e.g. the local-data-
|
|
117
|
+
* server's app-specific factory) to emit `local-change-recorded` events
|
|
118
|
+
* onto the same channel the supervisor subscribes to.
|
|
119
|
+
*/
|
|
120
|
+
readonly changeNotifier?: ChangeNotifier;
|
|
121
|
+
readonly subject?: {
|
|
122
|
+
readonly subjectType: SubjectType;
|
|
123
|
+
readonly subjectId: string;
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Factory for the app-scoped app-specific operations exposed on the
|
|
127
|
+
* ApiContext. Provided by the harness (local-data-server) since it owns
|
|
128
|
+
* the syncable-namespace registry and storage layout.
|
|
129
|
+
*/
|
|
130
|
+
readonly getAppSpecific?: (subject: ApiSubject) => AppSpecificOperations | null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
declare function createStarkeepSdk(options: StarkeepSdkOptions): Promise<StarkeepSdk>;
|
|
134
|
+
|
|
135
|
+
export { type AccessControlOperations, type ApiOperations, type DataOperations, type IndexOperations, type StarkeepSdk, type StarkeepSdkOptions, createStarkeepSdk };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// src/sdk.ts
|
|
2
|
+
import {
|
|
3
|
+
createHLCClock,
|
|
4
|
+
createDataRecord,
|
|
5
|
+
dataRecordObjectKey,
|
|
6
|
+
categoryOf,
|
|
7
|
+
isCategoryId
|
|
8
|
+
} from "@starkeep/protocol-primitives";
|
|
9
|
+
import { readFile } from "fs/promises";
|
|
10
|
+
import { basename } from "path";
|
|
11
|
+
import { createUnifiedIndex } from "@starkeep/query-orchestrator";
|
|
12
|
+
import { createChangeNotifier } from "@starkeep/sync-engine";
|
|
13
|
+
import { createAccessControlEngine, createEnforcedDatabaseAdapter } from "@starkeep/access-control";
|
|
14
|
+
import { createSharedSpaceApi } from "@starkeep/shared-space-api";
|
|
15
|
+
async function sha256Hex(data) {
|
|
16
|
+
const copy = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
17
|
+
const buf = await crypto.subtle.digest("SHA-256", copy);
|
|
18
|
+
return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
19
|
+
}
|
|
20
|
+
function metadataCategory(typeOrCategory) {
|
|
21
|
+
return isCategoryId(typeOrCategory) ? typeOrCategory : categoryOf(typeOrCategory);
|
|
22
|
+
}
|
|
23
|
+
async function createStarkeepSdk(options) {
|
|
24
|
+
const {
|
|
25
|
+
databaseAdapter: rawDatabaseAdapter,
|
|
26
|
+
objectStorageAdapter,
|
|
27
|
+
accessPolicyStore,
|
|
28
|
+
sharingTokenStore,
|
|
29
|
+
ownerId,
|
|
30
|
+
nodeId,
|
|
31
|
+
syncStateStore,
|
|
32
|
+
subject
|
|
33
|
+
} = options;
|
|
34
|
+
await rawDatabaseAdapter.init();
|
|
35
|
+
await objectStorageAdapter.init();
|
|
36
|
+
const initialHlcState = await syncStateStore?.getHlcClockState() ?? void 0;
|
|
37
|
+
let pendingClockState = null;
|
|
38
|
+
let clockFlushTimer = null;
|
|
39
|
+
const clock = options.clock ?? createHLCClock({
|
|
40
|
+
nodeId,
|
|
41
|
+
wallClockFunction: Date.now,
|
|
42
|
+
initialState: initialHlcState,
|
|
43
|
+
onTick: syncStateStore ? (state) => {
|
|
44
|
+
pendingClockState = state;
|
|
45
|
+
if (clockFlushTimer) return;
|
|
46
|
+
clockFlushTimer = setTimeout(() => {
|
|
47
|
+
clockFlushTimer = null;
|
|
48
|
+
if (pendingClockState) {
|
|
49
|
+
void syncStateStore.setHlcClockState(pendingClockState);
|
|
50
|
+
}
|
|
51
|
+
}, 5e3);
|
|
52
|
+
} : void 0
|
|
53
|
+
});
|
|
54
|
+
const changeNotifier = options.changeNotifier ?? createChangeNotifier();
|
|
55
|
+
const accessControlEngine = createAccessControlEngine({
|
|
56
|
+
policyStore: accessPolicyStore,
|
|
57
|
+
tokenStore: sharingTokenStore,
|
|
58
|
+
clock,
|
|
59
|
+
ownerId
|
|
60
|
+
});
|
|
61
|
+
await accessControlEngine.loadPolicies();
|
|
62
|
+
const databaseAdapter = subject ? createEnforcedDatabaseAdapter({
|
|
63
|
+
databaseAdapter: rawDatabaseAdapter,
|
|
64
|
+
accessControlEngine,
|
|
65
|
+
subjectType: subject.subjectType,
|
|
66
|
+
subjectId: subject.subjectId
|
|
67
|
+
}) : rawDatabaseAdapter;
|
|
68
|
+
const unifiedIndex = createUnifiedIndex({ databaseAdapter });
|
|
69
|
+
function logChange(record) {
|
|
70
|
+
changeNotifier.emit({
|
|
71
|
+
eventType: "local-change-recorded",
|
|
72
|
+
recordIds: [record.id],
|
|
73
|
+
timestamp: clock.now()
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const sharedSpaceApi = createSharedSpaceApi({
|
|
77
|
+
databaseAdapter,
|
|
78
|
+
objectStorageAdapter,
|
|
79
|
+
clock,
|
|
80
|
+
ownerId,
|
|
81
|
+
changeNotifier,
|
|
82
|
+
getAppSpecific: options.getAppSpecific
|
|
83
|
+
});
|
|
84
|
+
async function writeRecordAndMetadata(input, fileBytes, contentType, originalFilename) {
|
|
85
|
+
const contentHash = await sha256Hex(fileBytes);
|
|
86
|
+
const objectStorageKey = dataRecordObjectKey(input.type, contentHash);
|
|
87
|
+
const record = createDataRecord(
|
|
88
|
+
{
|
|
89
|
+
...input,
|
|
90
|
+
contentHash,
|
|
91
|
+
objectStorageKey,
|
|
92
|
+
mimeType: contentType,
|
|
93
|
+
sizeBytes: fileBytes.length,
|
|
94
|
+
originalFilename
|
|
95
|
+
},
|
|
96
|
+
clock
|
|
97
|
+
);
|
|
98
|
+
await databaseAdapter.put(record);
|
|
99
|
+
if (input.metadata && metadataCategory(input.type) !== "other") {
|
|
100
|
+
await databaseAdapter.putMetadata(input.type, {
|
|
101
|
+
...input.metadata,
|
|
102
|
+
recordId: record.id
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
logChange(record);
|
|
106
|
+
return record;
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
data: {
|
|
110
|
+
async putWithFile(input, file, contentType) {
|
|
111
|
+
const contentHash = await sha256Hex(file);
|
|
112
|
+
const objectStorageKey = dataRecordObjectKey(input.type, contentHash);
|
|
113
|
+
await objectStorageAdapter.put(objectStorageKey, file, { contentType });
|
|
114
|
+
return writeRecordAndMetadata(
|
|
115
|
+
{ ...input, contentHash, objectStorageKey },
|
|
116
|
+
file,
|
|
117
|
+
contentType,
|
|
118
|
+
input.originalFilename ?? null
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
async putWithLocalFile(input, filePath, contentType) {
|
|
122
|
+
const fileBytes = await readFile(filePath);
|
|
123
|
+
const contentHash = await sha256Hex(fileBytes);
|
|
124
|
+
const objectStorageKey = dataRecordObjectKey(input.type, contentHash);
|
|
125
|
+
if (objectStorageAdapter.putSymlink) {
|
|
126
|
+
await objectStorageAdapter.putSymlink(objectStorageKey, filePath, { contentType });
|
|
127
|
+
} else {
|
|
128
|
+
await objectStorageAdapter.put(objectStorageKey, fileBytes, { contentType });
|
|
129
|
+
}
|
|
130
|
+
return writeRecordAndMetadata(
|
|
131
|
+
input,
|
|
132
|
+
fileBytes,
|
|
133
|
+
contentType,
|
|
134
|
+
input.originalFilename ?? basename(filePath)
|
|
135
|
+
);
|
|
136
|
+
},
|
|
137
|
+
async putWithExistingBlob(input, blob) {
|
|
138
|
+
const record = createDataRecord(
|
|
139
|
+
{
|
|
140
|
+
...input,
|
|
141
|
+
contentHash: blob.contentHash,
|
|
142
|
+
objectStorageKey: blob.objectStorageKey,
|
|
143
|
+
mimeType: blob.mimeType,
|
|
144
|
+
sizeBytes: blob.sizeBytes,
|
|
145
|
+
originalFilename: input.originalFilename ?? null
|
|
146
|
+
},
|
|
147
|
+
clock
|
|
148
|
+
);
|
|
149
|
+
await databaseAdapter.put(record);
|
|
150
|
+
if (input.metadata) {
|
|
151
|
+
await databaseAdapter.putMetadata(input.type, {
|
|
152
|
+
...input.metadata,
|
|
153
|
+
recordId: record.id
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
logChange(record);
|
|
157
|
+
return record;
|
|
158
|
+
},
|
|
159
|
+
async get(recordId) {
|
|
160
|
+
const record = await databaseAdapter.get(recordId);
|
|
161
|
+
if (!record) return null;
|
|
162
|
+
if (record.deletedAt) return null;
|
|
163
|
+
return record;
|
|
164
|
+
},
|
|
165
|
+
async update(recordId, patch) {
|
|
166
|
+
const existing = await databaseAdapter.get(recordId);
|
|
167
|
+
if (!existing) {
|
|
168
|
+
throw new Error(`No data record found with id ${recordId}`);
|
|
169
|
+
}
|
|
170
|
+
const updated = {
|
|
171
|
+
...existing,
|
|
172
|
+
originalFilename: patch.originalFilename ?? existing.originalFilename,
|
|
173
|
+
parentId: patch.parentId ?? existing.parentId,
|
|
174
|
+
version: existing.version + 1,
|
|
175
|
+
updatedAt: clock.now()
|
|
176
|
+
};
|
|
177
|
+
await databaseAdapter.put(updated);
|
|
178
|
+
logChange(updated);
|
|
179
|
+
return updated;
|
|
180
|
+
},
|
|
181
|
+
async delete(recordId) {
|
|
182
|
+
const existing = await databaseAdapter.get(recordId);
|
|
183
|
+
if (!existing) return;
|
|
184
|
+
const ts = clock.now();
|
|
185
|
+
await databaseAdapter.delete(recordId, ts);
|
|
186
|
+
await databaseAdapter.deleteMetadata(existing.type, recordId);
|
|
187
|
+
const tombstone = {
|
|
188
|
+
...existing,
|
|
189
|
+
updatedAt: ts,
|
|
190
|
+
deletedAt: ts,
|
|
191
|
+
version: existing.version + 1
|
|
192
|
+
};
|
|
193
|
+
logChange(tombstone);
|
|
194
|
+
},
|
|
195
|
+
async query(params) {
|
|
196
|
+
const result = await databaseAdapter.query(params);
|
|
197
|
+
return result.records;
|
|
198
|
+
},
|
|
199
|
+
// `typeId` may be a record's extension or a category id; the adapter
|
|
200
|
+
// derives the per-category metadata table. The `other` category has no
|
|
201
|
+
// metadata table, so these are no-ops for it.
|
|
202
|
+
async putMetadata(typeId, row) {
|
|
203
|
+
if (metadataCategory(typeId) === "other") return;
|
|
204
|
+
await databaseAdapter.putMetadata(typeId, row);
|
|
205
|
+
},
|
|
206
|
+
async getMetadata(typeId, recordId) {
|
|
207
|
+
if (metadataCategory(typeId) === "other") return null;
|
|
208
|
+
return databaseAdapter.getMetadata(typeId, recordId);
|
|
209
|
+
},
|
|
210
|
+
async getMetadataByIds(typeId, recordIds) {
|
|
211
|
+
if (metadataCategory(typeId) === "other") return /* @__PURE__ */ new Map();
|
|
212
|
+
return databaseAdapter.getMetadataByIds(typeId, recordIds);
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
index: {
|
|
216
|
+
async search(query) {
|
|
217
|
+
return unifiedIndex.search(query);
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
accessControl: {
|
|
221
|
+
async createPolicy(input) {
|
|
222
|
+
if (subject) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
"createPolicy is not available on an app-scoped SDK. Policies are managed by the admin layer."
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
return accessControlEngine.createPolicy(input);
|
|
228
|
+
},
|
|
229
|
+
async revokePolicy(policyId) {
|
|
230
|
+
if (subject) {
|
|
231
|
+
throw new Error(
|
|
232
|
+
"revokePolicy is not available on an app-scoped SDK. Policies are managed by the admin layer."
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
return accessControlEngine.revokePolicy(policyId);
|
|
236
|
+
},
|
|
237
|
+
async listPolicies(listOptions) {
|
|
238
|
+
return accessControlEngine.listPolicies(listOptions);
|
|
239
|
+
},
|
|
240
|
+
async checkAccess(request) {
|
|
241
|
+
return accessControlEngine.checkAccess(request);
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
api: {
|
|
245
|
+
get router() {
|
|
246
|
+
return sharedSpaceApi.router;
|
|
247
|
+
},
|
|
248
|
+
async handleRequest(request) {
|
|
249
|
+
return sharedSpaceApi.handleRequest(request);
|
|
250
|
+
},
|
|
251
|
+
handleWebSocketConnect(connection) {
|
|
252
|
+
return sharedSpaceApi.handleWebSocketConnect(connection);
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
changeNotifier,
|
|
256
|
+
clock,
|
|
257
|
+
async close() {
|
|
258
|
+
if (clockFlushTimer) {
|
|
259
|
+
clearTimeout(clockFlushTimer);
|
|
260
|
+
clockFlushTimer = null;
|
|
261
|
+
}
|
|
262
|
+
if (pendingClockState && syncStateStore) {
|
|
263
|
+
await syncStateStore.setHlcClockState(pendingClockState);
|
|
264
|
+
}
|
|
265
|
+
await databaseAdapter.close();
|
|
266
|
+
await objectStorageAdapter.close();
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
export {
|
|
271
|
+
createStarkeepSdk
|
|
272
|
+
};
|
|
273
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sdk.ts"],"sourcesContent":["import {\n createHLCClock,\n createDataRecord,\n dataRecordObjectKey,\n categoryOf,\n isCategoryId,\n type DataRecord,\n type MetadataRow,\n} from \"@starkeep/protocol-primitives\";\nimport { readFile } from \"node:fs/promises\";\nimport { basename } from \"node:path\";\nasync function sha256Hex(data: Uint8Array | Buffer): Promise<string> {\n const copy = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) as ArrayBuffer;\n const buf = await crypto.subtle.digest(\"SHA-256\", copy);\n return Array.from(new Uint8Array(buf))\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\nimport { createUnifiedIndex } from \"@starkeep/query-orchestrator\";\nimport { createChangeNotifier } from \"@starkeep/sync-engine\";\nimport { createAccessControlEngine, createEnforcedDatabaseAdapter } from \"@starkeep/access-control\";\nimport { createSharedSpaceApi } from \"@starkeep/shared-space-api\";\nimport type {\n StarkeepSdk,\n StarkeepSdkOptions,\n DataPutInput,\n} from \"./types.js\";\n\n// Resolve a record's type/extension (or a category id) to its metadata\n// category. `other` has no metadata table, so callers skip persistence for it.\nfunction metadataCategory(typeOrCategory: string): string {\n return isCategoryId(typeOrCategory) ? typeOrCategory : categoryOf(typeOrCategory);\n}\n\nexport async function createStarkeepSdk(\n options: StarkeepSdkOptions,\n): Promise<StarkeepSdk> {\n const {\n databaseAdapter: rawDatabaseAdapter,\n objectStorageAdapter,\n accessPolicyStore,\n sharingTokenStore,\n ownerId,\n nodeId,\n syncStateStore,\n subject,\n } = options;\n\n await rawDatabaseAdapter.init();\n await objectStorageAdapter.init();\n\n // Seed the clock from persisted state and debounce write-back on tick,\n // so a restart resumes with an HLC causally after anything we emitted.\n // The clock state is global (one clock per node) — the supervisor's\n // per-app cursors live alongside it in the same store but are owned\n // elsewhere.\n const initialHlcState =\n (await syncStateStore?.getHlcClockState()) ?? undefined;\n let pendingClockState: { wallTime: number; counter: number } | null = null;\n let clockFlushTimer: NodeJS.Timeout | null = null;\n const clock =\n options.clock ??\n createHLCClock({\n nodeId,\n wallClockFunction: Date.now,\n initialState: initialHlcState,\n onTick: syncStateStore\n ? (state) => {\n pendingClockState = state;\n if (clockFlushTimer) return;\n clockFlushTimer = setTimeout(() => {\n clockFlushTimer = null;\n if (pendingClockState) {\n void syncStateStore.setHlcClockState(pendingClockState);\n }\n }, 5000);\n }\n : undefined,\n });\n\n // One shared change notifier. Writes emit `local-change-recorded`; the\n // supervisor's per-app sync engines forward their own pull/conflict events\n // onto this same notifier so consumers (sharedSpaceApi, SSE clients) see\n // one unified stream. Callers may inject a notifier (e.g. the local-data-\n // server hoists it to share with the app-specific factory, which emits\n // its own local-change events tagged with the writing app's id).\n const changeNotifier = options.changeNotifier ?? createChangeNotifier();\n\n // When a subject is provided, wrap the adapter so every operation is\n // gated by access control and the private-storage structural rule.\n const accessControlEngine = createAccessControlEngine({\n policyStore: accessPolicyStore,\n tokenStore: sharingTokenStore,\n clock,\n ownerId,\n });\n await accessControlEngine.loadPolicies();\n\n const databaseAdapter = subject\n ? createEnforcedDatabaseAdapter({\n databaseAdapter: rawDatabaseAdapter,\n accessControlEngine,\n subjectType: subject.subjectType,\n subjectId: subject.subjectId,\n })\n : rawDatabaseAdapter;\n\n const unifiedIndex = createUnifiedIndex({ databaseAdapter });\n\n /**\n * Emit a `local-change-recorded` event for a write. The supervisor wakes its\n * exchange loop in response; the records-table row itself is the durable\n * source of truth for what to ship (no separate change log).\n */\n function logChange(record: DataRecord): void {\n changeNotifier.emit({\n eventType: \"local-change-recorded\",\n recordIds: [record.id],\n timestamp: clock.now(),\n });\n }\n\n const sharedSpaceApi = createSharedSpaceApi({\n databaseAdapter,\n objectStorageAdapter,\n clock,\n ownerId,\n changeNotifier,\n getAppSpecific: options.getAppSpecific,\n });\n\n async function writeRecordAndMetadata(\n input: DataPutInput,\n fileBytes: Uint8Array,\n contentType: string,\n originalFilename: string | null,\n ): Promise<DataRecord> {\n const contentHash = await sha256Hex(fileBytes);\n const objectStorageKey = dataRecordObjectKey(input.type, contentHash);\n\n const record = createDataRecord(\n {\n ...input,\n contentHash,\n objectStorageKey,\n mimeType: contentType,\n sizeBytes: fileBytes.length,\n originalFilename,\n },\n clock,\n );\n await databaseAdapter.put(record);\n if (input.metadata && metadataCategory(input.type) !== \"other\") {\n await databaseAdapter.putMetadata(input.type, {\n ...input.metadata,\n recordId: record.id,\n });\n }\n logChange(record);\n return record;\n }\n\n return {\n data: {\n async putWithFile(input, file, contentType) {\n const contentHash = await sha256Hex(file);\n const objectStorageKey = dataRecordObjectKey(input.type, contentHash);\n\n await objectStorageAdapter.put(objectStorageKey, file, { contentType });\n return writeRecordAndMetadata(\n { ...input, contentHash, objectStorageKey } as DataPutInput,\n file,\n contentType,\n input.originalFilename ?? null,\n );\n },\n\n async putWithLocalFile(input, filePath, contentType) {\n const fileBytes = await readFile(filePath);\n const contentHash = await sha256Hex(fileBytes);\n const objectStorageKey = dataRecordObjectKey(input.type, contentHash);\n\n if (objectStorageAdapter.putSymlink) {\n await objectStorageAdapter.putSymlink(objectStorageKey, filePath, { contentType });\n } else {\n await objectStorageAdapter.put(objectStorageKey, fileBytes, { contentType });\n }\n\n return writeRecordAndMetadata(\n input,\n fileBytes,\n contentType,\n input.originalFilename ?? basename(filePath),\n );\n },\n\n async putWithExistingBlob(input, blob) {\n // Bytes are already in object storage (uploaded out-of-band, e.g. via\n // a presigned PUT). Skip the upload + re-hash; trust the caller's\n // contentHash and sizeBytes. The records-table row is otherwise\n // identical to what putWithFile would produce.\n const record = createDataRecord(\n {\n ...input,\n contentHash: blob.contentHash,\n objectStorageKey: blob.objectStorageKey,\n mimeType: blob.mimeType,\n sizeBytes: blob.sizeBytes,\n originalFilename: input.originalFilename ?? null,\n } as DataPutInput as never,\n clock,\n );\n await databaseAdapter.put(record);\n if (input.metadata) {\n await databaseAdapter.putMetadata(input.type, {\n ...input.metadata,\n recordId: record.id,\n });\n }\n logChange(record);\n return record;\n },\n\n async get(recordId) {\n const record = await databaseAdapter.get(recordId);\n if (!record) return null;\n if (record.deletedAt) return null;\n return record;\n },\n\n async update(recordId, patch) {\n const existing = await databaseAdapter.get(recordId);\n if (!existing) {\n throw new Error(`No data record found with id ${recordId}`);\n }\n const updated: DataRecord = {\n ...existing,\n originalFilename: patch.originalFilename ?? existing.originalFilename,\n parentId: patch.parentId ?? existing.parentId,\n version: existing.version + 1,\n updatedAt: clock.now(),\n };\n await databaseAdapter.put(updated);\n logChange(updated);\n return updated;\n },\n\n async delete(recordId) {\n const existing = await databaseAdapter.get(recordId);\n if (!existing) return;\n const ts = clock.now();\n await databaseAdapter.delete(recordId, ts);\n await databaseAdapter.deleteMetadata(existing.type, recordId);\n const tombstone: DataRecord = {\n ...existing,\n updatedAt: ts,\n deletedAt: ts,\n version: existing.version + 1,\n };\n logChange(tombstone);\n },\n\n async query(params) {\n const result = await databaseAdapter.query(params);\n return result.records;\n },\n\n // `typeId` may be a record's extension or a category id; the adapter\n // derives the per-category metadata table. The `other` category has no\n // metadata table, so these are no-ops for it.\n async putMetadata(typeId: string, row: MetadataRow) {\n if (metadataCategory(typeId) === \"other\") return;\n await databaseAdapter.putMetadata(typeId, row);\n },\n\n async getMetadata(typeId, recordId) {\n if (metadataCategory(typeId) === \"other\") return null;\n return databaseAdapter.getMetadata(typeId, recordId);\n },\n\n async getMetadataByIds(typeId, recordIds) {\n if (metadataCategory(typeId) === \"other\") return new Map();\n return databaseAdapter.getMetadataByIds(typeId, recordIds);\n },\n },\n\n index: {\n async search(query) {\n return unifiedIndex.search(query);\n },\n },\n\n accessControl: {\n async createPolicy(input) {\n if (subject) {\n throw new Error(\n \"createPolicy is not available on an app-scoped SDK. Policies are managed by the admin layer.\",\n );\n }\n return accessControlEngine.createPolicy(input);\n },\n\n async revokePolicy(policyId) {\n if (subject) {\n throw new Error(\n \"revokePolicy is not available on an app-scoped SDK. Policies are managed by the admin layer.\",\n );\n }\n return accessControlEngine.revokePolicy(policyId);\n },\n\n async listPolicies(listOptions) {\n return accessControlEngine.listPolicies(listOptions);\n },\n\n async checkAccess(request) {\n return accessControlEngine.checkAccess(request);\n },\n },\n\n api: {\n get router() {\n return sharedSpaceApi.router;\n },\n async handleRequest(request) {\n return sharedSpaceApi.handleRequest(request);\n },\n handleWebSocketConnect(connection) {\n return sharedSpaceApi.handleWebSocketConnect(connection);\n },\n },\n\n changeNotifier,\n clock,\n\n async close() {\n if (clockFlushTimer) {\n clearTimeout(clockFlushTimer);\n clockFlushTimer = null;\n }\n if (pendingClockState && syncStateStore) {\n await syncStateStore.setHlcClockState(pendingClockState);\n }\n await databaseAdapter.close();\n await objectStorageAdapter.close();\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@starkeep/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/starkeep-dev/starkeep-core.git",
|
|
26
|
+
"directory": "packages/sdk"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/starkeep-dev/starkeep-core/tree/main/packages/sdk",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@starkeep/protocol-primitives": "0.1.0",
|
|
31
|
+
"@starkeep/storage-adapter": "0.1.0",
|
|
32
|
+
"@starkeep/access-control": "0.1.0",
|
|
33
|
+
"@starkeep/sync-engine": "0.1.0",
|
|
34
|
+
"@starkeep/shared-space-api": "0.1.0",
|
|
35
|
+
"@starkeep/query-orchestrator": "0.1.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^25.4.0",
|
|
39
|
+
"tsup": "^8.0.0",
|
|
40
|
+
"typescript": "^5.7.0",
|
|
41
|
+
"vitest": "^3.0.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"typecheck": "tsc --noEmit",
|
|
47
|
+
"lint": "eslint src/"
|
|
48
|
+
}
|
|
49
|
+
}
|