@hypequery/deployment 0.7.1 → 0.7.2
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 +51 -332
- package/package.json +16 -4
package/README.md
CHANGED
|
@@ -1,371 +1,90 @@
|
|
|
1
1
|
# @hypequery/deployment
|
|
2
2
|
|
|
3
|
-
Provider-neutral
|
|
4
|
-
building blocks for Hypequery deployment bundles.
|
|
3
|
+
Provider-neutral building blocks for receiving, verifying, activating, and hosting Hypequery deployment bundles.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
`hypequery deploy`, reconstructs only manifest-declared files in temporary
|
|
8
|
-
storage, and revalidates the release, bundle manifest, file hashes, deployment
|
|
9
|
-
identity, and runtime references before handing the submission to a store.
|
|
5
|
+
This package is for Cloud providers and self-hosted control planes. Application teams normally use `hypequery deploy` through `@hypequery/cli` instead.
|
|
10
6
|
|
|
11
|
-
##
|
|
7
|
+
## What it protects
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
request into a case-insensitive header record and an `AsyncIterable<Uint8Array>`
|
|
15
|
-
body, then map the returned status, headers, and JSON body onto the framework's
|
|
16
|
-
response.
|
|
9
|
+
Every deployment is treated as immutable content. Before storage or execution, the package verifies:
|
|
17
10
|
|
|
18
|
-
|
|
11
|
+
- the target-bound release envelope;
|
|
12
|
+
- the closed bundle manifest;
|
|
13
|
+
- declared paths and byte limits;
|
|
14
|
+
- every file hash and artifact reference;
|
|
15
|
+
- deployment, bundle, and release identities;
|
|
16
|
+
- activation revision consistency.
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
consumed;
|
|
22
|
-
- `DeploymentAuthorizer` authorizes the canonical project and environment once
|
|
23
|
-
the release envelope has been validated;
|
|
24
|
-
- `DeploymentSubmissionStore` atomically persists or recognizes the fully
|
|
25
|
-
verified release identity.
|
|
18
|
+
Symbolic links, undeclared files, path traversal, missing content, and identity mismatches fail closed.
|
|
26
19
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
## Provider building blocks
|
|
21
|
+
|
|
22
|
+
- streaming authenticated intake;
|
|
23
|
+
- provider-owned authentication, authorization, and storage interfaces;
|
|
24
|
+
- a durable reference filesystem store;
|
|
25
|
+
- compare-and-swap activation and rollback;
|
|
26
|
+
- Node and Fetch control-plane adapters;
|
|
27
|
+
- immutable runtime materialization;
|
|
28
|
+
- readiness-gated runtime supervision;
|
|
29
|
+
- named-query data-plane execution;
|
|
30
|
+
- a reference single-host composition.
|
|
31
|
+
|
|
32
|
+
## Minimal intake
|
|
31
33
|
|
|
32
34
|
```ts
|
|
33
35
|
import { createDeploymentIntake } from '@hypequery/deployment';
|
|
34
36
|
|
|
35
37
|
const intake = createDeploymentIntake({
|
|
36
38
|
authenticator: {
|
|
37
|
-
|
|
38
|
-
return authenticateToken(token);
|
|
39
|
-
},
|
|
39
|
+
authenticate: ({ token }) => authenticateToken(token),
|
|
40
40
|
},
|
|
41
41
|
authorizer: {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
},
|
|
42
|
+
authorize: ({ principal, target }) =>
|
|
43
|
+
canDeploy(principal, target),
|
|
45
44
|
},
|
|
46
45
|
store: {
|
|
47
|
-
|
|
48
|
-
return persistVerifiedSubmission(submission);
|
|
49
|
-
},
|
|
46
|
+
accept: (submission) => persistVerifiedSubmission(submission),
|
|
50
47
|
},
|
|
51
48
|
});
|
|
52
49
|
```
|
|
53
50
|
|
|
54
|
-
|
|
55
|
-
idempotent replays return HTTP status `200` and `status: "already-exists"`.
|
|
56
|
-
Malformed, unauthorized, oversized, or inconsistent submissions fail closed
|
|
57
|
-
with a bounded JSON error response. Temporary upload data is removed on success
|
|
58
|
-
and failure.
|
|
59
|
-
|
|
60
|
-
## Bundle verification
|
|
51
|
+
Authentication happens before upload bytes are consumed. The store receives a fully verified temporary bundle and must persist required bytes before returning.
|
|
61
52
|
|
|
62
|
-
|
|
63
|
-
filesystem. It rejects symbolic links and undeclared entries, enforces byte
|
|
64
|
-
limits, recomputes every hash and identity, and returns an immutable verified
|
|
65
|
-
snapshot.
|
|
66
|
-
|
|
67
|
-
## Filesystem store
|
|
68
|
-
|
|
69
|
-
`createFileSystemDeploymentSubmissionStore` is the reference durable store for
|
|
70
|
-
a single host. It copies verified bundle bytes out of temporary intake storage,
|
|
71
|
-
revalidates the copy, and atomically publishes the release only after its bundle
|
|
72
|
-
is durable. Replaying the same release returns `already-exists` after the stored
|
|
73
|
-
state has been fully revalidated.
|
|
53
|
+
## Single-host reference
|
|
74
54
|
|
|
75
55
|
```ts
|
|
76
|
-
import {
|
|
77
|
-
createDeploymentIntake,
|
|
78
|
-
createFileSystemDeploymentSubmissionStore,
|
|
79
|
-
} from '@hypequery/deployment';
|
|
56
|
+
import { createFileSystemDeploymentHost } from '@hypequery/deployment';
|
|
80
57
|
|
|
81
|
-
const
|
|
58
|
+
const service = createFileSystemDeploymentHost({
|
|
82
59
|
directory: '/var/lib/hypequery/deployments',
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
authorizer,
|
|
88
|
-
store,
|
|
89
|
-
});
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
The closed layout is content-addressed:
|
|
93
|
-
|
|
94
|
-
```text
|
|
95
|
-
<directory>/
|
|
96
|
-
bundles/<bundle identity>/...
|
|
97
|
-
releases/<release identity>/release.json
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
Bundle directories are published before release directories. A crash can leave
|
|
101
|
-
an unreferenced bundle that a later submission safely reuses, but cannot expose
|
|
102
|
-
a release whose bundle is incomplete. Files and directories are opened without
|
|
103
|
-
following symbolic links where Node exposes that facility, file contents and
|
|
104
|
-
directory entries are revalidated on reads and replays, and temporary staging
|
|
105
|
-
directories are removed after success or failure.
|
|
106
|
-
|
|
107
|
-
The configured directory is an operator-controlled local trust boundary. This
|
|
108
|
-
store does not provide remote replication, activation, lifecycle state, or
|
|
109
|
-
protection from an administrator modifying its files. `read(releaseIdentity)`
|
|
110
|
-
returns only a completely revalidated stored submission.
|
|
111
|
-
|
|
112
|
-
## Activation registry
|
|
113
|
-
|
|
114
|
-
`createFileSystemDeploymentActivationRegistry` adds explicit target activation
|
|
115
|
-
without changing accepted release or bundle bytes. The registry requires a
|
|
116
|
-
release reader that completely revalidates an accepted release and its closed
|
|
117
|
-
bundle before returning it; the filesystem submission store satisfies that
|
|
118
|
-
contract directly.
|
|
119
|
-
|
|
120
|
-
```ts
|
|
121
|
-
import {
|
|
122
|
-
createFileSystemDeploymentActivationRegistry,
|
|
123
|
-
createFileSystemDeploymentSubmissionStore,
|
|
124
|
-
} from '@hypequery/deployment';
|
|
125
|
-
|
|
126
|
-
const directory = '/var/lib/hypequery/deployments';
|
|
127
|
-
const releases = createFileSystemDeploymentSubmissionStore({ directory });
|
|
128
|
-
const activations = createFileSystemDeploymentActivationRegistry({
|
|
129
|
-
directory,
|
|
130
|
-
releases,
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
const target = { project: 'analytics', environment: 'production' };
|
|
134
|
-
const current = await activations.current(target);
|
|
135
|
-
const result = await activations.activate({
|
|
136
|
-
target,
|
|
137
|
-
releaseIdentity,
|
|
138
|
-
expectedRevision: current?.revision ?? null,
|
|
139
|
-
});
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
`expectedRevision` is a compare-and-swap precondition. A different active
|
|
143
|
-
revision returns `conflict`; requesting the release that is already active
|
|
144
|
-
returns `already-active`. Activating an older accepted release performs a
|
|
145
|
-
rollback through the same operation and produces a new revision, so stale
|
|
146
|
-
pre-rollback callers cannot pass the comparison after an ABA sequence.
|
|
147
|
-
|
|
148
|
-
The filesystem implementation stores an immutable, domain-separated activation
|
|
149
|
-
record for every transition and derives the current release by verifying the
|
|
150
|
-
append-only chain. It has no mutable pointer file or persistent lock to become
|
|
151
|
-
stale after a crash. Activation does not load runtime code, route traffic,
|
|
152
|
-
perform health checks, or authorize callers; those remain provider concerns.
|
|
153
|
-
|
|
154
|
-
## HTTP control plane
|
|
155
|
-
|
|
156
|
-
`createDeploymentControlPlane` combines intake and activation behind closed v1
|
|
157
|
-
routes. Activation reads and writes use a separate target-scoped authorizer;
|
|
158
|
-
write authorization completes before the small JSON request body is consumed.
|
|
159
|
-
The Fetch and Node adapters preserve streaming multipart submission bodies.
|
|
160
|
-
|
|
161
|
-
```ts
|
|
162
|
-
import {
|
|
163
|
-
createDeploymentControlPlane,
|
|
164
|
-
createDeploymentControlPlaneNodeHandler,
|
|
165
|
-
} from '@hypequery/deployment';
|
|
166
|
-
|
|
167
|
-
const controlPlane = createDeploymentControlPlane({
|
|
168
|
-
intake,
|
|
169
|
-
activations,
|
|
170
|
-
authenticator,
|
|
171
|
-
authorizer: {
|
|
172
|
-
async authorize({ principal, action, target }) {
|
|
173
|
-
return canControlDeployment(principal, action, target);
|
|
174
|
-
},
|
|
60
|
+
targets: [{ project: 'analytics', environment: 'production' }],
|
|
61
|
+
intake: {
|
|
62
|
+
authenticator: deploymentAuthenticator,
|
|
63
|
+
authorizer: deploymentAuthorizer,
|
|
175
64
|
},
|
|
65
|
+
controlPlane: {
|
|
66
|
+
authenticator: operatorAuthenticator,
|
|
67
|
+
authorizer: operatorAuthorizer,
|
|
68
|
+
},
|
|
69
|
+
configureDataPlane,
|
|
176
70
|
});
|
|
177
71
|
|
|
178
|
-
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
The control plane exposes release submission, compare-and-swap activation,
|
|
182
|
-
current state, and bounded cursor history. It returns stable, bounded JSON error
|
|
183
|
-
codes and suppresses internal provider and filesystem details. The HTTP
|
|
184
|
-
contract is specified in `specs/deployment/0003-control-plane-http.md`.
|
|
185
|
-
|
|
186
|
-
## Runtime materialization
|
|
187
|
-
|
|
188
|
-
`createDeploymentRuntimeMaterializer` converts the current target activation
|
|
189
|
-
into a private runtime snapshot. It revalidates the accepted release and closed
|
|
190
|
-
bundle, copies and hashes each runtime artifact without following symbolic
|
|
191
|
-
links, and confirms the activation revision again before returning.
|
|
192
|
-
|
|
193
|
-
```ts
|
|
194
|
-
import { createDeploymentRuntimeMaterializer } from '@hypequery/deployment';
|
|
195
|
-
|
|
196
|
-
const materializer = createDeploymentRuntimeMaterializer({
|
|
197
|
-
activations,
|
|
198
|
-
releases: store,
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
const snapshot = await materializer.current({
|
|
202
|
-
project: 'analytics',
|
|
203
|
-
environment: 'production',
|
|
204
|
-
});
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
Artifact `read()` methods return fresh byte copies, so neither callers nor later
|
|
208
|
-
changes to durable storage can alter a materialized snapshot. Runtime imports,
|
|
209
|
-
process lifecycle, readiness, and traffic switching remain separate concerns.
|
|
210
|
-
|
|
211
|
-
## Runtime supervision
|
|
212
|
-
|
|
213
|
-
`createDeploymentRuntimeSupervisor` starts materialized snapshots through a
|
|
214
|
-
runtime factory, checks candidate readiness, confirms activation again, and
|
|
215
|
-
atomically changes the generation used for new named-query invocations. Failed
|
|
216
|
-
or superseded candidates never displace a healthy generation.
|
|
217
|
-
|
|
218
|
-
```ts
|
|
219
|
-
import {
|
|
220
|
-
createDeploymentRuntimeSupervisor,
|
|
221
|
-
createNodeWorkerDeploymentRuntimeFactory,
|
|
222
|
-
} from '@hypequery/deployment';
|
|
223
|
-
|
|
224
|
-
const supervisor = createDeploymentRuntimeSupervisor({
|
|
225
|
-
materializer,
|
|
226
|
-
factory: createNodeWorkerDeploymentRuntimeFactory({
|
|
227
|
-
async resolveEnvironment(snapshot, { signal }) {
|
|
228
|
-
const connection = await resolveClickHouseConnection(snapshot.target, { signal });
|
|
229
|
-
return {
|
|
230
|
-
CLICKHOUSE_URL: connection.url,
|
|
231
|
-
CLICKHOUSE_DATABASE: connection.database,
|
|
232
|
-
CLICKHOUSE_USERNAME: connection.username,
|
|
233
|
-
CLICKHOUSE_PASSWORD: connection.password,
|
|
234
|
-
};
|
|
235
|
-
},
|
|
236
|
-
}),
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
await supervisor.reconcile({ project: 'analytics', environment: 'production' });
|
|
240
|
-
const result = await supervisor.invoke({
|
|
241
|
-
target: { project: 'analytics', environment: 'production' },
|
|
242
|
-
query: 'orders',
|
|
243
|
-
argument: { input, ctx: { tenantId } },
|
|
244
|
-
});
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
Calls already assigned to an old generation may finish after cutover. That
|
|
248
|
-
generation receives no new calls and closes when its in-flight work reaches
|
|
249
|
-
zero or the drain deadline expires. The reference Node factory loads exact
|
|
250
|
-
materialized bytes in worker threads and removes their temporary files on
|
|
251
|
-
shutdown. Provider factories can implement Python, process, container, or
|
|
252
|
-
remote-sandbox isolation behind the same lifecycle interface.
|
|
253
|
-
|
|
254
|
-
`resolveEnvironment(snapshot, { signal })` lets a provider resolve secrets and
|
|
255
|
-
configuration for one immutable deployment target before its worker imports any
|
|
256
|
-
artifact. When configured, the returned string record replaces the inherited
|
|
257
|
-
process environment for that worker, so concurrent targets do not need to
|
|
258
|
-
mutate shared `process.env`. Returning an empty record creates a worker with an
|
|
259
|
-
empty environment. Omitting the resolver preserves Node's default environment
|
|
260
|
-
inheritance for existing single-host integrations.
|
|
261
|
-
|
|
262
|
-
Resolvers should return the smallest environment the deployment needs and
|
|
263
|
-
honour the startup abort signal during external secret-store calls. Environment
|
|
264
|
-
resolution failure prevents the candidate from becoming ready and removes its
|
|
265
|
-
temporary artifact bytes.
|
|
266
|
-
|
|
267
|
-
The reference worker is a lifecycle boundary, not a hostile-code security
|
|
268
|
-
sandbox. Only trusted deployment code should use it directly.
|
|
269
|
-
|
|
270
|
-
## Data-plane execution
|
|
271
|
-
|
|
272
|
-
`createDeploymentDataPlane` executes named-query routes from one validated,
|
|
273
|
-
immutable deployment contract. It applies bounded input defaults and unknown
|
|
274
|
-
property behavior, enforces access and tenant policy, dispatches the declared
|
|
275
|
-
implementation kind through an injected adapter, and validates output before
|
|
276
|
-
returning it.
|
|
277
|
-
|
|
278
|
-
```ts
|
|
279
|
-
import {
|
|
280
|
-
createDeploymentDataPlane,
|
|
281
|
-
createDeploymentRuntimeSupervisorExecutor,
|
|
282
|
-
} from '@hypequery/deployment';
|
|
283
|
-
|
|
284
|
-
const executeRuntimeReference = createDeploymentRuntimeSupervisorExecutor({
|
|
285
|
-
supervisor,
|
|
286
|
-
target,
|
|
287
|
-
activationRevision: snapshot.activation.revision,
|
|
288
|
-
argument: ({ input, principal, tenant }) => ({ input, principal, tenant }),
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
const dataPlane = createDeploymentDataPlane({
|
|
292
|
-
deployment: snapshot.deployment,
|
|
293
|
-
authenticate,
|
|
294
|
-
resolveTenant,
|
|
295
|
-
executeSemanticPlan,
|
|
296
|
-
executeCompiledSql,
|
|
297
|
-
executeRuntimeReference,
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
const result = await dataPlane.execute({
|
|
301
|
-
method: 'POST',
|
|
302
|
-
path: '/analytics/queries/orders',
|
|
303
|
-
credentials,
|
|
304
|
-
input: { status: 'paid' },
|
|
305
|
-
});
|
|
72
|
+
await service.start();
|
|
306
73
|
```
|
|
307
74
|
|
|
308
|
-
|
|
309
|
-
construct the data plane. A later activation therefore cannot accidentally run
|
|
310
|
-
against stale route or schema metadata. Argument mapping remains explicit so a
|
|
311
|
-
host can preserve the handler contract of its chosen runtime.
|
|
75
|
+
Distributed providers can keep the same interfaces while replacing persistence, runtime isolation, secret resolution, routing, and observability.
|
|
312
76
|
|
|
313
|
-
|
|
314
|
-
only validated values, the fixed implementation artifact, and closed typed SQL
|
|
315
|
-
parameter bindings; it does not select credentials or interpolate SQL.
|
|
77
|
+
## Trust boundary
|
|
316
78
|
|
|
317
|
-
|
|
79
|
+
The reference Node worker manages lifecycle and immutable generations for trusted deployment code; it is not a hostile-code sandbox. The filesystem store assumes its configured directory is controlled by the operator.
|
|
318
80
|
|
|
319
|
-
|
|
320
|
-
dispatch pinned to the same activation revision. Reconciliation builds a new
|
|
321
|
-
data plane from the supervisor's immutable generation view and publishes it
|
|
322
|
-
only after confirming that the active generation did not change during
|
|
323
|
-
configuration.
|
|
81
|
+
## Specifications
|
|
324
82
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
They accept either query parameters or one bounded UTF-8 JSON body, reject
|
|
328
|
-
duplicate JSON property names, forward cancellation, and return bounded JSON
|
|
329
|
-
errors. Public cache metadata is emitted only when execution confirms the
|
|
330
|
-
request was public, tenant-independent, and unauthenticated.
|
|
83
|
+
- [Deployment transport](../../specs/deployment/README.md)
|
|
84
|
+
- [Security protocol](../../specs/security-protocol/README.md)
|
|
331
85
|
|
|
332
|
-
|
|
333
|
-
filesystem submission store, activation registry, intake, control plane,
|
|
334
|
-
runtime materializer, supervisor, and generation-pinned data plane. It
|
|
335
|
-
reconciles configured targets at startup and schedules reconciliation after a
|
|
336
|
-
durable activation without changing an already-successful activation response
|
|
337
|
-
if runtime startup later fails.
|
|
338
|
-
|
|
339
|
-
```ts
|
|
340
|
-
import {
|
|
341
|
-
createDeploymentDataPlaneNodeHandler,
|
|
342
|
-
createFileSystemDeploymentHost,
|
|
343
|
-
} from '@hypequery/deployment';
|
|
344
|
-
|
|
345
|
-
const service = createFileSystemDeploymentHost({
|
|
346
|
-
directory: '/var/lib/hypequery/deployments',
|
|
347
|
-
targets: [{ project: 'analytics', environment: 'production' }],
|
|
348
|
-
intake: { authenticator: deploymentAuthenticator, authorizer: deploymentAuthorizer },
|
|
349
|
-
controlPlane: { authenticator: operatorAuthenticator, authorizer: operatorAuthorizer },
|
|
350
|
-
configureDataPlane: () => ({
|
|
351
|
-
authenticate: queryAuthenticator,
|
|
352
|
-
resolveTenant,
|
|
353
|
-
executeSemanticPlan,
|
|
354
|
-
executeCompiledSql,
|
|
355
|
-
runtimeArgument: ({ input, principal, tenant }) => ({ input, principal, tenant }),
|
|
356
|
-
}),
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
await service.start();
|
|
360
|
-
const queryHandler = createDeploymentDataPlaneNodeHandler(
|
|
361
|
-
service.host.dataPlane({ project: 'analytics', environment: 'production' }),
|
|
362
|
-
);
|
|
363
|
-
```
|
|
86
|
+
Requires Node.js 20 or newer and ESM.
|
|
364
87
|
|
|
365
|
-
|
|
366
|
-
adapter interfaces while supplying provider-owned persistence, runtime, SQL,
|
|
367
|
-
authentication, tenant, routing, and observability implementations. The
|
|
368
|
-
filesystem assembly is a reference single-host composition, not a distributed
|
|
369
|
-
control plane.
|
|
88
|
+
## License
|
|
370
89
|
|
|
371
|
-
|
|
90
|
+
Apache-2.0.
|
package/package.json
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypequery/deployment",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.7.2",
|
|
4
|
+
"description": "Verified deployment intake, activation, and runtime hosting for Hypequery analytics",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"hypequery",
|
|
7
|
+
"deployment",
|
|
8
|
+
"clickhouse",
|
|
9
|
+
"analytics",
|
|
10
|
+
"verification",
|
|
11
|
+
"runtime"
|
|
12
|
+
],
|
|
5
13
|
"license": "Apache-2.0",
|
|
6
14
|
"type": "module",
|
|
7
15
|
"main": "dist/index.js",
|
|
@@ -18,7 +26,7 @@
|
|
|
18
26
|
"README.md"
|
|
19
27
|
],
|
|
20
28
|
"dependencies": {
|
|
21
|
-
"@hypequery/protocol": "0.10.
|
|
29
|
+
"@hypequery/protocol": "0.10.2"
|
|
22
30
|
},
|
|
23
31
|
"devDependencies": {
|
|
24
32
|
"@types/node": "^22.5.0",
|
|
@@ -30,9 +38,13 @@
|
|
|
30
38
|
},
|
|
31
39
|
"repository": {
|
|
32
40
|
"type": "git",
|
|
33
|
-
"url": "https://github.com/hypequery/hypequery.git",
|
|
41
|
+
"url": "git+https://github.com/hypequery/hypequery.git",
|
|
34
42
|
"directory": "packages/deployment"
|
|
35
43
|
},
|
|
44
|
+
"homepage": "https://hypequery.com",
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/hypequery/hypequery/issues"
|
|
47
|
+
},
|
|
36
48
|
"publishConfig": {
|
|
37
49
|
"access": "public"
|
|
38
50
|
},
|