@teikyo/blob 0.0.0-stage → 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 +204 -2
- package/lib/auth/grants.d.ts +16 -0
- package/lib/auth/grants.js +26 -0
- package/lib/auth/oauth.d.ts +5 -0
- package/lib/auth/oauth.js +6 -0
- package/lib/auth/token.d.ts +23 -0
- package/lib/auth/token.js +88 -0
- package/lib/capability.d.ts +19 -0
- package/lib/capability.js +25 -0
- package/lib/context.d.ts +8 -0
- package/lib/context.js +16 -0
- package/lib/did.d.ts +2 -0
- package/lib/did.js +8 -0
- package/lib/errors.d.ts +12 -0
- package/lib/errors.js +53 -0
- package/lib/handlers/body.d.ts +2 -0
- package/lib/handlers/body.js +3 -0
- package/lib/handlers/guard.d.ts +17 -0
- package/lib/handlers/guard.js +27 -0
- package/lib/handlers/index.d.ts +6 -0
- package/lib/handlers/index.js +153 -0
- package/lib/handlers/manage.d.ts +5 -0
- package/lib/handlers/manage.js +81 -0
- package/lib/handlers/read.d.ts +4 -0
- package/lib/handlers/read.js +118 -0
- package/lib/handlers/router.d.ts +31 -0
- package/lib/handlers/router.js +55 -0
- package/lib/handlers/types.d.ts +36 -0
- package/lib/handlers/types.js +1 -0
- package/lib/handlers/write.d.ts +3 -0
- package/lib/handlers/write.js +78 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.js +11 -0
- package/lib/limits.d.ts +6 -0
- package/lib/limits.js +11 -0
- package/lib/plugin.d.ts +51 -0
- package/lib/plugin.js +190 -0
- package/lib/range.d.ts +11 -0
- package/lib/range.js +30 -0
- package/lib/readiness.d.ts +7 -0
- package/lib/readiness.js +17 -0
- package/lib/resources.d.ts +8 -0
- package/lib/resources.js +9 -0
- package/lib/transport.d.ts +4 -0
- package/lib/transport.js +15 -0
- package/lib/types.d.ts +34 -0
- package/lib/types.js +1 -0
- package/package.json +60 -3
package/lib/plugin.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { createBlobService, DEFAULT_LIMITS } from '@hozon/blob';
|
|
2
|
+
import { BLOB_STORE as BLOB_STORE_DEFINITION, blobStoreDefinition } from '@hozon/store-blob';
|
|
3
|
+
import { pluginName } from '@sozai/http-server';
|
|
4
|
+
import { ACCESS_GRANTS, ACCESS_REVOCATION } from '@teikyo/access';
|
|
5
|
+
import { HOZON_DB } from '@teikyo/hozon';
|
|
6
|
+
import { getAuthInfo, OAUTH_RESOURCE } from '@teikyo/oauth';
|
|
7
|
+
import { grantsAuthorizer } from './auth/grants.js';
|
|
8
|
+
import { oauthAuthenticator } from './auth/oauth.js';
|
|
9
|
+
import { tokenAuthenticator } from './auth/token.js';
|
|
10
|
+
import { createReadCapabilityMinter } from './capability.js';
|
|
11
|
+
import { transportFromHono } from './context.js';
|
|
12
|
+
import { createBlobHandlers, DEFAULT_BASE_PATH, DEFAULT_MAX_UPLOAD_SIZE, DEFAULT_UPLOAD_TIMEOUT_MS } from './handlers/index.js';
|
|
13
|
+
import { resolveBasePath } from './handlers/router.js';
|
|
14
|
+
import { assertUploadLimits } from './limits.js';
|
|
15
|
+
import { createReadinessCheck } from './readiness.js';
|
|
16
|
+
export const DEFAULT_STAGING_MAX_AGE_MS = 86400000;
|
|
17
|
+
export const BLOB_STORE = pluginName()('blob:store');
|
|
18
|
+
function isPositiveInteger(value) {
|
|
19
|
+
return Number.isSafeInteger(value) && value > 0;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Serve blob storage on `basePath`. Authenticates with kokuin invocation tokens by default (or
|
|
23
|
+
* OAuth bearer tokens with `oauth`) and authorizes through `access:grants` unless a custom
|
|
24
|
+
* `authorizer` is given.
|
|
25
|
+
*/ export function blobPlugin(params) {
|
|
26
|
+
const { oauth, revocation = false } = params;
|
|
27
|
+
const defaultAuthenticator = params.authenticator == null && oauth == null;
|
|
28
|
+
if (oauth != null && params.authenticator != null) {
|
|
29
|
+
throw new Error('oauth cannot be combined with a custom authenticator');
|
|
30
|
+
}
|
|
31
|
+
if (defaultAuthenticator && (params.audience == null || params.audience === '')) {
|
|
32
|
+
throw new Error('audience is required by the default token authenticator');
|
|
33
|
+
}
|
|
34
|
+
if (revocation && !defaultAuthenticator) {
|
|
35
|
+
throw new Error('revocation requires the default token authenticator');
|
|
36
|
+
}
|
|
37
|
+
if (params.signer != null && !defaultAuthenticator) {
|
|
38
|
+
// Minted capabilities are only accepted as proofs by the default token authenticator.
|
|
39
|
+
throw new Error('signer requires the default token authenticator');
|
|
40
|
+
}
|
|
41
|
+
const basePath = resolveBasePath(params.basePath ?? DEFAULT_BASE_PATH);
|
|
42
|
+
if (basePath === '') {
|
|
43
|
+
throw new Error('basePath must not be the root path');
|
|
44
|
+
}
|
|
45
|
+
const maxUploadSize = params.maxUploadSize ?? DEFAULT_MAX_UPLOAD_SIZE;
|
|
46
|
+
const uploadTimeoutMs = params.uploadTimeoutMs ?? DEFAULT_UPLOAD_TIMEOUT_MS;
|
|
47
|
+
assertUploadLimits({
|
|
48
|
+
maxUploadSize,
|
|
49
|
+
uploadTimeoutMs,
|
|
50
|
+
maxBlobSize: params.limits?.maxBlobSize ?? DEFAULT_LIMITS.maxBlobSize
|
|
51
|
+
});
|
|
52
|
+
const stagingMaxAgeMs = params.stagingMaxAgeMs ?? DEFAULT_STAGING_MAX_AGE_MS;
|
|
53
|
+
if (!isPositiveInteger(stagingMaxAgeMs) || stagingMaxAgeMs <= uploadTimeoutMs) {
|
|
54
|
+
throw new Error('stagingMaxAgeMs must be an integer above uploadTimeoutMs');
|
|
55
|
+
}
|
|
56
|
+
const dependsOn = [
|
|
57
|
+
HOZON_DB
|
|
58
|
+
];
|
|
59
|
+
if (params.authorizer == null) dependsOn.push(ACCESS_GRANTS);
|
|
60
|
+
if (revocation) dependsOn.push(ACCESS_REVOCATION);
|
|
61
|
+
if (oauth != null) dependsOn.push(OAUTH_RESOURCE);
|
|
62
|
+
return {
|
|
63
|
+
name: BLOB_STORE,
|
|
64
|
+
dependsOn,
|
|
65
|
+
async setup (ctx) {
|
|
66
|
+
const db = ctx.use(HOZON_DB);
|
|
67
|
+
db.register(blobStoreDefinition);
|
|
68
|
+
// Run migrations now so a schema failure aborts startup.
|
|
69
|
+
await db.getStore(BLOB_STORE_DEFINITION);
|
|
70
|
+
const service = createBlobService({
|
|
71
|
+
db,
|
|
72
|
+
backend: params.backend,
|
|
73
|
+
codec: params.codec,
|
|
74
|
+
chunkSize: params.chunkSize,
|
|
75
|
+
lock: params.lock,
|
|
76
|
+
limits: params.limits
|
|
77
|
+
});
|
|
78
|
+
try {
|
|
79
|
+
const { removed, failed } = await service.pruneStaging(new Date(Date.now() - stagingMaxAgeMs));
|
|
80
|
+
if (removed > 0 || failed > 0) {
|
|
81
|
+
ctx.logger.info('pruned blob staging: {removed} removed, {failed} failed', {
|
|
82
|
+
removed,
|
|
83
|
+
failed
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
ctx.logger.error('blob staging prune failed', {
|
|
88
|
+
error
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
let authenticator;
|
|
92
|
+
let checkDelegation;
|
|
93
|
+
if (oauth != null) {
|
|
94
|
+
authenticator = oauthAuthenticator();
|
|
95
|
+
} else if (params.authenticator != null) {
|
|
96
|
+
// No checkDelegation: it trusts `principal.token`, so it only vouches for principals
|
|
97
|
+
// produced by the token authenticator. Delegated principals from here fail closed.
|
|
98
|
+
authenticator = params.authenticator;
|
|
99
|
+
} else {
|
|
100
|
+
const token = tokenAuthenticator({
|
|
101
|
+
audience: params.audience,
|
|
102
|
+
revocation: revocation ? ctx.use(ACCESS_REVOCATION).verifyToken : undefined,
|
|
103
|
+
verifyOptions: params.verifyOptions
|
|
104
|
+
});
|
|
105
|
+
authenticator = token;
|
|
106
|
+
checkDelegation = token.checkDelegation;
|
|
107
|
+
}
|
|
108
|
+
let authorizer;
|
|
109
|
+
if (params.authorizer == null) {
|
|
110
|
+
authorizer = grantsAuthorizer({
|
|
111
|
+
grants: ctx.use(ACCESS_GRANTS),
|
|
112
|
+
checkDelegation,
|
|
113
|
+
scopes: oauth?.scopes
|
|
114
|
+
});
|
|
115
|
+
} else if (checkDelegation != null) {
|
|
116
|
+
// `delegatedFrom` is an unverified claim until checkDelegation accepts the proof, so a
|
|
117
|
+
// custom authorizer only sees delegated principals whose proof covers the request.
|
|
118
|
+
const custom = params.authorizer;
|
|
119
|
+
const verifyDelegation = checkDelegation;
|
|
120
|
+
authorizer = async (input)=>{
|
|
121
|
+
const { principal, action, blobID } = input;
|
|
122
|
+
if (principal?.delegatedFrom != null && await verifyDelegation({
|
|
123
|
+
principal,
|
|
124
|
+
action,
|
|
125
|
+
blobID
|
|
126
|
+
}) !== true) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
return await custom(input);
|
|
130
|
+
};
|
|
131
|
+
} else {
|
|
132
|
+
authorizer = params.authorizer;
|
|
133
|
+
}
|
|
134
|
+
const handlers = createBlobHandlers({
|
|
135
|
+
service,
|
|
136
|
+
authenticator,
|
|
137
|
+
authorizer,
|
|
138
|
+
basePath,
|
|
139
|
+
maxUploadSize,
|
|
140
|
+
uploadTimeoutMs,
|
|
141
|
+
secureTransport: params.secureTransport,
|
|
142
|
+
logger: ctx.logger
|
|
143
|
+
});
|
|
144
|
+
// The handler enforces maxUploadSize while streaming and its own upload deadline.
|
|
145
|
+
ctx.limits(basePath, {
|
|
146
|
+
bodyBytes: false,
|
|
147
|
+
timeoutMs: false
|
|
148
|
+
});
|
|
149
|
+
if (oauth != null) {
|
|
150
|
+
ctx.middleware(ctx.use(OAUTH_RESOURCE).requireBearer(), basePath);
|
|
151
|
+
}
|
|
152
|
+
const handle = (c)=>{
|
|
153
|
+
const context = {
|
|
154
|
+
transport: transportFromHono(c)
|
|
155
|
+
};
|
|
156
|
+
if (oauth != null) {
|
|
157
|
+
const info = getAuthInfo(c);
|
|
158
|
+
const principal = {
|
|
159
|
+
subject: info.subject,
|
|
160
|
+
scopes: [
|
|
161
|
+
...info.scopes
|
|
162
|
+
]
|
|
163
|
+
};
|
|
164
|
+
context.principal = principal;
|
|
165
|
+
}
|
|
166
|
+
return handlers(c.req.raw, context);
|
|
167
|
+
};
|
|
168
|
+
ctx.route('all', basePath, handle);
|
|
169
|
+
ctx.route('all', `${basePath}/*`, handle);
|
|
170
|
+
ctx.addReadinessCheck(createReadinessCheck({
|
|
171
|
+
probe: ()=>service.list({
|
|
172
|
+
limit: 1
|
|
173
|
+
}),
|
|
174
|
+
logger: ctx.logger
|
|
175
|
+
}));
|
|
176
|
+
const api = {
|
|
177
|
+
service,
|
|
178
|
+
handlers
|
|
179
|
+
};
|
|
180
|
+
if (params.signer != null) {
|
|
181
|
+
api.mintReadCapability = createReadCapabilityMinter({
|
|
182
|
+
signer: params.signer,
|
|
183
|
+
serviceAudience: params.audience,
|
|
184
|
+
canonicalize: (id)=>service.codec.canonicalize(id)
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return api;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
package/lib/range.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Inclusive byte range, matching HTTP `Range` / `Content-Range` semantics. */
|
|
2
|
+
export type ParsedRange = {
|
|
3
|
+
start: number;
|
|
4
|
+
end: number;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Parse a single-range HTTP `Range` header against a known content length.
|
|
8
|
+
* Returns `null` when the header is absent or not a `bytes` range (ignored, serve the whole body),
|
|
9
|
+
* a range when satisfiable, or `'unsatisfiable'` for malformed, multi-range or out-of-bounds ranges.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseRange(header: string | null, length: number): ParsedRange | null | 'unsatisfiable';
|
package/lib/range.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Inclusive byte range, matching HTTP `Range` / `Content-Range` semantics. */ /**
|
|
2
|
+
* Parse a single-range HTTP `Range` header against a known content length.
|
|
3
|
+
* Returns `null` when the header is absent or not a `bytes` range (ignored, serve the whole body),
|
|
4
|
+
* a range when satisfiable, or `'unsatisfiable'` for malformed, multi-range or out-of-bounds ranges.
|
|
5
|
+
*/ export function parseRange(header, length) {
|
|
6
|
+
if (header == null) return null;
|
|
7
|
+
const value = header.trim();
|
|
8
|
+
if (!/^bytes\s*=/i.test(value)) return null;
|
|
9
|
+
const match = /^bytes\s*=\s*(\d*)-(\d*)$/i.exec(value);
|
|
10
|
+
if (match == null) return 'unsatisfiable';
|
|
11
|
+
const [, startText, endText] = match;
|
|
12
|
+
if (length <= 0 || startText === '' && endText === '') return 'unsatisfiable';
|
|
13
|
+
// Suffix form `bytes=-N`: the final N bytes.
|
|
14
|
+
if (startText === '') {
|
|
15
|
+
const suffix = Number(endText);
|
|
16
|
+
if (suffix === 0) return 'unsatisfiable';
|
|
17
|
+
return {
|
|
18
|
+
start: Math.max(0, length - suffix),
|
|
19
|
+
end: length - 1
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const start = Number(startText);
|
|
23
|
+
if (!Number.isSafeInteger(start) || start >= length) return 'unsatisfiable';
|
|
24
|
+
const end = endText === '' ? length - 1 : Math.min(Number(endText), length - 1);
|
|
25
|
+
if (end < start) return 'unsatisfiable';
|
|
26
|
+
return {
|
|
27
|
+
start,
|
|
28
|
+
end
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Logger } from '@sozai/log';
|
|
2
|
+
export type ReadinessCheckParams = {
|
|
3
|
+
probe: () => Promise<unknown>;
|
|
4
|
+
logger: Logger;
|
|
5
|
+
};
|
|
6
|
+
/** Readiness check that logs on state transitions only, so a persistent outage logs once. */
|
|
7
|
+
export declare function createReadinessCheck(params: ReadinessCheckParams): () => Promise<boolean>;
|
package/lib/readiness.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Readiness check that logs on state transitions only, so a persistent outage logs once. */ export function createReadinessCheck(params) {
|
|
2
|
+
const { probe, logger } = params;
|
|
3
|
+
let healthy = true;
|
|
4
|
+
return async ()=>{
|
|
5
|
+
try {
|
|
6
|
+
await probe();
|
|
7
|
+
if (!healthy) logger.info('blob store readiness check recovered');
|
|
8
|
+
healthy = true;
|
|
9
|
+
} catch (error) {
|
|
10
|
+
if (healthy) logger.error('blob store readiness check failed', {
|
|
11
|
+
error
|
|
12
|
+
});
|
|
13
|
+
healthy = false;
|
|
14
|
+
}
|
|
15
|
+
return healthy;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { BlobAction } from './types.js';
|
|
2
|
+
/** Grant resource string; `blobID` must be the canonical ID. */
|
|
3
|
+
export declare function grantResource(action: BlobAction, blobID?: string): string;
|
|
4
|
+
/** Capability permission (`act` and `res`); `blobID` must be the canonical ID. */
|
|
5
|
+
export declare function capabilityPermission(action: BlobAction, blobID?: string): {
|
|
6
|
+
act: string;
|
|
7
|
+
res: string;
|
|
8
|
+
};
|
package/lib/resources.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Grant resource string; `blobID` must be the canonical ID. */ export function grantResource(action, blobID) {
|
|
2
|
+
return blobID == null ? `blob/${action}` : `blob/${action}/${blobID}`;
|
|
3
|
+
}
|
|
4
|
+
/** Capability permission (`act` and `res`); `blobID` must be the canonical ID. */ export function capabilityPermission(action, blobID) {
|
|
5
|
+
return {
|
|
6
|
+
act: `blob/${action}`,
|
|
7
|
+
res: blobID == null ? 'blob/*' : `blob/${blobID}`
|
|
8
|
+
};
|
|
9
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BlobRequestContext } from './types.js';
|
|
2
|
+
export type SecureTransportOption = boolean | ((request: Request, context: BlobRequestContext) => boolean);
|
|
3
|
+
/** Whether the request's transport satisfies the `secureTransport` option. Fails closed. */
|
|
4
|
+
export declare function isTransportAllowed(option: SecureTransportOption, request: Request, context: BlobRequestContext): boolean;
|
package/lib/transport.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function isLoopback(address) {
|
|
2
|
+
const addr = address.toLowerCase();
|
|
3
|
+
if (addr === '::1') return true;
|
|
4
|
+
const v4 = addr.startsWith('::ffff:') ? addr.slice(7) : addr;
|
|
5
|
+
// Exactly four decimal octets, 0-255, no leading zeros, first octet 127.
|
|
6
|
+
const octets = v4.split('.');
|
|
7
|
+
return octets.length === 4 && octets[0] === '127' && octets.every((octet)=>/^(0|[1-9]\d{0,2})$/.test(octet) && Number(octet) <= 255);
|
|
8
|
+
}
|
|
9
|
+
/** Whether the request's transport satisfies the `secureTransport` option. Fails closed. */ export function isTransportAllowed(option, request, context) {
|
|
10
|
+
if (typeof option === 'function') return option(request, context);
|
|
11
|
+
if (!option) return true;
|
|
12
|
+
const transport = context.transport;
|
|
13
|
+
if (transport == null) return false;
|
|
14
|
+
return transport.secure === true || isLoopback(transport.localAddress) && isLoopback(transport.remoteAddress);
|
|
15
|
+
}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SignedPayload } from '@kokuin/token';
|
|
2
|
+
export type TokenPayload = SignedPayload;
|
|
3
|
+
export type BlobAction = 'read' | 'write' | 'delete' | 'pin' | 'list';
|
|
4
|
+
export type TransportInfo = {
|
|
5
|
+
/** TLS on the connection the server accepted. */
|
|
6
|
+
secure: boolean;
|
|
7
|
+
localAddress: string;
|
|
8
|
+
remoteAddress: string;
|
|
9
|
+
};
|
|
10
|
+
export type BlobPrincipal = {
|
|
11
|
+
subject: string;
|
|
12
|
+
/**
|
|
13
|
+
* Identity the caller claims to act for. A caller-supplied claim, only verified by
|
|
14
|
+
* `checkDelegation`: the plugin enforces it for any authorizer when the default token
|
|
15
|
+
* authenticator is used. Embedders calling `createBlobHandlers` with `tokenAuthenticator`
|
|
16
|
+
* directly must call `checkDelegation` in their authorizer.
|
|
17
|
+
*/
|
|
18
|
+
delegatedFrom?: string;
|
|
19
|
+
token?: TokenPayload;
|
|
20
|
+
scopes?: Array<string>;
|
|
21
|
+
};
|
|
22
|
+
export type BlobRequestContext = {
|
|
23
|
+
transport?: TransportInfo;
|
|
24
|
+
/** Principal already verified by adapter middleware (e.g. OAuth), if any. */
|
|
25
|
+
principal?: BlobPrincipal;
|
|
26
|
+
};
|
|
27
|
+
export type BlobAuthenticator = (request: Request, context: BlobRequestContext) => Promise<BlobPrincipal | null>;
|
|
28
|
+
export type BlobAuthorizer = (input: {
|
|
29
|
+
action: BlobAction;
|
|
30
|
+
blobID?: string;
|
|
31
|
+
principal: BlobPrincipal | null;
|
|
32
|
+
request: Request;
|
|
33
|
+
context: BlobRequestContext;
|
|
34
|
+
}) => Promise<boolean>;
|
package/lib/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/package.json
CHANGED
|
@@ -1,6 +1,63 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teikyo/blob",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Blob storage HTTP plugin with access control",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/TairuFramework/teikyo.git",
|
|
8
|
+
"directory": "packages/blob"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"type": "module",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./lib/index.js"
|
|
15
|
+
},
|
|
16
|
+
"main": "lib/index.js",
|
|
17
|
+
"types": "lib/index.d.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"lib/*"
|
|
20
|
+
],
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@hozon/blob": "^0.1.1",
|
|
23
|
+
"@hozon/store-blob": "^0.2.2",
|
|
24
|
+
"@kokuin/capability": "^0.5.1",
|
|
25
|
+
"@kokuin/token": "^0.6.1",
|
|
26
|
+
"@sozai/codec": "^0.4.3",
|
|
27
|
+
"@sozai/http-server": "^0.2.0",
|
|
28
|
+
"@sozai/log": "^0.4.0",
|
|
29
|
+
"@teikyo/access": "^0.2.0",
|
|
30
|
+
"@teikyo/hozon": "^0.2.0",
|
|
31
|
+
"@teikyo/oauth": "^0.1.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@hozon/blob-backend": "^0.2.0",
|
|
35
|
+
"@hozon/blob-id": "^0.1.1",
|
|
36
|
+
"@hozon/db": "^0.3.0",
|
|
37
|
+
"@hozon/provider": "^0.1.2",
|
|
38
|
+
"@noble/curves": "^2.4.0",
|
|
39
|
+
"@types/node": "^26.6.5",
|
|
40
|
+
"del-cli": "^8.0.1",
|
|
41
|
+
"hono": "^4.13.13",
|
|
42
|
+
"vitest": "5.0.5"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"hono": "^4.13.13"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=24"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "pnpm run build:clean && pnpm run build:js && pnpm run build:types",
|
|
55
|
+
"build:clean": "del-cli lib",
|
|
56
|
+
"build:js": "del-cli \"lib/**/*.js\" && swc src -d ./lib --config-file ../../node_modules/@kigu/dev/swc.json --strip-leading-paths",
|
|
57
|
+
"build:types": "del-cli \"lib/**/*.d.ts\" \"lib/**/*.d.ts.map\" && tsc --emitDeclarationOnly --skipLibCheck",
|
|
58
|
+
"build:types:ci": "tsc --emitDeclarationOnly --skipLibCheck --declarationMap false",
|
|
59
|
+
"test": "pnpm run test:types && pnpm run test:unit",
|
|
60
|
+
"test:types": "tsc --noEmit --skipLibCheck -p tsconfig.test.json",
|
|
61
|
+
"test:unit": "vitest run"
|
|
62
|
+
}
|
|
6
63
|
}
|