@objectstack/service-package 1.0.0 → 4.0.4
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/README.md +125 -0
- package/package.json +26 -1
- package/.turbo/turbo-build.log +0 -22
- package/src/index.ts +0 -216
- package/tsconfig.json +0 -17
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# @objectstack/service-package
|
|
2
|
+
|
|
3
|
+
> Runtime package publishing, retrieval, and lifecycle management for ObjectStack — the storage layer behind the dynamic marketplace.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@objectstack/service-package)
|
|
6
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
`service-package` persists ObjectStack packages — the unit of metadata distribution consisting of a `manifest` plus its `objects`, `views`, `apps`, `flows`, `agents`, `tools`, and `translations` — into the `sys_packages` system table so they can be published, listed, and delivered to runtime kernels that load them through `@objectstack/service-marketplace`.
|
|
11
|
+
|
|
12
|
+
Typical consumers:
|
|
13
|
+
|
|
14
|
+
- **Marketplace servers** that host first-party and third-party packages.
|
|
15
|
+
- **Self-hosted platforms** that want an internal registry for tenants' custom metadata.
|
|
16
|
+
- **CI pipelines** that publish package artifacts from a Git repository.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add @objectstack/service-package
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { ObjectKernel } from '@objectstack/core';
|
|
28
|
+
import { PackageServicePlugin, type PackageService } from '@objectstack/service-package';
|
|
29
|
+
|
|
30
|
+
const kernel = new ObjectKernel();
|
|
31
|
+
|
|
32
|
+
// Register after a driver/ObjectQL plugin so `ctx.getService('objectql')` resolves.
|
|
33
|
+
kernel.use(new PackageServicePlugin());
|
|
34
|
+
|
|
35
|
+
await kernel.bootstrap();
|
|
36
|
+
|
|
37
|
+
const packages = kernel.getService<PackageService>('package')!;
|
|
38
|
+
|
|
39
|
+
await packages.publish({
|
|
40
|
+
manifest: {
|
|
41
|
+
id: 'crm',
|
|
42
|
+
version: '1.2.0',
|
|
43
|
+
name: 'CRM Package',
|
|
44
|
+
/* …full ObjectStackManifest… */
|
|
45
|
+
},
|
|
46
|
+
metadata: {
|
|
47
|
+
objects: [/* … */],
|
|
48
|
+
views: [/* … */],
|
|
49
|
+
apps: [/* … */],
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const latest = await packages.get('crm'); // defaults to 'latest'
|
|
54
|
+
const all = await packages.list();
|
|
55
|
+
await packages.delete('crm', '1.0.0');
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Key Exports
|
|
59
|
+
|
|
60
|
+
| Export | Kind | Description |
|
|
61
|
+
|:---|:---|:---|
|
|
62
|
+
| `PackageServicePlugin` | class (default) | Kernel plugin that installs the service and ensures the `sys_packages` table exists. |
|
|
63
|
+
| `PackageService` | interface | Service contract registered under `'package'`: `publish`, `get`, `list`, `delete`. |
|
|
64
|
+
| `PackageRecord` | type | Stored row shape: `id`, `version`, `manifest`, `metadata`, `hash`, `created_at`, `updated_at`. |
|
|
65
|
+
| `PackageMetadata` | type | Artifact container for `objects`, `views`, `apps`, `flows`, `agents`, `tools`, `translations`. |
|
|
66
|
+
|
|
67
|
+
## Behavior
|
|
68
|
+
|
|
69
|
+
- **Upsert by (id, version)** — re-publishing the same version overwrites `manifest`, `metadata`, `hash`, and `updated_at`.
|
|
70
|
+
- **Integrity hash** — SHA-256 of `{ manifest, metadata }` is computed on publish and stored alongside the row.
|
|
71
|
+
- **`get(id, 'latest')`** resolves to the most recently inserted version. Use an explicit version string for pinned lookups.
|
|
72
|
+
- **`list()`** returns the latest version per package, ordered by `created_at DESC`.
|
|
73
|
+
- **System bypass** — the service uses `IDataEngine.execute()` directly (raw SQL), so security/RLS middleware is not applied to registry storage; callers are responsible for authorizing publish/delete at their API layer.
|
|
74
|
+
|
|
75
|
+
## Storage Schema
|
|
76
|
+
|
|
77
|
+
`service-package` creates (idempotently) the following table on `start()`:
|
|
78
|
+
|
|
79
|
+
```sql
|
|
80
|
+
CREATE TABLE IF NOT EXISTS sys_packages (
|
|
81
|
+
id TEXT NOT NULL,
|
|
82
|
+
version TEXT NOT NULL,
|
|
83
|
+
manifest TEXT NOT NULL,
|
|
84
|
+
metadata TEXT NOT NULL,
|
|
85
|
+
hash TEXT NOT NULL,
|
|
86
|
+
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
87
|
+
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
88
|
+
PRIMARY KEY (id, version)
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
CREATE INDEX IF NOT EXISTS idx_packages_latest
|
|
92
|
+
ON sys_packages(id, created_at DESC);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Requirements
|
|
96
|
+
|
|
97
|
+
- A driver plugin that registers an `IDataEngine` under the service name `'objectql'` with `execute()` support — typically [`@objectstack/driver-sql`](../../plugins/driver-sql) or [`@objectstack/driver-turso`](../../plugins/driver-turso). `@objectstack/driver-memory` can be used for tests but does not persist across restarts.
|
|
98
|
+
|
|
99
|
+
## When to use
|
|
100
|
+
|
|
101
|
+
- ✅ Building a marketplace backend or internal package registry.
|
|
102
|
+
- ✅ Distributing metadata-only packages to many runtime instances.
|
|
103
|
+
- ✅ Versioned audit trail of schema/UI/flow changes.
|
|
104
|
+
|
|
105
|
+
## When not to use
|
|
106
|
+
|
|
107
|
+
- ❌ Not a package manager for npm/TypeScript source packages — use npm.
|
|
108
|
+
- ❌ Not a runtime plugin loader — pair with [`@objectstack/service-marketplace`](../service-marketplace) or a custom loader for that.
|
|
109
|
+
|
|
110
|
+
## Related Packages
|
|
111
|
+
|
|
112
|
+
- [`@objectstack/core`](../../core) — kernel hosting this plugin.
|
|
113
|
+
- [`@objectstack/spec`](../../spec) — provides `ObjectStackManifest` and `IDataEngine` contracts.
|
|
114
|
+
- [`@objectstack/driver-sql`](../../plugins/driver-sql), [`@objectstack/driver-turso`](../../plugins/driver-turso) — supply the `'objectql'` service.
|
|
115
|
+
|
|
116
|
+
## Links
|
|
117
|
+
|
|
118
|
+
- 📖 Docs: <https://objectstack.ai/docs>
|
|
119
|
+
- 📚 API Reference: <https://objectstack.ai/docs/references>
|
|
120
|
+
- 🧭 Protocol: <https://objectstack.ai/docs/protocol>
|
|
121
|
+
- 🧪 Examples: [`examples/`](../../../examples)
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
Apache-2.0 © ObjectStack
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/service-package",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.4",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Package management service for ObjectStack — publish, install, and manage packages",
|
|
6
6
|
"type": "module",
|
|
@@ -22,6 +22,31 @@
|
|
|
22
22
|
"typescript": "^6.0.2",
|
|
23
23
|
"vitest": "^4.1.4"
|
|
24
24
|
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"objectstack",
|
|
27
|
+
"service",
|
|
28
|
+
"package",
|
|
29
|
+
"registry",
|
|
30
|
+
"marketplace"
|
|
31
|
+
],
|
|
32
|
+
"author": "ObjectStack",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/objectstack-ai/framework.git",
|
|
36
|
+
"directory": "packages/services/service-package"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://objectstack.ai/docs",
|
|
39
|
+
"bugs": "https://github.com/objectstack-ai/framework/issues",
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"README.md"
|
|
46
|
+
],
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18.0.0"
|
|
49
|
+
},
|
|
25
50
|
"scripts": {
|
|
26
51
|
"build": "tsup --config ../../../tsup.config.ts",
|
|
27
52
|
"test": "vitest run --passWithNoTests"
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @objectstack/service-package@1.0.0 build /home/runner/work/framework/framework/packages/services/service-package
|
|
3
|
-
> tsup --config ../../../tsup.config.ts
|
|
4
|
-
|
|
5
|
-
[34mCLI[39m Building entry: src/index.ts
|
|
6
|
-
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
|
-
[34mCLI[39m tsup v8.5.1
|
|
8
|
-
[34mCLI[39m Using tsup config: /home/runner/work/framework/framework/tsup.config.ts
|
|
9
|
-
[34mCLI[39m Target: es2020
|
|
10
|
-
[34mCLI[39m Cleaning output folder
|
|
11
|
-
[34mESM[39m Build start
|
|
12
|
-
[34mCJS[39m Build start
|
|
13
|
-
[32mESM[39m [1mdist/index.js [22m[32m5.10 KB[39m
|
|
14
|
-
[32mESM[39m [1mdist/index.js.map [22m[32m9.90 KB[39m
|
|
15
|
-
[32mESM[39m ⚡️ Build success in 64ms
|
|
16
|
-
[32mCJS[39m [1mdist/index.cjs [22m[32m6.14 KB[39m
|
|
17
|
-
[32mCJS[39m [1mdist/index.cjs.map [22m[32m9.95 KB[39m
|
|
18
|
-
[32mCJS[39m ⚡️ Build success in 68ms
|
|
19
|
-
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in 14387ms
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m1.38 KB[39m
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.cts [22m[32m1.38 KB[39m
|
package/src/index.ts
DELETED
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
|
|
3
|
-
import type { Plugin, PluginContext } from '@objectstack/core';
|
|
4
|
-
import { createHash } from 'node:crypto';
|
|
5
|
-
import type { ObjectStackManifest } from '@objectstack/spec/kernel';
|
|
6
|
-
import type { IDataEngine } from '@objectstack/spec/contracts';
|
|
7
|
-
|
|
8
|
-
export interface PackageMetadata {
|
|
9
|
-
objects?: any[];
|
|
10
|
-
views?: any[];
|
|
11
|
-
apps?: any[];
|
|
12
|
-
flows?: any[];
|
|
13
|
-
agents?: any[];
|
|
14
|
-
tools?: any[];
|
|
15
|
-
translations?: any[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface PackageRecord {
|
|
19
|
-
id: string;
|
|
20
|
-
version: string;
|
|
21
|
-
manifest: ObjectStackManifest;
|
|
22
|
-
metadata: PackageMetadata;
|
|
23
|
-
hash: string;
|
|
24
|
-
created_at: string;
|
|
25
|
-
updated_at: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface PackageService {
|
|
29
|
-
publish(data: { manifest: ObjectStackManifest; metadata: PackageMetadata }): Promise<{ success: boolean; error?: string }>;
|
|
30
|
-
get(packageId: string, version?: string): Promise<PackageRecord | null>;
|
|
31
|
-
list(): Promise<PackageRecord[]>;
|
|
32
|
-
delete(packageId: string, version?: string): Promise<{ success: boolean }>;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Package Management Service Plugin
|
|
37
|
-
*
|
|
38
|
-
* Provides package publishing, retrieval, and management capabilities.
|
|
39
|
-
* Stores package metadata in the sys.packages table for dynamic loading.
|
|
40
|
-
*/
|
|
41
|
-
export class PackageServicePlugin implements Plugin {
|
|
42
|
-
name = 'package-service';
|
|
43
|
-
|
|
44
|
-
async init(ctx: PluginContext): Promise<void> {
|
|
45
|
-
// Service will be registered in start() after ObjectQL is available
|
|
46
|
-
ctx.logger.debug('Package service plugin initialized');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async start(ctx: PluginContext): Promise<void> {
|
|
50
|
-
const logger = ctx.logger;
|
|
51
|
-
|
|
52
|
-
// Get ObjectQL service (available in start() hook after dependencies are initialized)
|
|
53
|
-
const objectql = ctx.getService<IDataEngine>('objectql');
|
|
54
|
-
if (!objectql || !objectql.execute) {
|
|
55
|
-
throw new Error('ObjectQL service with execute() support is required for PackageService');
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Create sys_packages table if it doesn't exist
|
|
59
|
-
try {
|
|
60
|
-
await this.ensureTable(objectql, logger);
|
|
61
|
-
} catch (error) {
|
|
62
|
-
logger.error('Failed to create sys_packages table', error as Error);
|
|
63
|
-
throw error;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Create the package service
|
|
67
|
-
const packageService: PackageService = {
|
|
68
|
-
async publish(data: { manifest: ObjectStackManifest; metadata: PackageMetadata }) {
|
|
69
|
-
try {
|
|
70
|
-
const hash = createHash('sha256')
|
|
71
|
-
.update(JSON.stringify({ manifest: data.manifest, metadata: data.metadata }))
|
|
72
|
-
.digest('hex');
|
|
73
|
-
|
|
74
|
-
await objectql.execute!({
|
|
75
|
-
sql: `
|
|
76
|
-
INSERT INTO sys_packages (id, version, manifest, metadata, hash, created_at, updated_at)
|
|
77
|
-
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
|
78
|
-
ON CONFLICT(id, version) DO UPDATE SET
|
|
79
|
-
manifest = excluded.manifest,
|
|
80
|
-
metadata = excluded.metadata,
|
|
81
|
-
hash = excluded.hash,
|
|
82
|
-
updated_at = CURRENT_TIMESTAMP
|
|
83
|
-
`,
|
|
84
|
-
args: [
|
|
85
|
-
data.manifest.id,
|
|
86
|
-
data.manifest.version,
|
|
87
|
-
JSON.stringify(data.manifest),
|
|
88
|
-
JSON.stringify(data.metadata),
|
|
89
|
-
hash,
|
|
90
|
-
],
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
logger.info(`Published package: ${data.manifest.id}@${data.manifest.version}`);
|
|
94
|
-
return { success: true };
|
|
95
|
-
} catch (error) {
|
|
96
|
-
logger.error('Failed to publish package', error as Error);
|
|
97
|
-
return {
|
|
98
|
-
success: false,
|
|
99
|
-
error: (error as Error).message,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
async get(packageId: string, version: string = 'latest') {
|
|
105
|
-
try {
|
|
106
|
-
const sql = version === 'latest'
|
|
107
|
-
? `SELECT * FROM sys_packages WHERE id = ? ORDER BY created_at DESC LIMIT 1`
|
|
108
|
-
: `SELECT * FROM sys_packages WHERE id = ? AND version = ?`;
|
|
109
|
-
|
|
110
|
-
const args = version === 'latest' ? [packageId] : [packageId, version];
|
|
111
|
-
const result = await objectql.execute!({ sql, args });
|
|
112
|
-
|
|
113
|
-
if (result.rows.length === 0) {
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const row = result.rows[0];
|
|
118
|
-
return {
|
|
119
|
-
id: row.id,
|
|
120
|
-
version: row.version,
|
|
121
|
-
manifest: JSON.parse(row.manifest),
|
|
122
|
-
metadata: JSON.parse(row.metadata),
|
|
123
|
-
hash: row.hash,
|
|
124
|
-
created_at: row.created_at,
|
|
125
|
-
updated_at: row.updated_at,
|
|
126
|
-
};
|
|
127
|
-
} catch (error) {
|
|
128
|
-
logger.error(`Failed to get package: ${packageId}`, error as Error);
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
|
|
133
|
-
async list() {
|
|
134
|
-
try {
|
|
135
|
-
const result = await objectql.execute!({
|
|
136
|
-
sql: `
|
|
137
|
-
SELECT * FROM sys_packages
|
|
138
|
-
WHERE (id, created_at) IN (
|
|
139
|
-
SELECT id, MAX(created_at) FROM sys_packages GROUP BY id
|
|
140
|
-
)
|
|
141
|
-
ORDER BY created_at DESC
|
|
142
|
-
`,
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
return result.rows.map((row: any) => ({
|
|
146
|
-
id: row.id,
|
|
147
|
-
version: row.version,
|
|
148
|
-
manifest: JSON.parse(row.manifest),
|
|
149
|
-
metadata: JSON.parse(row.metadata),
|
|
150
|
-
hash: row.hash,
|
|
151
|
-
created_at: row.created_at,
|
|
152
|
-
updated_at: row.updated_at,
|
|
153
|
-
}));
|
|
154
|
-
} catch (error) {
|
|
155
|
-
logger.error('Failed to list packages', error as Error);
|
|
156
|
-
return [];
|
|
157
|
-
}
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
async delete(packageId: string, version?: string) {
|
|
161
|
-
try {
|
|
162
|
-
const sql = version
|
|
163
|
-
? `DELETE FROM sys_packages WHERE id = ? AND version = ?`
|
|
164
|
-
: `DELETE FROM sys_packages WHERE id = ?`;
|
|
165
|
-
|
|
166
|
-
const args = version ? [packageId, version] : [packageId];
|
|
167
|
-
await objectql.execute!({ sql, args });
|
|
168
|
-
|
|
169
|
-
logger.info(`Deleted package: ${packageId}${version ? `@${version}` : ''}`);
|
|
170
|
-
return { success: true };
|
|
171
|
-
} catch (error) {
|
|
172
|
-
logger.error('Failed to delete package', error as Error);
|
|
173
|
-
return { success: false };
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
ctx.registerService('package', packageService);
|
|
179
|
-
logger.info('Package service initialized');
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
private async ensureTable(objectql: IDataEngine, logger: any): Promise<void> {
|
|
183
|
-
try {
|
|
184
|
-
// Create the sys_packages table
|
|
185
|
-
await objectql.execute!({
|
|
186
|
-
sql: `
|
|
187
|
-
CREATE TABLE IF NOT EXISTS sys_packages (
|
|
188
|
-
id TEXT NOT NULL,
|
|
189
|
-
version TEXT NOT NULL,
|
|
190
|
-
manifest TEXT NOT NULL,
|
|
191
|
-
metadata TEXT NOT NULL,
|
|
192
|
-
hash TEXT NOT NULL,
|
|
193
|
-
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
194
|
-
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
195
|
-
PRIMARY KEY (id, version)
|
|
196
|
-
)
|
|
197
|
-
`,
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
// Create index for faster latest version queries
|
|
201
|
-
await objectql.execute!({
|
|
202
|
-
sql: `
|
|
203
|
-
CREATE INDEX IF NOT EXISTS idx_packages_latest
|
|
204
|
-
ON sys_packages(id, created_at DESC)
|
|
205
|
-
`,
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
logger.debug('sys_packages table ensured');
|
|
209
|
-
} catch (error) {
|
|
210
|
-
// Table might already exist, log and continue
|
|
211
|
-
logger.debug('sys_packages table creation skipped (may already exist)');
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export { PackageServicePlugin as default };
|
package/tsconfig.json
DELETED