@globalfishingwatch/skills 0.0.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.
Files changed (47) hide show
  1. package/README.md +89 -0
  2. package/dist/decode-url/SKILL.md +51 -0
  3. package/dist/decode-url/index.js +20339 -0
  4. package/dist/decode-url/scripts/decode-url.mjs +18 -0
  5. package/dist/decode-url/scripts/register-gfw-resolver.mjs +24 -0
  6. package/dist/encode-url/MAINTENANCE.md +53 -0
  7. package/dist/encode-url/SKILL.md +135 -0
  8. package/dist/encode-url/index.js +20211 -0
  9. package/dist/encode-url/references/areas.json +1401 -0
  10. package/dist/encode-url/references/examples-conversations.md +113 -0
  11. package/dist/encode-url/references/examples.md +234 -0
  12. package/dist/encode-url/references/filters.md +67 -0
  13. package/dist/encode-url/references/highlighted-workspaces.md +65 -0
  14. package/dist/encode-url/references/layers.md +96 -0
  15. package/dist/encode-url/references/ports.json +27507 -0
  16. package/dist/encode-url/references/query-params.md +114 -0
  17. package/dist/encode-url/references/routes.md +24 -0
  18. package/dist/encode-url/scripts/encode-url.mjs +26 -0
  19. package/dist/encode-url/scripts/register-gfw-resolver.mjs +24 -0
  20. package/dist/index.js +20514 -0
  21. package/dist/package.json +35 -0
  22. package/package.json +35 -0
  23. package/src/decode-url/SKILL.md +51 -0
  24. package/src/decode-url/decode.ts +87 -0
  25. package/src/decode-url/index.ts +4 -0
  26. package/src/decode-url/scripts/decode-url.mjs +18 -0
  27. package/src/decode-url/scripts/register-gfw-resolver.mjs +24 -0
  28. package/src/decode-url/workspaces.ts +34 -0
  29. package/src/encode-url/MAINTENANCE.md +53 -0
  30. package/src/encode-url/SKILL.md +135 -0
  31. package/src/encode-url/config.ts +8 -0
  32. package/src/encode-url/dictionary.ts +305 -0
  33. package/src/encode-url/encode.ts +79 -0
  34. package/src/encode-url/index.ts +4 -0
  35. package/src/encode-url/references/areas.json +1401 -0
  36. package/src/encode-url/references/examples-conversations.md +113 -0
  37. package/src/encode-url/references/examples.md +234 -0
  38. package/src/encode-url/references/filters.md +67 -0
  39. package/src/encode-url/references/highlighted-workspaces.md +65 -0
  40. package/src/encode-url/references/layers.md +96 -0
  41. package/src/encode-url/references/ports.json +27507 -0
  42. package/src/encode-url/references/query-params.md +114 -0
  43. package/src/encode-url/references/routes.md +24 -0
  44. package/src/encode-url/routes.ts +166 -0
  45. package/src/encode-url/scripts/encode-url.mjs +26 -0
  46. package/src/encode-url/scripts/register-gfw-resolver.mjs +24 -0
  47. package/src/index.ts +6 -0
@@ -0,0 +1,114 @@
1
+ # GFW map query params (state)
2
+
3
+ Pass these unabbreviated in `state` — the encoder abbreviates them.
4
+ Source of truth (params documented with JSDoc — read these when a param is missing here):
5
+ `apps/fishing-map/types/index.ts` (`WorkspaceState`/`AppState`/`QueryParams`), `apps/fishing-map/features/reports/reports.types.ts` (`ReportState`), `apps/fishing-map/features/vessel/vessel.types.ts` (`VesselProfileState`), `apps/fishing-map/features/search/search.types.ts` (`VesselSearchState`).
6
+ Defaults live in `DEFAULT_WORKSPACE` (`apps/fishing-map/data/config.ts`) and `DEFAULT_REPORT_STATE` (`features/reports/reports.config.ts`).
7
+
8
+ ## Core (all routes)
9
+
10
+ | Param | Type | Notes |
11
+ | ------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
12
+ | `latitude`, `longitude`, `zoom` | number | Viewport. zoom 0 = world, ~4-6 = country, ~12 = port |
13
+ | `start`, `end` | ISO datetime | Time range, e.g. `2026-07-01T00:00:00.000Z` |
14
+ | `dataviewInstances` | array | The layers (see layers.md) |
15
+ | `timebarVisualisation` | string | `heatmap` (activity) \| `heatmapDetections` \| `events` \| `vessel` \| `vesselGroup` \| `environment` |
16
+ | `visibleEvents` | array \| `all` \| `none` | Event types on vessel tracks: `fishing`, `encounter`, `port_visit`, `loitering`, `gaps` (default `all`) |
17
+ | `timebarGraph` | string | `speed` \| `elevation` \| `none` |
18
+ | `sidebarOpen` | boolean | |
19
+ | `readOnly` | boolean | Share links that shouldn't be edited |
20
+ | `daysFromLatest` | number | Rolling window ending at latest available day |
21
+ | `bivariateDataviews` | [id, id] | Two activity layers compared in one bivariate ramp |
22
+ | `mapAnnotations`, `mapRulers` | arrays | User drawings/measurements; toggle without removing via `mapAnnotationsVisible` / `mapRulersVisible` (booleans, default `true`) |
23
+ | `mapDrawing` | `polygons` \| `points` \| false | Enables the draw-on-map feature; `mapDrawingEditId` targets an existing drawn feature for edit |
24
+ | `activityVisualizationMode` | string | Render mode for activity layers: `heatmap` (default) \| `heatmap-high-res` \| `heatmap-low-res` \| `positions` |
25
+ | `detectionsVisualizationMode` | string | Same values as activity; for detections layers (default `heatmap`) |
26
+ | `environmentVisualizationMode` | string | `heatmap` \| `heatmap-low-res` (default) — no high res for environment layers |
27
+ | `vesselGroupsVisualizationMode` | string | `footprint` (default) \| `footprint-high-res` |
28
+ | `vesselsColorBy` | string | Property coloring vessel tracks/points: `track` \| `speed` \| `elevation` |
29
+
30
+ Internal/auto-generated params — never set them: `reportAreaBounds`, `skipColorDomainSampling`, `migramarLayer`, `includeRelatedIdentities`, `trackCorrectionId`, `sidePanelId`/`sidePanelSubcontentId`/`sidePanelContent`.
31
+
32
+ ## dataviewInstances item
33
+
34
+ ```json
35
+ {
36
+ "id": "ais", // instance id (layers.md)
37
+ "dataviewId": "apparent-fishing-effort-ais-v-{PIPE_DATASET_VERSION}", // only for layers added on top of defaults; version token resolved by the encoder
38
+ "config": {
39
+ "visible": true,
40
+ "color": "#9CA4FF", // set together with colorRamp — use that ramp's hex (see palette below)
41
+ "colorRamp": "lilac", // teal|orange|magenta|yellow|lilac|sky|green|red|salmon
42
+ "filters": { "flag": ["FRA"], "geartype": ["trawlers"] }
43
+ }
44
+ }
45
+ ```
46
+
47
+ `color`/`colorRamp` are one choice, not two. Pick a ramp and set `color` to its paired hex:
48
+
49
+ | colorRamp | color | colorRamp | color | colorRamp | color |
50
+ | --------- | --------- | --------- | --------- | --------- | --------- |
51
+ | teal | `#00FFBC` | sky | `#00EEFF` | green | `#A6FF59` |
52
+ | lilac | `#9CA4FF` | red | `#FF6854` | orange | `#FFAA0D` |
53
+ | salmon | `#FFAE9B` | yellow | `#FFEA00` | magenta | `#FF64CE` |
54
+
55
+ To show the SAME dataset twice with different filters (e.g. Spanish vs French fishing), reuse the default instance (`ais`) for one and add a second instance with a unique id (`fishing-effort-ais__<timestamp>`) + `dataviewId`.
56
+
57
+ ## Area report (`report` route)
58
+
59
+ | Param | Values |
60
+ | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
61
+ | `reportCategory` | `activity` (default) \| `detections` \| `events` \| `environment` \| `others` \| `vessel-groups` |
62
+ | `reportActivitySubCategory` | `fishing` \| `presence` — sub-filter within an activity report |
63
+ | `reportDetectionsSubCategory` | `sar` \| `viirs` \| `sentinel-2` — sub-filter within a detections report |
64
+ | `reportEventsSubCategory` | event type within an events report: `encounter` (default) \| `loitering` \| `port_visit` \| `gap` |
65
+ | `reportVesselsSubCategory` | vessels-tab sub-tab: `flag` (default) \| `geartype` \| `vesselType` \| `source` \| `coverage` |
66
+ | `reportActivityGraph` | `evolution` (default) \| `beforeAfter` \| `periodComparison` \| `datasetComparison` |
67
+ | `reportTimeComparison` | `{ "start", "compareStart", "duration", "durationType": "days"\|"months" }` — for `beforeAfter`/`periodComparison` |
68
+ | `reportComparisonDataviewIds` | `{ "main", "compare" }` dataview ids — for `datasetComparison` |
69
+ | `reportVesselGraph` | `flag` (default) \| `geartype` \| `vesselType` |
70
+ | `reportBufferValue` / `reportBufferUnit` / `reportBufferOperation` | number / `nauticalmiles`\|`kilometers` / `dissolve`\|`difference` |
71
+ | `reportVesselFilter` | free-text filter on the vessels list — see syntax below |
72
+ | `reportVesselPage`, `reportResultsPerPage` | pagination (page is 0-based; per-page min 10, max 50, default 10) |
73
+ | `reportVesselOrderProperty` / `reportVesselOrderDirection` | sort: `shipname` (default) \| `flag` \| `shiptype` / `asc` (default) \| `desc` |
74
+ | `reportLoadVessels` | boolean, load vessel list immediately |
75
+ | `reportEventsGraph` | events-report chart: `evolution` (default) \| `byFlag` \| `byRFMO` \| `byFAO` \| `byEEZ` |
76
+ | `reportEventsPortsFilter`, `reportEventsPortsPage`, `reportEventsPortsResultsPerPage` | ports list controls in an events report (same semantics as the vessel-table ones) |
77
+
78
+ Note: the URL param is `reportResultsPerPage` even though the app state field is named `reportVesselResultsPerPage` — always use `reportResultsPerPage` in `state`.
79
+
80
+ `reportVesselFilter` syntax (also `reportEventsPortsFilter`): comma = search by multiple fields, `|` = OR, leading `-` = exclude. E.g. `flag:china, gear:trawlers`, `-spain`, `cargo|passenger`.
81
+
82
+ ## Ports report (`ports-report` route)
83
+
84
+ `portsReportName` (e.g. `CAMARONES`), `portsReportCountry` (ISO3), `portsReportDatasetId` (`public-global-port-visits-events:v4.0`).
85
+
86
+ ## Vessel profile (`vessel` route)
87
+
88
+ | Param | Values |
89
+ | ------------------------------------------- | --------------------------------------------------------------- |
90
+ | `vesselDatasetId` | `public-global-vessel-identity:v4.0` |
91
+ | `vesselSelfReportedId` / `vesselRegistryId` | vessel identity ids |
92
+ | `vesselIdentitySource` | `selfReportedInfo` \| `registryInfo` |
93
+ | `vesselSection` | `activity` \| `related_vessels` \| `areas` \| `insights` |
94
+ | `vesselArea` | `fao` \| `eez` \| `mpa` \| `rfmo` |
95
+ | `vesselRelated` | `encounters` \| `owners` — when `vesselSection=related_vessels` |
96
+ | `vesselActivityMode` | `voyage` \| `type` |
97
+
98
+ ## Vessel search (`vessel-search` route)
99
+
100
+ | Param | Notes |
101
+ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
102
+ | `query` | free text: name, MMSI, IMO, callsign |
103
+ | `searchOption` | `basic` (default) \| `advanced` |
104
+ | `sources` | dataset ids restricting the search (advanced only) |
105
+ | advanced fields | `flag` (array ISO3), `owner`, `ssvid` (MMSI), `imo`, `callsign`, `geartypes`, `shiptypes`, `transmissionDateFrom`/`transmissionDateTo` |
106
+ | `infoSource` | identity source filter |
107
+
108
+ `transmissionDateFrom`/`transmissionDateTo` are advanced search FIELDS (dates, `YYYY-MM-DD`) — different namespace from the `firstTransmissionDate`/`lastTransmissionDate` URL params (`fTD`/`lTD`) that always appear (often empty) in search URLs.
109
+
110
+ ## User page (`user` route)
111
+
112
+ `userTab`: `info` | `workspaces` | `datasets` | `reports` | `vesselGroups`.
113
+
114
+ <!-- Maintainers: every enumerated value above maps to a source symbol — see "Value sources" in ../MAINTENANCE.md for update checks. -->
@@ -0,0 +1,24 @@
1
+ # GFW map routes
2
+
3
+ App served under basename `/map` (already included in the script's `path` output).
4
+ Source: `apps/fishing-map/routes/_app` (TanStack Router file routes).
5
+
6
+ | Route type | Path pattern | Path params | When to use |
7
+ | --------------------- | ------------------------------------------------------------ | --------------------- | ------------------------------------------------------------------------------------ |
8
+ | `workspace` | `/` or `/$category/$workspaceId` | category, workspaceId | Browse/compare layers on the map |
9
+ | `workspaces-list` | `/$category` | category | List curated workspaces of a category |
10
+ | `report` | `/$category/$workspaceId/report/$datasetId/$areaId` | + datasetId, areaId | Aggregated report over an area (EEZ/FAO/RFMO/MPA) |
11
+ | `report` (global) | `/$category/$workspaceId/report` | — (no datasetId/areaId) | Whole-world aggregated report; omit `datasetId`+`areaId`, category defaults to `reports` |
12
+ | `report` (saved) | `/report/$reportId` | reportId | Open a user-saved report |
13
+ | `vessel` | `/$category/$workspaceId/vessel/$vesselId` | + vesselId | Vessel profile (identity, track, events) |
14
+ | `vessel-search` | `/$category/$workspaceId/vessel-search` | — | Search vessels by name/MMSI/IMO or advanced filters |
15
+ | `vessel-group-report` | `/$category/$workspaceId/vessel-group-report/$vesselGroupId` | + vesselGroupId | Report over a vessel group |
16
+ | `ports-report` | `/$category/$workspaceId/ports-report/$portId` | + portId | Port activity profile (visits, vessels) |
17
+ | `user` | `/user` | — | User info, saved workspaces (`userTab=workspaces`), datasets, reports, vessel groups |
18
+
19
+ - `category`: `fishing-activity` (default) | `marine-manager` | `reports`
20
+ - `workspaceId`: `default-public` (default public workspace)
21
+ - report `datasetId` values: `public-eez-areas`, `public-fao-major`, `public-rfmo`, `public-mpa-all`
22
+ - `areaId`: numeric for EEZs (see `areas.json`), FAO major area code, RFMO id
23
+ - `vesselId`: GFW vessel id (uuid-like, from vessel search results)
24
+ - `portId`: e.g. `arg-camarones` (see `ports.json`)
@@ -0,0 +1,166 @@
1
+ import {
2
+ DEFAULT_PATH_BASENAME,
3
+ DEFAULT_WORKSPACE_CATEGORY,
4
+ DEFAULT_WORKSPACE_ID,
5
+ ROUTE_PATHS,
6
+ } from '@fishing-map/config'
7
+
8
+ export { DEFAULT_PATH_BASENAME as DEFAULT_BASENAME }
9
+
10
+ export type MapRouteType =
11
+ | 'workspace'
12
+ | 'workspaces-list'
13
+ | 'report'
14
+ | 'vessel'
15
+ | 'vessel-search'
16
+ | 'vessel-group-report'
17
+ | 'ports-report'
18
+ | 'user'
19
+
20
+ export type MapRouteParams = {
21
+ category?: string
22
+ workspaceId?: string
23
+ datasetId?: string
24
+ areaId?: string
25
+ reportId?: string
26
+ vesselId?: string
27
+ vesselGroupId?: string
28
+ portId?: string
29
+ }
30
+
31
+ export type MapRoute = { type: MapRouteType } & MapRouteParams
32
+
33
+ export type RouteNavigation = {
34
+ to: string
35
+ params: Record<string, string>
36
+ }
37
+
38
+ const required = (route: MapRoute, param: keyof MapRouteParams): string => {
39
+ const value = route[param]
40
+ if (!value) {
41
+ throw new Error(`route.${param} is required for route type "${route.type}"`)
42
+ }
43
+ return value
44
+ }
45
+
46
+ /**
47
+ * Returns the TanStack Router `to` pattern and `params` for a route,
48
+ * matching the file routes in apps/fishing-map/routes/_app
49
+ */
50
+ export const getRouteNavigation = (route: MapRoute): RouteNavigation => {
51
+ const category = route.category || DEFAULT_WORKSPACE_CATEGORY
52
+ const workspaceId = route.workspaceId || DEFAULT_WORKSPACE_ID
53
+ const workspaceParams = { category, workspaceId }
54
+ switch (route.type) {
55
+ case 'workspace':
56
+ if (!route.category && !route.workspaceId) {
57
+ return { to: ROUTE_PATHS.HOME, params: {} }
58
+ }
59
+ return { to: ROUTE_PATHS.WORKSPACE, params: workspaceParams }
60
+ case 'workspaces-list':
61
+ return { to: ROUTE_PATHS.WORKSPACES_LIST, params: { category } }
62
+ case 'report':
63
+ if (route.reportId) {
64
+ return { to: ROUTE_PATHS.REPORT, params: { reportId: route.reportId } }
65
+ }
66
+ if (!route.datasetId && !route.areaId) {
67
+ return {
68
+ to: ROUTE_PATHS.WORKSPACE_REPORT,
69
+ params: { category: route.category || 'reports', workspaceId },
70
+ }
71
+ }
72
+ return {
73
+ to: ROUTE_PATHS.WORKSPACE_REPORT_FULL,
74
+ params: {
75
+ ...workspaceParams,
76
+ datasetId: required(route, 'datasetId'),
77
+ areaId: required(route, 'areaId'),
78
+ },
79
+ }
80
+ case 'vessel':
81
+ if (!route.category && !route.workspaceId) {
82
+ return { to: ROUTE_PATHS.VESSEL, params: { vesselId: required(route, 'vesselId') } }
83
+ }
84
+ return {
85
+ to: ROUTE_PATHS.WORKSPACE_VESSEL,
86
+ params: { ...workspaceParams, vesselId: required(route, 'vesselId') },
87
+ }
88
+ case 'vessel-search':
89
+ return { to: ROUTE_PATHS.WORKSPACE_SEARCH, params: workspaceParams }
90
+ case 'vessel-group-report':
91
+ return {
92
+ to: ROUTE_PATHS.VESSEL_GROUP_REPORT,
93
+ params: { ...workspaceParams, vesselGroupId: required(route, 'vesselGroupId') },
94
+ }
95
+ case 'ports-report':
96
+ return {
97
+ to: ROUTE_PATHS.PORT_REPORT,
98
+ params: { ...workspaceParams, portId: required(route, 'portId') },
99
+ }
100
+ case 'user':
101
+ return { to: ROUTE_PATHS.USER, params: {} }
102
+ default:
103
+ throw new Error(`Unknown route type "${(route as MapRoute).type}"`)
104
+ }
105
+ }
106
+
107
+ export const buildRoutePath = (navigation: RouteNavigation): string => {
108
+ const path = navigation.to
109
+ .split('/')
110
+ .map((segment) =>
111
+ segment.startsWith('$')
112
+ ? encodeURIComponent(navigation.params[segment.slice(1)] || '')
113
+ : segment
114
+ )
115
+ .join('/')
116
+ return path === '' ? '/' : path
117
+ }
118
+
119
+ // Static-segment patterns listed before parametric ones of the same length,
120
+ // so e.g. /user wins over /$category. Types map each ROUTE_PATHS pattern.
121
+ const ROUTE_PATTERNS: [string, MapRouteType][] = [
122
+ [ROUTE_PATHS.USER, 'user'],
123
+ [ROUTE_PATHS.SEARCH, 'vessel-search'],
124
+ [ROUTE_PATHS.REPORT, 'report'],
125
+ [ROUTE_PATHS.VESSEL, 'vessel'],
126
+ [ROUTE_PATHS.WORKSPACES_LIST, 'workspaces-list'],
127
+ [ROUTE_PATHS.WORKSPACE, 'workspace'],
128
+ [ROUTE_PATHS.WORKSPACE_SEARCH, 'vessel-search'],
129
+ [ROUTE_PATHS.WORKSPACE_VESSEL, 'vessel'],
130
+ [ROUTE_PATHS.WORKSPACE_REPORT, 'report'],
131
+ [ROUTE_PATHS.WORKSPACE_REPORT_FULL, 'report'],
132
+ [ROUTE_PATHS.VESSEL_GROUP_REPORT, 'vessel-group-report'],
133
+ [ROUTE_PATHS.PORT_REPORT, 'ports-report'],
134
+ ]
135
+
136
+ const matchPattern = (pattern: string, segments: string[]): MapRouteParams | undefined => {
137
+ const patternSegments = pattern.split('/').filter(Boolean)
138
+ if (patternSegments.length !== segments.length) return undefined
139
+ const params: Record<string, string> = {}
140
+ for (let i = 0; i < patternSegments.length; i++) {
141
+ const patternSegment = patternSegments[i]
142
+ if (patternSegment.startsWith('$')) {
143
+ params[patternSegment.slice(1)] = segments[i]
144
+ } else if (patternSegment !== segments[i]) {
145
+ return undefined
146
+ }
147
+ }
148
+ return params
149
+ }
150
+
151
+ /**
152
+ * Matches a pathname (basename already stripped) against the ROUTE_PATHS patterns
153
+ */
154
+ export const matchRoutePath = (pathname: string): MapRoute => {
155
+ const segments = pathname.split('/').filter(Boolean).map(decodeURIComponent)
156
+ if (segments.length === 0) {
157
+ return { type: 'workspace' }
158
+ }
159
+ for (const [pattern, type] of ROUTE_PATTERNS) {
160
+ const params = matchPattern(pattern, segments)
161
+ if (params) {
162
+ return { type, ...params }
163
+ }
164
+ }
165
+ return { type: 'workspace', category: segments[0], workspaceId: segments[1] }
166
+ }
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import './register-gfw-resolver.mjs'
3
+
4
+ const readStdin = async () => {
5
+ let data = ''
6
+ for await (const chunk of process.stdin) data += chunk
7
+ return data
8
+ }
9
+
10
+ const input = process.argv[2] && process.argv[2] !== '-' ? process.argv[2] : await readStdin()
11
+ if (!input?.trim()) {
12
+ console.error(
13
+ `Usage: node encode-url.mjs '{"route":{"type":"workspace"},"state":{...}}' (or pipe JSON via stdin)`
14
+ )
15
+ process.exit(1)
16
+ }
17
+
18
+ const { encodeMapUrl } = await import('@globalfishingwatch/skills/encode-url')
19
+
20
+ try {
21
+ const result = encodeMapUrl(JSON.parse(input))
22
+ console.log(JSON.stringify(result, null, 2))
23
+ } catch (error) {
24
+ console.error(error.message)
25
+ process.exit(1)
26
+ }
@@ -0,0 +1,24 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { registerHooks } from 'node:module'
3
+ import path from 'node:path'
4
+ import { fileURLToPath, pathToFileURL } from 'node:url'
5
+
6
+ // Each skill ships its own self-contained bundle next to it. Resolve it from either layout:
7
+ // - dist/<skill>/scripts (dist output, this skill copied standalone) -> ../index.js
8
+ // - <skill>/scripts (monorepo source, unbuilt) -> ../../dist/<skill>/index.js
9
+ const skillDir = fileURLToPath(new URL('..', import.meta.url))
10
+ const skillName = path.basename(skillDir)
11
+ const SPECIFIER = `@globalfishingwatch/skills/${skillName}`
12
+ const BUNDLE = [
13
+ path.join(skillDir, 'index.js'),
14
+ path.join(skillDir, '..', '..', 'dist', skillName, 'index.js'),
15
+ ].find(existsSync)
16
+
17
+ registerHooks({
18
+ resolve(specifier, context, nextResolve) {
19
+ if (specifier === SPECIFIER && BUNDLE) {
20
+ return { url: pathToFileURL(BUNDLE).href, format: 'module', shortCircuit: true }
21
+ }
22
+ return nextResolve(specifier, context)
23
+ },
24
+ })
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from './decode-url/decode'
2
+ export * from './decode-url/workspaces'
3
+ export * from './encode-url/config'
4
+ export * from './encode-url/dictionary'
5
+ export * from './encode-url/encode'
6
+ export * from './encode-url/routes'