@kernhq/module-quire 0.12.0 → 0.13.1
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/dist/contract/permissions.d.ts.map +1 -1
- package/dist/contract/permissions.js +1 -0
- package/dist/contract/permissions.js.map +1 -1
- package/dist/contract/router.d.ts +92 -6
- package/dist/contract/router.d.ts.map +1 -1
- package/dist/contract/router.js +94 -6
- package/dist/contract/router.js.map +1 -1
- package/dist/server/_impl.d.ts +62 -6
- package/dist/server/_impl.d.ts.map +1 -1
- package/dist/server/_impl.js +116 -29
- package/dist/server/_impl.js.map +1 -1
- package/dist/server/services/index.d.ts +2 -0
- package/dist/server/services/index.d.ts.map +1 -1
- package/dist/server/services/index.js +2 -0
- package/dist/server/services/index.js.map +1 -1
- package/dist/server/services/publications.d.ts +70 -23
- package/dist/server/services/publications.d.ts.map +1 -1
- package/dist/server/services/publications.js +286 -8
- package/dist/server/services/publications.js.map +1 -1
- package/dist/server/services/versions.d.ts +22 -1
- package/dist/server/services/versions.d.ts.map +1 -1
- package/dist/server/services/versions.js +44 -15
- package/dist/server/services/versions.js.map +1 -1
- package/migrations/0009_public_asset_references.sql +35 -0
- package/migrations/meta/_journal.json +7 -0
- package/package.json +1 -1
- package/src/client/components/PublishDialog.svelte +75 -12
- package/src/client/components/SidebarRecents.svelte +17 -1
- package/src/client/i18n.ts +14 -0
- package/src/client/mock.ts +10 -0
- package/src/client/pages/PageView.svelte +11 -11
- package/src/client/public-url.ts +25 -13
- package/src/contract/permissions.ts +1 -0
- package/src/contract/router.ts +100 -6
package/dist/server/_impl.d.ts
CHANGED
|
@@ -4942,7 +4942,7 @@ export declare function implement_(kernel: Kernel): {
|
|
|
4942
4942
|
headers: Record<string, string | string[] | undefined>;
|
|
4943
4943
|
workspaceId?: string;
|
|
4944
4944
|
}, import("zod").ZodObject<{
|
|
4945
|
-
workspaceId: import("zod
|
|
4945
|
+
workspaceId: import("zod").ZodString;
|
|
4946
4946
|
slug: import("zod").ZodString;
|
|
4947
4947
|
token: import("zod").ZodDefault<import("zod").ZodNullable<import("zod").ZodString>>;
|
|
4948
4948
|
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
@@ -5014,7 +5014,7 @@ export declare function implement_(kernel: Kernel): {
|
|
|
5014
5014
|
headers: Record<string, string | string[] | undefined>;
|
|
5015
5015
|
workspaceId?: string;
|
|
5016
5016
|
}, import("zod").ZodObject<{
|
|
5017
|
-
workspaceId: import("zod
|
|
5017
|
+
workspaceId: import("zod").ZodString;
|
|
5018
5018
|
slug: import("zod").ZodString;
|
|
5019
5019
|
path: import("zod").ZodDefault<import("zod").ZodString>;
|
|
5020
5020
|
basePath: import("zod").ZodDefault<import("zod").ZodString>;
|
|
@@ -5079,7 +5079,7 @@ export declare function implement_(kernel: Kernel): {
|
|
|
5079
5079
|
headers: Record<string, string | string[] | undefined>;
|
|
5080
5080
|
workspaceId?: string;
|
|
5081
5081
|
}, import("zod").ZodObject<{
|
|
5082
|
-
workspaceId: import("zod
|
|
5082
|
+
workspaceId: import("zod").ZodString;
|
|
5083
5083
|
slug: import("zod").ZodString;
|
|
5084
5084
|
q: import("zod").ZodString;
|
|
5085
5085
|
limit: import("zod").ZodDefault<import("zod").ZodNumber>;
|
|
@@ -5138,7 +5138,7 @@ export declare function implement_(kernel: Kernel): {
|
|
|
5138
5138
|
headers: Record<string, string | string[] | undefined>;
|
|
5139
5139
|
workspaceId?: string;
|
|
5140
5140
|
}, import("zod").ZodObject<{
|
|
5141
|
-
workspaceId: import("zod
|
|
5141
|
+
workspaceId: import("zod").ZodString;
|
|
5142
5142
|
slug: import("zod").ZodString;
|
|
5143
5143
|
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
5144
5144
|
entries: import("zod").ZodArray<import("zod").ZodObject<{
|
|
@@ -5193,7 +5193,7 @@ export declare function implement_(kernel: Kernel): {
|
|
|
5193
5193
|
headers: Record<string, string | string[] | undefined>;
|
|
5194
5194
|
workspaceId?: string;
|
|
5195
5195
|
}, import("zod").ZodObject<{
|
|
5196
|
-
workspaceId: import("zod
|
|
5196
|
+
workspaceId: import("zod").ZodString;
|
|
5197
5197
|
slug: import("zod").ZodString;
|
|
5198
5198
|
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
5199
5199
|
indexable: import("zod").ZodBoolean;
|
|
@@ -5221,6 +5221,62 @@ export declare function implement_(kernel: Kernel): {
|
|
|
5221
5221
|
}, import("zod/v4/core").$strip>;
|
|
5222
5222
|
};
|
|
5223
5223
|
}>>, Record<never, never>>;
|
|
5224
|
+
asset: import("@orpc/server").Procedure<import("@orpc/server").MergedInitialContext<RequestContext & Record<never, never>, RequestContext & Record<never, never> & Omit<RequestContext & Record<never, never>, keyof RequestContext> & Omit<RequestContext & Record<never, never>, "workspaceId" | "kernel" | "principal" | "requestId" | "ip" | "headers">, RequestContext>, Omit<import("@orpc/server").MergedCurrentContext<RequestContext, Record<never, never>>, "workspaceId" | "kernel" | "principal" | "requestId" | "ip" | "headers"> & {
|
|
5225
|
+
principal: {
|
|
5226
|
+
kind: "service" | "user" | "api_key" | "anonymous";
|
|
5227
|
+
userId: (string & import("zod").$brand<"UserId">) | null;
|
|
5228
|
+
email: string | null;
|
|
5229
|
+
name: string | null;
|
|
5230
|
+
locale: "en" | "fa" | "ar" | "de";
|
|
5231
|
+
instanceAdmin: boolean;
|
|
5232
|
+
service: string | null;
|
|
5233
|
+
memberships: {
|
|
5234
|
+
workspaceId: string & import("zod").$brand<"WorkspaceId">;
|
|
5235
|
+
role: "owner" | "admin" | "member" | "guest";
|
|
5236
|
+
roleIds: string[];
|
|
5237
|
+
groupIds: string[];
|
|
5238
|
+
status: "active" | "invited" | "suspended";
|
|
5239
|
+
}[];
|
|
5240
|
+
permissionVersion: number;
|
|
5241
|
+
apiKeyScope?: "read" | "read_write" | undefined;
|
|
5242
|
+
};
|
|
5243
|
+
kernel: Kernel;
|
|
5244
|
+
requestId: string;
|
|
5245
|
+
ip: string;
|
|
5246
|
+
headers: Record<string, string | string[] | undefined>;
|
|
5247
|
+
workspaceId?: string;
|
|
5248
|
+
}, import("zod").ZodObject<{
|
|
5249
|
+
workspaceId: import("zod").ZodString;
|
|
5250
|
+
slug: import("zod").ZodString;
|
|
5251
|
+
asset: import("zod").ZodString;
|
|
5252
|
+
token: import("zod").ZodDefault<import("zod").ZodNullable<import("zod").ZodString>>;
|
|
5253
|
+
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
5254
|
+
contentType: import("zod").ZodString;
|
|
5255
|
+
bytes: import("zod").ZodString;
|
|
5256
|
+
maxAge: import("zod").ZodNumber;
|
|
5257
|
+
}, import("zod/v4/core").$strip>, import("@orpc/server").MergedErrorMap<Record<never, never>, import("@orpc/server").MergedErrorMap<Record<never, never>, {
|
|
5258
|
+
BAD_REQUEST: {
|
|
5259
|
+
data: import("zod").ZodOptional<import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>>>;
|
|
5260
|
+
};
|
|
5261
|
+
UNAUTHORIZED: {};
|
|
5262
|
+
FORBIDDEN: {
|
|
5263
|
+
data: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
5264
|
+
permission: import("zod").ZodOptional<import("zod").ZodString>;
|
|
5265
|
+
}, import("zod/v4/core").$strip>>;
|
|
5266
|
+
};
|
|
5267
|
+
NOT_FOUND: {};
|
|
5268
|
+
CONFLICT: {
|
|
5269
|
+
data: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
5270
|
+
reason: import("zod").ZodOptional<import("zod").ZodString>;
|
|
5271
|
+
}, import("zod/v4/core").$strip>>;
|
|
5272
|
+
};
|
|
5273
|
+
RATE_LIMITED: {};
|
|
5274
|
+
MODULE_DISABLED: {
|
|
5275
|
+
data: import("zod").ZodObject<{
|
|
5276
|
+
module: import("zod").ZodString;
|
|
5277
|
+
}, import("zod/v4/core").$strip>;
|
|
5278
|
+
};
|
|
5279
|
+
}>>, Record<never, never>>;
|
|
5224
5280
|
unlock: import("@orpc/server").Procedure<import("@orpc/server").MergedInitialContext<RequestContext & Record<never, never>, RequestContext & Record<never, never> & Omit<RequestContext & Record<never, never>, keyof RequestContext> & Omit<RequestContext & Record<never, never>, "workspaceId" | "kernel" | "principal" | "requestId" | "ip" | "headers">, RequestContext>, Omit<import("@orpc/server").MergedCurrentContext<RequestContext, Record<never, never>>, "workspaceId" | "kernel" | "principal" | "requestId" | "ip" | "headers"> & {
|
|
5225
5281
|
principal: {
|
|
5226
5282
|
kind: "service" | "user" | "api_key" | "anonymous";
|
|
@@ -5246,7 +5302,7 @@ export declare function implement_(kernel: Kernel): {
|
|
|
5246
5302
|
headers: Record<string, string | string[] | undefined>;
|
|
5247
5303
|
workspaceId?: string;
|
|
5248
5304
|
}, import("zod").ZodObject<{
|
|
5249
|
-
workspaceId: import("zod
|
|
5305
|
+
workspaceId: import("zod").ZodString;
|
|
5250
5306
|
slug: import("zod").ZodString;
|
|
5251
5307
|
password: import("zod").ZodString;
|
|
5252
5308
|
}, import("zod/v4/core").$strip>, import("zod").ZodObject<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_impl.d.ts","sourceRoot":"","sources":["../../src/server/_impl.ts"],"names":[],"mappings":"AACA,OAAO,EACL,YAAY,EACZ,kBAAkB,EAElB,KAAK,MAAM,EAEX,cAAc,EACd,KAAK,cAAc,EAIpB,MAAM,gBAAgB,CAAA;AAWvB;;;;;;;GAOG;AACH,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"_impl.d.ts","sourceRoot":"","sources":["../../src/server/_impl.ts"],"names":[],"mappings":"AACA,OAAO,EACL,YAAY,EACZ,kBAAkB,EAElB,KAAK,MAAM,EAEX,cAAc,EACd,KAAK,cAAc,EAIpB,MAAM,gBAAgB,CAAA;AAWvB;;;;;;;GAOG;AACH,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,cAAc,EAAE,CAAA;AAqF3D,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2uCxC"}
|
package/dist/server/_impl.js
CHANGED
|
@@ -3,7 +3,7 @@ import { defineModule, defineServerModule, KernError, o, packageVersion, require
|
|
|
3
3
|
import { implement } from '@orpc/server';
|
|
4
4
|
import { MODULE_ID, quireContract, quireEvents } from '../contract/index.js';
|
|
5
5
|
import { toComment } from './services/comments.js';
|
|
6
|
-
import { quireServices } from './services/index.js';
|
|
6
|
+
import { quireServices, resolveWorkspaceSegment } from './services/index.js';
|
|
7
7
|
import { createNotify } from './services/notify.js';
|
|
8
8
|
import { documentNameOf, toPage } from './services/pages.js';
|
|
9
9
|
import { toPublication } from './services/publications.js';
|
|
@@ -28,11 +28,41 @@ const os = implement(quireContract).$context();
|
|
|
28
28
|
* a URL is owed "there is nothing here" and not "there is something here that this customer has
|
|
29
29
|
* switched off". The one thing it must not do is `requireMember`.
|
|
30
30
|
*/
|
|
31
|
+
const UUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
31
32
|
const publicSurface = o.middleware(async ({ context, next }, input) => {
|
|
32
33
|
const { workspaceId } = input;
|
|
33
|
-
|
|
34
|
+
/*
|
|
35
|
+
* A middleware runs *before* the contract validates its input, so this is the first and only
|
|
36
|
+
* thing that sees the workspace segment — and it used to hand whatever arrived to
|
|
37
|
+
* `kernel.isModuleEnabled`, whose own schema is `z.uuid()`. So `/api/quire/public/not-a-uuid/x`
|
|
38
|
+
* came back as a ZodError this module has no case for: **HTTP 500** on the one surface that
|
|
39
|
+
* promises everything unresolvable is a 404, and a stack trace written at error level for every
|
|
40
|
+
* such request, on an endpoint anyone on the internet can reach as fast as they like.
|
|
41
|
+
*
|
|
42
|
+
* The shape is checked here instead. Anything that is not a workspace id is the same "there is
|
|
43
|
+
* nothing here" as a workspace that has switched Quire off — which is the right answer, because a
|
|
44
|
+
* signed-out stranger is owed no more than that. `.catch` covers the rest for the same reason: a
|
|
45
|
+
* settings lookup that fails must not become a 500 either.
|
|
46
|
+
*/
|
|
47
|
+
if (typeof workspaceId !== 'string')
|
|
48
|
+
throw new KernError('NOT_FOUND', 'There is no published site at this address');
|
|
49
|
+
/*
|
|
50
|
+
* A public URL names its workspace by **slug**, because the address the share dialog copies is
|
|
51
|
+
* meant to be sent to somebody — and a uuid in a link is a receipt, not an address. This
|
|
52
|
+
* middleware is the first thing that sees the segment, so it is also the first thing that used to
|
|
53
|
+
* refuse it: a `UUID.test` here answered 404 for the module's own published URLs while the same
|
|
54
|
+
* site served perfectly under its id. Resolving happens here so that `isModuleEnabled` below is
|
|
55
|
+
* asked about a workspace rather than about a slug, whose schema is `z.uuid()` and would 500.
|
|
56
|
+
*
|
|
57
|
+
* A slug that names no workspace, and a slug that names one with no such publication, both reach
|
|
58
|
+
* the same 404 as a workspace nobody has — so this resolves an address without answering "does
|
|
59
|
+
* this workspace exist" for anyone who asks.
|
|
60
|
+
*/
|
|
61
|
+
const resolved = await resolveWorkspaceSegment(context.kernel, workspaceId).catch(() => null);
|
|
62
|
+
if (!resolved)
|
|
34
63
|
throw new KernError('NOT_FOUND', 'There is no published site at this address');
|
|
35
|
-
|
|
64
|
+
const enabled = await context.kernel.isModuleEnabled(resolved, MODULE_ID).catch(() => false);
|
|
65
|
+
if (!enabled)
|
|
36
66
|
throw new KernError('NOT_FOUND', 'There is no published site at this address');
|
|
37
67
|
return next();
|
|
38
68
|
});
|
|
@@ -50,11 +80,44 @@ const publicSurface = o.middleware(async ({ context, next }, input) => {
|
|
|
50
80
|
* procedure must answer a denied administrator and an anonymous request identically.
|
|
51
81
|
*/
|
|
52
82
|
const anonymousOnly = o.middleware(({ context, next }) => next({ context: { ...context, principal: ANONYMOUS } }));
|
|
83
|
+
/**
|
|
84
|
+
* The one refusal every unservable publication shares, in one place so it cannot drift.
|
|
85
|
+
*
|
|
86
|
+
* It drifted. A publication whose root page had since been trashed was 404 from `site` and `page`,
|
|
87
|
+
* **200 with an empty body** from `search` and `sitemap`, and `indexable: true` from `robots` — so
|
|
88
|
+
* four procedures written to be indistinguishable from "no such slug" were, between them, an
|
|
89
|
+
* existence oracle for a fifth state nobody had enumerated. And `page` said "page" where the others
|
|
90
|
+
* said "site", which separated a locked publication from a missing one on the endpoint least able
|
|
91
|
+
* to afford it.
|
|
92
|
+
*
|
|
93
|
+
* The rule the wording now follows: **a refusal about the publication says "site", and a refusal
|
|
94
|
+
* about a path inside a servable one says "page".** A reader who mistypes a page of a real handbook
|
|
95
|
+
* is the only person who ever sees the second sentence.
|
|
96
|
+
*/
|
|
97
|
+
const noSite = () => new KernError('NOT_FOUND', 'There is no published site at this address');
|
|
53
98
|
export function implement_(kernel) {
|
|
54
99
|
const scoped = os.use(workspaceScoped(MODULE_ID));
|
|
55
100
|
const open = os.use(publicSurface).use(anonymousOnly);
|
|
56
101
|
const svc = quireServices(kernel);
|
|
57
102
|
const notify = createNotify(kernel);
|
|
103
|
+
/**
|
|
104
|
+
* A publication that can actually be served, and its whole tree, or `noSite`.
|
|
105
|
+
*
|
|
106
|
+
* Every anonymous read but `site` and `robots` starts here, so the four ways of not being
|
|
107
|
+
* servable — no such slug, expired, still locked, nothing left under the root — are one answer
|
|
108
|
+
* written once rather than four handlers agreeing by hand. `site` needs its own shape because
|
|
109
|
+
* `locked` is a state it reports rather than refuses, and `robots` because it never refuses at
|
|
110
|
+
* all.
|
|
111
|
+
*/
|
|
112
|
+
const servable = async (tx, workspaceId, slug, token) => {
|
|
113
|
+
const pub = await svc.publications.bySlug(tx, workspaceId, slug);
|
|
114
|
+
if (!(await svc.publications.unlocked(pub, token)))
|
|
115
|
+
throw noSite();
|
|
116
|
+
const nodes = await svc.publications.tree(tx, workspaceId, pub);
|
|
117
|
+
if (!nodes[0])
|
|
118
|
+
throw noSite();
|
|
119
|
+
return { pub, nodes };
|
|
120
|
+
};
|
|
58
121
|
const run = (context, workspaceId, fn) => kernel.database.withWorkspace(workspaceId, fn, { userId: context.principal.userId });
|
|
59
122
|
/**
|
|
60
123
|
* "May you do this to *that* page", answered through the page's ancestor chain.
|
|
@@ -834,17 +897,17 @@ export function implement_(kernel) {
|
|
|
834
897
|
* transaction, so a write that slipped in here fails in Postgres rather than in a review.
|
|
835
898
|
*/
|
|
836
899
|
public: {
|
|
837
|
-
site: open.public.site.handler(({ input }) => svc.publications.read(input.workspaceId, async (tx) => {
|
|
838
|
-
const pub = await svc.publications.bySlug(tx,
|
|
900
|
+
site: open.public.site.handler(({ input }) => svc.publications.read(input.workspaceId, async (tx, ws) => {
|
|
901
|
+
const pub = await svc.publications.bySlug(tx, ws, input.slug);
|
|
839
902
|
const theme = pub.theme;
|
|
840
903
|
if (!(await svc.publications.unlocked(pub, input.token)))
|
|
841
904
|
return { slug: pub.slug, theme, locked: true, site: null };
|
|
842
|
-
const nodes = await svc.publications.tree(tx,
|
|
905
|
+
const nodes = await svc.publications.tree(tx, ws, pub);
|
|
843
906
|
// No root means the root page is archived, trashed, opted out, unpublished or not a page.
|
|
844
907
|
// The publication row survives that; the site does not.
|
|
845
908
|
const root = nodes[0];
|
|
846
909
|
if (!root)
|
|
847
|
-
throw
|
|
910
|
+
throw noSite();
|
|
848
911
|
const newest = nodes.reduce((at, node) => Math.max(at, node.published_at.getTime()), root.published_at.getTime());
|
|
849
912
|
return {
|
|
850
913
|
slug: pub.slug,
|
|
@@ -865,18 +928,15 @@ export function implement_(kernel) {
|
|
|
865
928
|
},
|
|
866
929
|
};
|
|
867
930
|
})),
|
|
868
|
-
page: open.public.page.handler(({ input }) => svc.publications.read(input.workspaceId, async (tx) => {
|
|
869
|
-
const
|
|
870
|
-
if (!(await svc.publications.unlocked(pub, input.token)))
|
|
871
|
-
throw new KernError('NOT_FOUND', 'There is no published page at this address');
|
|
872
|
-
const nodes = await svc.publications.tree(tx, input.workspaceId, pub);
|
|
931
|
+
page: open.public.page.handler(({ input }) => svc.publications.read(input.workspaceId, async (tx, ws) => {
|
|
932
|
+
const { nodes } = await servable(tx, ws, input.slug, input.token);
|
|
873
933
|
const node = svc.publications.find(nodes, input.path);
|
|
874
934
|
return {
|
|
875
935
|
path: node.path,
|
|
876
936
|
title: node.title || 'Untitled',
|
|
877
937
|
icon: node.icon,
|
|
878
938
|
coverUrl: node.cover_url,
|
|
879
|
-
html: await svc.publications.html(tx,
|
|
939
|
+
html: await svc.publications.html(tx, ws, node, nodes, input.basePath),
|
|
880
940
|
/*
|
|
881
941
|
* The version's timestamp, not the page's. `pages.updated_at` moves every time somebody
|
|
882
942
|
* types in the draft, so publishing it would tell the internet when an unpublished
|
|
@@ -887,12 +947,9 @@ export function implement_(kernel) {
|
|
|
887
947
|
breadcrumbs: trailTo(nodes, node),
|
|
888
948
|
};
|
|
889
949
|
})),
|
|
890
|
-
search: open.public.search.handler(({ input }) => svc.publications.read(input.workspaceId, async (tx) => {
|
|
891
|
-
const
|
|
892
|
-
|
|
893
|
-
throw new KernError('NOT_FOUND', 'There is no published site at this address');
|
|
894
|
-
const nodes = await svc.publications.tree(tx, input.workspaceId, pub);
|
|
895
|
-
const hits = await svc.publications.search(tx, input.workspaceId, nodes, input.q, input.limit);
|
|
950
|
+
search: open.public.search.handler(({ input }) => svc.publications.read(input.workspaceId, async (tx, ws) => {
|
|
951
|
+
const { nodes } = await servable(tx, ws, input.slug, input.token);
|
|
952
|
+
const hits = await svc.publications.search(tx, ws, nodes, input.q, input.limit);
|
|
896
953
|
return {
|
|
897
954
|
items: hits.map((hit) => ({
|
|
898
955
|
path: hit.node.path,
|
|
@@ -901,14 +958,19 @@ export function implement_(kernel) {
|
|
|
901
958
|
})),
|
|
902
959
|
};
|
|
903
960
|
})),
|
|
904
|
-
sitemap: open.public.sitemap.handler(({ input }) => svc.publications.read(input.workspaceId, async (tx) => {
|
|
905
|
-
const pub = await svc.publications.bySlug(tx,
|
|
961
|
+
sitemap: open.public.sitemap.handler(({ input }) => svc.publications.read(input.workspaceId, async (tx, ws) => {
|
|
962
|
+
const pub = await svc.publications.bySlug(tx, ws, input.slug);
|
|
906
963
|
// A sitemap exists for crawlers, so a site nobody is meant to crawl has an empty one
|
|
907
964
|
// rather than a private one. Answering with the tree here would put every path of a
|
|
908
965
|
// password-protected handbook in a file whose whole purpose is to be fetched by robots.
|
|
909
966
|
if (pub.passwordHash || !pub.indexable)
|
|
910
967
|
return { entries: [] };
|
|
911
|
-
const nodes = await svc.publications.tree(tx,
|
|
968
|
+
const nodes = await svc.publications.tree(tx, ws, pub);
|
|
969
|
+
// A publication whose root has since been trashed serves nothing, so it is the same 404
|
|
970
|
+
// as a slug nobody took — an empty sitemap here would say "this address is a site" to a
|
|
971
|
+
// crawler that has just been told the opposite by every other procedure.
|
|
972
|
+
if (!nodes[0])
|
|
973
|
+
throw noSite();
|
|
912
974
|
return {
|
|
913
975
|
entries: nodes.map((node) => ({
|
|
914
976
|
path: node.path,
|
|
@@ -916,19 +978,44 @@ export function implement_(kernel) {
|
|
|
916
978
|
})),
|
|
917
979
|
};
|
|
918
980
|
})),
|
|
919
|
-
robots: open.public.robots.handler(({ input }) => svc.publications.read(input.workspaceId, async (tx) => {
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
981
|
+
robots: open.public.robots.handler(({ input }) => svc.publications.read(input.workspaceId, async (tx, ws) => {
|
|
982
|
+
/*
|
|
983
|
+
* The one place a missing publication is not an error: a crawler asking about a slug
|
|
984
|
+
* nobody has taken, one that has expired, and one behind a password have to get the same
|
|
985
|
+
* answer, or this becomes the oracle every other procedure refuses to be.
|
|
986
|
+
*
|
|
987
|
+
* There is a **fourth** state and this comment used to list three. A publication whose
|
|
988
|
+
* root page has since been trashed, archived, opted out or unpublished still has its row:
|
|
989
|
+
* `site`, `page`, `search` and `sitemap` all answer 404 for it, and robots answered
|
|
990
|
+
* `indexable: true` with a sitemap path — so the one procedure written to never
|
|
991
|
+
* distinguish anything was the only one admitting that the slug existed. It is the walk
|
|
992
|
+
* rather than the row that decides, which is why the tree is read here.
|
|
993
|
+
*/
|
|
994
|
+
const pub = await svc.publications.bySlug(tx, ws, input.slug).catch(() => null);
|
|
924
995
|
if (!pub || pub.passwordHash || !pub.indexable)
|
|
925
996
|
return { indexable: false, sitemapPath: null };
|
|
997
|
+
const nodes = await svc.publications.tree(tx, ws, pub);
|
|
998
|
+
if (!nodes[0])
|
|
999
|
+
return { indexable: false, sitemapPath: null };
|
|
926
1000
|
// Relative to whatever prefix the route layer serves this site under; the module has no
|
|
927
1001
|
// way to know that, and guessing would put a wrong absolute URL in a robots file.
|
|
928
1002
|
return { indexable: true, sitemapPath: 'sitemap.xml' };
|
|
929
1003
|
})),
|
|
930
|
-
|
|
931
|
-
|
|
1004
|
+
/**
|
|
1005
|
+
* The bytes of one picture on a published page.
|
|
1006
|
+
*
|
|
1007
|
+
* It goes through `servable` like every other read, so a picture is behind the same door, the
|
|
1008
|
+
* same expiry and the same walk as the prose it sits in — and `svc.publications.asset` then
|
|
1009
|
+
* insists the file is referenced by a version *in this tree* before it fetches anything. That
|
|
1010
|
+
* second check is what stops a reference lifted out of one published site from resolving
|
|
1011
|
+
* against another in the same workspace.
|
|
1012
|
+
*/
|
|
1013
|
+
asset: open.public.asset.handler(({ input }) => svc.publications.read(input.workspaceId, async (tx, ws) => {
|
|
1014
|
+
const { nodes } = await servable(tx, ws, input.slug, input.token);
|
|
1015
|
+
return svc.publications.asset(tx, ws, nodes, input.asset);
|
|
1016
|
+
})),
|
|
1017
|
+
unlock: open.public.unlock.handler(({ input }) => svc.publications.read(input.workspaceId, async (tx, ws) => {
|
|
1018
|
+
const pub = await svc.publications.bySlug(tx, ws, input.slug);
|
|
932
1019
|
// A site with no password has no door to open, and saying so would confirm the slug
|
|
933
1020
|
// exists to somebody who has not been told anything else about it.
|
|
934
1021
|
if (!pub.passwordHash)
|