14 KiB
Speckle Compatibility Contract
1. Mục tiêu
Tài liệu này định nghĩa contract tối thiểu mà nền tảng ASP.NET/Blazor mới phải giữ để Speckle Connector, Speckle SDK và Speckle Viewer hiện tại có thể làm việc với server mới.
Mục tiêu không phải clone toàn bộ Speckle Server. Mục tiêu là giữ đúng các điểm chạm quan trọng:
- Connector đăng nhập được.
- Connector đọc danh sách project/model/version được.
- Connector tạo project/model/version được.
- Connector upload object graph được.
- Connector pull object graph được.
- Viewer load được model/version/object.
- File upload/import cơ bản hoạt động.
2. Mapping thuật ngữ
| Speckle legacy | API mới | Ý nghĩa |
|---|---|---|
| Stream | Project | Không gian chứa model, version, user role. |
| Branch | Model | Nhánh dữ liệu trong project. |
| Commit | Version | Snapshot trỏ tới root object. |
| Object | Object | JSON object immutable, có id hash. |
| referencedObject | Root Object | Object gốc của version. |
| Blob | Blob/File | File thô hoặc attachment. |
Quy tắc:
- API mới nên dùng Project/Model/Version.
- Compatibility API vẫn phải trả Stream/Branch/Commit cho client legacy.
- ID có thể dùng cùng giá trị giữa Project và Stream để giảm mapping phức tạp.
3. Client cần support
| Client | Mức support MVP |
|---|---|
| Speckle Connector Desktop Manager | Login và lưu account. |
| Speckle Connectors v2/v3 | Push/Pull cơ bản. |
| Speckle SDK | Auth, stream/project query, object upload/download. |
| Speckle Viewer | Load object stream và viewer resource. |
| Speckle Web cũ | Không phải mục tiêu chính, chỉ support nếu không phát sinh thêm contract lớn. |
4. Auth contract
4.1 Default app IDs
Server mới cần pre-register các app ID phổ biến:
| App ID | Vai trò |
|---|---|
spklwebapp |
Speckle Web Manager. |
sdm |
Speckle Desktop Manager. |
sca |
Speckle Connector legacy. |
sdas |
Speckle Desktop Auth Service. |
sdui |
Desktop connector UI. |
connectrV3 |
Desktop Connectors v3. |
spklexcel |
Excel connector nếu cần. |
spklpwerbi |
PowerBI connector nếu cần. |
4.2 OAuth-like flow
GET /auth/accesscode
Mục tiêu: browser flow để user approve connector/app.
Yêu cầu:
- Nhận query params tương thích Speckle app flow.
- Nếu user chưa đăng nhập, chuyển đến trang login.
- Nếu app chưa trusted, hiển thị màn approve.
- Tạo access code ngắn hạn.
- Redirect về
redirectUrlcủa app hoặc localhost callback connector.
POST /auth/token
Mục tiêu: đổi access code thành bearer token.
Yêu cầu:
- Nhận payload tương thích connector.
- Validate app ID, access code, redirect URL/challenge nếu có.
- Trả token dạng bearer để dùng với GraphQL và REST.
- Token phải có scope đúng.
Token storage:
- Token public trả cho client có thể giữ format Speckle:
10 ký tự id + 32 ký tự secret. - DB chỉ lưu token id, token digest, owner, scopes, appId, expiry, last chars.
- Không lưu plain token secret.
POST /auth/logout
Mục tiêu: revoke session web hoặc token hiện tại nếu client gọi.
5. Scope và permission
Scope tối thiểu:
| Scope | Ý nghĩa |
|---|---|
streams:read |
Đọc project/model/version/object. |
streams:write |
Tạo/sửa project/model/version, upload object. |
profile:read |
Đọc thông tin user hiện tại. |
profile:email |
Đọc email user. |
users:read |
Đọc collaborator/team. |
users:invite |
Invite user nếu connector/web cần. |
Project role tối thiểu:
| Role | Quyền |
|---|---|
| Owner | Full quyền project. |
| Contributor | Upload, tạo model/version, comment. |
| Reviewer | Xem, comment, issue, approval nếu được giao. |
| Viewer | Chỉ xem/pull. |
6. REST object endpoints
6.1 POST /objects/{streamId}
Mục tiêu: upload object batch.
Yêu cầu compatibility:
streamIdđược map sangprojectId.- Auth cần
streams:writevà quyền ghi project. - Request là
multipart/form-data. - Mỗi file part chứa JSON array object.
- MIME hợp lệ:
application/gzip,text/plain,application/json,application/octet-stream. - Nếu gzip, server phải gunzip rồi parse JSON.
- Mỗi object phải có id/hash hợp lệ hoặc server tính theo rule Speckle.
- Server lưu object dạng immutable và deduplicate.
- Response thành công: HTTP
201.
Lỗi tối thiểu:
| Điều kiện | Status |
|---|---|
| Không có quyền | 401 hoặc 403. |
| Project không tồn tại | 404. |
| Payload không parse được | 400. |
| Batch quá lớn | 413. |
6.2 GET /objects/{streamId}/{objectId}
Mục tiêu: download object và children phục vụ connector/viewer legacy.
Yêu cầu:
- Auth cần
streams:readhoặc project public. - Trả dữ liệu object dạng JSON/gzip theo hành vi Speckle hiện tại.
- Phải support object root và traversal children nếu client yêu cầu qua query hiện có.
6.3 GET /objects/{streamId}/{objectId}/single
Mục tiêu: download một object đơn lẻ.
Yêu cầu:
- Không expand children.
- Dùng cho client cần object cụ thể.
6.4 POST /api/diff/{streamId}
Mục tiêu: client gửi danh sách object id, server trả danh sách object còn thiếu.
Yêu cầu:
- Auth cần
streams:write. - Body nhận danh sách object ids theo format Speckle legacy.
- Response là danh sách object ids server chưa có.
- Endpoint này giúp giảm upload trùng.
6.5 POST /api/getobjects/{streamId}
Mục tiêu: client pull nhiều object theo danh sách id.
Yêu cầu:
- Auth cần
streams:read. - Body có field
objectslà JSON string của array object ids. - Response gzip.
- Nếu
Accept: text/plain, response content type làtext/plain; charset=UTF-8. - Format text stream: mỗi dòng
objectId<TAB>json.
6.6 POST /api/v2/projects/{streamId}/object-stream
Mục tiêu: object streaming v2 cho viewer/client mới.
Yêu cầu:
- Auth cần
streams:readhoặc project public. - Body:
{
"objectIds": ["object-id-1", "object-id-2"],
"attributeMask": {
"include": ["id", "speckle_type"]
}
}
attributeMaskcó thể làincludehoặcexclude.- Response gzip text stream.
streamIdvẫn dùng tên route legacy nhưng map sangprojectId.
7. Blob/file endpoints
7.1 POST /api/stream/{streamId}/blob
Upload blob vào project.
Yêu cầu:
- Auth cần quyền ghi comment/blob.
- Request multipart.
- Response
201vớiuploadResults.
7.2 POST /api/stream/{streamId}/blob/diff
Client gửi danh sách blob ids, server trả blob ids chưa có.
7.3 GET /api/stream/{streamId}/blob/{blobId}
Download blob.
Yêu cầu:
- Support public project nếu policy cho phép.
- Header
Content-Dispositiontheo filename.
7.4 DELETE /api/stream/{streamId}/blob/{blobId}
Xóa blob nếu user có quyền.
7.5 GET /api/stream/{streamId}/blobs
List blob metadata, có thể filter bằng fileName.
7.6 POST /api/file/{fileType}/{streamId}/{branchName?}
Legacy file upload/import endpoint.
Yêu cầu:
- Giữ cho connector/client cũ.
branchNamemặc địnhmain.- Sau upload phải tạo file upload record và enqueue import job.
- Response
201vớiuploadResults. - Nên trả warning deprecation nhưng không phá client.
8. Viewer derivative endpoints
8.1 GET /api/viewer-derivatives/{projectId}/{versionId}/manifest
Mục tiêu: viewer lấy manifest derivative nếu có.
Response khi chưa sẵn sàng:
{
"status": "queued-or-processing",
"progress": 0.5,
"stage": "generating-tiles"
}
Response khi sẵn sàng:
{
"status": "ready",
"manifest": {}
}
8.2 GET /api/viewer-derivatives/{projectId}/{versionId}/artifacts/*
Mục tiêu: viewer tải tile/buffer/metadata artifact.
Yêu cầu:
- Auth đọc project.
- Cache immutable cho artifact đã publish.
8.3 Worker publish endpoints
Các endpoint nội bộ cho worker:
PUT /api/viewer-derivatives/{projectId}/{versionId}/artifacts/*POST /api/viewer-derivatives/{projectId}/{versionId}/publishPOST /api/viewer-derivatives/{projectId}/{versionId}/fail
Chỉ internal worker token có quyền gọi.
9. GraphQL compatibility surface
9.1 Query tối thiểu
Server mới cần support tối thiểu:
type Query {
activeUser: User
serverInfo: ServerInfo
project(id: String!): Project!
stream(id: String!): Stream
streams(query: String, limit: Int, cursor: String): UserStreamCollection
}
Project fields tối thiểu:
type Project {
id: ID!
name: String!
description: String
visibility: ProjectVisibility!
role: String
createdAt: DateTime!
updatedAt: DateTime!
models(cursor: String, limit: Int, filter: ProjectModelsFilter): ModelCollection!
model(id: String!): Model!
modelByName(name: String!): Model!
versions(limit: Int, cursor: String): VersionCollection!
version(id: String!): Version!
object(id: String!): Object
}
Legacy Stream fields tối thiểu:
type Stream {
id: String!
name: String!
description: String
isPublic: Boolean!
role: String
branches(limit: Int, cursor: String): BranchCollection
branch(name: String): Branch
commits(limit: Int, cursor: String): CommitCollection
commit(id: String): Commit
object(id: String!): Object
}
9.2 Mutation tối thiểu
API mới:
type Mutation {
projectMutations: ProjectMutations!
modelMutations: ModelMutations!
versionMutations: VersionMutations!
fileUploadMutations: FileUploadMutations!
}
Legacy:
type Mutation {
streamCreate(stream: StreamCreateInput!): String
streamUpdate(stream: StreamUpdateInput!): Boolean!
streamDelete(id: String!): Boolean!
branchCreate(branch: BranchCreateInput!): String!
branchUpdate(branch: BranchUpdateInput!): Boolean!
branchDelete(branch: BranchDeleteInput!): Boolean!
commitCreate(commit: CommitCreateInput!): String!
commitUpdate(commit: CommitUpdateInput!): Boolean!
commitDelete(commit: CommitDeleteInput!): Boolean!
commitReceive(input: CommitReceivedInput!): Boolean!
objectCreate(objectInput: ObjectCreateInput!): [String!]!
}
9.3 Model/Version fields tối thiểu
type Model {
id: ID!
name: String!
displayName: String!
description: String
createdAt: DateTime!
updatedAt: DateTime!
versions(limit: Int, cursor: String, filter: ModelVersionsFilter): VersionCollection!
version(id: String!): Version!
projectId: String!
}
type Version {
id: ID!
referencedObject: String
message: String
sourceApplication: String
createdAt: DateTime!
totalChildrenCount: Int
parents: [String]
model: Model!
}
Legacy:
type Branch {
id: String!
name: String!
commits(limit: Int, cursor: String): CommitCollection
}
type Commit {
id: String!
referencedObject: String!
message: String
sourceApplication: String
totalChildrenCount: Int
branchName: String
parents: [String]
createdAt: DateTime
}
10. Object identity contract
Yêu cầu:
- Object id phải dài 32 ký tự nếu client gửi theo Speckle hash legacy.
- Nếu object có
idhoặchash, server phải tôn trọng nếu hợp lệ. - Nếu thiếu id, server tính hash theo thuật toán tương thích Speckle.
- Dedup key tối thiểu:
(projectId, objectId). - Object data lưu nguyên JSON để pull ra không mất field.
- Không normalize JSON làm thay đổi semantic object khi trả về client.
Acceptance test:
- Dùng cùng một object JSON, server mới phải tạo cùng object id như Speckle Server hoặc chấp nhận id client gửi.
- Upload cùng batch hai lần không tạo bản ghi trùng.
- Pull object sau upload trả data tương đương payload ban đầu.
11. Error compatibility
Không cần clone toàn bộ error message, nhưng cần giữ:
- HTTP status đúng.
- JSON body có
errorhoặc GraphQLerrors. - Không trả HTML error page cho API.
- Validation lỗi payload phải rõ để connector log được.
12. Contract test bắt buộc
MVP chỉ được xem là tương thích khi pass các test:
- Connector login qua
/auth/accesscodevà/auth/token. - Tạo project bằng legacy
streamCreate. - Tạo model bằng legacy
branchCreate. - Upload object qua
POST /objects/{projectId}. - Diff object qua
POST /api/diff/{projectId}. - Tạo version bằng legacy
commitCreate. - Pull version object bằng
GET /objects/{projectId}/{objectId}. - Pull nhiều object bằng
POST /api/getobjects/{projectId}. - Viewer load version qua object stream.
- Token read-only không được upload.
- Private project không đọc được nếu thiếu quyền.
13. Chính sách thay đổi contract
- Mọi endpoint trong tài liệu này là compatibility contract, không xoá trong MVP.
- Nếu cần thay đổi request/response, thêm version route mới thay vì sửa route cũ.
- Ghi contract test trước khi implement.
- Khi support thêm connector version mới, bổ sung query/mutation/endpoint thật vào tài liệu này.